sindresorhus / eslint-plugin-unicorn

More than 100 powerful ESLint rules
MIT License
4.28k stars 367 forks source link

Rule proposal: `no-unnecessary-negation` #856

Open noftaly opened 4 years ago

noftaly commented 4 years ago

Description

This rule would enforce users to simplify negations: !(a === b) -> a !== b. There might be other conditions like this that could be simplified.

Fail

if (!(a > b)) {}
const foo = !(bar === baz);
const foo = !(typeof bar === 'undefined');

Pass

if (a <= b) {}
const foo = bar !== baz;
const foo = typeof bar !== 'undefined';
const foo = !Array.isArray(bar);
fisker commented 4 years ago

I love this one, it will be great if we can convert big nested LogicExpressions.

papb commented 4 years ago

"Fun" fact: !(a > b) is not strictly equal to (a <= b)...

console.log(!(null > undefined)); // true
console.log(null <= undefined); // false
yakov116 commented 4 years ago

What about 2 conditions?

    if (!(a===b && c === d)) {}

Sometimes that way is easier to read

papb commented 4 years ago

Sometimes that way is easier to read

I agree; some times the non-DeMorgan way is more readable

noftaly commented 4 years ago

Then maybe add an option to not optimize when there are multiple ANDs, or nested ANDs and ORs? Or we also just could not add an option and let the user disable the rule inline.

Concerning the implementation, we could use some boolean-expressions optimization algorithms, and check if it is shorter than the original one, and if it is, replace? This will only work with boolean operators tho, not >. But do we really want to support that? As @papb said, fixing it might break the code

sindresorhus commented 3 years ago

if (!(a===b && c === d)) {}

I much prefer this too. If there's going to be an option, it should prefer this by default.

sindresorhus commented 3 years ago

But do we really want to support that? As @papb said, fixing it might break the code

Yes, but they should not be auto-fixable. We can make them use the suggestions API instead.

sindresorhus commented 3 years ago

we could use some boolean-expressions optimization algorithms

Do you have any in mind?

noftaly commented 3 years ago

Do you have any in mind?

Not really, and I searched online and did not found any in JS. But I was thinking of something like this: boolean-algebra It will, for example, simplify an expression like this image to this : !A + !B

sindresorhus commented 3 years ago

This is now accepted.

It should prefer readability by default, for example: if (!(a === b && c === d)) {}.

fisker commented 3 years ago

Similar rule https://github.com/SonarSource/eslint-plugin-sonarjs/blob/master/docs/rules/no-inverted-boolean-check.md

fregante commented 3 years ago

this: boolean-algebra It will, for example, simplify an expression like this

I was thinking about a rule that did this exactly: simplify-boolean-algebra or something like that.

Instead of manually handling each boolean-related awkwardness, why not just simplify whatever expression we see?

I regularly need this, however it's usually across if/else/elseif/nested-if/return rather than in one if (expression)

jguddas commented 2 years ago

The code is not in a presentable state, but it works 🎉

https://astexplorer.net/#/gist/c922ca1591e8db763030071c09df875a/3865211cf9b06a03034a0b199d0e65f6ff8f49c5

Please let me know when you find something that does not work as you expect and generally what you have tried out, so we can include that in our test cases.

jguddas commented 2 years ago

What about something like a - -b which can be simplified down to a + b, should that be included in this rule?

I feel like it fits a lot more in the scope of this rule than the prefer-simplified-expression stuff.

noftaly commented 2 years ago

You have to be careful simplifying expressions with - and + because of automatic type inference. There are edge cases where a - -b is not the same as a + b

> 3 - -[]
3
> 3 + []
'3'
> 3 - -{}
NaN
> 3 + {}
'3[object Object]'
> 3 - -Number
NaN
> 3 + Number
'3function Number() { [native code] }'
jguddas commented 2 years ago

You are right, what about fixing 3 - -{} to {} + 3, that would work?

fisker commented 2 years ago

There is a rust version https://github.com/rome/tools/blob/22dd11d5d03486d3b6f6679f398cf0bbc440a2ef/crates/rome_js_analyze/src/analyzers/complexity/use_simplified_logic_expression.rs

yukulele commented 1 year ago
if (!(a === b && c === d)) {}

become

if(a !== b || c !== d) {}
jguddas commented 1 year ago

@yukulele my solution was to add a reduceParens option that is disabled by default so you would only get that case if you want it to, by default it would stay !(a === b && c === d).