Print parentheses around identifier let where necessary (#13269)

This commit is contained in:
Stuart Cook 2021-05-07 05:10:45 +10:00 committed by GitHub
parent 96fce81438
commit 68bc4dfd31
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 154 additions and 12 deletions

View File

@ -74,7 +74,10 @@ export function ObjectExpression(
parent: any,
printStack: Array<any>,
): 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<any>,
): 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<any>,
): 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<any>,
): boolean {
return isFirstInStatement(printStack, { considerDefaultExports: true });
return isFirstInContext(printStack, {
expressionStatement: true,
exportDefault: true,
});
}
export function ArrowFunctionExpression(node: any, parent: any): boolean {
@ -281,7 +292,34 @@ 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<t.Node>,
): 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,
}) ||
t.isOptionalMemberExpression(parent, {
object: node,
computed: true,
optional: false,
});
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 +334,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<any>,
{ considerArrow = false, considerDefaultExports = false } = {},
// in a particular context.
function isFirstInContext(
printStack: Array<t.Node>,
{
expressionStatement = false,
arrowBody = false,
exportDefault = false,
forHead = false,
forInHead = false,
forOfHead = false,
},
): boolean {
let i = printStack.length - 1;
let node = printStack[i];
@ -307,10 +352,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;
}

View File

@ -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 []);
}

View File

@ -0,0 +1,4 @@
{
"sourceType": "script",
"strictMode": false
}

View File

@ -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 []);
};

View File

@ -763,6 +763,18 @@ describe("programmatic generation", function () {
expect(output).toBe("interface A {}");
});
});
describe("identifier let", () => {
it("detects open bracket from non-optional OptionalMemberExpression", () => {
const ast = parse(`for (let?.[x];;);`, {
sourceType: "script",
strictMode: "false",
});
ast.program.body[0].init.optional = false;
const output = generate(ast).code;
expect(output).toBe("for ((let)[x];;);");
});
});
});
describe("CodeGenerator", function () {