From 66b1c1a1910c686c09e34b69c41132a4303379f7 Mon Sep 17 00:00:00 2001 From: mdm317 Date: Fri, 17 Oct 2025 00:23:47 +0900 Subject: [PATCH 1/6] fix : remove isAlwaysTruthyOperand --- .../analyzeChain.ts | 61 +- .../prefer-optional-chain.test.ts | 755 +++++------------- 2 files changed, 206 insertions(+), 610 deletions(-) diff --git a/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/analyzeChain.ts b/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/analyzeChain.ts index 84c1ca6e9593..e197e382f731 100644 --- a/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/analyzeChain.ts +++ b/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/analyzeChain.ts @@ -48,40 +48,6 @@ function includesType( return false; } -function isAlwaysTruthyOperand( - comparedName: TSESTree.Node, - nullishComparisonType: NullishComparisonType, - parserServices: ParserServicesWithTypeInformation, -): boolean { - const ANY_UNKNOWN_FLAGS = ts.TypeFlags.Any | ts.TypeFlags.Unknown; - const comparedNameType = parserServices.getTypeAtLocation(comparedName); - - if (isTypeFlagSet(comparedNameType, ANY_UNKNOWN_FLAGS)) { - return false; - } - switch (nullishComparisonType) { - case NullishComparisonType.Boolean: - case NullishComparisonType.NotBoolean: { - const types = unionConstituents(comparedNameType); - return types.every(type => !isFalsyType(type)); - } - case NullishComparisonType.NotStrictEqualUndefined: - case NullishComparisonType.NotStrictEqualNull: - case NullishComparisonType.StrictEqualNull: - case NullishComparisonType.StrictEqualUndefined: - return !isTypeFlagSet( - comparedNameType, - ts.TypeFlags.Null | ts.TypeFlags.Undefined, - ); - case NullishComparisonType.NotEqualNullOrUndefined: - case NullishComparisonType.EqualNullOrUndefined: - return !isTypeFlagSet( - comparedNameType, - ts.TypeFlags.Null | ts.TypeFlags.Undefined, - ); - } -} - function isValidAndLastChainOperand( ComparisonValueType: TSESTree.Node, comparisonType: ComparisonType, @@ -710,20 +676,6 @@ export function analyzeChain( } break; } - case NullishComparisonType.StrictEqualNull: - case NullishComparisonType.NotStrictEqualNull: { - if ( - comparisonResult === NodeComparisonResult.Subset && - isAlwaysTruthyOperand( - lastOperand.comparedName, - lastOperand.comparisonType, - parserServices, - ) - ) { - lastChain = operand; - } - break; - } } } maybeReportThenReset(); @@ -768,16 +720,11 @@ export function analyzeChain( : isValidOrLastChainOperand; if ( comparisonResult === NodeComparisonResult.Subset && - (isAlwaysTruthyOperand( - lastOperand.comparedName, - lastOperand.comparisonType, + isValidLastChainOperand( + lastChainOperand.comparisonValue, + lastChainOperand.comparisonType, parserServices, - ) || - isValidLastChainOperand( - lastChainOperand.comparisonValue, - lastChainOperand.comparisonType, - parserServices, - )) + ) ) { lastChain = lastChainOperand; } diff --git a/packages/eslint-plugin/tests/rules/prefer-optional-chain/prefer-optional-chain.test.ts b/packages/eslint-plugin/tests/rules/prefer-optional-chain/prefer-optional-chain.test.ts index a990d70b60bb..6336aabe0f44 100644 --- a/packages/eslint-plugin/tests/rules/prefer-optional-chain/prefer-optional-chain.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-optional-chain/prefer-optional-chain.test.ts @@ -864,441 +864,12 @@ describe('chain ending with comparison', () => { { code: ` declare const foo: { bar: number }; - foo && foo.bar == x; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar == x; - `, - }, - { - code: ` - declare const foo: { bar: number }; - foo && foo.bar == null; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar == null; - `, - }, - { - code: ` - declare const foo: { bar: number }; - foo && foo.bar == undefined; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar == undefined; - `, - }, - { - code: ` - declare const foo: { bar: number }; - foo && foo.bar === x; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar === x; - `, - }, - { - code: ` - declare const foo: { bar: number }; - foo && foo.bar === undefined; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar === undefined; - `, - }, - { - code: ` - declare const foo: { bar: number }; - foo && foo.bar !== 0; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar !== 0; - `, - }, - { - code: ` - declare const foo: { bar: number }; - foo && foo.bar !== 1; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar !== 1; - `, - }, - { - code: ` - declare const foo: { bar: number }; - foo && foo.bar !== '123'; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar !== '123'; - `, - }, - { - code: ` - declare const foo: { bar: number }; - foo && foo.bar !== {}; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar !== {}; - `, - }, - { - code: ` - declare const foo: { bar: number }; - foo && foo.bar !== false; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar !== false; - `, - }, - { - code: ` - declare const foo: { bar: number }; - foo && foo.bar !== true; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar !== true; - `, - }, - { - code: ` - declare const foo: { bar: number }; - foo && foo.bar !== null; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar !== null; - `, - }, - { - code: ` - declare const foo: { bar: number }; - foo && foo.bar !== x; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar !== x; - `, - }, - { - code: ` - declare const foo: { bar: number }; - foo && foo.bar != 0; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar != 0; - `, - }, - { - code: ` - declare const foo: { bar: number }; - foo && foo.bar != 1; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar != 1; - `, - }, - { - code: ` - declare const foo: { bar: number }; - foo && foo.bar != '123'; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar != '123'; - `, - }, - { - code: ` - declare const foo: { bar: number }; - foo && foo.bar != {}; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar != {}; - `, - }, - { - code: ` - declare const foo: { bar: number }; - foo && foo.bar != false; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar != false; - `, - }, - { - code: ` - declare const foo: { bar: number }; - foo && foo.bar != true; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar != true; - `, - }, - { - code: ` - declare const foo: { bar: number }; - foo && foo.bar != null; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar != null; - `, - }, - { - code: ` - declare const foo: { bar: number }; - foo && foo.bar != x; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar != x; - `, - }, - { - code: ` - declare const foo: { bar: number }; - foo != null && foo.bar == x; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar == x; - `, - }, - { - code: ` - declare const foo: { bar: number }; - foo != null && foo.bar == null; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar == null; - `, - }, - { - code: ` - declare const foo: { bar: number }; - foo != null && foo.bar == undefined; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar == undefined; - `, - }, - { - code: ` - declare const foo: { bar: number }; - foo != null && foo.bar === x; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar === x; - `, - }, - { - code: ` - declare const foo: { bar: number }; - foo != null && foo.bar === undefined; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar === undefined; - `, - }, - { - code: ` - declare const foo: { bar: number }; - foo != null && foo.bar !== 0; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar !== 0; - `, - }, - { - code: ` - declare const foo: { bar: number }; - foo != null && foo.bar !== 1; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar !== 1; - `, - }, - { - code: ` - declare const foo: { bar: number }; - foo != null && foo.bar !== '123'; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar !== '123'; - `, - }, - { - code: ` - declare const foo: { bar: number }; - foo != null && foo.bar !== {}; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar !== {}; - `, - }, - { - code: ` - declare const foo: { bar: number }; - foo != null && foo.bar !== false; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar !== false; - `, - }, - { - code: ` - declare const foo: { bar: number }; - foo != null && foo.bar !== true; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar !== true; - `, - }, - { - code: ` - declare const foo: { bar: number }; - foo != null && foo.bar !== null; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar !== null; - `, - }, - { - code: ` - declare const foo: { bar: number }; - foo != null && foo.bar !== x; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar !== x; - `, - }, - { - code: ` - declare const foo: { bar: number }; - foo != null && foo.bar != 0; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar != 0; - `, - }, - { - code: ` - declare const foo: { bar: number }; - foo != null && foo.bar != 1; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar != 1; - `, - }, - { - code: ` - declare const foo: { bar: number }; - foo != null && foo.bar != '123'; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar != '123'; - `, - }, - { - code: ` - declare const foo: { bar: number }; - foo != null && foo.bar != {}; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar != {}; - `, - }, - { - code: ` - declare const foo: { bar: number }; - foo != null && foo.bar != false; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar != false; - `, - }, - { - code: ` - declare const foo: { bar: number }; - foo != null && foo.bar != true; + foo && foo.bar != null; `, errors: [{ messageId: 'preferOptionalChain', suggestions: null }], output: ` declare const foo: { bar: number }; - foo?.bar != true; + foo?.bar != null; `, }, { @@ -1312,39 +883,6 @@ describe('chain ending with comparison', () => { foo?.bar != null; `, }, - { - code: ` - declare const foo: { bar: number }; - foo != null && foo.bar != x; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar != x; - `, - }, - { - code: ` - declare const foo: { bar: number } | 1; - foo && foo.bar == x; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number } | 1; - foo?.bar == x; - `, - }, - { - code: ` - declare const foo: { bar: number } | 0; - foo != null && foo.bar == x; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number } | 0; - foo?.bar == x; - `, - }, { code: '!foo || foo.bar != 0;', errors: [{ messageId: 'preferOptionalChain', suggestions: null }], @@ -1505,17 +1043,6 @@ describe('chain ending with comparison', () => { errors: [{ messageId: 'preferOptionalChain', suggestions: null }], output: `foo?.bar !== null;`, }, - { - code: ` - declare const foo: { bar: number }; - !foo || foo.bar == x; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar == x; - `, - }, { code: ` declare const foo: { bar: number }; @@ -1538,17 +1065,6 @@ describe('chain ending with comparison', () => { foo?.bar == undefined; `, }, - { - code: ` - declare const foo: { bar: number }; - !foo || foo.bar === x; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar === x; - `, - }, { code: ` declare const foo: { bar: number }; @@ -1637,17 +1153,6 @@ describe('chain ending with comparison', () => { foo?.bar !== null; `, }, - { - code: ` - declare const foo: { bar: number }; - !foo || foo.bar !== x; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar !== x; - `, - }, { code: ` declare const foo: { bar: number }; @@ -1714,40 +1219,6 @@ describe('chain ending with comparison', () => { foo?.bar != true; `, }, - { - code: ` - declare const foo: { bar: number }; - !foo || foo.bar != null; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar != null; - `, - }, - { - code: ` - declare const foo: { bar: number }; - !foo || foo.bar != x; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar != x; - `, - }, - - { - code: ` - declare const foo: { bar: number }; - foo == null || foo.bar == x; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar == x; - `, - }, { code: ` declare const foo: { bar: number }; @@ -1770,17 +1241,6 @@ describe('chain ending with comparison', () => { foo?.bar == undefined; `, }, - { - code: ` - declare const foo: { bar: number }; - foo == null || foo.bar === x; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar === x; - `, - }, { code: ` declare const foo: { bar: number }; @@ -1869,17 +1329,6 @@ describe('chain ending with comparison', () => { foo?.bar !== null; `, }, - { - code: ` - declare const foo: { bar: number }; - foo == null || foo.bar !== x; - `, - errors: [{ messageId: 'preferOptionalChain', suggestions: null }], - output: ` - declare const foo: { bar: number }; - foo?.bar !== x; - `, - }, // yoda case { code: "foo != null && null != foo.bar && '123' == foo.bar.baz;", @@ -1938,6 +1387,174 @@ describe('chain ending with comparison', () => { 'foo != null && foo.bar != false;', 'foo != null && foo.bar != true;', 'foo != null && foo.bar != x;', + ` + declare const foo: { bar: number }; + foo && foo.bar == x; + `, + ` + declare const foo: { bar: number }; + foo && foo.bar == null; + `, + ` + declare const foo: { bar: number }; + foo && foo.bar == undefined; + `, + ` + declare const foo: { bar: number }; + foo && foo.bar === x; + `, + ` + declare const foo: { bar: number }; + foo && foo.bar === undefined; + `, + ` + declare const foo: { bar: number }; + foo && foo.bar !== 0; + `, + ` + declare const foo: { bar: number }; + foo && foo.bar !== 1; + `, + ` + declare const foo: { bar: number }; + foo && foo.bar !== '123'; + `, + ` + declare const foo: { bar: number }; + foo && foo.bar !== {}; + `, + ` + declare const foo: { bar: number }; + foo && foo.bar !== false; + `, + ` + declare const foo: { bar: number }; + foo && foo.bar !== true; + `, + ` + declare const foo: { bar: number }; + foo && foo.bar !== null; + `, + ` + declare const foo: { bar: number }; + foo && foo.bar !== x; + `, + ` + declare const foo: { bar: number }; + foo && foo.bar != 0; + `, + ` + declare const foo: { bar: number }; + foo && foo.bar != 1; + `, + ` + declare const foo: { bar: number }; + foo && foo.bar != '123'; + `, + ` + declare const foo: { bar: number }; + foo && foo.bar != {}; + `, + ` + declare const foo: { bar: number }; + foo && foo.bar != false; + `, + ` + declare const foo: { bar: number }; + foo && foo.bar != true; + `, + ` + declare const foo: { bar: number }; + foo && foo.bar != x; + `, + ` + declare const foo: { bar: number }; + foo != null && foo.bar == x; + `, + ` + declare const foo: { bar: number }; + foo != null && foo.bar == null; + `, + ` + declare const foo: { bar: number }; + foo != null && foo.bar == undefined; + `, + ` + declare const foo: { bar: number }; + foo != null && foo.bar === x; + `, + ` + declare const foo: { bar: number }; + foo != null && foo.bar === undefined; + `, + ` + declare const foo: { bar: number }; + foo != null && foo.bar !== 0; + `, + ` + declare const foo: { bar: number }; + foo != null && foo.bar !== 1; + `, + ` + declare const foo: { bar: number }; + foo != null && foo.bar !== '123'; + `, + ` + declare const foo: { bar: number }; + foo != null && foo.bar !== {}; + `, + ` + declare const foo: { bar: number }; + foo != null && foo.bar !== false; + `, + ` + declare const foo: { bar: number }; + foo != null && foo.bar !== true; + `, + ` + declare const foo: { bar: number }; + foo != null && foo.bar !== null; + `, + ` + declare const foo: { bar: number }; + foo != null && foo.bar !== x; + `, + ` + declare const foo: { bar: number }; + foo != null && foo.bar != 0; + `, + ` + declare const foo: { bar: number }; + foo != null && foo.bar != 1; + `, + ` + declare const foo: { bar: number }; + foo != null && foo.bar != '123'; + `, + ` + declare const foo: { bar: number }; + foo != null && foo.bar != {}; + `, + ` + declare const foo: { bar: number }; + foo != null && foo.bar != false; + `, + ` + declare const foo: { bar: number }; + foo != null && foo.bar != true; + `, + ` + declare const foo: { bar: number }; + foo != null && foo.bar != x; + `, + ` + declare const foo: { bar: number } | 1; + foo && foo.bar == x; + `, + ` + declare const foo: { bar: number } | 0; + foo != null && foo.bar == x; + `, ` declare const foo: { bar: number } | null; foo && foo.bar == x; @@ -2108,6 +1725,26 @@ describe('chain ending with comparison', () => { declare const foo: { bar: number } | undefined; foo !== undefined && foo !== undefined && foo.bar != 1; `, + ` + declare const foo: { bar: number }; + !foo || foo.bar == x; + `, + ` + declare const foo: { bar: number }; + !foo || foo.bar === x; + `, + ` + declare const foo: { bar: number }; + !foo || foo.bar !== x; + `, + ` + declare const foo: { bar: number }; + !foo || foo.bar != null; + `, + ` + declare const foo: { bar: number }; + !foo || foo.bar != x; + `, '!foo && foo.bar == 0;', '!foo && foo.bar == 1;', "!foo && foo.bar == '123';", @@ -2176,6 +1813,18 @@ describe('chain ending with comparison', () => { '!foo || foo.bar == x;', '!foo || foo.bar !== x;', '!foo || foo.bar !== undefined;', + ` + declare const foo: { bar: number }; + foo == null || foo.bar == x; + `, + ` + declare const foo: { bar: number }; + foo == null || foo.bar === x; + `, + ` + declare const foo: { bar: number }; + foo == null || foo.bar !== x; + `, 'foo == null || foo.bar != x;', 'foo == null || foo.bar != null;', 'foo == null || foo.bar != undefined;', From a38269604521b035a9e117a24c41876229f275f9 Mon Sep 17 00:00:00 2001 From: mdm317 Date: Fri, 17 Oct 2025 00:28:06 +0900 Subject: [PATCH 2/6] test : fix base case --- .../prefer-optional-chain.test.ts | 80 ------------------- 1 file changed, 80 deletions(-) diff --git a/packages/eslint-plugin/tests/rules/prefer-optional-chain/prefer-optional-chain.test.ts b/packages/eslint-plugin/tests/rules/prefer-optional-chain/prefer-optional-chain.test.ts index 6336aabe0f44..a81f48c55a3e 100644 --- a/packages/eslint-plugin/tests/rules/prefer-optional-chain/prefer-optional-chain.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-optional-chain/prefer-optional-chain.test.ts @@ -3138,7 +3138,6 @@ describe('base cases', () => { mutateCode: c => c.replaceAll('&&', '!== null &&'), mutateOutput: identity, operator: '&&', - skipIds: [20, 26], }), // but if the type is just `| null` - then it covers the cases and is // a valid conversion @@ -3150,45 +3149,6 @@ describe('base cases', () => { operator: '&&', useSuggestionFixer: true, }), - { - code: ` - declare const foo: { - bar: () => - | { baz: { buzz: (() => number) | null | undefined } | null | undefined } - | null - | undefined; - }; - foo.bar !== null && - foo.bar() !== null && - foo.bar().baz !== null && - foo.bar().baz.buzz !== null && - foo.bar().baz.buzz(); - `, - errors: [{ messageId: 'preferOptionalChain' }], - output: ` - declare const foo: { - bar: () => - | { baz: { buzz: (() => number) | null | undefined } | null | undefined } - | null - | undefined; - }; - foo.bar?.() !== null && - foo.bar().baz !== null && - foo.bar().baz.buzz !== null && - foo.bar().baz.buzz(); - `, - }, - { - code: ` - declare const foo: { bar: () => { baz: number } | null | undefined }; - foo.bar !== null && foo.bar?.() !== null && foo.bar?.().baz; - `, - errors: [{ messageId: 'preferOptionalChain' }], - output: ` - declare const foo: { bar: () => { baz: number } | null | undefined }; - foo.bar?.() !== null && foo.bar?.().baz; - `, - }, ], }); }); @@ -3303,7 +3263,6 @@ describe('base cases', () => { mutateCode: c => c.replaceAll('||', '=== null ||'), mutateOutput: identity, operator: '||', - skipIds: [20, 26], }), // but if the type is just `| null` - then it covers the cases and is // a valid conversion @@ -3320,45 +3279,6 @@ describe('base cases', () => { operator: '||', useSuggestionFixer: true, }), - { - code: ` - declare const foo: { - bar: () => - | { baz: { buzz: (() => number) | null | undefined } | null | undefined } - | null - | undefined; - }; - foo.bar === null || - foo.bar() === null || - foo.bar().baz === null || - foo.bar().baz.buzz === null || - foo.bar().baz.buzz(); - `, - errors: [{ messageId: 'preferOptionalChain' }], - output: ` - declare const foo: { - bar: () => - | { baz: { buzz: (() => number) | null | undefined } | null | undefined } - | null - | undefined; - }; - foo.bar?.() === null || - foo.bar().baz === null || - foo.bar().baz.buzz === null || - foo.bar().baz.buzz(); - `, - }, - { - code: ` - declare const foo: { bar: () => { baz: number } | null | undefined }; - foo.bar === null || foo.bar?.() === null || foo.bar?.().baz; - `, - errors: [{ messageId: 'preferOptionalChain' }], - output: ` - declare const foo: { bar: () => { baz: number } | null | undefined }; - foo.bar?.() === null || foo.bar?.().baz; - `, - }, ], }); }); From ca323edfffc2b644168b4c026a81242a19d6cbbc Mon Sep 17 00:00:00 2001 From: mdm317 Date: Fri, 17 Oct 2025 00:46:09 +0900 Subject: [PATCH 3/6] test : add some case for array/index access --- .../prefer-optional-chain/prefer-optional-chain.test.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/eslint-plugin/tests/rules/prefer-optional-chain/prefer-optional-chain.test.ts b/packages/eslint-plugin/tests/rules/prefer-optional-chain/prefer-optional-chain.test.ts index a81f48c55a3e..0c53b7b6332e 100644 --- a/packages/eslint-plugin/tests/rules/prefer-optional-chain/prefer-optional-chain.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-optional-chain/prefer-optional-chain.test.ts @@ -1877,6 +1877,14 @@ describe('chain ending with comparison', () => { 'foo != null || foo.bar !== false;', 'foo != null || foo.bar !== true;', 'foo != null || foo.bar !== null;', + ` + declare const record: Record; + record['key'] && record['key'].kind !== '1' + `, + ` + declare const array: { b?: string }[]; + !array[1] || array[1].b === "foo" + `, ], }); }); From 87605b08ea91bb85acdca5ebfd4fc82126d2ec3c Mon Sep 17 00:00:00 2001 From: mdm317 Date: Fri, 17 Oct 2025 01:20:17 +0900 Subject: [PATCH 4/6] fix : lint err --- .../src/rules/prefer-optional-chain-utils/analyzeChain.ts | 2 +- .../rules/prefer-optional-chain/prefer-optional-chain.test.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/analyzeChain.ts b/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/analyzeChain.ts index e197e382f731..a2b11134e540 100644 --- a/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/analyzeChain.ts +++ b/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/analyzeChain.ts @@ -10,7 +10,7 @@ import type { } from '@typescript-eslint/utils/ts-eslint'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import { isFalsyType, unionConstituents } from 'ts-api-utils'; +import { unionConstituents } from 'ts-api-utils'; import * as ts from 'typescript'; import type { LastChainOperand, ValidOperand } from './gatherLogicalOperands'; diff --git a/packages/eslint-plugin/tests/rules/prefer-optional-chain/prefer-optional-chain.test.ts b/packages/eslint-plugin/tests/rules/prefer-optional-chain/prefer-optional-chain.test.ts index 0c53b7b6332e..14367cfcf0d3 100644 --- a/packages/eslint-plugin/tests/rules/prefer-optional-chain/prefer-optional-chain.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-optional-chain/prefer-optional-chain.test.ts @@ -1879,11 +1879,11 @@ describe('chain ending with comparison', () => { 'foo != null || foo.bar !== null;', ` declare const record: Record; - record['key'] && record['key'].kind !== '1' + record['key'] && record['key'].kind !== '1'; `, ` declare const array: { b?: string }[]; - !array[1] || array[1].b === "foo" + !array[1] || array[1].b === 'foo'; `, ], }); From 837db2a44813a28089fca4d76f49e77f12266ca7 Mon Sep 17 00:00:00 2001 From: mdm317 Date: Sat, 18 Oct 2025 02:24:35 +0900 Subject: [PATCH 5/6] refactor : undeclaredVar --- .../prefer-optional-chain.test.ts | 78 +++++++++---------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/packages/eslint-plugin/tests/rules/prefer-optional-chain/prefer-optional-chain.test.ts b/packages/eslint-plugin/tests/rules/prefer-optional-chain/prefer-optional-chain.test.ts index 14367cfcf0d3..946895825cf8 100644 --- a/packages/eslint-plugin/tests/rules/prefer-optional-chain/prefer-optional-chain.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-optional-chain/prefer-optional-chain.test.ts @@ -1347,10 +1347,10 @@ describe('chain ending with comparison', () => { }, ], valid: [ - 'foo && foo.bar == x;', + 'foo && foo.bar == undeclaredVar;', 'foo && foo.bar == null;', 'foo && foo.bar == undefined;', - 'foo && foo.bar === x;', + 'foo && foo.bar === undeclaredVar;', 'foo && foo.bar === undefined;', 'foo && foo.bar !== 0;', 'foo && foo.bar !== 1;', @@ -1359,18 +1359,18 @@ describe('chain ending with comparison', () => { 'foo && foo.bar !== false;', 'foo && foo.bar !== true;', 'foo && foo.bar !== null;', - 'foo && foo.bar !== x;', + 'foo && foo.bar !== undeclaredVar;', 'foo && foo.bar != 0;', 'foo && foo.bar != 1;', "foo && foo.bar != '123';", 'foo && foo.bar != {};', 'foo && foo.bar != false;', 'foo && foo.bar != true;', - 'foo && foo.bar != x;', - 'foo != null && foo.bar == x;', + 'foo && foo.bar != undeclaredVar;', + 'foo != null && foo.bar == undeclaredVar;', 'foo != null && foo.bar == null;', 'foo != null && foo.bar == undefined;', - 'foo != null && foo.bar === x;', + 'foo != null && foo.bar === undeclaredVar;', 'foo != null && foo.bar === undefined;', 'foo != null && foo.bar !== 0;', 'foo != null && foo.bar !== 1;', @@ -1379,17 +1379,17 @@ describe('chain ending with comparison', () => { 'foo != null && foo.bar !== false;', 'foo != null && foo.bar !== true;', 'foo != null && foo.bar !== null;', - 'foo != null && foo.bar !== x;', + 'foo != null && foo.bar !== undeclaredVar;', 'foo != null && foo.bar != 0;', 'foo != null && foo.bar != 1;', "foo != null && foo.bar != '123';", 'foo != null && foo.bar != {};', 'foo != null && foo.bar != false;', 'foo != null && foo.bar != true;', - 'foo != null && foo.bar != x;', + 'foo != null && foo.bar != undeclaredVar;', ` declare const foo: { bar: number }; - foo && foo.bar == x; + foo && foo.bar == undeclaredVar; `, ` declare const foo: { bar: number }; @@ -1401,7 +1401,7 @@ describe('chain ending with comparison', () => { `, ` declare const foo: { bar: number }; - foo && foo.bar === x; + foo && foo.bar === undeclaredVar; `, ` declare const foo: { bar: number }; @@ -1437,7 +1437,7 @@ describe('chain ending with comparison', () => { `, ` declare const foo: { bar: number }; - foo && foo.bar !== x; + foo && foo.bar !== undeclaredVar; `, ` declare const foo: { bar: number }; @@ -1465,11 +1465,11 @@ describe('chain ending with comparison', () => { `, ` declare const foo: { bar: number }; - foo && foo.bar != x; + foo && foo.bar != undeclaredVar; `, ` declare const foo: { bar: number }; - foo != null && foo.bar == x; + foo != null && foo.bar == undeclaredVar; `, ` declare const foo: { bar: number }; @@ -1481,7 +1481,7 @@ describe('chain ending with comparison', () => { `, ` declare const foo: { bar: number }; - foo != null && foo.bar === x; + foo != null && foo.bar === undeclaredVar; `, ` declare const foo: { bar: number }; @@ -1517,7 +1517,7 @@ describe('chain ending with comparison', () => { `, ` declare const foo: { bar: number }; - foo != null && foo.bar !== x; + foo != null && foo.bar !== undeclaredVar; `, ` declare const foo: { bar: number }; @@ -1545,19 +1545,19 @@ describe('chain ending with comparison', () => { `, ` declare const foo: { bar: number }; - foo != null && foo.bar != x; + foo != null && foo.bar != undeclaredVar; `, ` declare const foo: { bar: number } | 1; - foo && foo.bar == x; + foo && foo.bar == undeclaredVar; `, ` declare const foo: { bar: number } | 0; - foo != null && foo.bar == x; + foo != null && foo.bar == undeclaredVar; `, ` declare const foo: { bar: number } | null; - foo && foo.bar == x; + foo && foo.bar == undeclaredVar; `, ` declare const foo: { bar: number } | null; @@ -1569,7 +1569,7 @@ describe('chain ending with comparison', () => { `, ` declare const foo: { bar: number } | null; - foo && foo.bar === x; + foo && foo.bar === undeclaredVar; `, ` declare const foo: { bar: number } | null; @@ -1605,11 +1605,11 @@ describe('chain ending with comparison', () => { `, ` declare const foo: { bar: number } | null; - foo && foo.bar !== x; + foo && foo.bar !== undeclaredVar; `, ` declare const foo: { bar: number } | null; - foo != null && foo.bar == x; + foo != null && foo.bar == undeclaredVar; `, ` declare const foo: { bar: number } | null; @@ -1621,7 +1621,7 @@ describe('chain ending with comparison', () => { `, ` declare const foo: { bar: number } | null; - foo != null && foo.bar === x; + foo != null && foo.bar === undeclaredVar; `, ` declare const foo: { bar: number } | null; @@ -1657,7 +1657,7 @@ describe('chain ending with comparison', () => { `, ` declare const foo: { bar: number } | null; - foo != null && foo.bar !== x; + foo != null && foo.bar !== undeclaredVar; `, ` declare const foo: { bar: number } | null; @@ -1727,15 +1727,15 @@ describe('chain ending with comparison', () => { `, ` declare const foo: { bar: number }; - !foo || foo.bar == x; + !foo || foo.bar == undeclaredVar; `, ` declare const foo: { bar: number }; - !foo || foo.bar === x; + !foo || foo.bar === undeclaredVar; `, ` declare const foo: { bar: number }; - !foo || foo.bar !== x; + !foo || foo.bar !== undeclaredVar; `, ` declare const foo: { bar: number }; @@ -1743,7 +1743,7 @@ describe('chain ending with comparison', () => { `, ` declare const foo: { bar: number }; - !foo || foo.bar != x; + !foo || foo.bar != undeclaredVar; `, '!foo && foo.bar == 0;', '!foo && foo.bar == 1;', @@ -1793,7 +1793,7 @@ describe('chain ending with comparison', () => { declare const x: 0n | { a: string }; x && x.a; `, - '!foo || foo.bar != x;', + '!foo || foo.bar != undeclaredVar;', '!foo || foo.bar != null;', '!foo || foo.bar != undefined;', '!foo || foo.bar === 0;', @@ -1803,29 +1803,29 @@ describe('chain ending with comparison', () => { '!foo || foo.bar === false;', '!foo || foo.bar === true;', '!foo || foo.bar === null;', - '!foo || foo.bar === x;', + '!foo || foo.bar === undeclaredVar;', '!foo || foo.bar == 0;', '!foo || foo.bar == 1;', "!foo || foo.bar == '123';", '!foo || foo.bar == {};', '!foo || foo.bar == false;', '!foo || foo.bar == true;', - '!foo || foo.bar == x;', - '!foo || foo.bar !== x;', + '!foo || foo.bar == undeclaredVar;', + '!foo || foo.bar !== undeclaredVar;', '!foo || foo.bar !== undefined;', ` declare const foo: { bar: number }; - foo == null || foo.bar == x; + foo == null || foo.bar == undeclaredVar; `, ` declare const foo: { bar: number }; - foo == null || foo.bar === x; + foo == null || foo.bar === undeclaredVar; `, ` declare const foo: { bar: number }; - foo == null || foo.bar !== x; + foo == null || foo.bar !== undeclaredVar; `, - 'foo == null || foo.bar != x;', + 'foo == null || foo.bar != undeclaredVar;', 'foo == null || foo.bar != null;', 'foo == null || foo.bar != undefined;', 'foo == null || foo.bar === 0;', @@ -1835,15 +1835,15 @@ describe('chain ending with comparison', () => { 'foo == null || foo.bar === false;', 'foo == null || foo.bar === true;', 'foo == null || foo.bar === null;', - 'foo == null || foo.bar === x;', + 'foo == null || foo.bar === undeclaredVar;', 'foo == null || foo.bar == 0;', 'foo == null || foo.bar == 1;', "foo == null || foo.bar == '123';", 'foo == null || foo.bar == {};', 'foo == null || foo.bar == false;', 'foo == null || foo.bar == true;', - 'foo == null || foo.bar == x;', - 'foo == null || foo.bar !== x;', + 'foo == null || foo.bar == undeclaredVar;', + 'foo == null || foo.bar !== undeclaredVar;', 'foo == null || foo.bar !== undefined;', 'foo || foo.bar != 0;', 'foo || foo.bar != 1;', From fd98e654f086419e25d823e99745ef952a83a078 Mon Sep 17 00:00:00 2001 From: mdm317 Date: Sat, 18 Oct 2025 02:26:11 +0900 Subject: [PATCH 6/6] fix : weird test csae --- .../prefer-optional-chain.test.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/eslint-plugin/tests/rules/prefer-optional-chain/prefer-optional-chain.test.ts b/packages/eslint-plugin/tests/rules/prefer-optional-chain/prefer-optional-chain.test.ts index 946895825cf8..52c59b479b68 100644 --- a/packages/eslint-plugin/tests/rules/prefer-optional-chain/prefer-optional-chain.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-optional-chain/prefer-optional-chain.test.ts @@ -1778,20 +1778,20 @@ describe('chain ending with comparison', () => { 'foo == null && foo.bar != null;', 'foo == null && foo.bar != undefined;', ` - declare const x: false | { a: string }; - x && x.a == x; + declare const foo: false | { a: string }; + foo && foo.a == undeclaredVar; `, ` - declare const x: '' | { a: string }; - x && x.a == x; + declare const foo: '' | { a: string }; + foo && foo.a == undeclaredVar; `, ` - declare const x: 0 | { a: string }; - x && x.a == x; + declare const foo: 0 | { a: string }; + foo && foo.a == undeclaredVar; `, ` - declare const x: 0n | { a: string }; - x && x.a; + declare const foo: 0n | { a: string }; + foo && foo.a; `, '!foo || foo.bar != undeclaredVar;', '!foo || foo.bar != null;',