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,
|
minPrec: number,
|
||||||
noIn: ?boolean,
|
noIn: ?boolean,
|
||||||
): N.Expression {
|
): N.Expression {
|
||||||
const prec = this.state.type.binop;
|
let prec = this.state.type.binop;
|
||||||
if (prec != null && (!noIn || !this.match(tt._in))) {
|
if (prec != null && (!noIn || !this.match(tt._in))) {
|
||||||
if (prec > minPrec) {
|
if (prec > minPrec) {
|
||||||
const operator = this.state.value;
|
const operator = this.state.value;
|
||||||
@ -343,11 +343,17 @@ export default class ExpressionParser extends LValParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const op = this.state.type;
|
const op = this.state.type;
|
||||||
|
const logical = op === tt.logicalOR || op === tt.logicalAND;
|
||||||
|
const coalesce = op === tt.nullishCoalescing;
|
||||||
|
|
||||||
if (op === tt.pipeline) {
|
if (op === tt.pipeline) {
|
||||||
this.expectPlugin("pipelineOperator");
|
this.expectPlugin("pipelineOperator");
|
||||||
this.state.inPipeline = true;
|
this.state.inPipeline = true;
|
||||||
this.checkPipelineAtInfixOperator(left, leftStartPos);
|
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();
|
this.next();
|
||||||
@ -369,48 +375,25 @@ export default class ExpressionParser extends LValParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
node.right = this.parseExprOpRightExpr(op, prec, noIn);
|
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(
|
this.finishNode(
|
||||||
node,
|
node,
|
||||||
op === tt.logicalOR ||
|
logical || coalesce ? "LogicalExpression" : "BinaryExpression",
|
||||||
op === tt.logicalAND ||
|
|
||||||
op === tt.nullishCoalescing
|
|
||||||
? "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(
|
return this.parseExprOp(
|
||||||
node,
|
node,
|
||||||
|
|||||||
@ -139,22 +139,22 @@ export const types: { [name: string]: TokenType } = {
|
|||||||
tilde: new TokenType("~", { beforeExpr, prefix, startsExpr }),
|
tilde: new TokenType("~", { beforeExpr, prefix, startsExpr }),
|
||||||
pipeline: createBinop("|>", 0),
|
pipeline: createBinop("|>", 0),
|
||||||
nullishCoalescing: createBinop("??", 1),
|
nullishCoalescing: createBinop("??", 1),
|
||||||
logicalOR: createBinop("||", 2),
|
logicalOR: createBinop("||", 1),
|
||||||
logicalAND: createBinop("&&", 3),
|
logicalAND: createBinop("&&", 2),
|
||||||
bitwiseOR: createBinop("|", 4),
|
bitwiseOR: createBinop("|", 3),
|
||||||
bitwiseXOR: createBinop("^", 5),
|
bitwiseXOR: createBinop("^", 4),
|
||||||
bitwiseAND: createBinop("&", 6),
|
bitwiseAND: createBinop("&", 5),
|
||||||
equality: createBinop("==/!=/===/!==", 7),
|
equality: createBinop("==/!=/===/!==", 6),
|
||||||
relational: createBinop("</>/<=/>=", 8),
|
relational: createBinop("</>/<=/>=", 7),
|
||||||
bitShift: createBinop("<</>>/>>>", 9),
|
bitShift: createBinop("<</>>/>>>", 8),
|
||||||
plusMin: new TokenType("+/-", { beforeExpr, binop: 10, prefix, startsExpr }),
|
plusMin: new TokenType("+/-", { beforeExpr, binop: 9, prefix, startsExpr }),
|
||||||
// startsExpr: required by v8intrinsic plugin
|
// startsExpr: required by v8intrinsic plugin
|
||||||
modulo: new TokenType("%", { beforeExpr, binop: 11, startsExpr }),
|
modulo: new TokenType("%", { beforeExpr, binop: 10, startsExpr }),
|
||||||
star: createBinop("*", 11),
|
star: createBinop("*", 10),
|
||||||
slash: createBinop("/", 11),
|
slash: createBinop("/", 10),
|
||||||
exponent: new TokenType("**", {
|
exponent: new TokenType("**", {
|
||||||
beforeExpr,
|
beforeExpr,
|
||||||
binop: 12,
|
binop: 11,
|
||||||
rightAssociative: true,
|
rightAssociative: true,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@ -189,8 +189,8 @@ export const types: { [name: string]: TokenType } = {
|
|||||||
_null: createKeyword("null", { startsExpr }),
|
_null: createKeyword("null", { startsExpr }),
|
||||||
_true: createKeyword("true", { startsExpr }),
|
_true: createKeyword("true", { startsExpr }),
|
||||||
_false: createKeyword("false", { startsExpr }),
|
_false: createKeyword("false", { startsExpr }),
|
||||||
_in: createKeyword("in", { beforeExpr, binop: 8 }),
|
_in: createKeyword("in", { beforeExpr, binop: 7 }),
|
||||||
_instanceof: createKeyword("instanceof", { beforeExpr, binop: 8 }),
|
_instanceof: createKeyword("instanceof", { beforeExpr, binop: 7 }),
|
||||||
_typeof: createKeyword("typeof", { beforeExpr, prefix, startsExpr }),
|
_typeof: createKeyword("typeof", { beforeExpr, prefix, startsExpr }),
|
||||||
_void: createKeyword("void", { beforeExpr, prefix, startsExpr }),
|
_void: createKeyword("void", { beforeExpr, prefix, startsExpr }),
|
||||||
_delete: createKeyword("delete", { beforeExpr, prefix, startsExpr }),
|
_delete: createKeyword("delete", { beforeExpr, prefix, startsExpr }),
|
||||||
|
|||||||
@ -457,7 +457,7 @@
|
|||||||
"isAssign": false,
|
"isAssign": false,
|
||||||
"prefix": true,
|
"prefix": true,
|
||||||
"postfix": false,
|
"postfix": false,
|
||||||
"binop": 10,
|
"binop": 9,
|
||||||
"updateContext": null
|
"updateContext": null
|
||||||
},
|
},
|
||||||
"value": "+",
|
"value": "+",
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"plugins": ["estree"],
|
"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"],
|
"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