Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parse import reflection #14926

Merged
merged 16 commits into from Oct 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion babel.config.js
Expand Up @@ -772,7 +772,7 @@ function getTokenTypesMapping() {
);
}

const tokenTypesDefinition = typesDeclaration.init.properties;
const tokenTypesDefinition = typesDeclaration.init.expression.properties;
for (let i = 0; i < tokenTypesDefinition.length; i++) {
tokenTypesMapping.set(tokenTypesDefinition[i].key.name, i);
}
Expand Down
5 changes: 5 additions & 0 deletions packages/babel-generator/src/generators/modules.ts
Expand Up @@ -200,8 +200,13 @@ export function ImportDeclaration(this: Printer, node: t.ImportDeclaration) {

const isTypeKind = node.importKind === "type" || node.importKind === "typeof";
if (isTypeKind) {
this.printInnerComments(node, false);
this.word(node.importKind);
this.space();
} else if (node.module) {
this.printInnerComments(node, false);
this.word("module");
this.space();
}

const specifiers = node.specifiers.slice(0);
Expand Down
@@ -0,0 +1 @@
/* 0 */import /* 1 */module /* 2 */from /* 3 */from /* 4 */"./module.wasm"/* 5 */;
@@ -0,0 +1,3 @@
{
"plugins": ["importReflection"]
}
@@ -0,0 +1 @@
/* 0 */import /* 1 */module /* 2 */from /* 3 */ from /* 4 */"./module.wasm" /* 5 */;
@@ -0,0 +1 @@
import module foo from "./module.wasm";
@@ -0,0 +1 @@
import module foo from "./module.wasm";
@@ -0,0 +1,3 @@
{
"plugins": ["importReflection"]
}
1 change: 1 addition & 0 deletions packages/babel-parser/data/schema.json
Expand Up @@ -142,6 +142,7 @@
"functionSent",
"importAssertions",
"importMeta",
"importReflection",
"jsx",
"logicalAssignment",
"moduleStringNames",
Expand Down
4 changes: 3 additions & 1 deletion packages/babel-parser/src/index.ts
Expand Up @@ -80,7 +80,9 @@ function generateExportedTokenTypes(
internalTokenTypes: InternalTokenTypes,
): Record<string, ExportedTokenType> {
const tokenTypes: Record<string, ExportedTokenType> = {};
for (const typeName of Object.keys(internalTokenTypes)) {
for (const typeName of Object.keys(
internalTokenTypes,
) as (keyof InternalTokenTypes)[]) {
tokenTypes[typeName] = getExportedToken(internalTokenTypes[typeName]);
}
return tokenTypes;
Expand Down
3 changes: 3 additions & 0 deletions packages/babel-parser/src/parse-error/standard-errors.ts
Expand Up @@ -117,6 +117,9 @@ export default {
ImportCallSpreadArgument: "`...` is not allowed in `import()`.",
ImportJSONBindingNotDefault:
"A JSON module can only be imported with `default`.",
ImportReflectionHasAssertion: "`import module x` cannot have assertions.",
ImportReflectionNotBinding:
'Only `import module x from "./module"` is valid.',
IncompatibleRegExpUVFlags:
"The 'u' and 'v' regular expression flags cannot be enabled at the same time.",
InvalidBigIntLiteral: "Invalid BigIntLiteral.",
Expand Down
53 changes: 53 additions & 0 deletions packages/babel-parser/src/parser/statement.ts
Expand Up @@ -2612,6 +2612,24 @@ export default abstract class StatementParser extends ExpressionParser {
return false;
}

checkImportReflection(node: Undone<N.ImportDeclaration>) {
if (node.module) {
if (
node.specifiers.length !== 1 ||
node.specifiers[0].type !== "ImportDefaultSpecifier"
) {
this.raise(Errors.ImportReflectionNotBinding, {
at: node.specifiers[0].loc.start,
});
}
if (node.assertions?.length > 0) {
this.raise(Errors.ImportReflectionHasAssertion, {
at: node.specifiers[0].loc.start,
});
}
}
}

checkJSONModuleImport(
node: Undone<
N.ExportAllDeclaration | N.ExportNamedDeclaration | N.ImportDeclaration
Expand Down Expand Up @@ -2645,13 +2663,47 @@ export default abstract class StatementParser extends ExpressionParser {
}
}

parseMaybeImportReflection(node: Undone<N.ImportDeclaration>) {
let isImportReflection = false;
if (this.isContextual(tt._module)) {
const lookahead = this.lookahead();
if (tokenIsIdentifier(lookahead.type)) {
if (lookahead.type !== tt._from) {
// import module x
isImportReflection = true;
} else {
const nextNextTokenFirstChar = this.input.charCodeAt(
this.nextTokenStartSince(lookahead.end),
);
if (nextNextTokenFirstChar === charCodes.lowercaseF) {
// import module from from ...
isImportReflection = true;
Comment on lines +2678 to +2680
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a test for import module from foo, just to verify that it errors even if foo starts with f?

}
}
} else {
// import module { x } ...
// This is invalid, we will continue parsing and throw
// a recoverable error later
isImportReflection = true;
Comment on lines +2684 to +2687
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also add a test for import module "foo"?

}
}
if (isImportReflection) {
this.expectPlugin("importReflection");
this.next(); // eat tt._module;
node.module = true;
} else if (this.hasPlugin("importReflection")) {
node.module = false;
}
}

// Parses import declaration.
// https://tc39.es/ecma262/#prod-ImportDeclaration

parseImport(this: Parser, node: Undone<N.ImportDeclaration>): N.AnyImport {
// import '...'
node.specifiers = [];
if (!this.match(tt.string)) {
this.parseMaybeImportReflection(node);
// check if we have a default import like
// import React from "react";
const hasDefault = this.maybeParseDefaultImportSpecifier(node);
Expand Down Expand Up @@ -2684,6 +2736,7 @@ export default abstract class StatementParser extends ExpressionParser {
node.attributes = attributes;
}
}
this.checkImportReflection(node);
this.checkJSONModuleImport(node);

this.semicolon();
Expand Down
11 changes: 11 additions & 0 deletions packages/babel-parser/src/plugins/flow/index.ts
Expand Up @@ -164,6 +164,8 @@ const FlowErrors = ParseErrorEnum`flow`({
}) =>
`String enum members need to consistently either all use initializers, or use no initializers, in enum \`${enumName}\`.`,
GetterMayNotHaveThisParam: "A getter cannot have a `this` parameter.",
ImportReflectionHasImportType:
"An `import module` declaration can not use `type` or `typeof` keyword.",
ImportTypeShorthandOnlyInPureImport:
"The `type` and `typeof` keywords on named imports can only be used on regular `import` statements. It cannot be used with `import type` or `import typeof` statements.",
InexactInsideExact:
Expand Down Expand Up @@ -2715,6 +2717,15 @@ export default (superClass: typeof Parser) =>
return isMaybeDefaultImport(this.state.type);
}

checkImportReflection(node: Undone<N.ImportDeclaration>) {
super.checkImportReflection(node);
if (node.module && node.importKind !== "value") {
this.raise(FlowErrors.ImportReflectionHasImportType, {
at: node.specifiers[0].loc.start,
});
}
}

parseImportSpecifierLocal<
T extends
| N.ImportSpecifier
Expand Down
11 changes: 11 additions & 0 deletions packages/babel-parser/src/plugins/typescript/index.ts
Expand Up @@ -122,6 +122,8 @@ const TSErrors = ParseErrorEnum`typescript`({
ExpectedAmbientAfterExportDeclare:
"'export declare' must be followed by an ambient declaration.",
ImportAliasHasImportType: "An import alias can not use 'import type'.",
ImportReflectionHasImportType:
"An `import module` declaration can not use `type` modifier",
IncompatibleModifiers: ({
modifiers,
}: {
Expand Down Expand Up @@ -2596,6 +2598,15 @@ export default (superClass: ClassWithMixin<typeof Parser, IJSXParserMixin>) =>
}
}

checkImportReflection(node: Undone<N.ImportDeclaration>) {
super.checkImportReflection(node);
if (node.module && node.importKind !== "value") {
this.raise(TSErrors.ImportReflectionHasImportType, {
at: node.specifiers[0].loc.start,
});
}
}

/*
Don't bother doing this check in TypeScript code because:
1. We may have a nested export statement with the same name:
Expand Down
8 changes: 3 additions & 5 deletions packages/babel-parser/src/tokenizer/types.ts
Expand Up @@ -134,11 +134,9 @@ function createKeywordLike(
// For performance the token type helpers depend on the following declarations order.
// When adding new token types, please also check if the token helpers need update.

export type InternalTokenTypes = {
[name: string]: TokenType;
};
export type InternalTokenTypes = typeof tt;

export const tt: InternalTokenTypes = {
export const tt = {
// Punctuation token types.
bracketL: createToken("[", { beforeExpr, startsExpr }),
bracketHashL: createToken("#[", { beforeExpr, startsExpr }),
Expand Down Expand Up @@ -345,7 +343,7 @@ export const tt: InternalTokenTypes = {

// placeholder plugin
placeholder: createToken("%%", { startsExpr: true }),
};
} as const;

export function tokenIsIdentifier(token: TokenType): boolean {
return token >= tt._as && token <= tt.name;
Expand Down
1 change: 1 addition & 0 deletions packages/babel-parser/src/types.d.ts
Expand Up @@ -922,6 +922,7 @@ export interface ImportDeclaration extends NodeBase {
source: Literal;
importKind?: "type" | "typeof" | "value"; // TODO: Not in spec,
assertions?: ImportAttribute[];
module?: boolean;
}

export interface ImportSpecifier extends ModuleSpecifier {
Expand Down
1 change: 1 addition & 0 deletions packages/babel-parser/src/typings.d.ts
Expand Up @@ -22,6 +22,7 @@ export type Plugin =
| "jsx"
| "logicalAssignment"
| "importAssertions"
| "importReflection"
| "moduleBlocks"
| "moduleStringNames"
| "nullishCoalescingOperator"
Expand Down
@@ -0,0 +1 @@
import module foo from "./module.wasm";
@@ -0,0 +1,3 @@
{
"throws": "This experimental syntax requires enabling the parser plugin: \"importReflection\". (1:7)"
}
@@ -0,0 +1 @@
import module foo from "./module.json" assert { type: "json" }
@@ -0,0 +1,4 @@
{
"plugins": ["importReflection", "importAssertions"],
"sourceType": "module"
}
@@ -0,0 +1,61 @@
{
"type": "File",
"start":0,"end":62,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":62,"index":62}},
"errors": [
"SyntaxError: `import module x` cannot have assertions. (1:14)"
],
"program": {
"type": "Program",
"start":0,"end":62,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":62,"index":62}},
"sourceType": "module",
"interpreter": null,
"body": [
{
"type": "ImportDeclaration",
"start":0,"end":62,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":62,"index":62}},
"specifiers": [
{
"type": "ImportDefaultSpecifier",
"start":14,"end":17,"loc":{"start":{"line":1,"column":14,"index":14},"end":{"line":1,"column":17,"index":17}},
"local": {
"type": "Identifier",
"start":14,"end":17,"loc":{"start":{"line":1,"column":14,"index":14},"end":{"line":1,"column":17,"index":17},"identifierName":"foo"},
"name": "foo"
}
}
],
"module": true,
"source": {
"type": "StringLiteral",
"start":23,"end":38,"loc":{"start":{"line":1,"column":23,"index":23},"end":{"line":1,"column":38,"index":38}},
"extra": {
"rawValue": "./module.json",
"raw": "\"./module.json\""
},
"value": "./module.json"
},
"assertions": [
{
"type": "ImportAttribute",
"start":48,"end":60,"loc":{"start":{"line":1,"column":48,"index":48},"end":{"line":1,"column":60,"index":60}},
"key": {
"type": "Identifier",
"start":48,"end":52,"loc":{"start":{"line":1,"column":48,"index":48},"end":{"line":1,"column":52,"index":52},"identifierName":"type"},
"name": "type"
},
"value": {
"type": "StringLiteral",
"start":54,"end":60,"loc":{"start":{"line":1,"column":54,"index":54},"end":{"line":1,"column":60,"index":60}},
"extra": {
"rawValue": "json",
"raw": "\"json\""
},
"value": "json"
}
}
]
}
],
"directives": []
}
}
@@ -0,0 +1 @@
import module foo, {bar} from "./module.wasm";
@@ -0,0 +1,55 @@
{
"type": "File",
"start":0,"end":46,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":46,"index":46}},
"errors": [
"SyntaxError: Only `import module x from \"./module\"` is valid. (1:14)"
],
"program": {
"type": "Program",
"start":0,"end":46,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":46,"index":46}},
"sourceType": "module",
"interpreter": null,
"body": [
{
"type": "ImportDeclaration",
"start":0,"end":46,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":46,"index":46}},
"specifiers": [
{
"type": "ImportDefaultSpecifier",
"start":14,"end":17,"loc":{"start":{"line":1,"column":14,"index":14},"end":{"line":1,"column":17,"index":17}},
"local": {
"type": "Identifier",
"start":14,"end":17,"loc":{"start":{"line":1,"column":14,"index":14},"end":{"line":1,"column":17,"index":17},"identifierName":"foo"},
"name": "foo"
}
},
{
"type": "ImportSpecifier",
"start":20,"end":23,"loc":{"start":{"line":1,"column":20,"index":20},"end":{"line":1,"column":23,"index":23}},
"imported": {
"type": "Identifier",
"start":20,"end":23,"loc":{"start":{"line":1,"column":20,"index":20},"end":{"line":1,"column":23,"index":23},"identifierName":"bar"},
"name": "bar"
},
"local": {
"type": "Identifier",
"start":20,"end":23,"loc":{"start":{"line":1,"column":20,"index":20},"end":{"line":1,"column":23,"index":23},"identifierName":"bar"},
"name": "bar"
}
}
],
"module": true,
"source": {
"type": "StringLiteral",
"start":30,"end":45,"loc":{"start":{"line":1,"column":30,"index":30},"end":{"line":1,"column":45,"index":45}},
"extra": {
"rawValue": "./module.wasm",
"raw": "\"./module.wasm\""
},
"value": "./module.wasm"
}
}
],
"directives": []
}
}
@@ -0,0 +1 @@
export module x from "./module.wasm";
@@ -0,0 +1,8 @@
{
"plugins": [
"importReflection",
"exportDefaultFrom"
],
"sourceType": "module",
"throws": "Unexpected token, expected \"{\" (1:7)"
}
@@ -0,0 +1 @@
import type module foo from "./module.wasm";
@@ -0,0 +1,8 @@
{
"plugins": [
"importReflection",
"flow"
],
"sourceType": "module",
"throws": "Unexpected token, expected \"from\" (1:19)"
}
@@ -0,0 +1 @@
import module type foo from "./module.wasm";