Skip to content

Commit 767fa8d

Browse files
Add no-nested-ternary rule (#296)
Co-authored-by: Sindre Sorhus <[email protected]>
1 parent 0b9c0fe commit 767fa8d

File tree

5 files changed

+183
-0
lines changed

5 files changed

+183
-0
lines changed

docs/rules/no-nested-ternary.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Disallow nested ternary expressions
2+
3+
Improved version of the `[no-nested-ternary](https://eslint.org/docs/rules/no-nested-ternary)` ESLint rule, which allows cases where the nested ternary is only one level and wrapped in parens.
4+
5+
6+
## Fail
7+
8+
```js
9+
const foo = i > 5 ? i < 100 ? true : false : true;
10+
const foo = i > 5 ? true : (i < 100 ? true : (i < 1000 ? true : false));
11+
```
12+
13+
14+
## Pass
15+
16+
```js
17+
const foo = i > 5 ? (i < 100 ? true : false) : true;
18+
const foo = i > 5 ? (i < 100 ? true : false) : (i < 100 ? true : false);
19+
```
20+
21+
22+
## Partly fixable
23+
24+
This rule is only fixable when the nesting is up to one level. The rule will wrap the nested ternary in parens:
25+
26+
```js
27+
const foo = i > 5 ? i < 100 ? true : false : true
28+
```
29+
30+
will get fixed to
31+
32+
```js
33+
const foo = i > 5 ? (i < 100 ? true : false) : true
34+
```
35+
36+
37+
## Disabling ESLint `no-nested-ternary`
38+
39+
We recomend disabling the ESLint `no-nested-ternary` rule in favor of this one:
40+
41+
```json
42+
{
43+
"rules": {
44+
"no-nested-ternary": "off"
45+
}
46+
}
47+
```
48+
49+
The recommended preset that comes with this plugin already does this for you.

index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ module.exports = {
3434
'unicorn/no-for-loop': 'error',
3535
'unicorn/no-hex-escape': 'error',
3636
'unicorn/no-keyword-prefix': 'off',
37+
'no-nested-ternary': 'off',
38+
'unicorn/no-nested-ternary': 'error',
3739
'unicorn/no-new-buffer': 'error',
3840
'unicorn/no-process-exit': 'error',
3941
'unicorn/no-unreadable-array-destructuring': 'error',

readme.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ Configure it in `package.json`.
5252
"unicorn/no-for-loop": "error",
5353
"unicorn/no-hex-escape": "error",
5454
"unicorn/no-keyword-prefix": "off",
55+
"no-nested-ternary": "off",
56+
"unicorn/no-nested-ternary": "error",
5557
"unicorn/no-new-buffer": "error",
5658
"unicorn/no-process-exit": "error",
5759
"unicorn/no-unreadable-array-destructuring": "error",
@@ -101,6 +103,7 @@ Configure it in `package.json`.
101103
- [no-for-loop](docs/rules/no-for-loop.md) - Do not use a `for` loop that can be replaced with a `for-of` loop. *(partly fixable)*
102104
- [no-hex-escape](docs/rules/no-hex-escape.md) - Enforce the use of Unicode escapes instead of hexadecimal escapes. *(fixable)*
103105
- [no-keyword-prefix](docs/rules/no-keyword-prefix.md) - Disallow identifiers starting with `new` or `class`.
106+
- [no-nested-ternary](docs/rules/no-nested-ternary.md) - Disallow nested ternary expressions. *(partly fixable)*
104107
- [no-new-buffer](docs/rules/no-new-buffer.md) - Enforce the use of `Buffer.from()` and `Buffer.alloc()` instead of the deprecated `new Buffer()`. *(fixable)*
105108
- [no-process-exit](docs/rules/no-process-exit.md) - Disallow `process.exit()`.
106109
- [no-unreadable-array-destructuring](docs/rules/no-unreadable-array-destructuring.md) - Disallow unreadable array destructuring.

rules/no-nested-ternary.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
'use strict';
2+
const getDocsUrl = require('./utils/get-docs-url');
3+
4+
const isParethesized = (sourceCode, node) => {
5+
const previousToken = sourceCode.getTokenBefore(node);
6+
const nextToken = sourceCode.getTokenAfter(node);
7+
8+
return Boolean(previousToken && nextToken) &&
9+
previousToken.value === '(' &&
10+
previousToken.end <= node.start &&
11+
nextToken.value === ')' &&
12+
nextToken.start >= node.end;
13+
};
14+
15+
const create = context => {
16+
const sourceCode = context.getSourceCode();
17+
18+
return {
19+
ConditionalExpression: node => {
20+
const nodesToCheck = [node.alternate, node.consequent];
21+
22+
for (const childNode of nodesToCheck) {
23+
if (childNode.type !== 'ConditionalExpression') {
24+
continue;
25+
}
26+
27+
const message = 'Do not nest ternary expressions.';
28+
29+
// Nesting more than one level not allowed.
30+
if (childNode.alternate.type === 'ConditionalExpression' || childNode.consequent.type === 'ConditionalExpression') {
31+
context.report({node, message});
32+
break;
33+
} else if (!isParethesized(sourceCode, childNode)) {
34+
context.report({
35+
node: childNode,
36+
message,
37+
fix: fixer => [
38+
fixer.insertTextBefore(childNode, '('),
39+
fixer.insertTextAfter(childNode, ')')
40+
]
41+
});
42+
}
43+
}
44+
}
45+
};
46+
};
47+
48+
module.exports = {
49+
create,
50+
meta: {
51+
type: 'suggestion',
52+
docs: {
53+
url: getDocsUrl(__filename)
54+
},
55+
fixable: 'code'
56+
}
57+
};

test/no-nested-ternary.js

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import test from 'ava';
2+
import avaRuleTester from 'eslint-ava-rule-tester';
3+
import rule from '../rules/no-nested-ternary';
4+
5+
const ruleTester = avaRuleTester(test, {
6+
env: {
7+
es6: true
8+
}
9+
});
10+
11+
const errors = [
12+
{
13+
ruleId: 'no-nested-ternary',
14+
message: 'Do not nest ternary expressions.'
15+
}
16+
];
17+
18+
ruleTester.run('new-error', rule, {
19+
valid: [
20+
'const foo = i > 5 ? true : false;',
21+
'const foo = i > 5 ? true : (i < 100 ? true : false);',
22+
'const foo = i > 5 ? (i < 100 ? true : false) : true;',
23+
'const foo = i > 5 ? (i < 100 ? true : false) : (i < 100 ? true : false);',
24+
'const foo = i > 5 ? true : (i < 100 ? FOO(i > 50 ? false : true) : false);',
25+
'foo ? doBar() : doBaz();',
26+
'var foo = bar === baz ? qux : quxx;'
27+
],
28+
invalid: [
29+
{
30+
code: 'const foo = i > 5 ? true : (i < 100 ? true : (i < 1000 ? true : false));',
31+
errors
32+
},
33+
{
34+
code: 'const foo = i > 5 ? true : (i < 100 ? (i > 50 ? false : true) : false);',
35+
errors
36+
},
37+
{
38+
code: 'const foo = i > 5 ? i < 100 ? true : false : true;',
39+
output: 'const foo = i > 5 ? (i < 100 ? true : false) : true;',
40+
errors
41+
},
42+
{
43+
code: 'const foo = i > 5 ? i < 100 ? true : false : i < 100 ? true : false;',
44+
output: 'const foo = i > 5 ? (i < 100 ? true : false) : (i < 100 ? true : false);',
45+
errors: [
46+
{
47+
ruleId: 'no-nested-ternary',
48+
column: 21
49+
},
50+
{
51+
ruleId: 'no-nested-ternary',
52+
column: 46
53+
}
54+
]
55+
},
56+
{
57+
code: 'const foo = i > 5 ? true : i < 100 ? true : false;',
58+
output: 'const foo = i > 5 ? true : (i < 100 ? true : false);',
59+
errors
60+
},
61+
{
62+
code: 'foo ? bar : baz === qux ? quxx : foobar;',
63+
output: 'foo ? bar : (baz === qux ? quxx : foobar);',
64+
errors
65+
},
66+
{
67+
code: 'foo ? baz === qux ? quxx : foobar : bar;',
68+
output: 'foo ? (baz === qux ? quxx : foobar) : bar;',
69+
errors
70+
}
71+
]
72+
});

0 commit comments

Comments
 (0)