8000 [no-unnecessary-condition] False positive unnecessary optional chain on nested `void` values · Issue #9754 · typescript-eslint/typescript-eslint · GitHub
[go: up one dir, main page]

Skip to content

[no-unnecessary-condition] False positive unnecessary optional chain on nested void values < 8000 span class="f1-light color-fg-muted">#9754

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

Open
4 tasks done
lukekarrys opened this issue Aug 8, 2024 · 12 comments
Open
4 tasks done
Labels
accepting prs Go ahead, send a pull request that resolves this issue bug Something isn't working package: eslint-plugin Issues related to @typescript-eslint/eslint-plugin

Comments

@lukekarrys
Copy link
lukekarrys commented Aug 8, 2024

Before You File a Bug Report Please Confirm You Have Done The Following...

  • I have tried restarting my IDE and the issue persists.
  • I have updated to the latest version of the packages.
  • I have searched for related issues and found none that matched my issue.
  • I have read the FAQ and my problem is not listed.

Issue Description

Given the following code, I would expect it to lint successfully:

const maybe = <T>(v: T): T | void => v

const a = maybe({
  key1: {
    key2: maybe({
      i: 1,
    }),
  },
})?.key1.key2?.i
//           ^
// 9:14  error  Unnecessary optional chain on a non-nullish value  @typescript-eslint/no-unnecessary-condition

In my reproduction the second optional chain is removed if I run with --fix but then TypeScript errors with the following:

> tsc --noEmit

index.ts:9:15 - error TS2339: Property 'i' does not exist on type 'void | { i: number; }'.
  Property 'i' does not exist on type 'void'.

9 })?.key1.key2.i
                ~


Found 1 error in index.ts:9

I also want to note that this behavior does not occur if the value could be null only void. It successfully lints if I change the return of maybe to T | null.

In my reproduction I also tried to narrow it down as far as I could and found that these examples lint successfully:

// This lints fine
const b = maybe({
  key1: maybe({
    i: 1,
  }),
})?.key1?.i

// So does this
const c = {
  key1: {
    key2: maybe({
      i: 1,
    }),
  },
}.key1.key2?.i

Reproduction Repository Link

https://gist.github.com/lukekarrys/979ea0dd68b52937e9a257b4d2c9c293

Repro Steps

git clone https://gist.github.com/lukekarrys/979ea0dd68b52937e9a257b4d2c9c293
npm install
npm run lint

Versions

package version
@typescript-eslint/eslint-plugin 8.0.1
TypeScript 5.5.4
ESLint 9.8.0
node 22.4.0
@lukekarrys lukekarrys added bug Something isn't working triage Waiting for team members to take a look labels Aug 8, 2024
@lukekarrys lukekarrys changed the title [no-unnecessary-condition] False positive unnecessary optional chain on nested undefined values [no-unnecessary-condition] False positive unnecessary optional chain on nested void values Aug 8, 2024
@Josh-Cena
Copy link
Member

TIL that TypeScript allows optional chaining access on void 😅

Not sure this code is good to be allowed though, since void could be anything, including a non-null value. This code is just unsound.

@bradzacher
Copy link
Member

It doesn't look like this reproduces in our playground

@lukekarrys
Copy link
Author
lukekarrys commented Aug 8, 2024

It doesn't look like this reproduces in our playground

I originally wasn't able to reproduce it in the playground either, which is why I created the gist. But weirdly enough the link you just posted does reproduce for me now.

Screenshot 2024-08-07 at 11 43 49 PM

Copy link
github-actions bot commented Aug 8, 2024

Uh oh! @lukekarrys, the image you shared is missing helpful alt text. Check #9754 (comment).

Alt text is an invisible description that helps screen readers describe images to blind or low-vision users. If you are using markdown to display images, add your alt text inside the brackets of the markdown image.

Learn more about alt text at Basic writing and formatting syntax: images on GitHub Docs.

🤖 Beep boop! This comment was added automatically by github/accessibility-alt-text-bot.

@bradzacher
Copy link
Member

Something weird.... it wasn't working and then I open my link and it repros 😅
something must have been borked somewhere.. But it does repro at least!

@lukekarrys
Copy link
Author
lukekarrys commented Aug 8, 2024

TIL that TypeScript allows optional chaining access on void 😅

Not sure this code is good to be allowed though, since void could be anything, including a non-null value. This code is just unsound.

I agree that the code is unsound and also quite convoluted. Here is the slightly more real world example that led me to create this reproduction (playground link):

const maybeGenerator = () => {
  if (Math.random()) {
    return
  }
  return {
    *g() {
      yield { i: 1 }
    },
  }
}

maybeGenerator()?.g().next().value?.i
//                                ~~
//  12:35  error  Unnecessary optional chain on a non-nullish value  @typescript-eslint/no-unnecessary-condition

@bradzacher
Copy link
Member

Not sure this code is good to be allowed though, since void could be anything, including a non-null value. This code is just unsound.

I'd definitely agree with Josh here this code is very unsound!
void is akin to any when used in a return type location.

Really we should treat it like any in the rule (i.e. we just ignore optional chains off any).

@lukekarrys
Copy link
Author

Really we should treat it like any in the rule (i.e. we just ignore optional chains off any).

That sounds good to me. My only annoyance with the current behavior is that it will fix by removing the optional chain, but then TypeScript will error instead.

So treating it the same as any will fix that part.

@bradzacher bradzacher added package: eslint-plugin Issues related to @typescript-eslint/eslint-plugin accepting prs Go ahead, send a pull request that resolves this issue and removed triage Waiting for team members to take a look labels Aug 8, 2024
@lukekarrys
Copy link
Author

I did find this test case which asserts void is allowed currently:

let variable = 'abc' as string | void;
variable?.[0];

After finding that I experimented a little more and found that it only happens when the root object is nullish and the nested property could be void: playground link.

For consistency (if changing the current test cases would be considered a regression) could nested properties be treated the same way as the test case?

@lukekarrys
Copy link
Author
lukekarrys commented Aug 8, 2024

@bradzacher Based on my last comment, do you have an opinion on if the fix should be:

  1. Make void act the same as any, and alter the existing test case
  2. Make void act the same on a nested property of a null|undefined object as it does in the linked test case

I’d like to attempt a PR for this.

@Josh-Cena
Copy link
Member

+1 to make it behave as any

@bradzacher
Copy link
Member

Ideally - both fixes! 😁

Void should generally be treated as any because it's unsafe to assume anything but that.

But also as you pointed out it looks like void is not correctly handled in this case when the chaining is off a nullish object.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
accepting prs Go ahead, send a pull request that resolves this issue bug Something isn't working package: eslint-plugin Issues related to @typescript-eslint/eslint-plugin
Projects
None yet
Development

No branches or pull requests

3 participants
0