Skip to content

Commit

Permalink
Implement import defer parsing support (#15845)
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolo-ribaudo committed Sep 14, 2023
1 parent bbe78ed commit c5badb5
Show file tree
Hide file tree
Showing 28 changed files with 204 additions and 22 deletions.
@@ -0,0 +1 @@
import defer * as x from "y";
@@ -0,0 +1 @@
import defer * as x from "y";
@@ -1,4 +1,4 @@
{
"plugins": ["sourcePhaseImports"],
"plugins": ["sourcePhaseImports", "deferredImportEvaluation"],
"parserOpts": { "createImportExpressions": true }
}
1 change: 1 addition & 0 deletions packages/babel-parser/data/schema.json
Expand Up @@ -163,6 +163,7 @@
"decorators",
"decorators-legacy",
"decoratorAutoAccessors",
"deferredImportEvaluation",
"destructuringPrivate",
"doExpressions",
"dynamicImport",
Expand Down
6 changes: 4 additions & 2 deletions packages/babel-parser/src/parse-error/standard-errors.ts
Expand Up @@ -66,6 +66,8 @@ export default {
"Decorators must be placed *after* the 'export' keyword. Remove the 'decoratorsBeforeExport: false' option to use the '@decorator export class {}' syntax.",
DecoratorSemicolon: "Decorators must not be followed by a semicolon.",
DecoratorStaticBlock: "Decorators can't be used with a static block.",
DeferImportRequiresNamespace:
'Only `import defer * as x from "./module"` is valid.',
DeletePrivateField: "Deleting a private field is not allowed.",
DestructureNamedImport:
"ES2015 named imports do not destructure. Use another statement for destructuring after the import.",
Expand All @@ -75,6 +77,8 @@ export default {
`\`${exportName}\` has already been exported. Exported identifiers must be unique.`,
DuplicateProto: "Redefinition of __proto__ property.",
DuplicateRegExpFlags: "Duplicate regular expression flag.",
DynamicImportPhaseRequiresImportExpressions: ({ phase }: { phase: string }) =>
`'import.${phase}(...)' can only be parsed when using the 'createImportExpressions' option.`,
ElementAfterRest: "Rest element must be last element.",
EscapedCharNotAnIdentifier: "Invalid Unicode escape.",
ExportBindingIsString: ({
Expand Down Expand Up @@ -235,8 +239,6 @@ export default {
"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
25 changes: 18 additions & 7 deletions packages/babel-parser/src/parser/expression.ts
Expand Up @@ -1667,16 +1667,27 @@ 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");
} else if (this.isContextual(tt._source) || this.isContextual(tt._defer)) {
const isSource = this.isContextual(tt._source);

// TODO: The proposal doesn't mention import.defer yet because it was
// pending on a decision for import.source. Wait to enable it until it's
// included in the proposal.
if (!isSource) this.unexpected();

this.expectPlugin(
isSource ? "sourcePhaseImports" : "deferredImportEvaluation",
);
if (!this.options.createImportExpressions) {
throw this.raise(
Errors.SourcePhaseDynamicImportRequiresImportExpressions,
{ at: this.state.startLoc },
);
throw this.raise(Errors.DynamicImportPhaseRequiresImportExpressions, {
at: this.state.startLoc,
phase: this.state.value,
});
}
this.next();
(node as Undone<N.ImportExpression>).phase = "source";
(node as Undone<N.ImportExpression>).phase = isSource
? "source"
: "defer";
return this.parseImportCall(node as Undone<N.ImportExpression>);
}

Expand Down
24 changes: 18 additions & 6 deletions packages/babel-parser/src/parser/statement.ts
Expand Up @@ -2871,18 +2871,23 @@ export default abstract class StatementParser extends ExpressionParser {

checkImportReflection(node: Undone<N.ImportDeclaration>) {
const { specifiers } = node;
const isSingleDefaultBinding =
specifiers.length === 1 &&
specifiers[0].type === "ImportDefaultSpecifier";
const singleBindingType =
specifiers.length === 1 ? specifiers[0].type : null;

if (node.phase === "source") {
if (!isSingleDefaultBinding) {
if (singleBindingType !== "ImportDefaultSpecifier") {
this.raise(Errors.SourcePhaseImportRequiresDefault, {
at: specifiers[0].loc.start,
});
}
} else if (node.phase === "defer") {
if (singleBindingType !== "ImportNamespaceSpecifier") {
this.raise(Errors.DeferImportRequiresNamespace, {
at: specifiers[0].loc.start,
});
}
} else if (node.module) {
if (!isSingleDefaultBinding) {
if (singleBindingType !== "ImportDefaultSpecifier") {
this.raise(Errors.ImportReflectionNotBinding, {
at: specifiers[0].loc.start,
});
Expand Down Expand Up @@ -2930,7 +2935,11 @@ export default abstract class StatementParser extends ExpressionParser {

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

applyImportPhase(
Expand Down Expand Up @@ -2960,6 +2969,9 @@ export default abstract class StatementParser extends ExpressionParser {
if (phase === "source") {
this.expectPlugin("sourcePhaseImports", loc);
(node as N.ImportDeclaration).phase = "source";
} else if (phase === "defer") {
this.expectPlugin("deferredImportEvaluation", loc);
(node as N.ImportDeclaration).phase = "defer";
} 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 @@ -284,6 +284,7 @@ export const tt = {
_assert: createKeywordLike("assert", { startsExpr }),
_async: createKeywordLike("async", { startsExpr }),
_await: createKeywordLike("await", { startsExpr }),
_defer: createKeywordLike("defer", { startsExpr }),
_from: createKeywordLike("from", { startsExpr }),
_get: createKeywordLike("get", { startsExpr }),
_let: createKeywordLike("let", { startsExpr }),
Expand Down
4 changes: 2 additions & 2 deletions packages/babel-parser/src/types.d.ts
Expand Up @@ -627,7 +627,7 @@ export interface NewExpression extends CallOrNewBase {
export interface ImportExpression extends NodeBase {
type: "ImportExpression";
source: Expression;
phase?: null | "source";
phase?: null | "source" | "defer";
options: Expression | null;
}

Expand Down Expand Up @@ -928,7 +928,7 @@ export interface ImportDeclaration extends NodeBase {
>;
source: Literal;
importKind?: "type" | "typeof" | "value"; // TODO: Not in spec,
phase?: null | "source";
phase?: null | "source" | "defer";
attributes?: ImportAttribute[];
// @deprecated
assertions?: ImportAttribute[];
Expand Down
1 change: 1 addition & 0 deletions packages/babel-parser/src/typings.d.ts
Expand Up @@ -8,6 +8,7 @@ export type Plugin =
| "classStaticBlock" // Enabled by default
| "decimal"
| "decorators-legacy"
| "deferredImportEvaluation"
| "decoratorAutoAccessors"
| "destructuringPrivate"
| "doExpressions"
Expand Down
@@ -0,0 +1 @@
import.defer("x", { with: { attr: "val" } });
@@ -0,0 +1,4 @@
{
"plugins": ["deferredImportEvaluation", "importAttributes"],
"createImportExpressions": true
}
@@ -0,0 +1 @@
import.defer("foo");
@@ -0,0 +1,4 @@
{
"plugins": ["deferredImportEvaluation"],
"throws": "TODO (disabled test)"
}
@@ -0,0 +1 @@
import.defer("foo");
@@ -0,0 +1,4 @@
{
"plugins": ["deferredImportEvaluation"],
"createImportExpressions": true
}
@@ -0,0 +1 @@
import defer * as ns from "x" with { attr: "val" };
@@ -0,0 +1,3 @@
{
"throws": "This experimental syntax requires enabling the parser plugin: \"importAttributes\". (1:35)"
}
@@ -0,0 +1 @@
import defer * as ns from "x";
@@ -0,0 +1,38 @@
{
"type": "File",
"start":0,"end":30,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":30,"index":30}},
"program": {
"type": "Program",
"start":0,"end":30,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":30,"index":30}},
"sourceType": "module",
"interpreter": null,
"body": [
{
"type": "ImportDeclaration",
"start":0,"end":30,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":30,"index":30}},
"phase": "defer",
"specifiers": [
{
"type": "ImportNamespaceSpecifier",
"start":13,"end":20,"loc":{"start":{"line":1,"column":13,"index":13},"end":{"line":1,"column":20,"index":20}},
"local": {
"type": "Identifier",
"start":18,"end":20,"loc":{"start":{"line":1,"column":18,"index":18},"end":{"line":1,"column":20,"index":20},"identifierName":"ns"},
"name": "ns"
}
}
],
"source": {
"type": "StringLiteral",
"start":26,"end":29,"loc":{"start":{"line":1,"column":26,"index":26},"end":{"line":1,"column":29,"index":29}},
"extra": {
"rawValue": "x",
"raw": "\"x\""
},
"value": "x"
}
}
],
"directives": []
}
}
@@ -0,0 +1 @@
import defer x from "x";
@@ -0,0 +1,41 @@
{
"type": "File",
"start":0,"end":24,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":24,"index":24}},
"errors": [
"SyntaxError: Only `import defer * as x from \"./module\"` is valid. (1:13)"
],
"program": {
"type": "Program",
"start":0,"end":24,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":24,"index":24}},
"sourceType": "module",
"interpreter": null,
"body": [
{
"type": "ImportDeclaration",
"start":0,"end":24,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":24,"index":24}},
"phase": "defer",
"specifiers": [
{
"type": "ImportDefaultSpecifier",
"start":13,"end":14,"loc":{"start":{"line":1,"column":13,"index":13},"end":{"line":1,"column":14,"index":14}},
"local": {
"type": "Identifier",
"start":13,"end":14,"loc":{"start":{"line":1,"column":13,"index":13},"end":{"line":1,"column":14,"index":14},"identifierName":"x"},
"name": "x"
}
}
],
"source": {
"type": "StringLiteral",
"start":20,"end":23,"loc":{"start":{"line":1,"column":20,"index":20},"end":{"line":1,"column":23,"index":23}},
"extra": {
"rawValue": "x",
"raw": "\"x\""
},
"value": "x"
}
}
],
"directives": []
}
}
@@ -0,0 +1 @@
import defer { x } from "x";
@@ -0,0 +1,46 @@
{
"type": "File",
"start":0,"end":28,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":28,"index":28}},
"errors": [
"SyntaxError: Only `import defer * as x from \"./module\"` is valid. (1:15)"
],
"program": {
"type": "Program",
"start":0,"end":28,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":28,"index":28}},
"sourceType": "module",
"interpreter": null,
"body": [
{
"type": "ImportDeclaration",
"start":0,"end":28,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":28,"index":28}},
"phase": "defer",
"specifiers": [
{
"type": "ImportSpecifier",
"start":15,"end":16,"loc":{"start":{"line":1,"column":15,"index":15},"end":{"line":1,"column":16,"index":16}},
"imported": {
"type": "Identifier",
"start":15,"end":16,"loc":{"start":{"line":1,"column":15,"index":15},"end":{"line":1,"column":16,"index":16},"identifierName":"x"},
"name": "x"
},
"local": {
"type": "Identifier",
"start":15,"end":16,"loc":{"start":{"line":1,"column":15,"index":15},"end":{"line":1,"column":16,"index":16},"identifierName":"x"},
"name": "x"
}
}
],
"source": {
"type": "StringLiteral",
"start":24,"end":27,"loc":{"start":{"line":1,"column":24,"index":24},"end":{"line":1,"column":27,"index":27}},
"extra": {
"rawValue": "x",
"raw": "\"x\""
},
"value": "x"
}
}
],
"directives": []
}
}
@@ -0,0 +1,4 @@
{
"sourceType": "module",
"plugins": ["deferredImportEvaluation"]
}
1 change: 1 addition & 0 deletions packages/babel-parser/typings/babel-parser.d.ts
Expand Up @@ -12,6 +12,7 @@ type Plugin =
| "classStaticBlock" // Enabled by default
| "decimal"
| "decorators-legacy"
| "deferredImportEvaluation"
| "decoratorAutoAccessors"
| "destructuringPrivate"
| "doExpressions"
Expand Down
4 changes: 2 additions & 2 deletions packages/babel-types/src/ast-types/generated/index.ts
Expand Up @@ -864,7 +864,7 @@ export interface ImportDeclaration extends BaseNode {
attributes?: Array<ImportAttribute> | null;
importKind?: "type" | "typeof" | "value" | null;
module?: boolean | null;
phase?: "source" | null;
phase?: "source" | "defer" | null;
}

export interface ImportDefaultSpecifier extends BaseNode {
Expand All @@ -888,7 +888,7 @@ export interface ImportExpression extends BaseNode {
type: "ImportExpression";
source: Expression;
options?: Expression | null;
phase?: "source" | null;
phase?: "source" | "defer" | null;
}

export interface MetaProperty extends BaseNode {
Expand Down
4 changes: 2 additions & 2 deletions packages/babel-types/src/definitions/core.ts
Expand Up @@ -1746,7 +1746,7 @@ defineType("ImportDeclaration", {
},
phase: {
default: null,
validate: assertOneOf("source"),
validate: assertOneOf("source", "defer"),
},
specifiers: {
validate: chain(
Expand Down Expand Up @@ -1817,7 +1817,7 @@ defineType("ImportExpression", {
fields: {
phase: {
default: null,
validate: assertOneOf("source"),
validate: assertOneOf("source", "defer"),
},
source: {
validate: assertNodeType("Expression"),
Expand Down

0 comments on commit c5badb5

Please sign in to comment.