diff --git a/packages/babel-parser/src/parser/statement.js b/packages/babel-parser/src/parser/statement.js index 86659a2fd8..d547ebcec5 100644 --- a/packages/babel-parser/src/parser/statement.js +++ b/packages/babel-parser/src/parser/statement.js @@ -1470,35 +1470,109 @@ export default class StatementParser extends ExpressionParser { // Parses module export declaration. - // TODO: better type. Node is an N.AnyExport. - parseExport(node: N.Node): N.Node { - // export * from '...' - if (this.shouldParseExportStar()) { - this.parseExportStar(node); - if (node.type === "ExportAllDeclaration") return node; - } else if (this.isExportDefaultSpecifier()) { - this.expectPlugin("exportDefaultFrom"); - const specifier = this.startNode(); - specifier.exported = this.parseIdentifier(true); - const specifiers = [this.finishNode(specifier, "ExportDefaultSpecifier")]; - node.specifiers = specifiers; - if (this.match(tt.comma) && this.lookahead().type === tt.star) { - this.expect(tt.comma); - const specifier = this.startNode(); - this.expect(tt.star); - this.expectContextual("as"); - specifier.exported = this.parseIdentifier(); - specifiers.push(this.finishNode(specifier, "ExportNamespaceSpecifier")); - } else { - this.parseExportSpecifiersMaybe(node); - } + parseExport(node: N.Node): N.AnyExport { + const hasDefault = this.maybeParseExportDefaultSpecifier(node); + const parseAfterDefault = !hasDefault || this.eat(tt.comma); + const hasStar = parseAfterDefault && this.eatExportStar(node); + const hasNamespace = + hasStar && this.maybeParseExportNamespaceSpecifier(node); + const parseAfterNamespace = + parseAfterDefault && (!hasNamespace || this.eat(tt.comma)); + const isFromRequired = hasDefault || hasStar; + + if (hasStar && !hasNamespace) { + if (hasDefault) this.unexpected(); this.parseExportFrom(node, true); - } else if (this.eat(tt._default)) { + + return this.finishNode(node, "ExportAllDeclaration"); + } + + const hasSpecifiers = this.maybeParseExportNamedSpecifiers(node); + + if ( + (hasDefault && parseAfterDefault && !hasStar && !hasSpecifiers) || + (hasNamespace && parseAfterNamespace && !hasSpecifiers) + ) { + throw this.unexpected(null, tt.braceL); + } + + let hasDeclaration; + if (isFromRequired || hasSpecifiers) { + hasDeclaration = false; + this.parseExportFrom(node, isFromRequired); + } else { + hasDeclaration = this.maybeParseExportDeclaration(node); + } + + if (isFromRequired || hasSpecifiers || hasDeclaration) { + this.checkExport(node, true); + return this.finishNode(node, "ExportNamedDeclaration"); + } + + if (this.eat(tt._default)) { // export default ... node.declaration = this.parseExportDefaultExpression(); this.checkExport(node, true, true); + return this.finishNode(node, "ExportDefaultDeclaration"); - } else if (this.shouldParseExportDeclaration()) { + } + + throw this.unexpected(null, tt.braceL); + } + + // eslint-disable-next-line no-unused-vars + eatExportStar(node: N.Node): boolean { + return this.eat(tt.star); + } + + maybeParseExportDefaultSpecifier(node: N.Node): boolean { + if (this.isExportDefaultSpecifier()) { + // export defaultObj ... + this.expectPlugin("exportDefaultFrom"); + const specifier = this.startNode(); + specifier.exported = this.parseIdentifier(true); + node.specifiers = [this.finishNode(specifier, "ExportDefaultSpecifier")]; + return true; + } + return false; + } + + maybeParseExportNamespaceSpecifier(node: N.Node): boolean { + if (this.isContextual("as")) { + if (!node.specifiers) node.specifiers = []; + this.expectPlugin("exportNamespaceFrom"); + + const specifier = this.startNodeAt( + this.state.lastTokStart, + this.state.lastTokStartLoc, + ); + + this.next(); + + specifier.exported = this.parseIdentifier(true); + node.specifiers.push( + this.finishNode(specifier, "ExportNamespaceSpecifier"), + ); + return true; + } + return false; + } + + maybeParseExportNamedSpecifiers(node: N.Node): boolean { + if (this.match(tt.braceL)) { + if (!node.specifiers) node.specifiers = []; + node.specifiers.push(...this.parseExportSpecifiers()); + + node.source = null; + node.declaration = null; + + return true; + } + return false; + } + + maybeParseExportDeclaration(node: N.Node): boolean { + if (this.shouldParseExportDeclaration()) { if (this.isContextual("async")) { const next = this.lookahead(); @@ -1511,14 +1585,10 @@ export default class StatementParser extends ExpressionParser { node.specifiers = []; node.source = null; node.declaration = this.parseExportDeclaration(node); - } else { - // export { x, y as z } [from '...'] - node.declaration = null; - node.specifiers = this.parseExportSpecifiers(); - this.parseExportFrom(node); + + return true; } - this.checkExport(node, true); - return this.finishNode(node, "ExportNamedDeclaration"); + return false; } isAsyncFunction() { @@ -1605,17 +1675,9 @@ export default class StatementParser extends ExpressionParser { ); } - parseExportSpecifiersMaybe(node: N.ExportNamedDeclaration): void { - if (this.eat(tt.comma)) { - node.specifiers = node.specifiers.concat(this.parseExportSpecifiers()); - } - } - parseExportFrom(node: N.ExportNamedDeclaration, expect?: boolean): void { if (this.eatContextual("from")) { - node.source = this.match(tt.string) - ? this.parseExprAtom() - : this.unexpected(); + node.source = this.parseImportSource(); this.checkExport(node); } else { if (expect) { @@ -1628,39 +1690,6 @@ export default class StatementParser extends ExpressionParser { this.semicolon(); } - shouldParseExportStar(): boolean { - return this.match(tt.star); - } - - parseExportStar(node: N.ExportNamedDeclaration): void { - this.expect(tt.star); - - if (this.isContextual("as")) { - this.parseExportNamespace(node); - } else { - this.parseExportFrom(node, true); - this.finishNode(node, "ExportAllDeclaration"); - } - } - - parseExportNamespace(node: N.ExportNamedDeclaration): void { - this.expectPlugin("exportNamespaceFrom"); - - const specifier = this.startNodeAt( - this.state.lastTokStart, - this.state.lastTokStartLoc, - ); - - this.next(); - - specifier.exported = this.parseIdentifier(true); - - node.specifiers = [this.finishNode(specifier, "ExportNamespaceSpecifier")]; - - this.parseExportSpecifiersMaybe(node); - this.parseExportFrom(node, true); - } - shouldParseExportDeclaration(): boolean { if (this.match(tt.at)) { this.expectOnePlugin(["decorators", "decorators-legacy"]); @@ -1760,27 +1789,24 @@ export default class StatementParser extends ExpressionParser { } checkDuplicateExports( - node: N.Identifier | N.ExportNamedDeclaration | N.ExportSpecifier, + node: + | N.Identifier + | N.ExportNamedDeclaration + | N.ExportSpecifier + | N.ExportDefaultSpecifier, name: string, ): void { if (this.state.exportedIdentifiers.indexOf(name) > -1) { - this.raiseDuplicateExportError(node, name); + throw this.raise( + node.start, + name === "default" + ? "Only one default export allowed per module." + : `\`${name}\` has already been exported. Exported identifiers must be unique.`, + ); } this.state.exportedIdentifiers.push(name); } - raiseDuplicateExportError( - node: N.Identifier | N.ExportNamedDeclaration | N.ExportSpecifier, - name: string, - ): empty { - throw this.raise( - node.start, - name === "default" - ? "Only one default export allowed per module." - : `\`${name}\` has already been exported. Exported identifiers must be unique.`, - ); - } - // Parses a comma-separated list of module exports. parseExportSpecifiers(): Array { @@ -1820,23 +1846,26 @@ export default class StatementParser extends ExpressionParser { // Parses import declaration. - parseImport(node: N.Node): N.ImportDeclaration | N.TsImportEqualsDeclaration { + parseImport(node: N.Node): N.AnyImport { // import '...' - if (this.match(tt.string)) { - node.specifiers = []; - node.source = this.parseExprAtom(); - } else { - node.specifiers = []; - this.parseImportSpecifiers(node); + node.specifiers = []; + if (!this.match(tt.string)) { + const hasDefault = this.maybeParseDefaultImportSpecifier(node); + const parseNext = !hasDefault || this.eat(tt.comma); + const hasStar = parseNext && this.maybeParseStarImportSpecifier(node); + if (parseNext && !hasStar) this.parseNamedImportSpecifiers(node); this.expectContextual("from"); - node.source = this.match(tt.string) - ? this.parseExprAtom() - : this.unexpected(); } + node.source = this.parseImportSource(); this.semicolon(); return this.finishNode(node, "ImportDeclaration"); } + parseImportSource(): N.StringLiteral { + if (!this.match(tt.string)) this.unexpected(); + return this.parseExprAtom(); + } + // eslint-disable-next-line no-unused-vars shouldParseDefaultImport(node: N.ImportDeclaration): boolean { return this.match(tt.name); @@ -1853,9 +1882,7 @@ export default class StatementParser extends ExpressionParser { node.specifiers.push(this.finishNode(specifier, type)); } - // Parses a comma-separated list of module imports. - parseImportSpecifiers(node: N.ImportDeclaration): void { - let first = true; + maybeParseDefaultImportSpecifier(node: N.ImportDeclaration): boolean { if (this.shouldParseDefaultImport(node)) { // import defaultObj, { x, y as z } from '...' this.parseImportSpecifierLocal( @@ -1864,10 +1891,12 @@ export default class StatementParser extends ExpressionParser { "ImportDefaultSpecifier", "default import specifier", ); - - if (!this.eat(tt.comma)) return; + return true; } + return false; + } + maybeParseStarImportSpecifier(node: N.ImportDeclaration): boolean { if (this.match(tt.star)) { const specifier = this.startNode(); this.next(); @@ -1879,10 +1908,13 @@ export default class StatementParser extends ExpressionParser { "ImportNamespaceSpecifier", "import namespace specifier", ); - - return; + return true; } + return false; + } + parseNamedImportSpecifiers(node: N.ImportDeclaration) { + let first = true; this.expect(tt.braceL); while (!this.eat(tt.braceR)) { if (first) { diff --git a/packages/babel-parser/src/plugins/flow.js b/packages/babel-parser/src/plugins/flow.js index 16543413a5..0dbab81e3a 100644 --- a/packages/babel-parser/src/plugins/flow.js +++ b/packages/babel-parser/src/plugins/flow.js @@ -1847,17 +1847,15 @@ export default (superClass: Class): Class => super.assertModuleNodeAllowed(node); } - parseExport( - node: N.ExportNamedDeclaration | N.ExportAllDeclaration, - ): N.ExportNamedDeclaration | N.ExportAllDeclaration { - node = super.parseExport(node); + parseExport(node: N.Node): N.AnyExport { + const decl = super.parseExport(node); if ( - node.type === "ExportNamedDeclaration" || - node.type === "ExportAllDeclaration" + decl.type === "ExportNamedDeclaration" || + decl.type === "ExportAllDeclaration" ) { - node.exportKind = node.exportKind || "value"; + decl.exportKind = decl.exportKind || "value"; } - return node; + return decl; } parseExportDeclaration(node: N.ExportNamedDeclaration): ?N.Declaration { @@ -1893,27 +1891,26 @@ export default (superClass: Class): Class => } } - shouldParseExportStar(): boolean { - return ( - super.shouldParseExportStar() || - (this.isContextual("type") && this.lookahead().type === tt.star) - ); - } + eatExportStar(node: N.Node): boolean { + if (super.eatExportStar(...arguments)) return true; - parseExportStar(node: N.ExportNamedDeclaration): void { - if (this.eatContextual("type")) { + if (this.isContextual("type") && this.lookahead().type === tt.star) { node.exportKind = "type"; + this.next(); + this.next(); + return true; } - return super.parseExportStar(node); + return false; } - parseExportNamespace(node: N.ExportNamedDeclaration) { - if (node.exportKind === "type") { - this.unexpected(); + maybeParseExportNamespaceSpecifier(node: N.Node): boolean { + const pos = this.state.start; + const hasNamespace = super.maybeParseExportNamespaceSpecifier(node); + if (hasNamespace && node.exportKind === "type") { + this.unexpected(pos); } - - return super.parseExportNamespace(node); + return hasNamespace; } parseClassId(node: N.Class, isStatement: boolean, optionalId: ?boolean) { @@ -2225,7 +2222,7 @@ export default (superClass: Class): Class => } // parse typeof and type imports - parseImportSpecifiers(node: N.ImportDeclaration): void { + maybeParseDefaultImportSpecifier(node: N.ImportDeclaration): boolean { node.importKind = "value"; let kind = null; @@ -2252,7 +2249,7 @@ export default (superClass: Class): Class => } } - super.parseImportSpecifiers(node); + return super.maybeParseDefaultImportSpecifier(node); } // parse import-type/typeof shorthand diff --git a/packages/babel-parser/src/plugins/typescript.js b/packages/babel-parser/src/plugins/typescript.js index 3457001d8e..0a7c527dfa 100644 --- a/packages/babel-parser/src/plugins/typescript.js +++ b/packages/babel-parser/src/plugins/typescript.js @@ -1615,16 +1615,14 @@ export default (superClass: Class): Class => */ checkDuplicateExports() {} - parseImport( - node: N.Node, - ): N.ImportDeclaration | N.TsImportEqualsDeclaration { + parseImport(node: N.Node): N.AnyImport { if (this.match(tt.name) && this.lookahead().type === tt.eq) { return this.tsParseImportEqualsDeclaration(node); } return super.parseImport(node); } - parseExport(node: N.Node): N.Node { + parseExport(node: N.Node): N.AnyExport { if (this.match(tt._import)) { // `export import A = B;` this.expect(tt._import); diff --git a/packages/babel-parser/src/types.js b/packages/babel-parser/src/types.js index 433c9340d1..f00eb1be27 100644 --- a/packages/babel-parser/src/types.js +++ b/packages/babel-parser/src/types.js @@ -783,7 +783,9 @@ export type AnyExport = | ExportNamedDeclaration | ExportDefaultDeclaration | ExportAllDeclaration - | TsExportAssignment; + | TsExportAssignment + | TsImportEqualsDeclaration + | TsNamespaceExportDeclaration; export type ModuleSpecifier = NodeBase & { local: Identifier, @@ -820,7 +822,7 @@ export type ImportNamespaceSpecifier = ModuleSpecifier & { export type ExportNamedDeclaration = NodeBase & { type: "ExportNamedDeclaration", declaration: ?Declaration, - specifiers: $ReadOnlyArray, + specifiers: $ReadOnlyArray, source: ?Literal, exportKind?: "type" | "value", // TODO: Not in spec @@ -831,6 +833,11 @@ export type ExportSpecifier = NodeBase & { exported: Identifier, }; +export type ExportDefaultSpecifier = NodeBase & { + type: "ExportDefaultSpecifier", + exported: Identifier, +}; + export type ExportDefaultDeclaration = NodeBase & { type: "ExportDefaultDeclaration", declaration: diff --git a/packages/babel-parser/test/fixtures/experimental/export-extensions/default-and-named/output.json b/packages/babel-parser/test/fixtures/experimental/export-extensions/default-and-named/output.json index e4c389eba1..e65a50b154 100644 --- a/packages/babel-parser/test/fixtures/experimental/export-extensions/default-and-named/output.json +++ b/packages/babel-parser/test/fixtures/experimental/export-extensions/default-and-named/output.json @@ -145,7 +145,8 @@ "raw": "\"bar\"" }, "value": "bar" - } + }, + "declaration": null } ], "directives": [] diff --git a/packages/babel-parser/test/fixtures/experimental/export-extensions/ns-and-named/output.json b/packages/babel-parser/test/fixtures/experimental/export-extensions/ns-and-named/output.json index 0cdcb04cb4..0c6d340076 100644 --- a/packages/babel-parser/test/fixtures/experimental/export-extensions/ns-and-named/output.json +++ b/packages/babel-parser/test/fixtures/experimental/export-extensions/ns-and-named/output.json @@ -145,7 +145,8 @@ "raw": "\"bar\"" }, "value": "bar" - } + }, + "declaration": null } ], "directives": []