fix(eslint-plugin): [prefer-optional-chain] use suggestion instead of autofix for trailing binary operator#12328
Conversation
|
Thanks for the PR, @mdm317! 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 on https://opencollective.com/typescript-eslint. |
✅ Deploy Preview for typescript-eslint ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
View your CI Pipeline Execution ↗ for commit 34b96b4
☁️ Nx Cloud last updated this comment at |
It would help to have a complete example, but the following is sound and doesn't need to be changed: declare const foo: { bar: string | undefined } | null
foo == null || foo.bar == undefined
Can you provide an example of what you mean?
Can you provide an example of what you mean? |
kirkwaiblinger
left a comment
There was a problem hiding this comment.
The actual code changes here look good to me! And all the test cases I looked at looked like the correct change. Didn't check closely yet whether there any missed cases...
Would like some clarity on the questions in the PR description, though, before doing a thorough pass for any missed cases.
Thanks!
|
Sorry — my previous reply had mistakes in both the explanation and the examples. Re: Q1 & Q2My original example was wrong. There are still a few cases where the last operand is neither a declare const foo: { bar: number };
!foo || foo.bar === null;Personally, I'd recommend downgrading this not reporting it at all. Re: Q3The RHS constraint is literal, not primitive — I misspoke earlier. I'd suggest restricting the autofix to cases where all three conditions below hold.
If anything is unclear or seems off, I'd appreciate a follow-up — happy to clarify. |
|
@mdm317 can you confirm you stand behind through your response? The one you most recently posted looks to be pretty directly AI copy & paste. https://typescript-eslint.io/contributing/ai-policy |
|
@JoshuaKGoldberg I used AI to help translate my thoughts into English, not to generate the response itself, which may have made the wording sound AI-generated. I've updated the comment to better explain my reasoning. |
kirkwaiblinger
left a comment
There was a problem hiding this comment.
Sorry — my previous reply had mistakes in both the explanation and the examples. Here's a corrected version.
Re: Q1 & Q2
My original example was wrong.
There are still a few cases where the last operand is neither a
nullish checknor atruthy/falsy check. Should we keep those as autofixes?declare const foo: { bar: number }; !foo || foo.bar === null;Personally, I'd recommend downgrading this not reporting it at all. The code above can still cause issues such as #11700 See also @kirkwaiblinger's comment in #11700.
As far as this PR, I don't think we change anything here. Note that it's already a suggestion currently, not an autofix.
Re: Q3
The RHS constraint is literal, not primitive — I misspoke earlier. Please disregard my comments about the opt-in option.
I'd suggest restricting the autofix to cases where all three conditions below hold.
The chain should be used in a boolean context.
if (foo && foo.bar === 5) { /* safe */ } const x = foo && foo.bar === 5; // type of x changesThe RHS should be a literal.
A non-literal RHS breaks the algorithm implemented in #11533.
Two such cases are currently open:No prior operand should be a
truthy/falsycheckdeclare const s: string; // before — when s is "" !s || s.lastIndexOf('.') !== -1; // → true // after autofix — when s is "" s?.lastIndexOf('.') !== -1; // → falseIf anything is unclear or seems off, I'd appreciate a follow-up — happy to clarify.
This PR currently downgrades these cases, which were previously auto-fixed, to a suggestion, which is the expected resolution based on #11917 (comment).
If I understand correctly, the question is whether the autofix should be preserved in special cases that actually can be proven to be safe (satisfying the three conditions above), rather than downgraded to suggestions along with the rest of the cases? I'd say, no, let's not bother, because a) it's of marginal value b) it adds complexity, and c) it will delay merging this PR, which I'm anxious to merge 😄.
Regarding #12328 (comment), translating is a completely valid and understandable use of AI, thank you for letting us know 👍 For whatever reason, the wording in this PR turned out to be rather difficult to understand... You've submitted quite a few high quality PRs previously (which we really appreciate! ❤️) and I think we've always had an easy time understanding the conversations in them, so it was just a bit surprising to see a difference here (and we're bit apprehensive in general these days due to the increasing volume of bad, AI-authored PRs).
In any case, this looks good to me. Thanks for sending! 🙏
|
Thanks for the detailed explanation and review! And thanks for the kind words as well 😊 |
ebce2d4
into
typescript-eslint:main
| datasource | package | from | to | | ---------- | -------------------------------- | ------ | ------ | | npm | @typescript-eslint/eslint-plugin | 8.61.0 | 8.62.1 | | npm | @typescript-eslint/parser | 8.61.0 | 8.62.1 | ## [v8.62.1](https://github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#8621-2026-06-29) ##### 🩹 Fixes - **eslint-plugin:** \[no-unnecessary-type-assertion] parenthesize object literal at left edge of expression statement ([#12443](typescript-eslint/typescript-eslint#12443), [#12418](typescript-eslint/typescript-eslint#12418)) - **eslint-plugin:** \[no-unnecessary-boolean-literal-compare] preserve boolean result in fixer for nullable true comparisons ([#12365](typescript-eslint/typescript-eslint#12365)) - **eslint-plugin:** \[prefer-optional-chain] use suggestion instead of autofix for trailing binary operator ([#12328](typescript-eslint/typescript-eslint#12328)) ##### ❤️ Thank You - Kirk Waiblinger [@kirkwaiblinger](https://github.com/kirkwaiblinger) - mdm317 - Patrick Aleite - 송재욱 See [GitHub Releases](https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.62.1) for more information. You can read about our [versioning strategy](https://typescript-eslint.io/users/versioning) and [releases](https://typescript-eslint.io/users/releases) on our website. ## [v8.62.0](https://github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#8620-2026-06-22) ##### 🚀 Features - remove redundant package.json "files" ([#12444](typescript-eslint/typescript-eslint#12444)) ##### ❤️ Thank You - Kirk Waiblinger [@kirkwaiblinger](https://github.com/kirkwaiblinger) See [GitHub Releases](https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.62.0) for more information. You can read about our [versioning strategy](https://typescript-eslint.io/users/versioning) and [releases](https://typescript-eslint.io/users/releases) on our website. ## [v8.61.1](https://github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#8611-2026-06-15) ##### 🩹 Fixes - **eslint-plugin:** \[no-unnecessary-template-expression] respect ECMAScript line terminators ([#12388](typescript-eslint/typescript-eslint#12388)) - **eslint-plugin:** \[no-unnecessary-boolean-literal-compare] fix precedence bug in autofix ([#12413](typescript-eslint/typescript-eslint#12413)) - **eslint-plugin:** \[no-unnecessary-type-assertion] wrap object literal in parens when removing TSTypeAssertion in arrow body ([#12394](typescript-eslint/typescript-eslint#12394), [#12393](typescript-eslint/typescript-eslint#12393)) - **eslint-plugin:** \[no-unnecessary-type-assertion] avoid false positive for template literal expressions ([#12281](typescript-eslint/typescript-eslint#12281)) - **eslint-plugin:** \[consistent-indexed-object-style] do not remove comments when fixing ([#12396](typescript-eslint/typescript-eslint#12396), [#10577](typescript-eslint/typescript-eslint#10577)) ##### ❤️ Thank You - Anas [@anasm266](https://github.com/anasm266) - Deftera [@Deftera186](https://github.com/Deftera186) - Kirk Waiblinger [@kirkwaiblinger](https://github.com/kirkwaiblinger) - lumir - Sarath Francis [@sarathfrancis90](https://github.com/sarathfrancis90) See [GitHub Releases](https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.61.1) for more information. You can read about our [versioning strategy](https://typescript-eslint.io/users/versioning) and [releases](https://typescript-eslint.io/users/releases) on our website.
| datasource | package | from | to | | ---------- | -------------------------------- | ------ | ------ | | npm | @typescript-eslint/eslint-plugin | 8.61.0 | 8.62.1 | | npm | @typescript-eslint/parser | 8.61.0 | 8.62.1 | ## [v8.62.1](https://github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#8621-2026-06-29) ##### 🩹 Fixes - **eslint-plugin:** \[no-unnecessary-type-assertion] parenthesize object literal at left edge of expression statement ([#12443](typescript-eslint/typescript-eslint#12443), [#12418](typescript-eslint/typescript-eslint#12418)) - **eslint-plugin:** \[no-unnecessary-boolean-literal-compare] preserve boolean result in fixer for nullable true comparisons ([#12365](typescript-eslint/typescript-eslint#12365)) - **eslint-plugin:** \[prefer-optional-chain] use suggestion instead of autofix for trailing binary operator ([#12328](typescript-eslint/typescript-eslint#12328)) ##### ❤️ Thank You - Kirk Waiblinger [@kirkwaiblinger](https://github.com/kirkwaiblinger) - mdm317 - Patrick Aleite - 송재욱 See [GitHub Releases](https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.62.1) for more information. You can read about our [versioning strategy](https://typescript-eslint.io/users/versioning) and [releases](https://typescript-eslint.io/users/releases) on our website. ## [v8.62.0](https://github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#8620-2026-06-22) ##### 🚀 Features - remove redundant package.json "files" ([#12444](typescript-eslint/typescript-eslint#12444)) ##### ❤️ Thank You - Kirk Waiblinger [@kirkwaiblinger](https://github.com/kirkwaiblinger) See [GitHub Releases](https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.62.0) for more information. You can read about our [versioning strategy](https://typescript-eslint.io/users/versioning) and [releases](https://typescript-eslint.io/users/releases) on our website. ## [v8.61.1](https://github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#8611-2026-06-15) ##### 🩹 Fixes - **eslint-plugin:** \[no-unnecessary-template-expression] respect ECMAScript line terminators ([#12388](typescript-eslint/typescript-eslint#12388)) - **eslint-plugin:** \[no-unnecessary-boolean-literal-compare] fix precedence bug in autofix ([#12413](typescript-eslint/typescript-eslint#12413)) - **eslint-plugin:** \[no-unnecessary-type-assertion] wrap object literal in parens when removing TSTypeAssertion in arrow body ([#12394](typescript-eslint/typescript-eslint#12394), [#12393](typescript-eslint/typescript-eslint#12393)) - **eslint-plugin:** \[no-unnecessary-type-assertion] avoid false positive for template literal expressions ([#12281](typescript-eslint/typescript-eslint#12281)) - **eslint-plugin:** \[consistent-indexed-object-style] do not remove comments when fixing ([#12396](typescript-eslint/typescript-eslint#12396), [#10577](typescript-eslint/typescript-eslint#10577)) ##### ❤️ Thank You - Anas [@anasm266](https://github.com/anasm266) - Deftera [@Deftera186](https://github.com/Deftera186) - Kirk Waiblinger [@kirkwaiblinger](https://github.com/kirkwaiblinger) - lumir - Sarath Francis [@sarathfrancis90](https://github.com/sarathfrancis90) See [GitHub Releases](https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.61.1) for more information. You can read about our [versioning strategy](https://typescript-eslint.io/users/versioning) and [releases](https://typescript-eslint.io/users/releases) on our website.

PR Checklist
Overview
foo == null || foo.bar == undefined) are still autofixed.Additional Notes
It seems the unsafe rewrites only occur when the compared value is non-primitive.
When the compared value is primitive, the rewrite appears to be safe.
Possible options: