Skip to content

Commit

Permalink
Parse import reflection (#14926)
Browse files Browse the repository at this point in the history
Co-authored-by: Nicol貌 Ribaudo <nicolo.ribaudo@gmail.com>
  • Loading branch information
JLHwung and nicolo-ribaudo committed Oct 26, 2022
1 parent df733b1 commit dfc4b61
Show file tree
Hide file tree
Showing 77 changed files with 948 additions and 11 deletions.
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 @@ -201,8 +201,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 @@ -2654,6 +2654,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 @@ -2687,13 +2705,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;
}
}
} else {
// import module { x } ...
// This is invalid, we will continue parsing and throw
// a recoverable error later
isImportReflection = true;
}
}
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 @@ -2726,6 +2778,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 @@ -2718,6 +2720,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 @@ -121,6 +121,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 @@ -2603,6 +2605,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 @@ -346,7 +344,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";

0 comments on commit dfc4b61

Please sign in to comment.