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

Properly parse export default from when exportDefaultFrom is not enabled #11676

Merged
merged 6 commits into from Jun 5, 2020
Merged
Show file tree
Hide file tree
Changes from 4 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
2 changes: 2 additions & 0 deletions packages/babel-parser/src/parser/error-message.js
Expand Up @@ -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:
Expand Down
31 changes: 28 additions & 3 deletions packages/babel-parser/src/parser/statement.js
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why we need to special-case these two characters and why not, for example ( or *.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we see a string literal after from, it is considered a fromClause and thus it is indeed export default fromClause instead of export default AssignmentExpression.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was reading quaotationMark as questionMark 😁

);
}
return false;
}

parseExportFrom(node: N.ExportNamedDeclaration, expect?: boolean): void {
Expand Down Expand Up @@ -1911,6 +1925,17 @@ 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
JLHwung marked this conversation as resolved.
Show resolved Hide resolved
) {
this.raise(declaration.start, Errors.ExportDefaultFromAsIdentifier);
}
}
} else if (node.specifiers && node.specifiers.length) {
// Named exports
for (const specifier of node.specifiers) {
Expand Down
15 changes: 15 additions & 0 deletions packages/babel-parser/src/plugins/placeholders.js
Expand Up @@ -251,6 +251,21 @@ export default (superClass: Class<Parser>): Class<Parser> =>
return super.parseExport(node);
}

isExportDefaultSpecifier(): boolean {
if (this.match(tt._default)) {
const next = this.nextTokenStart();
if (this.isUnparsedContextual(next, "from")) {
if (
this.input.substr(this.nextTokenStartSince(next + 4), 2) ===
tt.placeholder.label
JLHwung marked this conversation as resolved.
Show resolved Hide resolved
) {
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.
Expand Down
9 changes: 6 additions & 3 deletions packages/babel-parser/src/tokenizer/index.js
Expand Up @@ -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 {
Expand Down
@@ -0,0 +1 @@
export default from (bar);
@@ -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": []
}
}
@@ -0,0 +1 @@
export default from ?? 42;
@@ -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": []
}
}
@@ -0,0 +1 @@
export default from;
@@ -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": []
}
}
@@ -0,0 +1,2 @@
export default from
"bar";
@@ -0,0 +1,4 @@
{
"sourceType": "module",
"plugins": ["exportDefaultFrom"]
}
@@ -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": []
}
}
@@ -0,0 +1 @@
export default \u{66}rom;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤦‍♂️IMO it should be addressed in the export-default-from proposal that while export default from is banned, export default \u{66}rom is still valid.

@@ -0,0 +1,4 @@
{
"plugins": ["exportDefaultFrom"],
"sourceType": "module"
}
@@ -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": []
}
}
@@ -0,0 +1 @@
export default from;
@@ -0,0 +1,4 @@
{
"plugins": ["exportDefaultFrom"],
"sourceType": "module"
}
@@ -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": []
}
}