Skip to content

avoid_unnecessary_negations

v0.8.0 Warning Fix Control Flow

This rule flags a negation that can be removed without changing the meaning — !!flag, !(a != b), !true, and !a == !b.

A double negation states a positive condition the long way. The reader has to unwind both operators before knowing what is actually being tested, and it is easy to miscount when the expression is longer.

Negating a boolean literal (!true) is just the other literal written indirectly. Negating both sides of an equality (!a == !b) cancels out entirely — the comparison gives the same answer without either !.

These usually appear when a condition is inverted during a change and the inner expression is left as it was.

if (!!isEnabled) {
start();
}
if (!(status != Status.active)) {
activate();
}
if (!true) {
unreachable();
}
if (!isReady == !isLoaded) {
sync();
}
if (isEnabled) {
start();
}
if (status == Status.active) {
activate();
}
if (false) {
unreachable();
}
if (isReady == isLoaded) {
sync();
}

Four shapes are reported: ! applied to a ! expression, ! applied to a != comparison, ! applied to a boolean literal, and ==/!= with a negation on both sides. Parentheses are unwrapped first, so !(!flag) is caught.

A single negation in a comparison (!a == b) is left alone — removing it would change the result. A negated == (!(a == b)) is also deliberately excluded: it is a single negation, and rewriting it to != is a style preference rather than a redundancy. Negated relational comparisons are handled by avoid_inverted_boolean_checks instead, so the same code is never reported twice.

This rule is in the core preset, so it is on with preset: core, preset: recommended or preset: opinionated.

To turn it off:

many_lints.yaml
rules:
avoid_unnecessary_negations: false

To keep the rule on but skip certain paths, use per-rule exclude.