From 059dfb8aa37f0676044e263fd09b2bf132ed6636 Mon Sep 17 00:00:00 2001 From: YeonJuan Date: Tue, 17 Dec 2024 21:05:17 +0900 Subject: [PATCH 1/3] fix(eslint-plugin): [no-unnecessary-condition] handle index signature --- .../src/rules/no-unnecessary-condition.ts | 4 +- .../rules/no-unnecessary-condition.test.ts | 53 ++++++------------- 2 files changed, 17 insertions(+), 40 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts index c9660416a81b..a5f0b346ad8a 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts @@ -757,9 +757,7 @@ export default createRule({ const indexInfo = checker.getIndexInfosOfType(type); return indexInfo.some( - info => - getTypeName(checker, info.keyType) === 'string' && - isNullableType(info.type), + info => getTypeName(checker, info.keyType) === 'string', ); }); 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..e88ae919dc32 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts @@ -1010,6 +1010,22 @@ declare const t: T; t.a.a.a.value; t.A?.A?.A?.VALUE; `, + ` +type Foo = { + key?: Record; +}; +declare const foo: Foo; +foo.key?.value?.length; + `, + ` +type Foo = { + key?: { + [key: string]: () => void; + }; +}; +declare const foo: Foo; +foo.key?.value?.(); + `, ], invalid: [ @@ -2915,43 +2931,6 @@ isString('fa' + 'lafel'); ), { code: ` -type A = { - [name in Lowercase]?: { - [name in Lowercase]: { - a: 1; - }; - }; -}; - -declare const a: A; - -a.a?.a?.a; - `, - errors: [ - { - column: 7, - endColumn: 9, - endLine: 12, - line: 12, - messageId: 'neverOptionalChain', - }, - ], - output: ` -type A = { - [name in Lowercase]?: { - [name in Lowercase]: { - a: 1; - }; - }; -}; - -declare const a: A; - -a.a?.a.a; - `, - }, - { - code: ` interface T { [name: Lowercase]: { [name: Lowercase]: { From 572d02f7def184b2abadeaa304582d6282959da3 Mon Sep 17 00:00:00 2001 From: YeonJuan Date: Wed, 18 Dec 2024 02:18:40 +0900 Subject: [PATCH 2/3] fix --- .../src/rules/no-unnecessary-condition.ts | 16 +++- .../rules/no-unnecessary-condition.test.ts | 76 +++++++++++++++++-- 2 files changed, 83 insertions(+), 9 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts index a5f0b346ad8a..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,9 +760,15 @@ export default createRule({ } const indexInfo = checker.getIndexInfosOfType(type); - return indexInfo.some( - info => getTypeName(checker, info.keyType) === 'string', - ); + 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 e88ae919dc32..3905da73296a 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,11 @@ const optionsWithExactOptionalPropertyTypes = { tsconfigRootDir: rootPath, }; +const optionsWithNoUncheckedIndexedAccess = { + project: './tsconfig.noUncheckedIndexedAccess.json', + tsconfigRootDir: rootPath, +}; + const necessaryConditionTest = (condition: string): string => ` declare const b1: ${condition}; declare const b2: boolean; @@ -1010,14 +1015,18 @@ declare const t: T; t.a.a.a.value; t.A?.A?.A?.VALUE; `, - ` + { + code: ` type Foo = { - key?: Record; + key?: Record; }; declare const foo: Foo; -foo.key?.value?.length; - `, - ` +foo.key?.someKey?.key; + `, + languageOptions: { parserOptions: optionsWithNoUncheckedIndexedAccess }, + }, + { + code: ` type Foo = { key?: { [key: string]: () => void; @@ -1025,7 +1034,25 @@ type Foo = { }; 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 }, + }, ], invalid: [ @@ -2931,6 +2958,43 @@ isString('fa' + 'lafel'); ), { code: ` +type A = { + [name in Lowercase]?: { + [name in Lowercase]: { + a: 1; + }; + }; +}; + +declare const a: A; + +a.a?.a?.a; + `, + errors: [ + { + column: 7, + endColumn: 9, + endLine: 12, + line: 12, + messageId: 'neverOptionalChain', + }, + ], + output: ` +type A = { + [name in Lowercase]?: { + [name in Lowercase]: { + a: 1; + }; + }; +}; + +declare const a: A; + +a.a?.a.a; + `, + }, + { + code: ` interface T { [name: Lowercase]: { [name: Lowercase]: { From 31f10b067b19b9f5ba5c69a90568c0f1471a6a92 Mon Sep 17 00:00:00 2001 From: YeonJuan Date: Wed, 18 Dec 2024 02:38:55 +0900 Subject: [PATCH 3/3] refactor --- .../rules/no-unnecessary-condition.test.ts | 103 +++++++++--------- 1 file changed, 49 insertions(+), 54 deletions(-) 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 3905da73296a..f179b7c1d8fb 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts @@ -29,7 +29,8 @@ const optionsWithExactOptionalPropertyTypes = { const optionsWithNoUncheckedIndexedAccess = { project: './tsconfig.noUncheckedIndexedAccess.json', - tsconfigRootDir: rootPath, + projectService: false, + tsconfigRootDir: getFixturesRootDir(), }; const necessaryConditionTest = (condition: string): string => ` @@ -596,11 +597,7 @@ const key = '1' as BrandedKey; foo?.[key]?.trim(); `, languageOptions: { - parserOptions: { - project: './tsconfig.noUncheckedIndexedAccess.json', - projectService: false, - tsconfigRootDir: getFixturesRootDir(), - }, + parserOptions: optionsWithNoUncheckedIndexedAccess, }, }, { @@ -654,11 +651,7 @@ function Foo(outer: Outer, key: Foo): number | undefined { } `, languageOptions: { - parserOptions: { - project: './tsconfig.noUncheckedIndexedAccess.json', - projectService: false, - tsconfigRootDir: getFixturesRootDir(), - }, + parserOptions: optionsWithNoUncheckedIndexedAccess, }, }, { @@ -671,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, }, }, ` @@ -1015,44 +1048,6 @@ declare const t: T; t.a.a.a.value; t.A?.A?.A?.VALUE; `, - { - 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 }, - }, ], invalid: [ pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy