Update coalesce precedence (#11017)
* refactor: reimplement nullish coalescing precedence tracking Co-authored-by: Toru Nagashima <public@mysticatea.dev> * fix: Coalesce has same precedence with LogicalOR * fix flow errors Co-authored-by: Toru Nagashima <public@mysticatea.dev>
This commit is contained in:
parent
6648d62f40
commit
45301c5304
@ -320,7 +320,7 @@ export default class ExpressionParser extends LValParser {
|
||||
minPrec: number,
|
||||
noIn: ?boolean,
|
||||
): N.Expression {
|
||||
const prec = this.state.type.binop;
|
||||
let prec = this.state.type.binop;
|
||||
if (prec != null && (!noIn || !this.match(tt._in))) {
|
||||
if (prec > minPrec) {
|
||||
const operator = this.state.value;
|
||||
@ -343,11 +343,17 @@ export default class ExpressionParser extends LValParser {
|
||||
}
|
||||
|
||||
const op = this.state.type;
|
||||
const logical = op === tt.logicalOR || op === tt.logicalAND;
|
||||
const coalesce = op === tt.nullishCoalescing;
|
||||
|
||||
if (op === tt.pipeline) {
|
||||
this.expectPlugin("pipelineOperator");
|
||||
this.state.inPipeline = true;
|
||||
this.checkPipelineAtInfixOperator(left, leftStartPos);
|
||||
} else if (coalesce) {
|
||||
// Handle the precedence of `tt.coalesce` as equal to the range of logical expressions.
|
||||
// In other words, `node.right` shouldn't contain logical expressions in order to check the mixed error.
|
||||
prec = ((tt.logicalAND: any): { binop: number }).binop;
|
||||
}
|
||||
|
||||
this.next();
|
||||
@ -369,48 +375,25 @@ export default class ExpressionParser extends LValParser {
|
||||
}
|
||||
|
||||
node.right = this.parseExprOpRightExpr(op, prec, noIn);
|
||||
|
||||
/* this check is for all ?? operators
|
||||
* a ?? b && c for this example
|
||||
* b && c => This is considered as a logical expression in the ast tree
|
||||
* a => Identifier
|
||||
* so for ?? operator we need to check in this case the right expression to have parenthesis
|
||||
* second case a && b ?? c
|
||||
* here a && b => This is considered as a logical expression in the ast tree
|
||||
* c => identifier
|
||||
* so now here for ?? operator we need to check the left expression to have parenthesis
|
||||
* if the parenthesis is missing we raise an error and throw it
|
||||
*/
|
||||
if (op === tt.nullishCoalescing) {
|
||||
if (
|
||||
left.type === "LogicalExpression" &&
|
||||
left.operator !== "??" &&
|
||||
!(left.extra && left.extra.parenthesized)
|
||||
) {
|
||||
throw this.raise(
|
||||
left.start,
|
||||
`Nullish coalescing operator(??) requires parens when mixing with logical operators`,
|
||||
);
|
||||
} else if (
|
||||
node.right.type === "LogicalExpression" &&
|
||||
node.right.operator !== "??" &&
|
||||
!(node.right.extra && node.right.extra.parenthesized)
|
||||
) {
|
||||
throw this.raise(
|
||||
node.right.start,
|
||||
`Nullish coalescing operator(??) requires parens when mixing with logical operators`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.finishNode(
|
||||
node,
|
||||
op === tt.logicalOR ||
|
||||
op === tt.logicalAND ||
|
||||
op === tt.nullishCoalescing
|
||||
? "LogicalExpression"
|
||||
: "BinaryExpression",
|
||||
logical || coalesce ? "LogicalExpression" : "BinaryExpression",
|
||||
);
|
||||
/* this check is for all ?? operators
|
||||
* a ?? b && c for this example
|
||||
* when op is coalesce and nextOp is logical (&&), throw at the pos of nextOp that it can not be mixed.
|
||||
* Symmetrically it also throws when op is logical and nextOp is coalesce
|
||||
*/
|
||||
const nextOp = this.state.type;
|
||||
if (
|
||||
(coalesce && (nextOp === tt.logicalOR || nextOp === tt.logicalAND)) ||
|
||||
(logical && nextOp === tt.nullishCoalescing)
|
||||
) {
|
||||
throw this.raise(
|
||||
this.state.start,
|
||||
`Nullish coalescing operator(??) requires parens when mixing with logical operators`,
|
||||
);
|
||||
}
|
||||
|
||||
return this.parseExprOp(
|
||||
node,
|
||||
|
||||
@ -139,22 +139,22 @@ export const types: { [name: string]: TokenType } = {
|
||||
tilde: new TokenType("~", { beforeExpr, prefix, startsExpr }),
|
||||
pipeline: createBinop("|>", 0),
|
||||
nullishCoalescing: createBinop("??", 1),
|
||||
logicalOR: createBinop("||", 2),
|
||||
logicalAND: createBinop("&&", 3),
|
||||
bitwiseOR: createBinop("|", 4),
|
||||
bitwiseXOR: createBinop("^", 5),
|
||||
bitwiseAND: createBinop("&", 6),
|
||||
equality: createBinop("==/!=/===/!==", 7),
|
||||
relational: createBinop("</>/<=/>=", 8),
|
||||
bitShift: createBinop("<</>>/>>>", 9),
|
||||
plusMin: new TokenType("+/-", { beforeExpr, binop: 10, prefix, startsExpr }),
|
||||
logicalOR: createBinop("||", 1),
|
||||
logicalAND: createBinop("&&", 2),
|
||||
bitwiseOR: createBinop("|", 3),
|
||||
bitwiseXOR: createBinop("^", 4),
|
||||
bitwiseAND: createBinop("&", 5),
|
||||
equality: createBinop("==/!=/===/!==", 6),
|
||||
relational: createBinop("</>/<=/>=", 7),
|
||||
bitShift: createBinop("<</>>/>>>", 8),
|
||||
plusMin: new TokenType("+/-", { beforeExpr, binop: 9, prefix, startsExpr }),
|
||||
// startsExpr: required by v8intrinsic plugin
|
||||
modulo: new TokenType("%", { beforeExpr, binop: 11, startsExpr }),
|
||||
star: createBinop("*", 11),
|
||||
slash: createBinop("/", 11),
|
||||
modulo: new TokenType("%", { beforeExpr, binop: 10, startsExpr }),
|
||||
star: createBinop("*", 10),
|
||||
slash: createBinop("/", 10),
|
||||
exponent: new TokenType("**", {
|
||||
beforeExpr,
|
||||
binop: 12,
|
||||
binop: 11,
|
||||
rightAssociative: true,
|
||||
}),
|
||||
|
||||
@ -189,8 +189,8 @@ export const types: { [name: string]: TokenType } = {
|
||||
_null: createKeyword("null", { startsExpr }),
|
||||
_true: createKeyword("true", { startsExpr }),
|
||||
_false: createKeyword("false", { startsExpr }),
|
||||
_in: createKeyword("in", { beforeExpr, binop: 8 }),
|
||||
_instanceof: createKeyword("instanceof", { beforeExpr, binop: 8 }),
|
||||
_in: createKeyword("in", { beforeExpr, binop: 7 }),
|
||||
_instanceof: createKeyword("instanceof", { beforeExpr, binop: 7 }),
|
||||
_typeof: createKeyword("typeof", { beforeExpr, prefix, startsExpr }),
|
||||
_void: createKeyword("void", { beforeExpr, prefix, startsExpr }),
|
||||
_delete: createKeyword("delete", { beforeExpr, prefix, startsExpr }),
|
||||
|
||||
@ -457,7 +457,7 @@
|
||||
"isAssign": false,
|
||||
"prefix": true,
|
||||
"postfix": false,
|
||||
"binop": 10,
|
||||
"binop": 9,
|
||||
"updateContext": null
|
||||
},
|
||||
"value": "+",
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
{
|
||||
"plugins": ["estree"],
|
||||
"throws": "Nullish coalescing operator(??) requires parens when mixing with logical operators (1:0)"
|
||||
"throws": "Nullish coalescing operator(??) requires parens when mixing with logical operators (1:7)"
|
||||
}
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
{
|
||||
"throws": "Nullish coalescing operator(??) requires parens when mixing with logical operators (1:5)"
|
||||
"throws": "Nullish coalescing operator(??) requires parens when mixing with logical operators (1:7)"
|
||||
}
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
{
|
||||
"throws": "Nullish coalescing operator(??) requires parens when mixing with logical operators (1:10)"
|
||||
"throws": "Nullish coalescing operator(??) requires parens when mixing with logical operators (1:12)"
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
{
|
||||
"plugins": ["estree"],
|
||||
"throws": "Nullish coalescing operator(??) requires parens when mixing with logical operators (1:0)"
|
||||
"throws": "Nullish coalescing operator(??) requires parens when mixing with logical operators (1:7)"
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user