Skip to content

Commit

Permalink
feat: add ternaryOperandBinaryExpressions option to no-extra-parens r…
Browse files Browse the repository at this point in the history
…ule (#17270)

* feat: add `conditionalTernary` option to `no-extra-parens` rule

* chore: improve coverage

* chore: improve coverage

* chore: avoid SequenceExpression miss

* chore: change option name to ternaryOperandBinaryExpressions

* docs: update docs

* docs: fix space inside code

* chore: update

* chore: clean

* docs: update

* chore: update
  • Loading branch information
kecrily committed Jun 30, 2023
1 parent 840a264 commit 4d411e4
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 2 deletions.
21 changes: 21 additions & 0 deletions docs/src/rules/no-extra-parens.md
Expand Up @@ -52,6 +52,7 @@ This rule has an object option for exceptions to the `"all"` option:
* `"conditionalAssign": false` allows extra parentheses around assignments in conditional test expressions
* `"returnAssign": false` allows extra parentheses around assignments in `return` statements
* `"nestedBinaryExpressions": false` allows extra parentheses in nested binary expressions
* `"ternaryOperandBinaryExpressions": false` allows extra parentheses around binary expressions that are operands of ternary `?:`
* `"ignoreJSX": "none|all|multi-line|single-line"` allows extra parentheses around no/all/multi-line/single-line JSX components. Defaults to `none`.
* `"enforceForArrowConditionals": false` allows extra parentheses around ternary expressions which are the body of an arrow function
* `"enforceForSequenceExpressions": false` allows extra parentheses around sequence expressions
Expand Down Expand Up @@ -189,6 +190,26 @@ x = (a * b) / c;

:::

### ternaryOperandBinaryExpressions

Examples of **correct** code for this rule with the `"all"` and `{ "ternaryOperandBinaryExpressions": false }` options:

::: correct

```js
/* eslint no-extra-parens: ["error", "all", { "ternaryOperandBinaryExpressions": false }] */

(a && b) ? foo : bar;

(a - b > a) ? foo : bar;

foo ? (bar || baz) : qux;

foo ? bar : (baz || qux);
```

:::

### ignoreJSX

Examples of **correct** code for this rule with the `all` and `{ "ignoreJSX": "all" }` options:
Expand Down
14 changes: 12 additions & 2 deletions lib/rules/no-extra-parens.js
Expand Up @@ -46,6 +46,7 @@ module.exports = {
type: "object",
properties: {
conditionalAssign: { type: "boolean" },
ternaryOperandBinaryExpressions: { type: "boolean" },
nestedBinaryExpressions: { type: "boolean" },
returnAssign: { type: "boolean" },
ignoreJSX: { enum: ["none", "all", "single-line", "multi-line"] },
Expand Down Expand Up @@ -76,6 +77,7 @@ module.exports = {
const precedence = astUtils.getPrecedence;
const ALL_NODES = context.options[0] !== "functions";
const EXCEPT_COND_ASSIGN = ALL_NODES && context.options[1] && context.options[1].conditionalAssign === false;
const EXCEPT_COND_TERNARY = ALL_NODES && context.options[1] && context.options[1].ternaryOperandBinaryExpressions === false;
const NESTED_BINARY = ALL_NODES && context.options[1] && context.options[1].nestedBinaryExpressions === false;
const EXCEPT_RETURN_ASSIGN = ALL_NODES && context.options[1] && context.options[1].returnAssign === false;
const IGNORE_JSX = ALL_NODES && context.options[1] && context.options[1].ignoreJSX;
Expand Down Expand Up @@ -886,18 +888,26 @@ module.exports = {
if (isReturnAssignException(node)) {
return;
}

const availableTypes = new Set(["BinaryExpression", "LogicalExpression"]);

if (
!(EXCEPT_COND_TERNARY && availableTypes.has(node.test.type)) &&
!isCondAssignException(node) &&
hasExcessParensWithPrecedence(node.test, precedence({ type: "LogicalExpression", operator: "||" }))
) {
report(node.test);
}

if (hasExcessParensWithPrecedence(node.consequent, PRECEDENCE_OF_ASSIGNMENT_EXPR)) {
if (
!(EXCEPT_COND_TERNARY && availableTypes.has(node.consequent.type)) &&
hasExcessParensWithPrecedence(node.consequent, PRECEDENCE_OF_ASSIGNMENT_EXPR)) {
report(node.consequent);
}

if (hasExcessParensWithPrecedence(node.alternate, PRECEDENCE_OF_ASSIGNMENT_EXPR)) {
if (
!(EXCEPT_COND_TERNARY && availableTypes.has(node.alternate.type)) &&
hasExcessParensWithPrecedence(node.alternate, PRECEDENCE_OF_ASSIGNMENT_EXPR)) {
report(node.alternate);
}
},
Expand Down
12 changes: 12 additions & 0 deletions tests/lib/rules/no-extra-parens.js
Expand Up @@ -304,6 +304,14 @@ ruleTester.run("no-extra-parens", rule, {
{ code: "while (((foo = bar()))) {}", options: ["all", { conditionalAssign: false }] },
{ code: "var a = (((b = c))) ? foo : bar;", options: ["all", { conditionalAssign: false }] },

// ["all", { ternaryOperandBinaryExpressions: false }] enables extra parens around conditional ternary
{ code: "(a && b) ? foo : bar", options: ["all", { ternaryOperandBinaryExpressions: false }] },
{ code: "(a - b > a) ? foo : bar", options: ["all", { ternaryOperandBinaryExpressions: false }] },
{ code: "foo ? (bar || baz) : qux", options: ["all", { ternaryOperandBinaryExpressions: false }] },
{ code: "foo ? bar : (baz || qux)", options: ["all", { ternaryOperandBinaryExpressions: false }] },
{ code: "(a, b) ? (c, d) : (e, f)", options: ["all", { ternaryOperandBinaryExpressions: false }] },
{ code: "(a = b) ? c : d", options: ["all", { ternaryOperandBinaryExpressions: false }] },

// ["all", { nestedBinaryExpressions: false }] enables extra parens around conditional assignments
{ code: "a + (b * c)", options: ["all", { nestedBinaryExpressions: false }] },
{ code: "(a * b) + c", options: ["all", { nestedBinaryExpressions: false }] },
Expand Down Expand Up @@ -922,6 +930,10 @@ ruleTester.run("no-extra-parens", rule, {
invalid("a ? b : (c = d)", "a ? b : c = d", "AssignmentExpression"),
invalid("(c = d) ? (b) : c", "(c = d) ? b : c", "Identifier", null, { options: ["all", { conditionalAssign: false }] }),
invalid("(c = d) ? b : (c)", "(c = d) ? b : c", "Identifier", null, { options: ["all", { conditionalAssign: false }] }),
invalid("(a) ? foo : bar", "a ? foo : bar", "Identifier", null, { options: ["all", { ternaryOperandBinaryExpressions: false }] }),
invalid("(a()) ? foo : bar", "a() ? foo : bar", "CallExpression", null, { options: ["all", { ternaryOperandBinaryExpressions: false }] }),
invalid("(a.b) ? foo : bar", "a.b ? foo : bar", "MemberExpression", null, { options: ["all", { ternaryOperandBinaryExpressions: false }] }),
invalid("(a || b) ? foo : (bar)", "(a || b) ? foo : bar", "Identifier", null, { options: ["all", { ternaryOperandBinaryExpressions: false }] }),
invalid("f((a = b))", "f(a = b)", "AssignmentExpression"),
invalid("a, (b = c)", "a, b = c", "AssignmentExpression"),
invalid("a = (b * c)", "a = b * c", "BinaryExpression"),
Expand Down

0 comments on commit 4d411e4

Please sign in to comment.