From 7761d52e0f0548f72f9780ab986d8f56716899e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Sun, 29 Dec 2019 12:35:05 -0500 Subject: [PATCH 1/3] refactor: reimplement nullish coalescing precedence tracking Co-authored-by: Toru Nagashima --- .../babel-parser/src/parser/expression.js | 61 +++++++------------ .../no-paren-and-nullish/options.json | 2 +- .../no-paren-nullish-and/options.json | 2 +- .../no-paren-nullish-or/options.json | 2 +- .../no-paren-or-nullish/options.json | 2 +- 5 files changed, 26 insertions(+), 43 deletions(-) diff --git a/packages/babel-parser/src/parser/expression.js b/packages/babel-parser/src/parser/expression.js index 2598eefc1d47..acf40c28497d 100644 --- a/packages/babel-parser/src/parser/expression.js +++ b/packages/babel-parser/src/parser/expression.js @@ -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.binop; } this.next(); @@ -369,49 +375,26 @@ export default class ExpressionParser extends LValParser { } node.right = this.parseExprOpRightExpr(op, prec, noIn); - + this.finishNode( + node, + logical || coalesce ? "LogicalExpression" : "BinaryExpression", + ); /* 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 + * 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 */ - 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`, - ); - } + 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`, + ); } - this.finishNode( - node, - op === tt.logicalOR || - op === tt.logicalAND || - op === tt.nullishCoalescing - ? "LogicalExpression" - : "BinaryExpression", - ); - return this.parseExprOp( node, leftStartPos, diff --git a/packages/babel-parser/test/fixtures/es2020/nullish-coalescing-operator/no-paren-and-nullish/options.json b/packages/babel-parser/test/fixtures/es2020/nullish-coalescing-operator/no-paren-and-nullish/options.json index 8adc3b6ed3e7..8b9f24dff59d 100644 --- a/packages/babel-parser/test/fixtures/es2020/nullish-coalescing-operator/no-paren-and-nullish/options.json +++ b/packages/babel-parser/test/fixtures/es2020/nullish-coalescing-operator/no-paren-and-nullish/options.json @@ -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)" } diff --git a/packages/babel-parser/test/fixtures/es2020/nullish-coalescing-operator/no-paren-nullish-and/options.json b/packages/babel-parser/test/fixtures/es2020/nullish-coalescing-operator/no-paren-nullish-and/options.json index b4d06207ab24..75afb6e1feec 100644 --- a/packages/babel-parser/test/fixtures/es2020/nullish-coalescing-operator/no-paren-nullish-and/options.json +++ b/packages/babel-parser/test/fixtures/es2020/nullish-coalescing-operator/no-paren-nullish-and/options.json @@ -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)" } diff --git a/packages/babel-parser/test/fixtures/es2020/nullish-coalescing-operator/no-paren-nullish-or/options.json b/packages/babel-parser/test/fixtures/es2020/nullish-coalescing-operator/no-paren-nullish-or/options.json index 7905b902574e..9fed7e79262b 100644 --- a/packages/babel-parser/test/fixtures/es2020/nullish-coalescing-operator/no-paren-nullish-or/options.json +++ b/packages/babel-parser/test/fixtures/es2020/nullish-coalescing-operator/no-paren-nullish-or/options.json @@ -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)" } diff --git a/packages/babel-parser/test/fixtures/es2020/nullish-coalescing-operator/no-paren-or-nullish/options.json b/packages/babel-parser/test/fixtures/es2020/nullish-coalescing-operator/no-paren-or-nullish/options.json index 8adc3b6ed3e7..8b9f24dff59d 100644 --- a/packages/babel-parser/test/fixtures/es2020/nullish-coalescing-operator/no-paren-or-nullish/options.json +++ b/packages/babel-parser/test/fixtures/es2020/nullish-coalescing-operator/no-paren-or-nullish/options.json @@ -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)" } From ddc7604906c852b2ecd216f10e53d47ccf1d95e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Wed, 15 Jan 2020 20:03:15 -0500 Subject: [PATCH 2/3] fix: Coalesce has same precedence with LogicalOR --- packages/babel-parser/src/tokenizer/types.js | 30 +++++++++---------- .../core/opts/tokens-true/output.json | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/babel-parser/src/tokenizer/types.js b/packages/babel-parser/src/tokenizer/types.js index ecb1e8376571..6e9b64defde5 100644 --- a/packages/babel-parser/src/tokenizer/types.js +++ b/packages/babel-parser/src/tokenizer/types.js @@ -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 }), diff --git a/packages/babel-parser/test/fixtures/core/opts/tokens-true/output.json b/packages/babel-parser/test/fixtures/core/opts/tokens-true/output.json index a21bf5e27309..47117344de0f 100644 --- a/packages/babel-parser/test/fixtures/core/opts/tokens-true/output.json +++ b/packages/babel-parser/test/fixtures/core/opts/tokens-true/output.json @@ -457,7 +457,7 @@ "isAssign": false, "prefix": true, "postfix": false, - "binop": 10, + "binop": 9, "updateContext": null }, "value": "+", From 6b2c9e8a8db2e82e2e63fff78d84706f91acb728 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Thu, 16 Jan 2020 09:33:36 -0500 Subject: [PATCH 3/3] fix flow errors --- packages/babel-parser/src/parser/expression.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/babel-parser/src/parser/expression.js b/packages/babel-parser/src/parser/expression.js index acf40c28497d..382eb30828f0 100644 --- a/packages/babel-parser/src/parser/expression.js +++ b/packages/babel-parser/src/parser/expression.js @@ -353,7 +353,7 @@ export default class ExpressionParser extends LValParser { } 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.binop; + prec = ((tt.logicalAND: any): { binop: number }).binop; } this.next();