Skip to content

Commit

Permalink
Add parsing support for the "source phase imports" proposal (#15829)
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolo-ribaudo committed Sep 18, 2023
1 parent 252d0cb commit 80d0c3a
Show file tree
Hide file tree
Showing 40 changed files with 512 additions and 11 deletions.
8 changes: 8 additions & 0 deletions packages/babel-generator/src/generators/modules.ts
Expand Up @@ -248,6 +248,10 @@ export function ImportDeclaration(this: Printer, node: t.ImportDeclaration) {
this.noIndentInnerCommentsHere();
this.word("module");
this.space();
} else if (node.phase) {
this.noIndentInnerCommentsHere();
this.word(node.phase);
this.space();
}

const specifiers = node.specifiers.slice(0);
Expand Down Expand Up @@ -315,6 +319,10 @@ export function ImportNamespaceSpecifier(

export function ImportExpression(this: Printer, node: t.ImportExpression) {
this.word("import");
if (node.phase) {
this.token(".");
this.word(node.phase);
}
this.token("(");
this.print(node.source, node);
if (node.options != null) {
Expand Down
@@ -0,0 +1,4 @@
{
"plugins": ["sourcePhaseImports"],
"parserOpts": { "createImportExpressions": true }
}
@@ -0,0 +1 @@
import source x from "y";
@@ -0,0 +1 @@
import source x from "y";
@@ -0,0 +1 @@
import.source("x");
@@ -0,0 +1 @@
import.source("x");
1 change: 1 addition & 0 deletions packages/babel-parser/data/schema.json
Expand Up @@ -192,6 +192,7 @@
"placeholders",
"privateIn",
"regexpUnicodeSets",
"sourcePhaseImports",
"throwExpressions",
"topLevelAwait",
"typescript",
Expand Down
4 changes: 4 additions & 0 deletions packages/babel-parser/src/parse-error/standard-errors.ts
Expand Up @@ -233,6 +233,10 @@ export default {
"In non-strict mode code, functions can only be declared at top level or inside a block.",
SloppyFunctionAnnexB:
"In non-strict mode code, functions can only be declared at top level, inside a block, or as the body of an if statement.",
SourcePhaseImportRequiresDefault:
'Only `import source x from "./module"` is valid.',
SourcePhaseDynamicImportRequiresImportExpressions:
"'import.source(...)' can only be parsed when using the 'createImportExpressions' option.",
StaticPrototype: "Classes may not have static property named prototype.",
SuperNotAllowed:
"`super()` is only valid inside a class constructor of a subclass. Maybe a typo in the method name ('constructor') or not extending another class?",
Expand Down
18 changes: 16 additions & 2 deletions packages/babel-parser/src/parser/expression.ts
Expand Up @@ -1652,7 +1652,10 @@ export default abstract class ExpressionParser extends LValParser {
}

// https://tc39.es/ecma262/#prod-ImportMeta
parseImportMetaProperty(node: Undone<N.MetaProperty>): N.MetaProperty {
parseImportMetaProperty(
this: Parser,
node: Undone<N.MetaProperty | N.ImportExpression>,
): N.MetaProperty | N.ImportExpression {
const id = this.createIdentifier(
this.startNodeAtNode<N.Identifier>(node),
"import",
Expand All @@ -1664,9 +1667,20 @@ export default abstract class ExpressionParser extends LValParser {
this.raise(Errors.ImportMetaOutsideModule, { at: id });
}
this.sawUnambiguousESM = true;
} else if (this.isContextual(tt._source)) {
this.expectPlugin("sourcePhaseImports");
if (!this.options.createImportExpressions) {
throw this.raise(
Errors.SourcePhaseDynamicImportRequiresImportExpressions,
{ at: this.state.startLoc },
);
}
this.next();
(node as Undone<N.ImportExpression>).phase = "source";
return this.parseImportCall(node as Undone<N.ImportExpression>);
}

return this.parseMetaProperty(node, id, "meta");
return this.parseMetaProperty(node as Undone<N.MetaProperty>, id, "meta");
}

parseLiteralAtNode<T extends N.Node>(
Expand Down
35 changes: 26 additions & 9 deletions packages/babel-parser/src/parser/statement.ts
Expand Up @@ -2870,13 +2870,21 @@ export default abstract class StatementParser extends ExpressionParser {
}

checkImportReflection(node: Undone<N.ImportDeclaration>) {
if (node.module) {
if (
node.specifiers.length !== 1 ||
node.specifiers[0].type !== "ImportDefaultSpecifier"
) {
const { specifiers } = node;
const isSingleDefaultBinding =
specifiers.length === 1 &&
specifiers[0].type === "ImportDefaultSpecifier";

if (node.phase === "source") {
if (!isSingleDefaultBinding) {
this.raise(Errors.SourcePhaseImportRequiresDefault, {
at: specifiers[0].loc.start,
});
}
} else if (node.module) {
if (!isSingleDefaultBinding) {
this.raise(Errors.ImportReflectionNotBinding, {
at: node.specifiers[0].loc.start,
at: specifiers[0].loc.start,
});
}
if (node.assertions?.length > 0) {
Expand Down Expand Up @@ -2921,7 +2929,8 @@ export default abstract class StatementParser extends ExpressionParser {
}

isPotentialImportPhase(isExport: boolean): boolean {
return !isExport && this.isContextual(tt._module);
if (isExport) return false;
return this.isContextual(tt._source) || this.isContextual(tt._module);
}

applyImportPhase(
Expand All @@ -2932,20 +2941,28 @@ export default abstract class StatementParser extends ExpressionParser {
): void {
if (isExport) {
if (!process.env.IS_PUBLISH) {
if (phase === "module") {
if (phase === "module" || phase === "source") {
throw new Error(
"Assertion failure: export declarations do not support the 'module' phase.",
`Assertion failure: export declarations do not support the '${phase}' phase.`,
);
}
}
return;
}

if (phase === "module") {
this.expectPlugin("importReflection", loc);
(node as N.ImportDeclaration).module = true;
} else if (this.hasPlugin("importReflection")) {
(node as N.ImportDeclaration).module = false;
}

if (phase === "source") {
this.expectPlugin("sourcePhaseImports", loc);
(node as N.ImportDeclaration).phase = "source";
} else if (this.hasPlugin("sourcePhaseImports")) {
(node as N.ImportDeclaration).phase = null;
}
}

/*
Expand Down
1 change: 1 addition & 0 deletions packages/babel-parser/src/tokenizer/types.ts
Expand Up @@ -291,6 +291,7 @@ export const tt = {
_of: createKeywordLike("of", { startsExpr }),
_sent: createKeywordLike("sent", { startsExpr }),
_set: createKeywordLike("set", { startsExpr }),
_source: createKeywordLike("source", { startsExpr }),
_static: createKeywordLike("static", { startsExpr }),
_using: createKeywordLike("using", { startsExpr }),
_yield: createKeywordLike("yield", { startsExpr }),
Expand Down
2 changes: 2 additions & 0 deletions packages/babel-parser/src/types.d.ts
Expand Up @@ -627,6 +627,7 @@ export interface NewExpression extends CallOrNewBase {
export interface ImportExpression extends NodeBase {
type: "ImportExpression";
source: Expression;
phase?: null | "source";
options: Expression | null;
}

Expand Down Expand Up @@ -927,6 +928,7 @@ export interface ImportDeclaration extends NodeBase {
>;
source: Literal;
importKind?: "type" | "typeof" | "value"; // TODO: Not in spec,
phase?: null | "source";
attributes?: ImportAttribute[];
// @deprecated
assertions?: ImportAttribute[];
Expand Down
1 change: 1 addition & 0 deletions packages/babel-parser/src/typings.d.ts
Expand Up @@ -36,6 +36,7 @@ export type Plugin =
| "placeholders"
| "privateIn" // Enabled by default
| "regexpUnicodeSets" // Enabled by default
| "sourcePhaseImports"
| "throwExpressions"
| "topLevelAwait"
| "v8intrinsic"
Expand Down
@@ -0,0 +1 @@
import source s from "x" with { attr: "val" };
@@ -0,0 +1,4 @@
{
"sourceType": "module",
"plugins": ["sourcePhaseImports", "importAttributes"]
}
@@ -0,0 +1,58 @@
{
"type": "File",
"start":0,"end":46,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":46,"index":46}},
"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}},
"phase": "source",
"specifiers": [
{
"type": "ImportDefaultSpecifier",
"start":14,"end":15,"loc":{"start":{"line":1,"column":14,"index":14},"end":{"line":1,"column":15,"index":15}},
"local": {
"type": "Identifier",
"start":14,"end":15,"loc":{"start":{"line":1,"column":14,"index":14},"end":{"line":1,"column":15,"index":15},"identifierName":"s"},
"name": "s"
}
}
],
"source": {
"type": "StringLiteral",
"start":21,"end":24,"loc":{"start":{"line":1,"column":21,"index":21},"end":{"line":1,"column":24,"index":24}},
"extra": {
"rawValue": "x",
"raw": "\"x\""
},
"value": "x"
},
"attributes": [
{
"type": "ImportAttribute",
"start":32,"end":43,"loc":{"start":{"line":1,"column":32,"index":32},"end":{"line":1,"column":43,"index":43}},
"key": {
"type": "Identifier",
"start":32,"end":36,"loc":{"start":{"line":1,"column":32,"index":32},"end":{"line":1,"column":36,"index":36},"identifierName":"attr"},
"name": "attr"
},
"value": {
"type": "StringLiteral",
"start":38,"end":43,"loc":{"start":{"line":1,"column":38,"index":38},"end":{"line":1,"column":43,"index":43}},
"extra": {
"rawValue": "val",
"raw": "\"val\""
},
"value": "val"
}
}
]
}
],
"directives": []
}
}
@@ -0,0 +1 @@
import.source("x", { with: { attr: "val" } });
@@ -0,0 +1,4 @@
{
"plugins": ["sourcePhaseImports", "importAttributes"],
"createImportExpressions": true
}
@@ -0,0 +1,76 @@
{
"type": "File",
"start":0,"end":46,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":46,"index":46}},
"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": "ExpressionStatement",
"start":0,"end":46,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":46,"index":46}},
"expression": {
"type": "ImportExpression",
"start":0,"end":45,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":45,"index":45}},
"phase": "source",
"source": {
"type": "StringLiteral",
"start":14,"end":17,"loc":{"start":{"line":1,"column":14,"index":14},"end":{"line":1,"column":17,"index":17}},
"extra": {
"rawValue": "x",
"raw": "\"x\""
},
"value": "x"
},
"options": {
"type": "ObjectExpression",
"start":19,"end":44,"loc":{"start":{"line":1,"column":19,"index":19},"end":{"line":1,"column":44,"index":44}},
"properties": [
{
"type": "ObjectProperty",
"start":21,"end":42,"loc":{"start":{"line":1,"column":21,"index":21},"end":{"line":1,"column":42,"index":42}},
"method": false,
"key": {
"type": "Identifier",
"start":21,"end":25,"loc":{"start":{"line":1,"column":21,"index":21},"end":{"line":1,"column":25,"index":25},"identifierName":"with"},
"name": "with"
},
"computed": false,
"shorthand": false,
"value": {
"type": "ObjectExpression",
"start":27,"end":42,"loc":{"start":{"line":1,"column":27,"index":27},"end":{"line":1,"column":42,"index":42}},
"properties": [
{
"type": "ObjectProperty",
"start":29,"end":40,"loc":{"start":{"line":1,"column":29,"index":29},"end":{"line":1,"column":40,"index":40}},
"method": false,
"key": {
"type": "Identifier",
"start":29,"end":33,"loc":{"start":{"line":1,"column":29,"index":29},"end":{"line":1,"column":33,"index":33},"identifierName":"attr"},
"name": "attr"
},
"computed": false,
"shorthand": false,
"value": {
"type": "StringLiteral",
"start":35,"end":40,"loc":{"start":{"line":1,"column":35,"index":35},"end":{"line":1,"column":40,"index":40}},
"extra": {
"rawValue": "val",
"raw": "\"val\""
},
"value": "val"
}
}
]
}
}
]
}
}
}
],
"directives": []
}
}
@@ -0,0 +1 @@
import.source("foo");
@@ -0,0 +1,4 @@
{
"plugins": ["sourcePhaseImports"],
"throws": "'import.source(...)' can only be parsed when using the 'createImportExpressions' option. (1:7)"
}
@@ -0,0 +1 @@
import.source("foo");
@@ -0,0 +1,4 @@
{
"plugins": ["sourcePhaseImports"],
"createImportExpressions": true
}
@@ -0,0 +1,31 @@
{
"type": "File",
"start":0,"end":21,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":21,"index":21}},
"program": {
"type": "Program",
"start":0,"end":21,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":21,"index":21}},
"sourceType": "module",
"interpreter": null,
"body": [
{
"type": "ExpressionStatement",
"start":0,"end":21,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":21,"index":21}},
"expression": {
"type": "ImportExpression",
"start":0,"end":20,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":20,"index":20}},
"phase": "source",
"source": {
"type": "StringLiteral",
"start":14,"end":19,"loc":{"start":{"line":1,"column":14,"index":14},"end":{"line":1,"column":19,"index":19}},
"extra": {
"rawValue": "foo",
"raw": "\"foo\""
},
"value": "foo"
}
}
}
],
"directives": []
}
}
@@ -0,0 +1 @@
import source from "x";

0 comments on commit 80d0c3a

Please sign in to comment.