Fixed nullish coalesce operator's precedence (#61372)

This commit is contained in:
Mateusz Burzyński
2025-06-30 13:49:03 -07:00
committed by GitHub
parent 58665cf3ae
commit 91cdbd518c
15 changed files with 240 additions and 30 deletions
+22 -11
View File
@@ -40472,18 +40472,29 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
function checkNullishCoalesceOperands(node: BinaryExpression) {
const { left, operatorToken, right } = node;
if (operatorToken.kind === SyntaxKind.QuestionQuestionToken) {
if (isBinaryExpression(left) && (left.operatorToken.kind === SyntaxKind.BarBarToken || left.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken)) {
grammarErrorOnNode(left, Diagnostics._0_and_1_operations_cannot_be_mixed_without_parentheses, tokenToString(left.operatorToken.kind), tokenToString(operatorToken.kind));
}
if (isBinaryExpression(right) && (right.operatorToken.kind === SyntaxKind.BarBarToken || right.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken)) {
grammarErrorOnNode(right, Diagnostics._0_and_1_operations_cannot_be_mixed_without_parentheses, tokenToString(right.operatorToken.kind), tokenToString(operatorToken.kind));
}
checkNullishCoalesceOperandLeft(node);
checkNullishCoalesceOperandRight(node);
if (node.operatorToken.kind !== SyntaxKind.QuestionQuestionToken) {
return;
}
if (isBinaryExpression(node.parent)) {
const { left, operatorToken } = node.parent;
if (isBinaryExpression(left) && operatorToken.kind === SyntaxKind.BarBarToken) {
grammarErrorOnNode(left, Diagnostics._0_and_1_operations_cannot_be_mixed_without_parentheses, tokenToString(SyntaxKind.QuestionQuestionToken), tokenToString(operatorToken.kind));
}
}
else if (isBinaryExpression(node.left)) {
const { operatorToken } = node.left;
if (operatorToken.kind === SyntaxKind.BarBarToken || operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) {
grammarErrorOnNode(node.left, Diagnostics._0_and_1_operations_cannot_be_mixed_without_parentheses, tokenToString(operatorToken.kind), tokenToString(SyntaxKind.QuestionQuestionToken));
}
}
else if (isBinaryExpression(node.right)) {
const { operatorToken } = node.right;
if (operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) {
grammarErrorOnNode(node.right, Diagnostics._0_and_1_operations_cannot_be_mixed_without_parentheses, tokenToString(SyntaxKind.QuestionQuestionToken), tokenToString(operatorToken.kind));
}
}
checkNullishCoalesceOperandLeft(node);
checkNullishCoalesceOperandRight(node);
}
function checkNullishCoalesceOperandLeft(node: BinaryExpression) {
+6 -6
View File
@@ -5675,17 +5675,17 @@ export const enum OperatorPrecedence {
// CoalesceExpression
Conditional,
// LogicalORExpression:
// LogicalANDExpression
// LogicalORExpression `||` LogicalANDExpression
LogicalOR,
// CoalesceExpression:
// CoalesceExpressionHead `??` BitwiseORExpression
// CoalesceExpressionHead:
// CoalesceExpression
// BitwiseORExpression
Coalesce = Conditional, // NOTE: This is wrong
// LogicalORExpression:
// LogicalANDExpression
// LogicalORExpression `||` LogicalANDExpression
LogicalOR,
Coalesce = LogicalOR,
// LogicalANDExpression:
// BitwiseORExpression
+57
View File
@@ -366,5 +366,62 @@ describe("unittests:: PrinterAPI", () => {
ts.factory.createNoSubstitutionTemplateLiteral("\n"),
ts.createSourceFile("source.ts", "", ts.ScriptTarget.ESNext),
));
printsCorrectly("binaryBarBarExpressionWithLeftConditionalExpression", {}, printer =>
printer.printNode(
ts.EmitHint.Unspecified,
ts.factory.createExpressionStatement(
ts.factory.createBinaryExpression(
ts.factory.createConditionalExpression(
ts.factory.createIdentifier("a"),
ts.factory.createToken(ts.SyntaxKind.QuestionToken),
ts.factory.createIdentifier("b"),
ts.factory.createToken(ts.SyntaxKind.ColonToken),
ts.factory.createIdentifier("c"),
),
ts.factory.createToken(ts.SyntaxKind.BarBarToken),
ts.factory.createIdentifier("d"),
),
),
ts.createSourceFile("source.ts", "", ts.ScriptTarget.ESNext),
));
printsCorrectly("binaryAmpersandAmpersandExpressionWithLeftConditionalExpression", {}, printer =>
printer.printNode(
ts.EmitHint.Unspecified,
ts.factory.createExpressionStatement(
ts.factory.createBinaryExpression(
ts.factory.createConditionalExpression(
ts.factory.createIdentifier("a"),
ts.factory.createToken(ts.SyntaxKind.QuestionToken),
ts.factory.createIdentifier("b"),
ts.factory.createToken(ts.SyntaxKind.ColonToken),
ts.factory.createIdentifier("c"),
),
ts.factory.createToken(ts.SyntaxKind.AmpersandAmpersandToken),
ts.factory.createIdentifier("d"),
),
),
ts.createSourceFile("source.ts", "", ts.ScriptTarget.ESNext),
));
printsCorrectly("binaryQuestionQuestionExpressionWithLeftConditionalExpression", {}, printer =>
printer.printNode(
ts.EmitHint.Unspecified,
ts.factory.createExpressionStatement(
ts.factory.createBinaryExpression(
ts.factory.createConditionalExpression(
ts.factory.createIdentifier("a"),
ts.factory.createToken(ts.SyntaxKind.QuestionToken),
ts.factory.createIdentifier("b"),
ts.factory.createToken(ts.SyntaxKind.ColonToken),
ts.factory.createIdentifier("c"),
),
ts.factory.createToken(ts.SyntaxKind.QuestionQuestionToken),
ts.factory.createIdentifier("d"),
),
),
ts.createSourceFile("source.ts", "", ts.ScriptTarget.ESNext),
));
});
});