From 70b92f868a00bb7eab315678cefe85930e548cf9 Mon Sep 17 00:00:00 2001 From: Stuart Cook Date: Thu, 6 May 2021 21:52:39 +1000 Subject: [PATCH] Print parentheses around identifier `let` where necessary --- .../babel-generator/src/node/parentheses.ts | 67 +++++++++++++++---- .../fixtures/edgecase/let-identifier/input.js | 31 +++++++++ .../edgecase/let-identifier/options.json | 4 ++ .../edgecase/let-identifier/output.js | 46 +++++++++++++ 4 files changed, 136 insertions(+), 12 deletions(-) create mode 100644 packages/babel-generator/test/fixtures/edgecase/let-identifier/input.js create mode 100644 packages/babel-generator/test/fixtures/edgecase/let-identifier/options.json create mode 100644 packages/babel-generator/test/fixtures/edgecase/let-identifier/output.js diff --git a/packages/babel-generator/src/node/parentheses.ts b/packages/babel-generator/src/node/parentheses.ts index e5666b974eb5..8d7bb6081dff 100644 --- a/packages/babel-generator/src/node/parentheses.ts +++ b/packages/babel-generator/src/node/parentheses.ts @@ -74,7 +74,10 @@ export function ObjectExpression( parent: any, printStack: Array, ): boolean { - return isFirstInStatement(printStack, { considerArrow: true }); + return isFirstInContext(printStack, { + expressionStatement: true, + arrowBody: true, + }); } export function DoExpression( @@ -83,7 +86,9 @@ export function DoExpression( printStack: Array, ): boolean { // `async do` can start an expression statement - return !node.async && isFirstInStatement(printStack); + return ( + !node.async && isFirstInContext(printStack, { expressionStatement: true }) + ); } export function Binary(node: any, parent: any): boolean { @@ -214,7 +219,10 @@ export function ClassExpression( parent: any, printStack: Array, ): boolean { - return isFirstInStatement(printStack, { considerDefaultExports: true }); + return isFirstInContext(printStack, { + expressionStatement: true, + exportDefault: true, + }); } export function UnaryLike(node: any, parent: any): boolean { @@ -230,7 +238,10 @@ export function FunctionExpression( parent: any, printStack: Array, ): boolean { - return isFirstInStatement(printStack, { considerDefaultExports: true }); + return isFirstInContext(printStack, { + expressionStatement: true, + exportDefault: true, + }); } export function ArrowFunctionExpression(node: any, parent: any): boolean { @@ -281,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. @@ -296,10 +328,17 @@ export function Identifier(node: t.Identifier, parent: t.Node): boolean { } // Walk up the print stack to determine if our node can come first -// in statement. -function isFirstInStatement( - printStack: Array, - { considerArrow = false, considerDefaultExports = false } = {}, +// in a particular context. +function isFirstInContext( + printStack: Array, + { + expressionStatement = false, + arrowBody = false, + exportDefault = false, + forHead = false, + forInHead = false, + forOfHead = false, + }, ): boolean { let i = printStack.length - 1; let node = printStack[i]; @@ -307,10 +346,14 @@ function isFirstInStatement( let parent = printStack[i]; while (i >= 0) { if ( - t.isExpressionStatement(parent, { expression: node }) || - (considerDefaultExports && + (expressionStatement && + t.isExpressionStatement(parent, { expression: node })) || + (exportDefault && t.isExportDefaultDeclaration(parent, { declaration: node })) || - (considerArrow && 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