Skip to content

Documentation: is no-unnecessary-condition really a looser alternative to strict-boolean-expressions? #1016

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
ark120202 opened this issue Sep 29, 2019 · 8 comments · Fixed by #1147
Labels
documentation Documentation ("docs") that needs adding/updating package: eslint-plugin Issues related to @typescript-eslint/eslint-plugin

Comments

@ark120202
Copy link

Currently in documentation of these rules there are these lines:

strict-boolean-expression - a stricter alternative to this rule.
no-unnecessary-condition - a looser alternative to this rule.

Which generally implies that when strict-boolean-expression is enabled there is no need for no-unnecessary-condition.

There is indeed some intersection:

const value = 'foo';
// Reported by no-unnecessary-condition and strict-boolean-expressions
if (value) {}

However strict-boolean-expressions just makes sure that condition is written clearly, it doesn't check that it actually can be true:

const value = 'foo';
// Reported by no-unnecessary-condition, but not by strict-boolean-expressions
if (value != null) {}
@bradzacher bradzacher added documentation Documentation ("docs") that needs adding/updating package: eslint-plugin Issues related to @typescript-eslint/eslint-plugin labels Sep 30, 2019
@bradzacher
Copy link
Member

Yeah I accepted no-unnecessary-condition because it was just different enough to strict-boolean-expressions to make it a headache to merge them.
I figured it would be better to get it out there for people to play with and then figure out what we can do (if anything) to merge the two down the line.

Part of me feels like these two rules could be merged into one with a lot of configurability. Though at the same time sometimes it is nice to have rules that just work and have zero config.

I really hope that typescript will build some of this in as compiler checks in the future

@ark120202
Copy link
Author

I think keeping them separated is better. A small intersection isn't really an issue, since they are reported for different reasons. They are quite different conceptually, no-unnecessary-condition is closer to strict-type-predicates than strict-boolean-expressions.

Part of me feels like these two rules could be merged into one with a lot of configurability. Though at the same time sometimes it is nice to have rules that just work and have zero config.

At least for me it's a lot easier to configure few simple rules than a single complex one.

I really hope that typescript will build some of this in as compiler checks in the future

I wouldn't expect it any time soon. All of TypeScript's options are designed in a way that you want to have all of them enabled once your code is typed well. But these rules are different, because even if your code is perfectly-typed you may still have to do useless for type system checks, since declarations you're consuming are usually not perfect. For example, even TypeScript has some incorrect types (microsoft/TypeScript#24706).

@ark120202
Copy link
Author

Looks like you have accidentally sent your first message the second time?

@bradzacher
Copy link
Member

uh. sorry - github borked because I hit comment and closed my laptop straight away.
Github said that my comment failed to post, and I didn't refresh my page to check if the error message was wrong.

For example, even TypeScript has some incorrect types

Yeah and it has caused us a number of bugs because of it. No good having types if they're purposely wrong....

I would like to see some of it be added as a compiler option, considering flow has "sketchy null" checks built into the compiler (which covers most of strict-boolean-expressions).

At least for me it's a lot easier to configure few simple rules than a single complex one.

definitely. It's about finding the right balance between small individual rules and rules that don't overlap. The hope is that you never introduce a rule that can get the user into a state where they have conflicting error messages.

@Retsam
Copy link
Contributor

Retsam commented Oct 21, 2019

I spent a fair bit of time thinking about my "looser"/"stronger" wording. I knew when I wrote it wasn't strictly true - there are some cases that no-unnecessary-condition catches that strict-boolean-expressions doesn't (mostly cases around literal types, I think).

But I found few enough cases that weren't either caught by strict-boolean-expressions that I thought it was close enough to true to use the "looser/stricter" terminology for documentation purposes.


But the core difference between the rules - and the main reason I'd argue against merging them - is that strict-boolean-expressions is largely a stylistic rule - avoiding "truthy/falsy" conditions is a particular JS coding style - while no-unnecessary-condition is more of a correctness rule.

Maybe "more opinionated"/"less opinionated" is a better comparison of the rules?

@Retsam
Copy link
Contributor

Retsam commented Oct 21, 2019

I really hope that typescript will build some of this in as compiler checks in the future

TS does have some of this as compiler checks, for example:

const foo = "foo";
const bar = "bar";
if(foo === bar) {} // ERROR: condition is always false, since "foo" does not overlap with "bar"

which is the exact same logic that this linter rule is doing.

But the problem with compiler checks is there's no good way to handle false positives. There's no equivalent to // eslint-disable-line [specific-rule] if the rule flags something that you want to allow.

And there's a decent number of valid false positives with the more in-depth checking that this rule does. I found two major categories:

  1. Code that is more defensive than the types allow, (e.g. for the sake of JS consumers). For example:
function hasRequiredParam(array: string[]) {
    // Throw an error for the plebeians without type checking
    if(!array) throw new Error("Function requires an array");
}
  1. Code that uses index types (array or object), due to how TS treats index types:
type Foo = {foo: () => string}
function example(array: Foo[]) {
    // According to the types `array[0]` is `Foo`, but in reality it could be `Foo | undefined`.  
    // So this check is necessary, but doesn't appear to be, based on the types.
    // Same issue happens with object index signatures.
    if(array[0]) {
        return array[0].foo();
    }
}

This is unfortunate, but lint rules are designed to deal with false-positives like this. But I don't think the compiler wants to get in the business of false-positive results; so I doubt that it's going to put "no-unnecessary-condition"s out of business anytime soon... (as awesome as that would be).

@glen-84
Copy link
Contributor

glen-84 commented Oct 27, 2019

In the 2nd case, couldn't you just type the array as (Foo | undefined)[]?

@Retsam
Copy link
Contributor

Retsam commented Oct 27, 2019

@glen-84 That would force you to check for undefined when indexing the array, but it makes everything else more difficult: you can't pass it to thing expecting Foo[], and if map over the array, you have to add non-null assertions to avoid spurious null-checks:

const array = [1,3,5] as Array<number | undefined>;
const res = array.map(x => { /* x is `number | undefined`, should be `number`  */ });

I do find it's generally fairly reasonable to type "dictionary" objects as something like

// All these are largely equivalent
type T1 = Record<string, Foo | undefined>
type T2 = Partial<Record<string, Foo>>
type T3 = {[key: string]: Foo | undefined}
type T4 = {[key in string]?: Foo}

I haven't found major drawbacks to this approach, (except some general bloat to typings); so this is how I fixed a lot of the false positives from applying no-unnecessary-condition to our codebase.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Apr 20, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
documentation Documentation ("docs") that needs adding/updating package: eslint-plugin Issues related to @typescript-eslint/eslint-plugin
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants