Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork2.8k
fix(eslint-plugin): [no-unnecessary-condition] ignore optionally accessing index signatures#6762
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,@Josh-Cena! 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. |
netlifybot commentedMar 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.
✅ Deploy Preview fortypescript-eslint ready!
To edit notification comments on pull requests, go to yourNetlify site settings. |
nx-cloudbot commentedMar 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.
4ac41b6
tofd1488a
Comparefunction getElem(dict: Record<string, { foo: string }>, key: string) { | ||
if (dict[key]) { | ||
return dict[key].foo; | ||
} else { | ||
return ''; | ||
} | ||
} |
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 was arguably a bug fix in the existing test case, because it shouldn't be reported either.
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 actually should be reported because the fixture config doesn't usenoUncheckedIndexAccess
!
I think we should probably guard the new code around whether or not the flag is on.
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.
@bradzacher This code is the exact same principle asdictionary[key] ??= defaultVal
. If we want to ignore that, we have to ignore others as well.
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.
They are and they aren't. You're correct in that they're both record index access in a logical position - so they're semantically the same case. However TS reports the types differently for the two cases.
In this instance TS will report the read type, meaning that ifnoUncheckedIndexAccess
is turned on then TS will report the type asT | undefimed
to us.
However inx ?? y
, TS will report the write type, meaning it reports justT
- no matter whether or notnoUncheckedIndexAccess
is on or not.
This is the fundamental difference between the two cases and why we are false positiving on the assignment operators, butnot on this case.
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.
However, we already ignore array index access, even withoutnoUncheckedIndexAccess
. See this test case:
declareconstarr:{foo:number}[];constbar=arr[42]?.foo??0;
I find it quite natural to extend the scope to all index signatures, since there isn't something special about array indices.
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.
The reason we did it for arrays and not objects is because defining arrays as(T | undefined)[]
is similar toRecord<string, T | undefined>
, but far, far less ergonomic syntactically and is arguably worse given how common it is to iterate arrays directly using an iterator method or for/of.
For objects it's pretty natural to union inundefined
because most usecases for reading is an index signature - the direct iteration methods (Object.entries
/Object.values
) are both rarer usecases so theundefined
doesn't get in the way as much.
Now that we havenoUncheckedIndexAccess
- we don't really need the logic at all, but I understand that some people think the compiler option goes too far and can get in the way (which is true to a degree).
The motivation behind this change isn't bringing the two sets up to parity (that's a much larger change that we should discuss as a group to decide if we think we should treat objects the same as arrays in this regard) - instead it's just fixing the current false positives we have due to the logical operator handling we added in#6594
codecovbot commentedMar 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.
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@## main #6762 +/- ##======================================= Coverage ? 87.30% ======================================= Files ? 384 Lines ? 13148 Branches ? 3863 ======================================= Hits ? 11479 Misses ? 1302 Partials ? 367
Flags with carried forward coverage won't be shown.Click here to find out more.
|
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.
Thanks for getting straight on this!
Almost there I think!
Uh oh!
There was an error while loading.Please reload this page.
function getElem(dict: Record<string, { foo: string }>, key: string) { | ||
if (dict[key]) { | ||
return dict[key].foo; | ||
} else { | ||
return ''; | ||
} | ||
} |
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 actually should be reported because the fixture config doesn't usenoUncheckedIndexAccess
!
I think we should probably guard the new code around whether or not the flag is on.
* Update dependency upgrades - non-major* Require valid disables with explanations* Temporarily disable typescript-eslint ruleRef:typescript-eslint/typescript-eslint#6762---------Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>Co-authored-by: Karl Horky <karl.horky@gmail.com>
👋@Josh-Cena just checking in, is this still something you have time for? |
Yes! I'll get to it. Sorry for the wait. I still don't know what the best way to fix this is, because I feel like accounting for index signature optionality, even without the compiler option, should be acceptable. |
hbiede commentedAug 15, 2023
Hoping this will be available in the near-term future. I'd love to use this rule without having to use dozens of casts |
karlhorky commentedAug 16, 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.
It seems like the TypeScript PRmicrosoft/TypeScript#54777 was merged, which closed the issue in the TypeScript repo: Does that mean that this PR is now obsolete, once the TS PR changes are released in a version of TypeScript (eg. maybe 5.2)? |
@karlhorky That PR changes the quick info of the write type so it excludes |
@bradzacher Sorry it's been a really long time and I think neither of us are up to speed on this now. Let's recap—what (potential) false-positives should we fix and what should we continue erroring on? Currently we ignore all |
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.
originally I was against us bringing arrays and objects into parity.
however given the statenoUncheckedIndexAccess
is in esp in regards to??=
- I shall acquiesce here and we can loosen the rule to just ignore really anything with an index signature.
A few comments but otherwise looking good.
if (checker.isTupleType(objType)) { | ||
// If indexing a tuple with a literal, the type will be sound | ||
return node.property.type !== AST_NODE_TYPES.Literal; | ||
} |
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 check is actually no longer sound with modern TS.
Tuples can be variadic now - (eg[T1, ...T2[]]
,[...T1[], T2]
,[T1, ...T2[], T3]
,[...T1[]]
) - so they're no more safe than an array.
I.e. forconst foo: [T1, ...T2[]]
-foo[1]
is possiblyundefined
} | ||
if (node.computed) { | ||
const indexType = getNodeType(node.property); | ||
if (isTypeAnyType(indexType)) { |
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.
question: do we want to handle thenever
case here?
I think it's okay not to just figured I'd flag it for completeness sake
// No need to check anything about any | ||
return true; | ||
} | ||
if (isTypeFlagSet(indexType, ts.TypeFlags.NumberLike)) { |
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 doens't handle "nominal" number types here.
EGtype T = number & { __brand: unknown }
would work in the signature but we would not match it here.
I think it's okay that we ignore it here - it's a super niche usecase.
Just commenting for completeness's sake.
if (isTypeFlagSet(indexType, ts.TypeFlags.NumberLike)) { | ||
return ( | ||
checker.getIndexTypeOfType(objType, ts.IndexKind.Number) !== | ||
undefined | ||
); | ||
} | ||
} |
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.
we should probably also handlesymbol
indexes as well.
They're super rare but it should just be one extraif
declareconstobj:{[k:symbol]:string};declareconstkey:symbol;constres=obj[key];res?.length// should not report
👋 ping@Josh-Cena, do you still have time for this one? |
Closing this PR as it's been stale for a while without activity. If anybody wants to drive it forward, please do post your own PR - and if you use this as a start, consider adding a co-author attribution at the end of your PR description. Thanks! 😊 |
Uh oh!
There was an error while loading.Please reload this page.
PR Checklist
Overview