diff --git a/packages/babel-parser/src/parser/error-message.js b/packages/babel-parser/src/parser/error-message.js index 033fe814e87f..430c44289213 100644 --- a/packages/babel-parser/src/parser/error-message.js +++ b/packages/babel-parser/src/parser/error-message.js @@ -42,6 +42,8 @@ export const ErrorMessages = Object.freeze({ DuplicateRegExpFlags: "Duplicate regular expression flag", ElementAfterRest: "Rest element must be last element", EscapedCharNotAnIdentifier: "Invalid Unicode escape", + ExportDefaultFromAsIdentifier: + "'from' is not allowed as an identifier after 'export default'", ForInOfLoopInitializer: "%0 loop variable declaration may not have an initializer", GeneratorInSingleStatementContext: diff --git a/packages/babel-parser/src/parser/statement.js b/packages/babel-parser/src/parser/statement.js index 521d13c9fb63..e201cfcd36cc 100644 --- a/packages/babel-parser/src/parser/statement.js +++ b/packages/babel-parser/src/parser/statement.js @@ -1857,10 +1857,24 @@ export default class StatementParser extends ExpressionParser { } const next = this.nextTokenStart(); - return ( + const hasFrom = this.isUnparsedContextual(next, "from"); + if ( this.input.charCodeAt(next) === charCodes.comma || - this.isUnparsedContextual(next, "from") - ); + (this.match(tt.name) && hasFrom) + ) { + return true; + } + // lookahead again when `export default from` is seen + if (this.match(tt._default) && hasFrom) { + const nextAfterFrom = this.input.charCodeAt( + this.nextTokenStartSince(next + 4), + ); + return ( + nextAfterFrom === charCodes.quotationMark || + nextAfterFrom === charCodes.apostrophe + ); + } + return false; } parseExportFrom(node: N.ExportNamedDeclaration, expect?: boolean): void { @@ -1911,6 +1925,18 @@ export default class StatementParser extends ExpressionParser { if (isDefault) { // Default exports this.checkDuplicateExports(node, "default"); + if (this.hasPlugin("exportDefaultFrom")) { + const declaration = ((node: any): N.ExportDefaultDeclaration) + .declaration; + if ( + declaration.type === "Identifier" && + declaration.name === "from" && + declaration.end - declaration.start === 4 && // does not contain escape + !declaration.extra?.parenthesized + ) { + this.raise(declaration.start, Errors.ExportDefaultFromAsIdentifier); + } + } } else if (node.specifiers && node.specifiers.length) { // Named exports for (const specifier of node.specifiers) { diff --git a/packages/babel-parser/src/plugins/placeholders.js b/packages/babel-parser/src/plugins/placeholders.js index 646494d1c5fe..69d961e3e6c9 100644 --- a/packages/babel-parser/src/plugins/placeholders.js +++ b/packages/babel-parser/src/plugins/placeholders.js @@ -251,6 +251,23 @@ export default (superClass: Class): Class => return super.parseExport(node); } + isExportDefaultSpecifier(): boolean { + if (this.match(tt._default)) { + const next = this.nextTokenStart(); + if (this.isUnparsedContextual(next, "from")) { + if ( + this.input.startsWith( + tt.placeholder.label, + this.nextTokenStartSince(next + 4), + ) + ) { + return true; + } + } + } + return super.isExportDefaultSpecifier(); + } + maybeParseExportDefaultSpecifier(node: N.Node): boolean { if (node.specifiers && node.specifiers.length > 0) { // "export %%NAME%%" has already been parsed by #parseExport. diff --git a/packages/babel-parser/src/tokenizer/index.js b/packages/babel-parser/src/tokenizer/index.js index a6e44c027d97..55cda33a6ed5 100644 --- a/packages/babel-parser/src/tokenizer/index.js +++ b/packages/babel-parser/src/tokenizer/index.js @@ -190,11 +190,14 @@ export default class Tokenizer extends ParserErrors { } nextTokenStart(): number { - const thisTokEnd = this.state.pos; - skipWhiteSpace.lastIndex = thisTokEnd; + return this.nextTokenStartSince(this.state.pos); + } + + nextTokenStartSince(pos: number): number { + skipWhiteSpace.lastIndex = pos; const skip = skipWhiteSpace.exec(this.input); // $FlowIgnore: The skipWhiteSpace ensures to match any string - return thisTokEnd + skip[0].length; + return pos + skip[0].length; } lookaheadCharCode(): number { diff --git a/packages/babel-parser/test/fixtures/esprima/es2015-export-declaration/export-default-from-as-identifier-2/input.js b/packages/babel-parser/test/fixtures/esprima/es2015-export-declaration/export-default-from-as-identifier-2/input.js new file mode 100644 index 000000000000..6539d5765f62 --- /dev/null +++ b/packages/babel-parser/test/fixtures/esprima/es2015-export-declaration/export-default-from-as-identifier-2/input.js @@ -0,0 +1 @@ +export default from (bar); diff --git a/packages/babel-parser/test/fixtures/esprima/es2015-export-declaration/export-default-from-as-identifier-2/output.json b/packages/babel-parser/test/fixtures/esprima/es2015-export-declaration/export-default-from-as-identifier-2/output.json new file mode 100644 index 000000000000..e873e886ca8d --- /dev/null +++ b/packages/babel-parser/test/fixtures/esprima/es2015-export-declaration/export-default-from-as-identifier-2/output.json @@ -0,0 +1,33 @@ +{ + "type": "File", + "start":0,"end":26,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":26}}, + "program": { + "type": "Program", + "start":0,"end":26,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":26}}, + "sourceType": "module", + "interpreter": null, + "body": [ + { + "type": "ExportDefaultDeclaration", + "start":0,"end":26,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":26}}, + "declaration": { + "type": "CallExpression", + "start":15,"end":25,"loc":{"start":{"line":1,"column":15},"end":{"line":1,"column":25}}, + "callee": { + "type": "Identifier", + "start":15,"end":19,"loc":{"start":{"line":1,"column":15},"end":{"line":1,"column":19},"identifierName":"from"}, + "name": "from" + }, + "arguments": [ + { + "type": "Identifier", + "start":21,"end":24,"loc":{"start":{"line":1,"column":21},"end":{"line":1,"column":24},"identifierName":"bar"}, + "name": "bar" + } + ] + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/esprima/es2015-export-declaration/export-default-from-as-identifier-3/input.js b/packages/babel-parser/test/fixtures/esprima/es2015-export-declaration/export-default-from-as-identifier-3/input.js new file mode 100644 index 000000000000..7690fcc07197 --- /dev/null +++ b/packages/babel-parser/test/fixtures/esprima/es2015-export-declaration/export-default-from-as-identifier-3/input.js @@ -0,0 +1 @@ +export default from ?? 42; diff --git a/packages/babel-parser/test/fixtures/esprima/es2015-export-declaration/export-default-from-as-identifier-3/output.json b/packages/babel-parser/test/fixtures/esprima/es2015-export-declaration/export-default-from-as-identifier-3/output.json new file mode 100644 index 000000000000..6af2532e8508 --- /dev/null +++ b/packages/babel-parser/test/fixtures/esprima/es2015-export-declaration/export-default-from-as-identifier-3/output.json @@ -0,0 +1,36 @@ +{ + "type": "File", + "start":0,"end":26,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":26}}, + "program": { + "type": "Program", + "start":0,"end":26,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":26}}, + "sourceType": "module", + "interpreter": null, + "body": [ + { + "type": "ExportDefaultDeclaration", + "start":0,"end":26,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":26}}, + "declaration": { + "type": "LogicalExpression", + "start":15,"end":25,"loc":{"start":{"line":1,"column":15},"end":{"line":1,"column":25}}, + "left": { + "type": "Identifier", + "start":15,"end":19,"loc":{"start":{"line":1,"column":15},"end":{"line":1,"column":19},"identifierName":"from"}, + "name": "from" + }, + "operator": "??", + "right": { + "type": "NumericLiteral", + "start":23,"end":25,"loc":{"start":{"line":1,"column":23},"end":{"line":1,"column":25}}, + "extra": { + "rawValue": 42, + "raw": "42" + }, + "value": 42 + } + } + } + ], + "directives": [] + } +} diff --git a/packages/babel-parser/test/fixtures/esprima/es2015-export-declaration/export-default-from-as-identifier/input.js b/packages/babel-parser/test/fixtures/esprima/es2015-export-declaration/export-default-from-as-identifier/input.js new file mode 100644 index 000000000000..79719db06e63 --- /dev/null +++ b/packages/babel-parser/test/fixtures/esprima/es2015-export-declaration/export-default-from-as-identifier/input.js @@ -0,0 +1 @@ +export default from; diff --git a/packages/babel-parser/test/fixtures/esprima/es2015-export-declaration/export-default-from-as-identifier/output.json b/packages/babel-parser/test/fixtures/esprima/es2015-export-declaration/export-default-from-as-identifier/output.json new file mode 100644 index 000000000000..efa51a6eeef9 --- /dev/null +++ b/packages/babel-parser/test/fixtures/esprima/es2015-export-declaration/export-default-from-as-identifier/output.json @@ -0,0 +1,22 @@ +{ + "type": "File", + "start":0,"end":20,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":20}}, + "program": { + "type": "Program", + "start":0,"end":20,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":20}}, + "sourceType": "module", + "interpreter": null, + "body": [ + { + "type": "ExportDefaultDeclaration", + "start":0,"end":20,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":20}}, + "declaration": { + "type": "Identifier", + "start":15,"end":19,"loc":{"start":{"line":1,"column":15},"end":{"line":1,"column":19},"identifierName":"from"}, + "name": "from" + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/experimental/export-extensions/default-default-asi/input.js b/packages/babel-parser/test/fixtures/experimental/export-extensions/default-default-asi/input.js new file mode 100644 index 000000000000..3b8c1ebc1ca0 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/export-extensions/default-default-asi/input.js @@ -0,0 +1,2 @@ +export default from +"bar"; diff --git a/packages/babel-parser/test/fixtures/experimental/export-extensions/default-default-asi/options.json b/packages/babel-parser/test/fixtures/experimental/export-extensions/default-default-asi/options.json new file mode 100644 index 000000000000..164d90422fb2 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/export-extensions/default-default-asi/options.json @@ -0,0 +1,4 @@ +{ + "sourceType": "module", + "plugins": ["exportDefaultFrom"] +} diff --git a/packages/babel-parser/test/fixtures/experimental/export-extensions/default-default-asi/output.json b/packages/babel-parser/test/fixtures/experimental/export-extensions/default-default-asi/output.json new file mode 100644 index 000000000000..de0d1cce86c4 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/export-extensions/default-default-asi/output.json @@ -0,0 +1,37 @@ +{ + "type": "File", + "start":0,"end":26,"loc":{"start":{"line":1,"column":0},"end":{"line":2,"column":6}}, + "program": { + "type": "Program", + "start":0,"end":26,"loc":{"start":{"line":1,"column":0},"end":{"line":2,"column":6}}, + "sourceType": "module", + "interpreter": null, + "body": [ + { + "type": "ExportNamedDeclaration", + "start":0,"end":26,"loc":{"start":{"line":1,"column":0},"end":{"line":2,"column":6}}, + "specifiers": [ + { + "type": "ExportDefaultSpecifier", + "start":7,"end":14,"loc":{"start":{"line":1,"column":7},"end":{"line":1,"column":14}}, + "exported": { + "type": "Identifier", + "start":7,"end":14,"loc":{"start":{"line":1,"column":7},"end":{"line":1,"column":14},"identifierName":"default"}, + "name": "default" + } + } + ], + "source": { + "type": "StringLiteral", + "start":20,"end":25,"loc":{"start":{"line":2,"column":0},"end":{"line":2,"column":5}}, + "extra": { + "rawValue": "bar", + "raw": "\"bar\"" + }, + "value": "bar" + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/experimental/export-extensions/default-from-identifier-escaped/input.js b/packages/babel-parser/test/fixtures/experimental/export-extensions/default-from-identifier-escaped/input.js new file mode 100644 index 000000000000..a202ed0db383 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/export-extensions/default-from-identifier-escaped/input.js @@ -0,0 +1 @@ +export default \u{66}rom; diff --git a/packages/babel-parser/test/fixtures/experimental/export-extensions/default-from-identifier-escaped/options.json b/packages/babel-parser/test/fixtures/experimental/export-extensions/default-from-identifier-escaped/options.json new file mode 100644 index 000000000000..fb17f33a28b5 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/export-extensions/default-from-identifier-escaped/options.json @@ -0,0 +1,4 @@ +{ + "plugins": ["exportDefaultFrom"], + "sourceType": "module" +} diff --git a/packages/babel-parser/test/fixtures/experimental/export-extensions/default-from-identifier-escaped/output.json b/packages/babel-parser/test/fixtures/experimental/export-extensions/default-from-identifier-escaped/output.json new file mode 100644 index 000000000000..622e63e1a1fd --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/export-extensions/default-from-identifier-escaped/output.json @@ -0,0 +1,22 @@ +{ + "type": "File", + "start":0,"end":25,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":25}}, + "program": { + "type": "Program", + "start":0,"end":25,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":25}}, + "sourceType": "module", + "interpreter": null, + "body": [ + { + "type": "ExportDefaultDeclaration", + "start":0,"end":25,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":25}}, + "declaration": { + "type": "Identifier", + "start":15,"end":24,"loc":{"start":{"line":1,"column":15},"end":{"line":1,"column":24},"identifierName":"from"}, + "name": "from" + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/experimental/export-extensions/default-from-identifier-parenthesized/input.js b/packages/babel-parser/test/fixtures/experimental/export-extensions/default-from-identifier-parenthesized/input.js new file mode 100644 index 000000000000..74c7cb934c5d --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/export-extensions/default-from-identifier-parenthesized/input.js @@ -0,0 +1 @@ +export default (from); diff --git a/packages/babel-parser/test/fixtures/experimental/export-extensions/default-from-identifier-parenthesized/options.json b/packages/babel-parser/test/fixtures/experimental/export-extensions/default-from-identifier-parenthesized/options.json new file mode 100644 index 000000000000..fb17f33a28b5 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/export-extensions/default-from-identifier-parenthesized/options.json @@ -0,0 +1,4 @@ +{ + "plugins": ["exportDefaultFrom"], + "sourceType": "module" +} diff --git a/packages/babel-parser/test/fixtures/experimental/export-extensions/default-from-identifier-parenthesized/output.json b/packages/babel-parser/test/fixtures/experimental/export-extensions/default-from-identifier-parenthesized/output.json new file mode 100644 index 000000000000..215a4df47bb6 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/export-extensions/default-from-identifier-parenthesized/output.json @@ -0,0 +1,26 @@ +{ + "type": "File", + "start":0,"end":22,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":22}}, + "program": { + "type": "Program", + "start":0,"end":22,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":22}}, + "sourceType": "module", + "interpreter": null, + "body": [ + { + "type": "ExportDefaultDeclaration", + "start":0,"end":22,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":22}}, + "declaration": { + "type": "Identifier", + "start":16,"end":20,"loc":{"start":{"line":1,"column":16},"end":{"line":1,"column":20},"identifierName":"from"}, + "name": "from", + "extra": { + "parenthesized": true, + "parenStart": 15 + } + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/experimental/export-extensions/invalid-default-from-identifier/input.js b/packages/babel-parser/test/fixtures/experimental/export-extensions/invalid-default-from-identifier/input.js new file mode 100644 index 000000000000..79719db06e63 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/export-extensions/invalid-default-from-identifier/input.js @@ -0,0 +1 @@ +export default from; diff --git a/packages/babel-parser/test/fixtures/experimental/export-extensions/invalid-default-from-identifier/options.json b/packages/babel-parser/test/fixtures/experimental/export-extensions/invalid-default-from-identifier/options.json new file mode 100644 index 000000000000..fb17f33a28b5 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/export-extensions/invalid-default-from-identifier/options.json @@ -0,0 +1,4 @@ +{ + "plugins": ["exportDefaultFrom"], + "sourceType": "module" +} diff --git a/packages/babel-parser/test/fixtures/experimental/export-extensions/invalid-default-from-identifier/output.json b/packages/babel-parser/test/fixtures/experimental/export-extensions/invalid-default-from-identifier/output.json new file mode 100644 index 000000000000..cc0e24e848fe --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/export-extensions/invalid-default-from-identifier/output.json @@ -0,0 +1,25 @@ +{ + "type": "File", + "start":0,"end":20,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":20}}, + "errors": [ + "SyntaxError: 'from' is not allowed as an identifier after 'export default' (1:15)" + ], + "program": { + "type": "Program", + "start":0,"end":20,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":20}}, + "sourceType": "module", + "interpreter": null, + "body": [ + { + "type": "ExportDefaultDeclaration", + "start":0,"end":20,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":20}}, + "declaration": { + "type": "Identifier", + "start":15,"end":19,"loc":{"start":{"line":1,"column":15},"end":{"line":1,"column":19},"identifierName":"from"}, + "name": "from" + } + } + ], + "directives": [] + } +} \ No newline at end of file