fix(eslint-plugin): [no-unnecessary-condition] use assignability checks in checkTypePredicates#12147
Conversation
|
Thanks for the PR, @976520! 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 1496319
☁️ Nx Cloud last updated this comment at |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #12147 +/- ##
=======================================
Coverage 86.71% 86.71%
=======================================
Files 513 513
Lines 16270 16271 +1
Branches 5059 5060 +1
=======================================
+ Hits 14108 14109 +1
Misses 1474 1474
Partials 688 688
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
…ks in checkTypePredicates
… assignability in checkTypePredicates
… from subtype check
14c521b to
fb9a6d4
Compare
bradzacher
left a comment
There was a problem hiding this comment.
looking good so far
interface Wider {
a: string;
}
interface Narrower {
a: string;
b?: number;
}
declare function isWider(x: unknown): x is Wider;
declare const n: Narrower;
if (isWider(n)) {
}Could you add this as a test case please.
It should error IIUC
|
The rule wasn't catching this because it compared types with |
…types and literals
Simplify the type guard check to use `checker.isTypeAssignableTo()` directly instead of restricting to union types. This catches cases where the argument type is structurally assignable to the predicate type (e.g. interfaces with optional properties, string literals).
2a1a1f9 to
9f79dd4
Compare
Use mutual assignability and union subtype checks instead of reference equality to detect unnecessary type guards. This catches cases like interfaces with optional properties (e.g. Narrower assignable to Wider) and subtypes of union predicate types (e.g. string subtype of string | number). Add eslint-disable for false positives caused by custom TypeScript typings that make structurally different runtime types appear equivalent.
9f79dd4 to
a07a056
Compare
| datasource | package | from | to | | ---------- | -------------------------------- | ------ | ------ | | npm | @typescript-eslint/eslint-plugin | 8.56.0 | 8.58.2 | | npm | @typescript-eslint/parser | 8.56.0 | 8.58.2 | ## [v8.58.2](https://github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#8582-2026-04-13) ##### 🩹 Fixes - **eslint-plugin:** \[no-unnecessary-condition] use assignability checks in checkTypePredicates ([#12147](typescript-eslint/typescript-eslint#12147)) - remove tsbuildinfo cache file from published packages ([#12187](typescript-eslint/typescript-eslint#12187)) ##### ❤️ Thank You - Abhijeet Singh [@cseas](https://github.com/cseas) - 송재욱 See [GitHub Releases](https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.58.2) 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.58.1](https://github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#8581-2026-04-08) ##### 🩹 Fixes - **eslint-plugin:** \[no-unused-vars] fix false negative for type predicate parameter ([#12004](typescript-eslint/typescript-eslint#12004)) ##### ❤️ Thank You - MinJae [@Ju-MINJAE](https://github.com/Ju-MINJAE) See [GitHub Releases](https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.58.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.58.0](https://github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#8580-2026-03-30) ##### 🚀 Features - support TypeScript 6 ([#12124](typescript-eslint/typescript-eslint#12124)) ##### 🩹 Fixes - **eslint-plugin:** \[prefer-regexp-exec] avoid fixing unknown RegExp flags ([#12161](typescript-eslint/typescript-eslint#12161)) - **eslint-plugin:** \[no-extraneous-class] handle index signatures ([#12142](typescript-eslint/typescript-eslint#12142)) - **eslint-plugin:** crash in `no-unnecessary-type-arguments` ([#12163](typescript-eslint/typescript-eslint#12163)) ##### ❤️ Thank You - ej shafran [@ej-shafran](https://github.com/ej-shafran) - Evyatar Daud [@StyleShit](https://github.com/StyleShit) - GG ZIBLAKING - milkboy2564 [@SeolJaeHyeok](https://github.com/SeolJaeHyeok) - teee32 [@teee32](https://github.com/teee32) See [GitHub Releases](https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.58.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.57.2](https://github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#8572-2026-03-23) ##### 🩹 Fixes - **eslint-plugin:** \[prefer-readonly-parameter-types] preserve type alias infomation ([#11954](typescript-eslint/typescript-eslint#11954)) - **eslint-plugin:** \[no-useless-default-assignment] skip reporting false positives for unresolved type parameters ([#12127](typescript-eslint/typescript-eslint#12127)) - **eslint-plugin:** \[no-unsafe-return] false positive on unwrapping generic ([#12125](typescript-eslint/typescript-eslint#12125)) - **eslint-plugin:** \[no-restricted-types] flag banned generics in extends or implements ([#12120](typescript-eslint/typescript-eslint#12120)) - **eslint-plugin:** \[array-type] ignore Array and ReadonlyArray without type arguments ([#11971](typescript-eslint/typescript-eslint#11971)) - **eslint-plugin:** \[prefer-optional-chain] remove dangling closing parenthesis ([#11865](typescript-eslint/typescript-eslint#11865)) ##### ❤️ Thank You - Kirk Waiblinger [@kirkwaiblinger](https://github.com/kirkwaiblinger) - Konv Suu - mdm317 - Newton Yuan [@NewtonYuan](https://github.com/NewtonYuan) - SungHyun627 [@SungHyun627](https://github.com/SungHyun627) - Tamashoo [@Tamashoo](https://github.com/Tamashoo) See [GitHub Releases](https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.57.2) 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.57.1](https://github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#8571-2026-03-16) ##### 🩹 Fixes - **eslint-plugin:** \[prefer-optional-chain] no report for property on intersection type ([#12126](typescript-eslint/typescript-eslint#12126)) ##### ❤️ Thank You - Newton Yuan [@NewtonYuan](https://github.com/NewtonYuan) See [GitHub Releases](https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.57.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.57.0](https://github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#8570-2026-03-09) ##### 🚀 Features - **eslint-plugin:** \[no-unnecessary-condition] allow literal loop conditions in for/do loops ([#12080](typescript-eslint/typescript-eslint#12080)) ##### 🩹 Fixes - **eslint-plugin:** \[no-base-to-string] fix false positive for toString with overloads ([#12089](typescript-eslint/typescript-eslint#12089)) - **eslint-plugin:** \[prefer-promise-reject-errors] add allow `TypeOrValueSpecifier` to prefer-promise-reject-errors ([#12094](typescript-eslint/typescript-eslint#12094)) - **typescript-estree:** if the template literal is tagged and the text has an invalid escape, `cooked` will be `null` ([#11355](typescript-eslint/typescript-eslint#11355)) - **eslint-plugin:** guard against negative paramIndex in no-useless-default-assignment ([#12077](typescript-eslint/typescript-eslint#12077)) - **eslint-plugin:** handle statically analyzable computed keys in prefer-readonly ([#12079](typescript-eslint/typescript-eslint#12079)) - **eslint-plugin:** \[strict-void-return] false positives with overloads ([#12055](typescript-eslint/typescript-eslint#12055)) ##### ❤️ Thank You - Brad Zacher [@bradzacher](https://github.com/bradzacher) - Brian Schlenker [@bschlenk](https://github.com/bschlenk) - Evyatar Daud [@StyleShit](https://github.com/StyleShit) - James Henry [@JamesHenry](https://github.com/JamesHenry) - Josh Goldberg - Kirk Waiblinger [@kirkwaiblinger](https://github.com/kirkwaiblinger) - Moses Odutusin [@thebolarin](https://github.com/thebolarin) - Newton Yuan [@NewtonYuan](https://github.com/NewtonYuan) - SungHyun627 [@SungHyun627](https://github.com/SungHyun627) - Younsang Na [@nayounsang](https://github.com/nayounsang) See [GitHub Releases](https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.57.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.56.1](https://github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#8561-2026-02-23) This was a version bump only for eslint-plugin to align it with other projects, there were no code changes. See [GitHub Releases](https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.56.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.56.0 | 8.58.2 | | npm | @typescript-eslint/parser | 8.56.0 | 8.58.2 | ## [v8.58.2](https://github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#8582-2026-04-13) ##### 🩹 Fixes - **eslint-plugin:** \[no-unnecessary-condition] use assignability checks in checkTypePredicates ([#12147](typescript-eslint/typescript-eslint#12147)) - remove tsbuildinfo cache file from published packages ([#12187](typescript-eslint/typescript-eslint#12187)) ##### ❤️ Thank You - Abhijeet Singh [@cseas](https://github.com/cseas) - 송재욱 See [GitHub Releases](https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.58.2) 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.58.1](https://github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#8581-2026-04-08) ##### 🩹 Fixes - **eslint-plugin:** \[no-unused-vars] fix false negative for type predicate parameter ([#12004](typescript-eslint/typescript-eslint#12004)) ##### ❤️ Thank You - MinJae [@Ju-MINJAE](https://github.com/Ju-MINJAE) See [GitHub Releases](https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.58.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.58.0](https://github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#8580-2026-03-30) ##### 🚀 Features - support TypeScript 6 ([#12124](typescript-eslint/typescript-eslint#12124)) ##### 🩹 Fixes - **eslint-plugin:** \[prefer-regexp-exec] avoid fixing unknown RegExp flags ([#12161](typescript-eslint/typescript-eslint#12161)) - **eslint-plugin:** \[no-extraneous-class] handle index signatures ([#12142](typescript-eslint/typescript-eslint#12142)) - **eslint-plugin:** crash in `no-unnecessary-type-arguments` ([#12163](typescript-eslint/typescript-eslint#12163)) ##### ❤️ Thank You - ej shafran [@ej-shafran](https://github.com/ej-shafran) - Evyatar Daud [@StyleShit](https://github.com/StyleShit) - GG ZIBLAKING - milkboy2564 [@SeolJaeHyeok](https://github.com/SeolJaeHyeok) - teee32 [@teee32](https://github.com/teee32) See [GitHub Releases](https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.58.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.57.2](https://github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#8572-2026-03-23) ##### 🩹 Fixes - **eslint-plugin:** \[prefer-readonly-parameter-types] preserve type alias infomation ([#11954](typescript-eslint/typescript-eslint#11954)) - **eslint-plugin:** \[no-useless-default-assignment] skip reporting false positives for unresolved type parameters ([#12127](typescript-eslint/typescript-eslint#12127)) - **eslint-plugin:** \[no-unsafe-return] false positive on unwrapping generic ([#12125](typescript-eslint/typescript-eslint#12125)) - **eslint-plugin:** \[no-restricted-types] flag banned generics in extends or implements ([#12120](typescript-eslint/typescript-eslint#12120)) - **eslint-plugin:** \[array-type] ignore Array and ReadonlyArray without type arguments ([#11971](typescript-eslint/typescript-eslint#11971)) - **eslint-plugin:** \[prefer-optional-chain] remove dangling closing parenthesis ([#11865](typescript-eslint/typescript-eslint#11865)) ##### ❤️ Thank You - Kirk Waiblinger [@kirkwaiblinger](https://github.com/kirkwaiblinger) - Konv Suu - mdm317 - Newton Yuan [@NewtonYuan](https://github.com/NewtonYuan) - SungHyun627 [@SungHyun627](https://github.com/SungHyun627) - Tamashoo [@Tamashoo](https://github.com/Tamashoo) See [GitHub Releases](https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.57.2) 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.57.1](https://github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#8571-2026-03-16) ##### 🩹 Fixes - **eslint-plugin:** \[prefer-optional-chain] no report for property on intersection type ([#12126](typescript-eslint/typescript-eslint#12126)) ##### ❤️ Thank You - Newton Yuan [@NewtonYuan](https://github.com/NewtonYuan) See [GitHub Releases](https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.57.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.57.0](https://github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#8570-2026-03-09) ##### 🚀 Features - **eslint-plugin:** \[no-unnecessary-condition] allow literal loop conditions in for/do loops ([#12080](typescript-eslint/typescript-eslint#12080)) ##### 🩹 Fixes - **eslint-plugin:** \[no-base-to-string] fix false positive for toString with overloads ([#12089](typescript-eslint/typescript-eslint#12089)) - **eslint-plugin:** \[prefer-promise-reject-errors] add allow `TypeOrValueSpecifier` to prefer-promise-reject-errors ([#12094](typescript-eslint/typescript-eslint#12094)) - **typescript-estree:** if the template literal is tagged and the text has an invalid escape, `cooked` will be `null` ([#11355](typescript-eslint/typescript-eslint#11355)) - **eslint-plugin:** guard against negative paramIndex in no-useless-default-assignment ([#12077](typescript-eslint/typescript-eslint#12077)) - **eslint-plugin:** handle statically analyzable computed keys in prefer-readonly ([#12079](typescript-eslint/typescript-eslint#12079)) - **eslint-plugin:** \[strict-void-return] false positives with overloads ([#12055](typescript-eslint/typescript-eslint#12055)) ##### ❤️ Thank You - Brad Zacher [@bradzacher](https://github.com/bradzacher) - Brian Schlenker [@bschlenk](https://github.com/bschlenk) - Evyatar Daud [@StyleShit](https://github.com/StyleShit) - James Henry [@JamesHenry](https://github.com/JamesHenry) - Josh Goldberg - Kirk Waiblinger [@kirkwaiblinger](https://github.com/kirkwaiblinger) - Moses Odutusin [@thebolarin](https://github.com/thebolarin) - Newton Yuan [@NewtonYuan](https://github.com/NewtonYuan) - SungHyun627 [@SungHyun627](https://github.com/SungHyun627) - Younsang Na [@nayounsang](https://github.com/nayounsang) See [GitHub Releases](https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.57.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.56.1](https://github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#8561-2026-02-23) This was a version bump only for eslint-plugin to align it with other projects, there were no code changes. See [GitHub Releases](https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.56.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.
|
I think the last test case should be allowed, it's useful to have valid access to the |
|
@ArnaudBarre you don't need a predicate at all in the last test case because Eg If the property is non-optional then the check is required and the rule does not report. |
|
Oh yeah true, that also explain the edge case (even if I think it would be better implemented by actually checking the the narrower type has at least one required property) The fact that you need to create a new variable in was not be obvious to me and could be added in the message |

PR Checklist
checkTypePredicates#11798Overview
checkTypePredicatespreviously only flagged a type guard call as unnecessary when the argument'stype was the exact same TS type object as the predicate's asserted type. This meant cases
where the argument is a strict subtype of the asserted type were silently passed over.
e.g,
isStringOrNumber(s)wheres: stringwas not flagged, even thoughstringis alwaysassignable to
string | number, making the check redundant.This pr extends the check to also report when the argument type is a strict subtype of the asserted
type — i.e., assignable one-way but not mutually assignable. Mutual assignability is explicitly
excluded to avoid false positives with structurally compatible types that still produce meaningful
narrowing.