Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork2.8k
feat(eslint-plugin): [prefer-optional-chain] handle cases where the first operands are unrelated to the rest of the chain and add type info#6397
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Conversation
Thanks for the PR,@bradzacher! typescript-eslint is a 100% community driven project, and we are incredibly grateful that you are contributing to that community. The core maintainers work on this in their personal time, so please understand that it may not be possible for them to review your work immediately. Thanks again! 🙏Please, if you or your company is finding typescript-eslint valuable, help us sustain the project by sponsoring it transparently onhttps://opencollective.com/typescript-eslint. |
nx-cloudbot commentedJan 31, 2023 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
netlifybot commentedJan 31, 2023 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
✅ Deploy Preview fortypescript-eslint ready!
To edit notification comments on pull requests, go to yourNetlify site configuration. |
This is now "check complete" - all existing tests are passing as are the new tests. This is going to be a big chunk of the logic to figure out the correct places to put an optional chain 🤔 |
nx-cloudbot commentedApr 17, 2023 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
@typescript-eslint/triage-team - this is functionally complete in terms of raw logic.
In some future state I can definitely see us including:
But I don't know if it's worth "holding up" this change to re-implement. Happy to do it if we think it's necessary, if not then we can review and land this as is. |
JoshuaKGoldberg commentedApr 17, 2023 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
😞 I do dislike removing functionality. Maybe we could put this out as an experimental rule without deleting the existing one? |
That would create a worse state, IMO - as we would have two nearly identical rules that are mostly differentiated by having a fixer or not. What would be the value prop for a user to switch to test the rule? We'd need a breaking change to remove the experimental rule name. Also "experimental" implies instability or that it may not be adopted - but the rule is perfectly stable (passes all the existing tests, and more) andwill be adopted - it just doesn't have the previously available (unsafe) fixer. |
Yeah that's a good point. Heh, I'm torn here. Expecting previously (perhaps slightly incorrect) satisfied users to be annoyed by the lack of autofixer. Given that the autofixer today is unsafe, I think it's reasonable for us to say"we've rewritten and removed it for safety - welcoming community PRs to add it back". |
It's really sad to see the fixer being removed, my team absolutely loved it when I turned the rule on since we worked on a code-base full of partial objects (and different patterns to deal with them). |
...maybe we could expose the old one under a name like "deprecated"? |
Also applies to The bigger issue is the conversion of return types - Honestly I'd be happy with having a flag like |
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Fun edge case I just discovered: declareconstfoo:{bar:number}|null;foo===null||foo.bar;foo!==null&&!foo.bar; In the first case Which means that my code will ignore it and try not to include it in the chain to report and fix. Will need to add special case logic to handle this as it's obviously intended to be chained together. This also opens up another question about how this rule functions (that we can probably punt on for now, but worth mentioning). foo&&foo.bar&&foo.bar.baz===1// rule will currently fix tofoo?.bar&&foo.bar.baz===1 Should the rule inspect through these "invalid" checks? Or should it ignore them entirely?
|
...hmm. My instinct is to ignore "invalid" checks as an odd edge case. If users complain we can add it as a bugfix later on - but if the code is roughly not valid, I'd hesitate to feel confident in making changes. |
bradzacher commentedJun 24, 2023 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
Note: I hate the tests with a passion because it's impossible for anyone to understand them as written without checking out and running the test 😢 Snapshot output would make it much simpler to review as you can see the expected reports and fixes properly rather than doing it opaquely. The test coverage should show how much is covered overall - which should be all of the code! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
This is really impressive code. Seriously awesome PR@bradzacher!
I've gone over it a few times and think I generally understand it. Particular ups on the approach of separating out the collection and analysis bits.
I say let's get this in for v6!
packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.ts OutdatedShow resolvedHide resolved
Uh oh!
There was an error while loading.Please reload this page.
packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.tsShow resolvedHide resolved
Uh oh!
There was an error while loading.Please reload this page.
packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.tsShow resolvedHide resolved
Uh oh!
There was an error while loading.Please reload this page.
packages/eslint-plugin/src/rules/prefer-optional-chain-utils/analyzeChain.ts OutdatedShow resolvedHide resolved
Uh oh!
There was an error while loading.Please reload this page.
packages/eslint-plugin/src/rules/prefer-optional-chain-utils/analyzeChain.ts OutdatedShow resolvedHide resolved
Uh oh!
There was an error while loading.Please reload this page.
// type parameters are considered | ||
'foo.bar<a>() && foo.bar<a, b>().baz;', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
type parameters
were renamed to type arguments in this context, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
}, | ||
], | ||
}, | ||
// type parameters are considered |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Same
Uh oh!
There was an error while loading.Please reload this page.
PR Checklist
prefer-optional-chain
] handle cases where the first logical(s) are unrelated to the proceeding logicals #6332,fixes[prefer-optional-chain] Only checks the first occurrence in conditional #1461,fixes[eslint-plugin] Breaking: enable type information for prefer-optional-chain #4820,fixesEnhancement: [prefer-optional-chain] Suggest optional chain when the start of the expression is already optionally chained #5697Overview
This is a big refactor of the lint rule so that it can be MUCH more robust and handle more cases.
The new algorithm is based off of flattening the logical expression to an array and then iterating over it. This new algorithm allows us to handle cases like this:
The new algorithm also includes a beefy recursive node comparison function which clearly compares two nodes semantically, instead of comparing them loosely based on source code text. This should lead to fewer weird cases, and also allows us to handle cases like this:
The TL;DR of the algorithm:
foo && foo.bar && foo.bar.baz --> [foo, foo.bar, foo.bar.baz]
foo != null && foo.bar !== undefined && foo.bar.baz --> [foo<NotEqualNullOrUndefined>, foo.bar<NotStrictEqualUndefined>, foo.bar.baz<Boolean>]
foo !== null && foo !== undefined
and group those as one operandfoo.bar
is a subset offoo.bar.baz.bam
, but not offoo.baz
)TODO:
foo?.bar && foo.bar
getComparedName
)&&
and||
conditionals separately (analyzeAndChain
andanalyzeOrChain
respectively)