diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts index c9660416a81b..19e292b0db8c 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts @@ -273,6 +273,10 @@ export default createRule({ compilerOptions, 'strictNullChecks', ); + const isNoUncheckedIndexedAccess = tsutils.isCompilerOptionEnabled( + compilerOptions, + 'noUncheckedIndexedAccess', + ); if ( !isStrictNullChecks && @@ -756,11 +760,15 @@ export default createRule({ } const indexInfo = checker.getIndexInfosOfType(type); - return indexInfo.some( - info => - getTypeName(checker, info.keyType) === 'string' && - isNullableType(info.type), - ); + return indexInfo.some(info => { + const isStringTypeName = + getTypeName(checker, info.keyType) === 'string'; + + return ( + isStringTypeName && + (isNoUncheckedIndexedAccess || isNullableType(info.type)) + ); + }); }); return !isOwnNullable && isNullableType(prevType); } diff --git a/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts b/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts index de69b94afac3..f179b7c1d8fb 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts @@ -27,6 +27,12 @@ const optionsWithExactOptionalPropertyTypes = { tsconfigRootDir: rootPath, }; +const optionsWithNoUncheckedIndexedAccess = { + project: './tsconfig.noUncheckedIndexedAccess.json', + projectService: false, + tsconfigRootDir: getFixturesRootDir(), +}; + const necessaryConditionTest = (condition: string): string => ` declare const b1: ${condition}; declare const b2: boolean; @@ -591,11 +597,7 @@ const key = '1' as BrandedKey; foo?.[key]?.trim(); `, languageOptions: { - parserOptions: { - project: './tsconfig.noUncheckedIndexedAccess.json', - projectService: false, - tsconfigRootDir: getFixturesRootDir(), - }, + parserOptions: optionsWithNoUncheckedIndexedAccess, }, }, { @@ -649,11 +651,7 @@ function Foo(outer: Outer, key: Foo): number | undefined { } `, languageOptions: { - parserOptions: { - project: './tsconfig.noUncheckedIndexedAccess.json', - projectService: false, - tsconfigRootDir: getFixturesRootDir(), - }, + parserOptions: optionsWithNoUncheckedIndexedAccess, }, }, { @@ -666,11 +664,51 @@ declare const key: Key; foo?.[key]?.trim(); `, languageOptions: { - parserOptions: { - project: './tsconfig.noUncheckedIndexedAccess.json', - projectService: false, - tsconfigRootDir: getFixturesRootDir(), - }, + parserOptions: optionsWithNoUncheckedIndexedAccess, + }, + }, + { + code: ` +type Foo = { + key?: Record; +}; +declare const foo: Foo; +foo.key?.someKey?.key; + `, + languageOptions: { + parserOptions: optionsWithNoUncheckedIndexedAccess, + }, + }, + { + code: ` +type Foo = { + key?: { + [key: string]: () => void; + }; +}; +declare const foo: Foo; +foo.key?.value?.(); + `, + languageOptions: { + parserOptions: optionsWithNoUncheckedIndexedAccess, + }, + }, + { + code: ` +type A = { + [name in Lowercase]?: { + [name in Lowercase]: { + a: 1; + }; + }; +}; + +declare const a: A; + +a.a?.a?.a; + `, + languageOptions: { + parserOptions: optionsWithNoUncheckedIndexedAccess, }, }, `