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.
Changes fromall commits
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -182,17 +182,32 @@ export default createRule<Options, MessageId>({ | ||
return checker.isTupleType(nodeType); | ||
} | ||
// Index accesses are exempt from certain checks about nullish-ness, because | ||
// the type returned by the checker is not the strictest | ||
function isIndexAccessExpression(node: TSESTree.Expression): boolean { | ||
if (node.type !== AST_NODE_TYPES.MemberExpression) { | ||
return false; | ||
} | ||
const objType = getNodeType(node.object); | ||
if (checker.isTupleType(objType)) { | ||
// If indexing a tuple with a literal, the type will be sound | ||
return node.property.type !== AST_NODE_TYPES.Literal; | ||
} | ||
Comment on lines +192 to +195 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 I.e. for | ||
if (node.computed) { | ||
bradzacher marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
const indexType = getNodeType(node.property); | ||
if (isTypeAnyType(indexType)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. question: do we want to handle the | ||
// 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 commentThe reason will be displayed to describe this comment to others.Learn more. This doens't handle "nominal" number types here. I think it's okay that we ignore it here - it's a super niche usecase. | ||
return ( | ||
checker.getIndexTypeOfType(objType, ts.IndexKind.Number) !== | ||
undefined | ||
); | ||
} | ||
} | ||
Comment on lines +202 to +208 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. we should probably also handle declareconstobj:{[k:symbol]:string};declareconstkey:symbol;constres=obj[key];res?.length// should not report | ||
return ( | ||
checker.getIndexTypeOfType(objType, ts.IndexKind.String) !== undefined | ||
); | ||
} | ||
@@ -215,7 +230,7 @@ export default createRule<Options, MessageId>({ | ||
// Since typescript array index signature types don't represent the | ||
// possibility of out-of-bounds access, if we're indexing into an array | ||
// just skip the check, to avoid false positives | ||
if (isIndexAccessExpression(node)) { | ||
return; | ||
} | ||
@@ -276,7 +291,7 @@ export default createRule<Options, MessageId>({ | ||
// possibility of out-of-bounds access, if we're indexing into an array | ||
// just skip the check, to avoid false positives | ||
if ( | ||
!isIndexAccessExpression(node) && | ||
!( | ||
node.type === AST_NODE_TYPES.ChainExpression && | ||
node.expression.type !== AST_NODE_TYPES.TSNonNullExpression && | ||
@@ -490,7 +505,7 @@ export default createRule<Options, MessageId>({ | ||
): boolean { | ||
const lhsNode = | ||
node.type === AST_NODE_TYPES.CallExpression ? node.callee : node.object; | ||
if (node.optional &&isIndexAccessExpression(lhsNode)) { | ||
return true; | ||
} | ||
if ( | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -318,15 +318,41 @@ if (returnsArr?.()[42]) { | ||
} | ||
returnsArr?.()[42]?.toUpperCase(); | ||
`, | ||
// nullish + index signature | ||
` | ||
declare const arr: string[][]; | ||
declare const x: any; | ||
arr[x] ?? []; | ||
`, | ||
` | ||
declare const arr: { foo: number }[]; | ||
const bar = arr[42]?.foo ?? 0; | ||
`, | ||
` | ||
declare const foo: Record<string, number>; | ||
declare const bar: string; | ||
foo[bar] ??= 0; | ||
`, | ||
` | ||
declare const foo: Record<string, number>; | ||
declare const bar: string; | ||
foo[bar] ?? 0; | ||
`, | ||
` | ||
function getElem(dict: Record<string, { foo: string }>, key: string) { | ||
if (dict[key]) { | ||
return dict[key].foo; | ||
} else { | ||
return ''; | ||
} | ||
} | ||
`, | ||
` | ||
declare const dict: Record<string, object>; | ||
if (dict['mightNotExist']) { | ||
} | ||
`, | ||
// Doesn't check the right-hand side of a logical expression | ||
// in a non-conditional context | ||
@@ -895,28 +921,18 @@ function nothing3(x: [string, string]) { | ||
], | ||
}, | ||
// Indexing cases | ||
{ | ||
// Should still check tuples when accessed with literal numbers, since they don't have | ||
// unsound index signatures | ||
code: ` | ||
declareconst x: [{ foo: string }]; | ||
if (x[0]) { | ||
} | ||
if (x[0]?.foo) { | ||
} | ||
`, | ||
output: ` | ||
declareconst x: [{ foo: string }]; | ||
if (x[0]) { | ||
} | ||
if (x[0].foo) { | ||
@@ -1670,26 +1686,6 @@ pick({ foo: 1, bar: 2 }, 'bar'); | ||
}, | ||
{ | ||
code: ` | ||
Comment on lines -1673 to -1679 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 commentThe reason will be displayed to describe this comment to others.Learn more. This actually should be reported because the fixture config doesn't use 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 commentThe reason will be displayed to describe this comment to others.Learn more. @bradzacher This code is the exact same principle as There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 if However in 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 commentThe reason will be displayed to describe this comment to others.Learn more. However, we already ignore array index access, even without 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 commentThe 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 For objects it's pretty natural to union in Now that we have 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 | ||
declare let foo: {}; | ||
foo ??= 1; | ||
`, | ||