eslint: Should we extend no-constant-condition to catch comparisons that are constant due to type mismatch?
What rule do you want to change?
no-constant-condition
Does this change cause the rule to produce more or fewer warnings?
More
How will the change be implemented? (New option, new default behavior, etc.)?
New default behavior
Please provide some example code that this change will affect:
(a + b ?? c)
(!foo == null)
(!foo ?? bar)
((a + b) / 2 ?? bar)
const bar = String(foo); (bar == null)
(String(foo.bar) ?? baz)
const bar = <div>Hi!</div>; (bar == null)
(<div>Hi!</div> ?? foo)
("hello" + name ?? "")
([foo?.bar ?? ""] ?? [])
What does the rule currently do for this code?
Nothing
What will the rule do after it’s changed?
Warn/Error
Are you willing to submit a pull request to implement this change?
Possibly
No Useless Null Checks
There are some types of comparisons which we can tell statically will always create the same result. One such example is performing a null check on an ObjectExpression. In many cases, these type of coding errors are harmless since they merely guard against edge cases which can’t actually exist. However, in other cases they represent a misunderstanding of how the code will behave. For example:
a + b ?? c
Misunderstanding of operator precedence.<SomeComponent /> ?? <Fallback />
Incorrect assumption about JSX returning the result of render function
no-constant-condition
guards against many of these types of errors but it does not currently try to understand comparisons that are constant due to the statically knowable type of the values being compared.
My question is: should it?
On one hand this seems like these are constant conditions that ESLint can determine, so it should! On the other hand, if ESLint wanted to report all possible constant conditions of this sort, it would end up implementing a type system, which is clearly out of scope for this project.
Speaking of type systems, it’s worth noting that many of these errors, type systems (Flow and TypeScript) do not currently report as errors.
Faced with the question of “how far should a rule like this go?” I decided to pick an arbitrary subset of “constant comparisons to null” and write a rule specifically to check for that. You can find it here.
Surprisingly it found hundreds of errors in our (very large) code base, so I believe the rule has some non-trivial value.
I would be curious to hear any maintainer’s thoughts on if this would be a good addition to no-constant-condition
, of if it seems out of scope in some way.
If the rule that checks for constant null comparisons seems like a good addition, then it might be worth considering other similar checks that could be performed, such as constant boolean comparisons: {} || fallback
.
Thanks to @bradzacher for some initial insight which lead to this rule.
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Reactions: 11
- Comments: 29 (28 by maintainers)
Commits related to this issue
- Add rule no-constant-binary-operand I proposed the core idea of this rule in https://github.com/eslint/eslint/issues/13752 as an addition to `no-constant-condition`, but on the advice of the TSC, it ... — committed to captbaritone/eslint by captbaritone 3 years ago
- feat!: Add rule no-constant-binary-operand I proposed the core idea of this rule in https://github.com/eslint/eslint/issues/13752 as an addition to `no-constant-condition`, but on the advice of the T... — committed to captbaritone/eslint by captbaritone 3 years ago
- feat: Add rule no-constant-binary-operand I proposed the core idea of this rule in https://github.com/eslint/eslint/issues/13752 as an addition to `no-constant-condition`, but on the advice of the TS... — committed to captbaritone/eslint by captbaritone 3 years ago
- feat: Add rule no-constant-binary-operand I proposed the core idea of this rule in https://github.com/eslint/eslint/issues/13752 as an addition to `no-constant-condition`, but on the advice of the TS... — committed to captbaritone/eslint by captbaritone 3 years ago
- feat: Add rule no-constant-binary-operand I proposed the core idea of this rule in https://github.com/eslint/eslint/issues/13752 as an addition to `no-constant-condition`, but on the advice of the TS... — committed to captbaritone/eslint by captbaritone 3 years ago
- feat: Add rule no-constant-binary-operand I proposed the core idea of this rule in https://github.com/eslint/eslint/issues/13752 as an addition to `no-constant-condition`, but on the advice of the TS... — committed to captbaritone/eslint by captbaritone 3 years ago
- feat: Add rule no-constant-binary-operand I proposed the core idea of this rule in https://github.com/eslint/eslint/issues/13752 as an addition to `no-constant-condition`, but on the advice of the TS... — committed to captbaritone/eslint by captbaritone 3 years ago
- feat: Add rule no-constant-binary-operand I proposed the core idea of this rule in https://github.com/eslint/eslint/issues/13752 as an addition to `no-constant-condition`, but on the advice of the TS... — committed to captbaritone/eslint by captbaritone 3 years ago
- feat: Add rule no-constant-binary-operand I proposed the core idea of this rule in https://github.com/eslint/eslint/issues/13752 as an addition to `no-constant-condition`, but on the advice of the TS... — committed to captbaritone/eslint by captbaritone 3 years ago
- feat: Add rule no-constant-binary-expression I proposed the core idea of this rule in https://github.com/eslint/eslint/issues/13752 as an addition to `no-constant-condition`, but on the advice of the... — committed to captbaritone/eslint by captbaritone 3 years ago
- feat: Add rule no-constant-binary-expression I proposed the core idea of this rule in https://github.com/eslint/eslint/issues/13752 as an addition to `no-constant-condition`, but on the advice of the... — committed to captbaritone/eslint by captbaritone 3 years ago
- feat: Add rule no-constant-binary-expression I proposed the core idea of this rule in https://github.com/eslint/eslint/issues/13752 as an addition to `no-constant-condition`, but on the advice of the... — committed to captbaritone/eslint by captbaritone 3 years ago
- feat: Add rule no-constant-binary-expression I proposed the core idea of this rule in https://github.com/eslint/eslint/issues/13752 as an addition to `no-constant-condition`, but on the advice of the... — committed to captbaritone/eslint by captbaritone 3 years ago
- feat: Add rule no-constant-binary-expression I proposed the core idea of this rule in https://github.com/eslint/eslint/issues/13752 as an addition to `no-constant-condition`, but on the advice of the... — committed to captbaritone/eslint by captbaritone 3 years ago
- feat: Add rule no-constant-binary-expression I proposed the core idea of this rule in https://github.com/eslint/eslint/issues/13752 as an addition to `no-constant-condition`, but on the advice of the... — committed to captbaritone/eslint by captbaritone 3 years ago
- feat: Add rule no-constant-binary-expression I proposed the core idea of this rule in https://github.com/eslint/eslint/issues/13752 as an addition to `no-constant-condition`, but on the advice of the... — committed to captbaritone/eslint by captbaritone 3 years ago
- feat: Add rule no-constant-binary-expression I proposed the core idea of this rule in https://github.com/eslint/eslint/issues/13752 as an addition to `no-constant-condition`, but on the advice of the... — committed to captbaritone/eslint by captbaritone 3 years ago
- feat: Add rule no-constant-binary-expression I proposed the core idea of this rule in https://github.com/eslint/eslint/issues/13752 as an addition to `no-constant-condition`, but on the advice of the... — committed to captbaritone/eslint by captbaritone 3 years ago
- feat: Add rule no-constant-binary-expression (#15296) * feat: Add rule no-constant-binary-expression I proposed the core idea of this rule in https://github.com/eslint/eslint/issues/13752 as an a... — committed to eslint/eslint by captbaritone 2 years ago
- feat: Add rule no-constant-binary-expression (#15296) * feat: Add rule no-constant-binary-expression I proposed the core idea of this rule in https://github.com/eslint/eslint/issues/13752 as an a... — committed to DeepSourceCorp/eslint by captbaritone 2 years ago
That’s a really good point, thanks for clarifying.
I can work on this. Busy this weekend, but can pick it up next week.
@mdjermanovic Thanks for breaking it down like this. Thinking about it in this way, I realize that there are a actually five things that our rule does more aggressively than
no-constant-condition
. Maybe it would be easier to consider them individually?5 Proposals for making
no-constant-condition
more aggressive||
,&&
) to be conditions @mdjermanovic seems ok with adding this behind an option==
,!==
) to be a “condition”. This would warn:myTestFunc({} == false)
{} ?? foo
,!foo == null
)Follow identifiers to their assignment – if there is only one – to catch things like:Update: Moved to #13776 13776const foo = {}; foo ? a : b
Infer the return value of JSXESLint has made a decision not to do thisIf anyone feels strongly about breaking any of these questions out into their own issues just let me know and I’ll do that.
To clarify the motivations - Jordan has already done some initial explorations of this on the Facebook codebase and found that there were several hundred violations.
All of them were due to user error.
Some were due to a simple misunderstanding of how operator precedence worked:
Some of them were due to deeper misunderstanding of how language features work:
In most cases you can apply the same reasoning to
== null
/=== null
checks, as the same mistakes apply there.We figured if we’ve found several hundred errors in the Facebook codebase, then likely people outside of Facebook have made the same errors.
Some of these examples Jordan mentioned are covered by the
no-unsafe-negation
lint rule, however this rule just checks negations in binary operations.Some might be covered by the
no-mixed-operators
lint rule, however:??
) aren’t caught with the default rule config.I’d be willing to champion this, it looks like a good fit for
no-constant-condition
.As @captbaritone noted, the rule would then cover all places where value of
Boolean(expression)
affects control flow:if (expression) {}
while (expression) {}
do {} while (expression)
for (foo; expression; bar) {}
expression ? foo : bar
expression || foo
expression && foo
If that value is constant, then there is some dead or useless code, which indicates an error.
The same code that already checks the first 5 cases would check the left of
||
/&&
, so this seems like a logical and easy addition to the rule.There is a similarity, but I think this doesn’t fit the “check the control flow condition” purpose of
no-constant-condition
, so this looks to me more like a separate rule.I’ll have to think some more about
??
, but!foo == null
also wouldn’t fitno-constant-condition
in my opinion.This can be a separate proposal for an enhancement in
no-constant-condition
, and also something to consider for new rules.Yes, we can assume that all built-in objects from the specification (
Object
,Object.prototype
, etc.) have not been overwritten and that they are exactly the same as defined in the specification.If we are sure that the object literal doesn’t include either of
"valueOf"
,"toString"
or non-computed & non-shorthand"__proto__"
, then we can assume that this expression is constant since it always evaluates tofalse
. Otherwise, it might betrue
as well.Yes, if
Boolean
is a reference to global variable, then we can assume that it is the originalBoolean
. Same for all other globals defined in the specification.We discussed this in today’s TSC meeting and want to add the rule to ESLint! We decided to name it
no-constant-binary-expression
since the it’s the expression that’s always evaluating the same way in these cases. Marking as accepted.TSC Summary: This proposal seeks to add three checks into
no-constant-condition
:||
,&&
) to be conditions==
,!==
) to be a “condition”. This would warn:myTestFunc({} == false)
{} ?? foo, !foo == null
)There is consensus that proposals 2 and 3 do not belong in
no-constant-condition
. @nzakas proposes that all three proposals be included in a newno-constant-binary-operand
rule.TSC Question: Shall we accept this as a new rule proposal for
no-constant-binary-operand
?I’d like to make a different proposal. What if we added a new rule called
no-constant-binary-operand
? I think that better describes proposals 1-3 (and maybe 4?), which we can now group together in one rule, and we can leaveno-constant-condition
as-is.@captbaritone Sorry, everyone is pretty strapped lately. I’ll make sure this gets discussed one way or another next week.
you don’t need to.
1 + null === 1
1 + undefined === NaN
1 + '' === '1'
1 + false === 1
1 + 1 === 2
1 + {} === '1[object Object]'
You know that if there is a
+
operator, the result will be a string, or a number. If there is a-
//
/*
, then the result will be a number.Thus you know that these operators will never result in a nullish value. From that it follows that for
1 + nullOrNumber ?? 2
, the nullish coalesce is never evaluated, as the+
is evaluated first, resulting in a string/number - which is never nullish.