diff --git a/packages/babel-generator/src/node/parentheses.ts b/packages/babel-generator/src/node/parentheses.ts index 58463a354f25..8d7bb6081dff 100644 --- a/packages/babel-generator/src/node/parentheses.ts +++ b/packages/babel-generator/src/node/parentheses.ts @@ -292,7 +292,28 @@ export function LogicalExpression(node: any, parent: any): boolean { } } -export function Identifier(node: t.Identifier, parent: t.Node): boolean { +export function Identifier( + node: t.Identifier, + parent: t.Node, + printStack: Array, +): boolean { + // Non-strict code allows the identifier `let`, but it cannot occur as-is in + // certain contexts to avoid ambiguity with contextual keyword `let`. + if (node.name === "let") { + // Some contexts only forbid `let [`, so check if the next token would + // be the left bracket of a computed member expression. + const isFollowedByBracket = t.isMemberExpression(parent, { + object: node, + computed: true, + }); + return isFirstInContext(printStack, { + expressionStatement: isFollowedByBracket, + forHead: isFollowedByBracket, + forInHead: isFollowedByBracket, + forOfHead: true, + }); + } + // ECMAScript specifically forbids a for-of loop from starting with the // token sequence `for (async of`, because it would be ambiguous with // `for (async of => {};;)`, so we need to add extra parentheses. @@ -310,7 +331,14 @@ export function Identifier(node: t.Identifier, parent: t.Node): boolean { // in a particular context. function isFirstInContext( printStack: Array, - { expressionStatement = false, arrowBody = false, exportDefault = false }, + { + expressionStatement = false, + arrowBody = false, + exportDefault = false, + forHead = false, + forInHead = false, + forOfHead = false, + }, ): boolean { let i = printStack.length - 1; let node = printStack[i]; @@ -322,7 +350,10 @@ function isFirstInContext( t.isExpressionStatement(parent, { expression: node })) || (exportDefault && t.isExportDefaultDeclaration(parent, { declaration: node })) || - (arrowBody && t.isArrowFunctionExpression(parent, { body: node })) + (arrowBody && t.isArrowFunctionExpression(parent, { body: node })) || + (forHead && t.isForStatement(parent, { init: node })) || + (forInHead && t.isForInStatement(parent, { left: node })) || + (forOfHead && t.isForOfStatement(parent, { left: node })) ) { return true; } diff --git a/packages/babel-generator/test/fixtures/edgecase/let-identifier/input.js b/packages/babel-generator/test/fixtures/edgecase/let-identifier/input.js new file mode 100644 index 000000000000..a008f568639b --- /dev/null +++ b/packages/babel-generator/test/fixtures/edgecase/let-identifier/input.js @@ -0,0 +1,31 @@ +/* ExpressionStatement */ +let; +\u006cet[x]; +(let)[x]; +a[let[x]]; + +/* ForStatement */ +for (let;;); +for (\u006cet[x];;); +for ((let)[x];;); +for (a[let[x]];;); + +/* ForInStatement */ +for (let in {}); +for (\u006cet[x] in {}); +for ((let)[x] in {}); +for (a[let[x]] in {}); + +/* ForOfStatement { await: false } */ +for ((let) of []); +for (\u006cet of []); +for ((let)[x] of []); +for (a[let] of []); + +/* ForOfStatement { await: true } */ +async () => { + for await ((let) of []); + for await (\u006cet of []); + for await ((let)[x] of []); + for await (a[let] of []); +} diff --git a/packages/babel-generator/test/fixtures/edgecase/let-identifier/options.json b/packages/babel-generator/test/fixtures/edgecase/let-identifier/options.json new file mode 100644 index 000000000000..081a74e6eafe --- /dev/null +++ b/packages/babel-generator/test/fixtures/edgecase/let-identifier/options.json @@ -0,0 +1,4 @@ +{ + "sourceType": "script", + "strictMode": false +} diff --git a/packages/babel-generator/test/fixtures/edgecase/let-identifier/output.js b/packages/babel-generator/test/fixtures/edgecase/let-identifier/output.js new file mode 100644 index 000000000000..62d41eff3059 --- /dev/null +++ b/packages/babel-generator/test/fixtures/edgecase/let-identifier/output.js @@ -0,0 +1,46 @@ +/* ExpressionStatement */ +let; +(let)[x]; +(let)[x]; +a[let[x]]; +/* ForStatement */ + +for (let;;); + +for ((let)[x];;); + +for ((let)[x];;); + +for (a[let[x]];;); +/* ForInStatement */ + + +for (let in {}); + +for ((let)[x] in {}); + +for ((let)[x] in {}); + +for (a[let[x]] in {}); +/* ForOfStatement { await: false } */ + + +for ((let) of []); + +for ((let) of []); + +for ((let)[x] of []); + +for (a[let] of []); +/* ForOfStatement { await: true } */ + + +async () => { + for await ((let) of []); + + for await ((let) of []); + + for await ((let)[x] of []); + + for await (a[let] of []); +}; \ No newline at end of file