🤖 Pick PR #62311 (Fix parenthesizer rules for manuall...) into release-5.9 (#62424)

Co-authored-by: Mateusz Burzyński <mateuszburzynski@gmail.com>
This commit is contained in:
TypeScript Bot
2025-09-25 10:24:24 -07:00
committed by GitHub
parent 0d9b9b92e2
commit bdb641a434
20 changed files with 338 additions and 1 deletions

View File

@@ -112,6 +112,16 @@ export function createParenthesizerRules(factory: NodeFactory): ParenthesizerRul
return parenthesizerRule;
}
function mixingBinaryOperatorsRequiresParentheses(a: SyntaxKind, b: SyntaxKind) {
if (a === SyntaxKind.QuestionQuestionToken) {
return b === SyntaxKind.AmpersandAmpersandToken || b === SyntaxKind.BarBarToken;
}
if (b === SyntaxKind.QuestionQuestionToken) {
return a === SyntaxKind.AmpersandAmpersandToken || a === SyntaxKind.BarBarToken;
}
return false;
}
/**
* Determines whether the operand to a BinaryExpression needs to be parenthesized.
*
@@ -121,6 +131,10 @@ export function createParenthesizerRules(factory: NodeFactory): ParenthesizerRul
* BinaryExpression.
*/
function binaryOperandNeedsParentheses(binaryOperator: SyntaxKind, operand: Expression, isLeftSideOfBinary: boolean, leftOperand: Expression | undefined) {
const emittedOperand = skipPartiallyEmittedExpressions(operand);
if (isBinaryExpression(emittedOperand) && mixingBinaryOperatorsRequiresParentheses(binaryOperator, emittedOperand.operatorToken.kind)) {
return true;
}
// If the operand has lower precedence, then it needs to be parenthesized to preserve the
// intent of the expression. For example, if the operand is `a + b` and the operator is
// `*`, then we need to parenthesize the operand to preserve the intended order of
@@ -140,7 +154,6 @@ export function createParenthesizerRules(factory: NodeFactory): ParenthesizerRul
// the intended order of operations: `(a ** b) ** c`
const binaryOperatorPrecedence = getOperatorPrecedence(SyntaxKind.BinaryExpression, binaryOperator);
const binaryOperatorAssociativity = getOperatorAssociativity(SyntaxKind.BinaryExpression, binaryOperator);
const emittedOperand = skipPartiallyEmittedExpressions(operand);
if (!isLeftSideOfBinary && operand.kind === SyntaxKind.ArrowFunction && binaryOperatorPrecedence > OperatorPrecedence.Assignment) {
// We need to parenthesize arrow functions on the right side to avoid it being
// parsed as parenthesized expression: `a && (() => {})`

View File

@@ -423,5 +423,311 @@ describe("unittests:: PrinterAPI", () => {
),
ts.createSourceFile("source.ts", "", ts.ScriptTarget.ESNext),
));
printsCorrectly("binaryBarBarExpressionWithLeftBinaryQuestionQuestionExpression", {}, printer =>
printer.printNode(
ts.EmitHint.Unspecified,
ts.factory.createExpressionStatement(
ts.factory.createBinaryExpression(
ts.factory.createBinaryExpression(
ts.factory.createIdentifier("a"),
ts.factory.createToken(ts.SyntaxKind.QuestionQuestionToken),
ts.factory.createIdentifier("b"),
),
ts.factory.createToken(ts.SyntaxKind.BarBarToken),
ts.factory.createIdentifier("c"),
),
),
ts.createSourceFile("source.ts", "", ts.ScriptTarget.ESNext),
));
printsCorrectly("binaryAmpersandAmpersandExpressionWithLeftBinaryQuestionQuestionExpression", {}, printer =>
printer.printNode(
ts.EmitHint.Unspecified,
ts.factory.createExpressionStatement(
ts.factory.createBinaryExpression(
ts.factory.createBinaryExpression(
ts.factory.createIdentifier("a"),
ts.factory.createToken(ts.SyntaxKind.QuestionQuestionToken),
ts.factory.createIdentifier("b"),
),
ts.factory.createToken(ts.SyntaxKind.AmpersandAmpersandToken),
ts.factory.createIdentifier("c"),
),
),
ts.createSourceFile("source.ts", "", ts.ScriptTarget.ESNext),
));
printsCorrectly("binaryBarBarExpressionWithRightBinaryQuestionQuestionExpression", {}, printer =>
printer.printNode(
ts.EmitHint.Unspecified,
ts.factory.createExpressionStatement(
ts.factory.createBinaryExpression(
ts.factory.createIdentifier("a"),
ts.factory.createToken(ts.SyntaxKind.BarBarToken),
ts.factory.createBinaryExpression(
ts.factory.createIdentifier("b"),
ts.factory.createToken(ts.SyntaxKind.QuestionQuestionToken),
ts.factory.createIdentifier("c"),
),
),
),
ts.createSourceFile("source.ts", "", ts.ScriptTarget.ESNext),
));
printsCorrectly("binaryAmpersandAmpersandExpressionWithRightBinaryQuestionQuestionExpression", {}, printer =>
printer.printNode(
ts.EmitHint.Unspecified,
ts.factory.createExpressionStatement(
ts.factory.createBinaryExpression(
ts.factory.createIdentifier("a"),
ts.factory.createToken(ts.SyntaxKind.AmpersandAmpersandToken),
ts.factory.createBinaryExpression(
ts.factory.createIdentifier("b"),
ts.factory.createToken(ts.SyntaxKind.QuestionQuestionToken),
ts.factory.createIdentifier("c"),
),
),
),
ts.createSourceFile("source.ts", "", ts.ScriptTarget.ESNext),
));
printsCorrectly("binaryQuestionQuestionExpressionWithLeftBinaryBarBarExpression", {}, printer =>
printer.printNode(
ts.EmitHint.Unspecified,
ts.factory.createExpressionStatement(
ts.factory.createBinaryExpression(
ts.factory.createBinaryExpression(
ts.factory.createIdentifier("a"),
ts.factory.createToken(ts.SyntaxKind.BarBarToken),
ts.factory.createIdentifier("b"),
),
ts.factory.createToken(ts.SyntaxKind.QuestionQuestionToken),
ts.factory.createIdentifier("c"),
),
),
ts.createSourceFile("source.ts", "", ts.ScriptTarget.ESNext),
));
printsCorrectly("binaryQuestionQuestionExpressionWithLeftBinaryAmpersandAmpersandExpression", {}, printer =>
printer.printNode(
ts.EmitHint.Unspecified,
ts.factory.createExpressionStatement(
ts.factory.createBinaryExpression(
ts.factory.createBinaryExpression(
ts.factory.createIdentifier("a"),
ts.factory.createToken(ts.SyntaxKind.AmpersandAmpersandToken),
ts.factory.createIdentifier("b"),
),
ts.factory.createToken(ts.SyntaxKind.QuestionQuestionToken),
ts.factory.createIdentifier("c"),
),
),
ts.createSourceFile("source.ts", "", ts.ScriptTarget.ESNext),
));
printsCorrectly("binaryQuestionQuestionExpressionWithRightBinaryBarBarExpression", {}, printer =>
printer.printNode(
ts.EmitHint.Unspecified,
ts.factory.createExpressionStatement(
ts.factory.createBinaryExpression(
ts.factory.createIdentifier("a"),
ts.factory.createToken(ts.SyntaxKind.QuestionQuestionToken),
ts.factory.createBinaryExpression(
ts.factory.createIdentifier("b"),
ts.factory.createToken(ts.SyntaxKind.BarBarToken),
ts.factory.createIdentifier("c"),
),
),
),
ts.createSourceFile("source.ts", "", ts.ScriptTarget.ESNext),
));
printsCorrectly("binaryQuestionQuestionExpressionWithRightBinaryAmpersandAmpersandExpression", {}, printer =>
printer.printNode(
ts.EmitHint.Unspecified,
ts.factory.createExpressionStatement(
ts.factory.createBinaryExpression(
ts.factory.createIdentifier("a"),
ts.factory.createToken(ts.SyntaxKind.QuestionQuestionToken),
ts.factory.createBinaryExpression(
ts.factory.createIdentifier("b"),
ts.factory.createToken(ts.SyntaxKind.AmpersandAmpersandToken),
ts.factory.createIdentifier("c"),
),
),
),
ts.createSourceFile("source.ts", "", ts.ScriptTarget.ESNext),
));
printsCorrectly("binaryQuestionQuestionExpressionWithLeftBinaryQuestionQuestionExpression", {}, printer =>
printer.printNode(
ts.EmitHint.Unspecified,
ts.factory.createExpressionStatement(
ts.factory.createBinaryExpression(
ts.factory.createBinaryExpression(
ts.factory.createIdentifier("a"),
ts.factory.createToken(ts.SyntaxKind.QuestionQuestionToken),
ts.factory.createIdentifier("b"),
),
ts.factory.createToken(ts.SyntaxKind.QuestionQuestionToken),
ts.factory.createIdentifier("c"),
),
),
ts.createSourceFile("source.ts", "", ts.ScriptTarget.ESNext),
));
printsCorrectly("binaryQuestionQuestionExpressionWithRightBinaryQuestionQuestionExpression", {}, printer =>
printer.printNode(
ts.EmitHint.Unspecified,
ts.factory.createExpressionStatement(
ts.factory.createBinaryExpression(
ts.factory.createIdentifier("a"),
ts.factory.createToken(ts.SyntaxKind.QuestionQuestionToken),
ts.factory.createBinaryExpression(
ts.factory.createIdentifier("b"),
ts.factory.createToken(ts.SyntaxKind.QuestionQuestionToken),
ts.factory.createIdentifier("c"),
),
),
),
ts.createSourceFile("source.ts", "", ts.ScriptTarget.ESNext),
));
printsCorrectly("binaryCommaExpressionWithLeftBinaryQuestionQuestionExpression", {}, printer =>
printer.printNode(
ts.EmitHint.Unspecified,
ts.factory.createExpressionStatement(
ts.factory.createBinaryExpression(
ts.factory.createBinaryExpression(
ts.factory.createIdentifier("a"),
ts.factory.createToken(ts.SyntaxKind.QuestionQuestionToken),
ts.factory.createIdentifier("b"),
),
ts.factory.createToken(ts.SyntaxKind.CommaToken),
ts.factory.createIdentifier("c"),
),
),
ts.createSourceFile("source.ts", "", ts.ScriptTarget.ESNext),
));
printsCorrectly("binaryCommaExpressionWithRightBinaryQuestionQuestionExpression", {}, printer =>
printer.printNode(
ts.EmitHint.Unspecified,
ts.factory.createExpressionStatement(
ts.factory.createBinaryExpression(
ts.factory.createIdentifier("a"),
ts.factory.createToken(ts.SyntaxKind.CommaToken),
ts.factory.createBinaryExpression(
ts.factory.createIdentifier("b"),
ts.factory.createToken(ts.SyntaxKind.QuestionQuestionToken),
ts.factory.createIdentifier("c"),
),
),
),
ts.createSourceFile("source.ts", "", ts.ScriptTarget.ESNext),
));
printsCorrectly("binaryEqualsEqualsExpressionWithLeftBinaryQuestionQuestionExpression", {}, printer =>
printer.printNode(
ts.EmitHint.Unspecified,
ts.factory.createExpressionStatement(
ts.factory.createBinaryExpression(
ts.factory.createBinaryExpression(
ts.factory.createIdentifier("a"),
ts.factory.createToken(ts.SyntaxKind.QuestionQuestionToken),
ts.factory.createIdentifier("b"),
),
ts.factory.createToken(ts.SyntaxKind.EqualsEqualsToken),
ts.factory.createIdentifier("c"),
),
),
ts.createSourceFile("source.ts", "", ts.ScriptTarget.ESNext),
));
printsCorrectly("binaryEqualsEqualsExpressionWithRightBinaryQuestionQuestionExpression", {}, printer =>
printer.printNode(
ts.EmitHint.Unspecified,
ts.factory.createExpressionStatement(
ts.factory.createBinaryExpression(
ts.factory.createIdentifier("a"),
ts.factory.createToken(ts.SyntaxKind.EqualsEqualsToken),
ts.factory.createBinaryExpression(
ts.factory.createIdentifier("b"),
ts.factory.createToken(ts.SyntaxKind.QuestionQuestionToken),
ts.factory.createIdentifier("c"),
),
),
),
ts.createSourceFile("source.ts", "", ts.ScriptTarget.ESNext),
));
printsCorrectly("binaryQuestionQuestionExpressionWithLeftBinaryCommaExpression", {}, printer =>
printer.printNode(
ts.EmitHint.Unspecified,
ts.factory.createExpressionStatement(
ts.factory.createBinaryExpression(
ts.factory.createBinaryExpression(
ts.factory.createIdentifier("a"),
ts.factory.createToken(ts.SyntaxKind.CommaToken),
ts.factory.createIdentifier("b"),
),
ts.factory.createToken(ts.SyntaxKind.QuestionQuestionToken),
ts.factory.createIdentifier("c"),
),
),
ts.createSourceFile("source.ts", "", ts.ScriptTarget.ESNext),
));
printsCorrectly("binaryQuestionQuestionExpressionWithRightBinaryCommaExpression", {}, printer =>
printer.printNode(
ts.EmitHint.Unspecified,
ts.factory.createExpressionStatement(
ts.factory.createBinaryExpression(
ts.factory.createIdentifier("a"),
ts.factory.createToken(ts.SyntaxKind.QuestionQuestionToken),
ts.factory.createBinaryExpression(
ts.factory.createIdentifier("b"),
ts.factory.createToken(ts.SyntaxKind.CommaToken),
ts.factory.createIdentifier("c"),
),
),
),
ts.createSourceFile("source.ts", "", ts.ScriptTarget.ESNext),
));
printsCorrectly("binaryQuestionQuestionExpressionWithLeftBinaryEqualsEqualsExpression", {}, printer =>
printer.printNode(
ts.EmitHint.Unspecified,
ts.factory.createExpressionStatement(
ts.factory.createBinaryExpression(
ts.factory.createBinaryExpression(
ts.factory.createIdentifier("a"),
ts.factory.createToken(ts.SyntaxKind.EqualsEqualsToken),
ts.factory.createIdentifier("b"),
),
ts.factory.createToken(ts.SyntaxKind.QuestionQuestionToken),
ts.factory.createIdentifier("c"),
),
),
ts.createSourceFile("source.ts", "", ts.ScriptTarget.ESNext),
));
printsCorrectly("binaryQuestionQuestionExpressionWithRightBinaryEqualsEqualsExpression", {}, printer =>
printer.printNode(
ts.EmitHint.Unspecified,
ts.factory.createExpressionStatement(
ts.factory.createBinaryExpression(
ts.factory.createIdentifier("a"),
ts.factory.createToken(ts.SyntaxKind.QuestionQuestionToken),
ts.factory.createBinaryExpression(
ts.factory.createIdentifier("b"),
ts.factory.createToken(ts.SyntaxKind.EqualsEqualsToken),
ts.factory.createIdentifier("c"),
),
),
),
ts.createSourceFile("source.ts", "", ts.ScriptTarget.ESNext),
));
});
});