eslint-plugin-ternary v2.0.0
eslint-plugin-ternary
Catch logical and conditional errors inside ternary expressions. Enforce best practises for JavaScript ternary operators.
The recommended config of this plugin sets default values on some existing core ESLint rules to enforce a consistent ternary style. Developers are free to tweak and overwrite any settings recommended by this plugin.
Installation
You'll first need to install ESLint:
$ npm install eslint --save-dev
Next, install eslint-plugin-ternary
:
$ npm install eslint-plugin-ternary --save-dev
Note: If you installed ESLint globally (using the -g
flag) then you must
also install eslint-plugin-ternary
globally.
Usage
Add ternary
to the plugins section of your .eslintrc
configuration file. You can omit the
eslint-plugin-
prefix:
{
"plugins": [ "ternary" ]
}
Then configure the rules you want to use under the rules section.
{
"rules": {
"ternary/no-dupe": "warn",
"ternary/no-unreachable": "error"
}
}
or start with the recommended rule set:
{
"extends": ["plugin:ternary/recommended"]
}
Recommended Config
The recommended plugin configuration will:
- allow single-line and multi-line ternaries
- enforce multi-line ternary conditions to have precedent (same line) operators
- forbid the use of ternary operators for default assignment
- forbid identical left and right-hand ternary expressions
- forbid equivalent and superfluous ternary conditions (test statements)
- forbid nested ternaries from appearing anywhere else but in the truthy/left-hand/consequent position
Here's why:
// Here the entire tenary operator is unneeded
let isYes = answer === 1 ? true : false
// Error: unnecessary ternary. The above can be automatically fixed to:
let isYes = answer === 1
// Here user.isMember can be truthy or falsy and the returned value will still be 2.00
const getFee = user => user.isMember ? 2.00 : 2.00
// Error: identical left and right-hand expressions '2.00'. What was probably meant:
const getFee = user => user.isMember = 2.00 : 3.00
// Here the value 'y' is unreachable code and will thus never be returned
condition1 && condition2 ? condition2 ? 'x' : 'y' : 'z'
// Error: duplicate ternary conditions 'condition2'. What was perhaps meant:
condition1 ? condition2 ? 'x' : 'y' : 'z'
// Here each ternary test is logically equivalent.
const result = condition1 || condition2 ? condition2 || condition1 ? 'x' : 'y' : 'z'
// Error: equivalent OR ternary conditions 'condition2 || condition1'
// Here condition2 is superfluous, although this does not technically result in unreachable code
const thing = condition1 || condition2 ? condition2 || condition3 ? 'x' : 'y' : 'z'
// Error: duplicate ternary OR conditions 'condition2'. What was perhaps meant:
const thing = condition1 || condition2 ? condition3 ? 'x' : 'y' : 'z'
// Here this multi-line ternary does not have it's operators on the same line
condition ?
'x' :
'y'
// Error: '?' and 'e' should be placed at the beginning of the line. This can be automatically fixed to:
condition
? 'x'
: 'y'
// Here we have a ternary nested as the right-hand value of a parent ternary:
const fn = (x, y) => x ? 1 : y ? 2 : 3
// Error: nested ternary conditions should appear as consequent (truthy) clause. Prefer:
const fn = (x, y) => x ? y ? 1 : 2 : 3
Why Nest Ternaries As Consequents
It might at first seem strange to prefer that nested (or chained) ternary conditions appear consequentially but there are several good reasons for this:
- All of the test conditions are grouped on the left
- All possible return values are grouped on the right
- It makes nested ternary conditions more readable and opinionated
- It forces us to think about test condition order. The 1st condition being tested inside a condition chain should be the most determinate (i.e early on conditions should matter the most)
- It forces the developer to factor in all conditions when evaluating what expression should be returned
Take the example functions above. In the first example, the y
condition is ignored, since if
x
is truthy the function will always return 1. In the second example 1
will only be returned if
both x
and y
are truthy. If it really is integral that your code does something based
on a single condition it probably shouldn't appear in a nested ternary condition in the first place.
In that case use a simple if statement and ensure your code checks that condition as soon as it can
be safely checked:
// the below is equivalent to: x ? 1 : y ? 2 : 3
const fn = (x, y) => {
if (x)
return 1
y ? 2 : 3
}
This is also why it is common practise to handle errors at the beginning of a code block.
As for grouping test conditions, here is an expanded version of the other nested ternary:
// the below is equivalent to: x ? y ? 1 : 2 : 3
const fn = (x, y) => {
if (x && y)
return 1
if (y) // if y is true and we reach this part of the code x must be false
return 2
else
return 3
}
In the above expanded ternary example, from first glance it might not be clear that the only way the function returns 3 is if both X and Y are false. We could of course rewrite it to make that explicit but then we are adding superfluous if conditions and in fact increasing code complexity.
In this case one could argue that the nested ternary function really is easier to comprehend than the expanded function, so we are not saving lines for the sake of being fancy, but for the sake of clarity and readability.
For a more detailed explanation of this recommended setting (one that includes data structures) checkout this fiddle.
Rules
rule | description | recommended | fixable |
---|---|---|---|
no-dupe | Forbid identical left and right-hand ternary expressions. | :bangbang: | |
no-unreachable | Forbid equivalent nested ternary conditions which causes unreachable code. | :bangbang: | |
no-unneeded | Forbid ternary operators that are strictly unnecessary. | :bangbang: | :wrench: |
nesting | Control where nested ternary operators can appear inside of a parent ternary | :bangbang: | |
operator-linebreak | Control where ternary and other symbols (?, :) should appear on newlines | :bangbang: | :wrench: |
Key
icon | description |
---|---|
:bangbang: | Reports as error in recommended configuration |
:warning: | Reports as warning in recommended configuration |
:wrench: | Rule is fixable with eslint --fix |
Demo
Release Notes
v1.0.3
- add demo to readme
- fix empty string being reported when OR duplicate exists
- extend recommended config to enforce ternary operators before expressions on multi line ternary
v1.0.4
- make plugin dependency free
- update dev deps
v2.0.0
- fix depth option not being respected (fixing this bug introduces a breaking change)
- update dev deps to next major versions
Maintainers
- Che Fisher - @GrayedFox
License
- (c) 2020 Che Fisher che.fisher@hey.com - ISC license.
Further Reading
Read Eric Elliot's 'Nested Ternaries are Great' for an excellent explanation of the difference between an if statement and if expression and why nested ternaries are not as bad as they are often made out to be.