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

Add parsing support for the "source phase imports" proposal #15829

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
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";