From aec9ef56aec63c6de4cc5fc8b9190dfb84ddd4f0 Mon Sep 17 00:00:00 2001 From: nayounsang Date: Thu, 5 Jun 2025 16:11:29 +0900 Subject: [PATCH 01/18] feat: add check void option --- .../PreferOptionalChainOptions.ts | 1 + packages/eslint-plugin/src/rules/prefer-optional-chain.ts | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/PreferOptionalChainOptions.ts b/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/PreferOptionalChainOptions.ts index de1147b54447..c799268477ee 100644 --- a/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/PreferOptionalChainOptions.ts +++ b/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/PreferOptionalChainOptions.ts @@ -10,5 +10,6 @@ export interface PreferOptionalChainOptions { checkNumber?: boolean; checkString?: boolean; checkUnknown?: boolean; + checkVoid?: boolean; requireNullish?: boolean; } diff --git a/packages/eslint-plugin/src/rules/prefer-optional-chain.ts b/packages/eslint-plugin/src/rules/prefer-optional-chain.ts index ffbcd213fedd..670c5e9c4713 100644 --- a/packages/eslint-plugin/src/rules/prefer-optional-chain.ts +++ b/packages/eslint-plugin/src/rules/prefer-optional-chain.ts @@ -82,6 +82,11 @@ export default createRule< description: 'Check operands that are typed as `unknown` when inspecting "loose boolean" operands.', }, + checkVoid: { + type: 'boolean', + description: + 'Check operands that are typed as `void` when inspecting "loose boolean" operands.', + }, requireNullish: { type: 'boolean', description: @@ -100,6 +105,7 @@ export default createRule< checkNumber: true, checkString: true, checkUnknown: true, + checkVoid: true, requireNullish: false, }, ], From e35a6f28d02d484d4a962ff7c328f4923bad5c20 Mon Sep 17 00:00:00 2001 From: nayounsang Date: Thu, 5 Jun 2025 16:11:42 +0900 Subject: [PATCH 02/18] test: add test case for check void --- .../prefer-optional-chain.test.ts | 16 ++++++++++++++++ 1 file changed, 16 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 c05f6218b24c..936268ace96d 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 @@ -1680,6 +1680,22 @@ describe('hand-crafted cases', () => { ], output: 'a?.prop;', }, + // check void + { + code: ` +declare const foo: { + method: undefined | (() => void); +}; +foo.method && foo.method(); + `, + errors: [{ messageId: 'preferOptionalChain' }], + output: ` +declare const foo: { + method: undefined | (() => void); +}; +foo.method?.(); + `, + }, ], valid: [ '!a || !b;', From eacc6225bf1711a9d0a7f21be3edebd8921e3d38 Mon Sep 17 00:00:00 2001 From: nayounsang Date: Thu, 5 Jun 2025 16:25:59 +0900 Subject: [PATCH 03/18] feat: check void type --- .../prefer-optional-chain-utils/gatherLogicalOperands.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.ts b/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.ts index 174f8982cad8..4e8ae6244ea1 100644 --- a/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.ts +++ b/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.ts @@ -111,6 +111,11 @@ function isValidFalseBooleanCheckType( if (options.checkBigInt === true) { allowedFlags |= ts.TypeFlags.BigIntLike; } + + if (options.checkVoid === true) { + allowedFlags |= ts.TypeFlags.Void; + } + return types.every(t => isTypeFlagSet(t, allowedFlags)); } From 88decc1ebd0e51be701f774e2b0425c5164a7b04 Mon Sep 17 00:00:00 2001 From: nayounsang Date: Thu, 5 Jun 2025 16:32:35 +0900 Subject: [PATCH 04/18] chore: add snapshot --- .../tests/schema-snapshots/prefer-optional-chain.shot | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/eslint-plugin/tests/schema-snapshots/prefer-optional-chain.shot b/packages/eslint-plugin/tests/schema-snapshots/prefer-optional-chain.shot index 7d300bdbdfdd..8ec3cc46b7b5 100644 --- a/packages/eslint-plugin/tests/schema-snapshots/prefer-optional-chain.shot +++ b/packages/eslint-plugin/tests/schema-snapshots/prefer-optional-chain.shot @@ -33,6 +33,10 @@ "description": "Check operands that are typed as `unknown` when inspecting \"loose boolean\" operands.", "type": "boolean" }, + "checkVoid": { + "description": "Check operands that are typed as `void` when inspecting \"loose boolean\" operands.", + "type": "boolean" + }, "requireNullish": { "description": "Skip operands that are not typed with `null` and/or `undefined` when inspecting \"loose boolean\" operands.", "type": "boolean" @@ -61,6 +65,8 @@ type Options = [ checkString?: boolean; /** Check operands that are typed as `unknown` when inspecting "loose boolean" operands. */ checkUnknown?: boolean; + /** Check operands that are typed as `void` when inspecting "loose boolean" operands. */ + checkVoid?: boolean; /** Skip operands that are not typed with `null` and/or `undefined` when inspecting "loose boolean" operands. */ requireNullish?: boolean; }, From 4171c5e4f536824127cd5b49a6818b8ed6f43b79 Mon Sep 17 00:00:00 2001 From: nayounsang Date: Thu, 5 Jun 2025 16:51:48 +0900 Subject: [PATCH 05/18] docs: add checkvoid section --- .../docs/rules/prefer-optional-chain.mdx | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/packages/eslint-plugin/docs/rules/prefer-optional-chain.mdx b/packages/eslint-plugin/docs/rules/prefer-optional-chain.mdx index 777c80068e69..6fa0720ba2c3 100644 --- a/packages/eslint-plugin/docs/rules/prefer-optional-chain.mdx +++ b/packages/eslint-plugin/docs/rules/prefer-optional-chain.mdx @@ -265,6 +265,37 @@ thing?.toString(); +### `checkVoid` + +{/* insert option description */} + +Examples of code for this rule with `{ checkVoid: true }`: + + + + +```ts option='{ "checkVoid": true }' +declare const thing: { + method: undefined | (() => void); +}; + +thing.method && thing.method(); +``` + + + + +```ts option='{ "checkVoid": true }' +declare const thing: { + method: undefined | (() => void); +}; + +thing.method?.(); +``` + + + + ### `requireNullish` {/* insert option description */} From 791461b8f14991f41acfeae54b9167323ab83c20 Mon Sep 17 00:00:00 2001 From: nayounsang Date: Thu, 5 Jun 2025 17:11:22 +0900 Subject: [PATCH 06/18] chore: update snapshot --- .../prefer-optional-chain.shot | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/prefer-optional-chain.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/prefer-optional-chain.shot index 8607ddca5bca..032898689ebb 100644 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/prefer-optional-chain.shot +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/prefer-optional-chain.shot @@ -149,6 +149,25 @@ declare const thing: bigint; thing?.toString(); +Incorrect +Options: { "checkVoid": true } + +declare const thing: { + method: undefined | (() => void); +}; + +thing.method && thing.method(); +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Prefer using an optional chain expression instead, as it's more concise and easier to read. + +Correct +Options: { "checkVoid": true } + +declare const thing: { + method: undefined | (() => void); +}; + +thing.method?.(); + Incorrect Options: { "requireNullish": true } From b3d2a4fc9dc7b8f310d32f234afa67bbb924543b Mon Sep 17 00:00:00 2001 From: nayounsang Date: Sat, 7 Jun 2025 17:22:41 +0900 Subject: [PATCH 07/18] refactor: inject option based on flagsToExcludeFromCheck --- .../gatherLogicalOperands.ts | 35 +++++++++---------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.ts b/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.ts index 4e8ae6244ea1..ab6f90b121a9 100644 --- a/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.ts +++ b/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.ts @@ -59,8 +59,6 @@ export interface InvalidOperand { type: OperandValidity.Invalid; } type Operand = InvalidOperand | ValidOperand; - -const NULLISH_FLAGS = ts.TypeFlags.Null | ts.TypeFlags.Undefined; function isValidFalseBooleanCheckType( node: TSESTree.Node, disallowFalseyLiteral: boolean, @@ -92,31 +90,30 @@ function isValidFalseBooleanCheckType( return false; } - let allowedFlags = NULLISH_FLAGS | ts.TypeFlags.Object; - if (options.checkAny === true) { - allowedFlags |= ts.TypeFlags.Any; + let flagsToExcludeFromCheck = 0; + if (options.checkAny !== true) { + flagsToExcludeFromCheck |= ts.TypeFlags.Any; } - if (options.checkUnknown === true) { - allowedFlags |= ts.TypeFlags.Unknown; + if (options.checkUnknown !== true) { + flagsToExcludeFromCheck |= ts.TypeFlags.Unknown; } - if (options.checkString === true) { - allowedFlags |= ts.TypeFlags.StringLike; + if (options.checkString !== true) { + flagsToExcludeFromCheck |= ts.TypeFlags.StringLike; } - if (options.checkNumber === true) { - allowedFlags |= ts.TypeFlags.NumberLike; + if (options.checkNumber !== true) { + flagsToExcludeFromCheck |= ts.TypeFlags.NumberLike; } - if (options.checkBoolean === true) { - allowedFlags |= ts.TypeFlags.BooleanLike; + if (options.checkBoolean !== true) { + flagsToExcludeFromCheck |= ts.TypeFlags.BooleanLike; } - if (options.checkBigInt === true) { - allowedFlags |= ts.TypeFlags.BigIntLike; + if (options.checkBigInt !== true) { + flagsToExcludeFromCheck |= ts.TypeFlags.BigIntLike; } - - if (options.checkVoid === true) { - allowedFlags |= ts.TypeFlags.Void; + if (options.checkVoid !== true) { + flagsToExcludeFromCheck |= ts.TypeFlags.Void; } - return types.every(t => isTypeFlagSet(t, allowedFlags)); + return types.every(t => !isTypeFlagSet(t, flagsToExcludeFromCheck)); } export function gatherLogicalOperands( From 656ec507805e8d67a23653389de4c93986ca7f48 Mon Sep 17 00:00:00 2001 From: nayounsang Date: Sat, 7 Jun 2025 17:23:05 +0900 Subject: [PATCH 08/18] test: add test for checkVoid = false --- .../prefer-optional-chain/prefer-optional-chain.test.ts | 9 +++++++++ 1 file changed, 9 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 936268ace96d..dd68bcfd6ac1 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 @@ -1894,6 +1894,15 @@ foo.method?.(); `, options: [{ checkUnknown: false }], }, + { + code: ` +declare const foo: { + method: undefined | (() => void); +}; +foo.method && foo.method(); + `, + options: [{ checkVoid: false }], + }, '(x = {}) && (x.y = true) != null && x.y.toString();', "('x' as `${'x'}`) && ('x' as `${'x'}`).length;", '`x` && `x`.length;', From 4b92ad4340a6128d29f50f9b92fc68baf645a794 Mon Sep 17 00:00:00 2001 From: nayounsang Date: Thu, 12 Jun 2025 17:20:44 +0900 Subject: [PATCH 09/18] test: add tc for all checks are excluded but invaild --- .../prefer-optional-chain.test.ts | 26 +++++++++++++++++++ 1 file changed, 26 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 dd68bcfd6ac1..caf2a232b554 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 @@ -1696,6 +1696,32 @@ declare const foo: { foo.method?.(); `, }, + // Exclude for everything else, an error occurs + { + code: noFormat`declare const foo: { x: { y: string } } | null; foo && foo.x;`, + errors: [ + { + messageId: 'preferOptionalChain', + suggestions: [ + { + messageId: 'optionalChainSuggest', + output: `declare const foo: { x: { y: string } } | null; foo?.x;`, + }, + ], + }, + ], + options: [ + { + checkAny: false, + checkBigInt: false, + checkBoolean: false, + checkNumber: false, + checkString: false, + checkUnknown: false, + checkVoid: false, + }, + ], + }, ], valid: [ '!a || !b;', From b3bf6b7298c60eaaced163d7f09eedec6511888a Mon Sep 17 00:00:00 2001 From: nayounsang Date: Sat, 12 Jul 2025 21:40:39 +0900 Subject: [PATCH 10/18] test: add test cases --- .../prefer-optional-chain.test.ts | 38 +++++++++++++------ 1 file changed, 26 insertions(+), 12 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 caf2a232b554..aeef33d21238 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 @@ -1683,17 +1683,26 @@ describe('hand-crafted cases', () => { // check void { code: ` -declare const foo: { - method: undefined | (() => void); -}; -foo.method && foo.method(); +declare const foo: void; +foo && foo(); `, errors: [{ messageId: 'preferOptionalChain' }], output: ` -declare const foo: { - method: undefined | (() => void); -}; -foo.method?.(); +declare const foo: void; +foo?.(); + `, + }, + // check flag with obj + { + code: ` + declare const x: { y: boolean }; + x && x.y; + `, + errors: [{ messageId: 'preferOptionalChain' }], + options: [{ checkBoolean: false }], + output: ` + declare const x: { y: boolean }; + x?.y; `, }, // Exclude for everything else, an error occurs @@ -1899,6 +1908,13 @@ foo.method?.(); `, options: [{ checkBoolean: false }], }, + { + code: ` + declare const x: { y: boolean }; + x?.y && x?.y.length; + `, + options: [{ checkBoolean: false }], + }, { code: ` declare const x: number; @@ -1922,10 +1938,8 @@ foo.method?.(); }, { code: ` -declare const foo: { - method: undefined | (() => void); -}; -foo.method && foo.method(); +declare const foo: void; +foo && foo.method(); `, options: [{ checkVoid: false }], }, From b0adaa81139f03d798218f8ecf97239bd21511a5 Mon Sep 17 00:00:00 2001 From: nayounsang Date: Sat, 12 Jul 2025 22:32:12 +0900 Subject: [PATCH 11/18] fix: ignore isValidFalseBooleanCheckType if there is no more op --- .../gatherLogicalOperands.ts | 16 +++++++++------- .../prefer-optional-chain.test.ts | 15 ++++++++++++--- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.ts b/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.ts index ab6f90b121a9..1d0c6aa0e56d 100644 --- a/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.ts +++ b/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.ts @@ -250,12 +250,13 @@ export function gatherLogicalOperands( case AST_NODE_TYPES.UnaryExpression: if ( operand.operator === '!' && - isValidFalseBooleanCheckType( - operand.argument, - areMoreOperands && node.operator === '||', - parserServices, - options, - ) + (!areMoreOperands || + isValidFalseBooleanCheckType( + operand.argument, + node.operator === '||', + parserServices, + options, + )) ) { result.push({ comparedName: operand.argument, @@ -276,9 +277,10 @@ export function gatherLogicalOperands( default: if ( + !areMoreOperands || isValidFalseBooleanCheckType( operand, - areMoreOperands && node.operator === '&&', + node.operator === '&&', parserServices, options, ) 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 aeef33d21238..6e1f61693467 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 @@ -1698,12 +1698,21 @@ foo?.(); declare const x: { y: boolean }; x && x.y; `, - errors: [{ messageId: 'preferOptionalChain' }], - options: [{ checkBoolean: false }], - output: ` + errors: [ + { + messageId: 'preferOptionalChain', + suggestions: [ + { + messageId: 'optionalChainSuggest', + output: ` declare const x: { y: boolean }; x?.y; `, + }, + ], + }, + ], + options: [{ checkBoolean: false }], }, // Exclude for everything else, an error occurs { From 2559e548991a4535f0c4a03c0de952cb581c0bdd Mon Sep 17 00:00:00 2001 From: nayounsang Date: Sat, 12 Jul 2025 22:52:35 +0900 Subject: [PATCH 12/18] feat: remove checkVoid option --- .../PreferOptionalChainOptions.ts | 1 - .../prefer-optional-chain-utils/gatherLogicalOperands.ts | 3 --- packages/eslint-plugin/src/rules/prefer-optional-chain.ts | 6 ------ .../prefer-optional-chain/prefer-optional-chain.test.ts | 8 -------- .../tests/schema-snapshots/prefer-optional-chain.shot | 6 ------ 5 files changed, 24 deletions(-) diff --git a/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/PreferOptionalChainOptions.ts b/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/PreferOptionalChainOptions.ts index c799268477ee..de1147b54447 100644 --- a/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/PreferOptionalChainOptions.ts +++ b/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/PreferOptionalChainOptions.ts @@ -10,6 +10,5 @@ export interface PreferOptionalChainOptions { checkNumber?: boolean; checkString?: boolean; checkUnknown?: boolean; - checkVoid?: boolean; requireNullish?: boolean; } diff --git a/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.ts b/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.ts index 1d0c6aa0e56d..12be2cfc57d1 100644 --- a/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.ts +++ b/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.ts @@ -109,9 +109,6 @@ function isValidFalseBooleanCheckType( if (options.checkBigInt !== true) { flagsToExcludeFromCheck |= ts.TypeFlags.BigIntLike; } - if (options.checkVoid !== true) { - flagsToExcludeFromCheck |= ts.TypeFlags.Void; - } return types.every(t => !isTypeFlagSet(t, flagsToExcludeFromCheck)); } diff --git a/packages/eslint-plugin/src/rules/prefer-optional-chain.ts b/packages/eslint-plugin/src/rules/prefer-optional-chain.ts index ba58a314f75a..241f4240e78d 100644 --- a/packages/eslint-plugin/src/rules/prefer-optional-chain.ts +++ b/packages/eslint-plugin/src/rules/prefer-optional-chain.ts @@ -82,11 +82,6 @@ export default createRule< description: 'Check operands that are typed as `unknown` when inspecting "loose boolean" operands.', }, - checkVoid: { - type: 'boolean', - description: - 'Check operands that are typed as `void` when inspecting "loose boolean" operands.', - }, requireNullish: { type: 'boolean', description: @@ -105,7 +100,6 @@ export default createRule< checkNumber: true, checkString: true, checkUnknown: true, - checkVoid: true, requireNullish: false, }, ], 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 6e1f61693467..3a1b3199d11f 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 @@ -1736,7 +1736,6 @@ foo?.(); checkNumber: false, checkString: false, checkUnknown: false, - checkVoid: false, }, ], }, @@ -1945,13 +1944,6 @@ foo?.(); `, options: [{ checkUnknown: false }], }, - { - code: ` -declare const foo: void; -foo && foo.method(); - `, - options: [{ checkVoid: false }], - }, '(x = {}) && (x.y = true) != null && x.y.toString();', "('x' as `${'x'}`) && ('x' as `${'x'}`).length;", '`x` && `x`.length;', diff --git a/packages/eslint-plugin/tests/schema-snapshots/prefer-optional-chain.shot b/packages/eslint-plugin/tests/schema-snapshots/prefer-optional-chain.shot index 8ec3cc46b7b5..7d300bdbdfdd 100644 --- a/packages/eslint-plugin/tests/schema-snapshots/prefer-optional-chain.shot +++ b/packages/eslint-plugin/tests/schema-snapshots/prefer-optional-chain.shot @@ -33,10 +33,6 @@ "description": "Check operands that are typed as `unknown` when inspecting \"loose boolean\" operands.", "type": "boolean" }, - "checkVoid": { - "description": "Check operands that are typed as `void` when inspecting \"loose boolean\" operands.", - "type": "boolean" - }, "requireNullish": { "description": "Skip operands that are not typed with `null` and/or `undefined` when inspecting \"loose boolean\" operands.", "type": "boolean" @@ -65,8 +61,6 @@ type Options = [ checkString?: boolean; /** Check operands that are typed as `unknown` when inspecting "loose boolean" operands. */ checkUnknown?: boolean; - /** Check operands that are typed as `void` when inspecting "loose boolean" operands. */ - checkVoid?: boolean; /** Skip operands that are not typed with `null` and/or `undefined` when inspecting "loose boolean" operands. */ requireNullish?: boolean; }, From 9fa30d7f137bc2b1d3770db9937abf5cc7537e2e Mon Sep 17 00:00:00 2001 From: nayounsang Date: Sat, 12 Jul 2025 23:07:24 +0900 Subject: [PATCH 13/18] docs: remove checkVoid on docs --- .../docs/rules/prefer-optional-chain.mdx | 31 ------------------- 1 file changed, 31 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/prefer-optional-chain.mdx b/packages/eslint-plugin/docs/rules/prefer-optional-chain.mdx index 6fa0720ba2c3..777c80068e69 100644 --- a/packages/eslint-plugin/docs/rules/prefer-optional-chain.mdx +++ b/packages/eslint-plugin/docs/rules/prefer-optional-chain.mdx @@ -265,37 +265,6 @@ thing?.toString(); -### `checkVoid` - -{/* insert option description */} - -Examples of code for this rule with `{ checkVoid: true }`: - - - - -```ts option='{ "checkVoid": true }' -declare const thing: { - method: undefined | (() => void); -}; - -thing.method && thing.method(); -``` - - - - -```ts option='{ "checkVoid": true }' -declare const thing: { - method: undefined | (() => void); -}; - -thing.method?.(); -``` - - - - ### `requireNullish` {/* insert option description */} From 1ef4c51f2199453484895983e6a3f8dc58ac61c0 Mon Sep 17 00:00:00 2001 From: nayounsang Date: Sat, 12 Jul 2025 23:36:23 +0900 Subject: [PATCH 14/18] fix: reupdate snapshot --- .../prefer-optional-chain.shot | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/prefer-optional-chain.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/prefer-optional-chain.shot index 032898689ebb..8607ddca5bca 100644 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/prefer-optional-chain.shot +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/prefer-optional-chain.shot @@ -149,25 +149,6 @@ declare const thing: bigint; thing?.toString(); -Incorrect -Options: { "checkVoid": true } - -declare const thing: { - method: undefined | (() => void); -}; - -thing.method && thing.method(); -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Prefer using an optional chain expression instead, as it's more concise and easier to read. - -Correct -Options: { "checkVoid": true } - -declare const thing: { - method: undefined | (() => void); -}; - -thing.method?.(); - Incorrect Options: { "requireNullish": true } From 87107cdac33353b3656a3b0c5f0b7a9ffd267e83 Mon Sep 17 00:00:00 2001 From: nayounsang Date: Wed, 16 Jul 2025 17:31:16 +0900 Subject: [PATCH 15/18] chore: revert gatherLogicalOperands.ts --- .../gatherLogicalOperands.ts | 47 +++++++++---------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.ts b/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.ts index 12be2cfc57d1..174f8982cad8 100644 --- a/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.ts +++ b/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.ts @@ -59,6 +59,8 @@ export interface InvalidOperand { type: OperandValidity.Invalid; } type Operand = InvalidOperand | ValidOperand; + +const NULLISH_FLAGS = ts.TypeFlags.Null | ts.TypeFlags.Undefined; function isValidFalseBooleanCheckType( node: TSESTree.Node, disallowFalseyLiteral: boolean, @@ -90,27 +92,26 @@ function isValidFalseBooleanCheckType( return false; } - let flagsToExcludeFromCheck = 0; - if (options.checkAny !== true) { - flagsToExcludeFromCheck |= ts.TypeFlags.Any; + let allowedFlags = NULLISH_FLAGS | ts.TypeFlags.Object; + if (options.checkAny === true) { + allowedFlags |= ts.TypeFlags.Any; } - if (options.checkUnknown !== true) { - flagsToExcludeFromCheck |= ts.TypeFlags.Unknown; + if (options.checkUnknown === true) { + allowedFlags |= ts.TypeFlags.Unknown; } - if (options.checkString !== true) { - flagsToExcludeFromCheck |= ts.TypeFlags.StringLike; + if (options.checkString === true) { + allowedFlags |= ts.TypeFlags.StringLike; } - if (options.checkNumber !== true) { - flagsToExcludeFromCheck |= ts.TypeFlags.NumberLike; + if (options.checkNumber === true) { + allowedFlags |= ts.TypeFlags.NumberLike; } - if (options.checkBoolean !== true) { - flagsToExcludeFromCheck |= ts.TypeFlags.BooleanLike; + if (options.checkBoolean === true) { + allowedFlags |= ts.TypeFlags.BooleanLike; } - if (options.checkBigInt !== true) { - flagsToExcludeFromCheck |= ts.TypeFlags.BigIntLike; + if (options.checkBigInt === true) { + allowedFlags |= ts.TypeFlags.BigIntLike; } - - return types.every(t => !isTypeFlagSet(t, flagsToExcludeFromCheck)); + return types.every(t => isTypeFlagSet(t, allowedFlags)); } export function gatherLogicalOperands( @@ -247,13 +248,12 @@ export function gatherLogicalOperands( case AST_NODE_TYPES.UnaryExpression: if ( operand.operator === '!' && - (!areMoreOperands || - isValidFalseBooleanCheckType( - operand.argument, - node.operator === '||', - parserServices, - options, - )) + isValidFalseBooleanCheckType( + operand.argument, + areMoreOperands && node.operator === '||', + parserServices, + options, + ) ) { result.push({ comparedName: operand.argument, @@ -274,10 +274,9 @@ export function gatherLogicalOperands( default: if ( - !areMoreOperands || isValidFalseBooleanCheckType( operand, - node.operator === '&&', + areMoreOperands && node.operator === '&&', parserServices, options, ) From cba0f378bf15bff89583a2f3d2b62f0f475f70b5 Mon Sep 17 00:00:00 2001 From: nayounsang Date: Wed, 16 Jul 2025 17:32:57 +0900 Subject: [PATCH 16/18] chore: revert perfer-optional-chain.test.ts --- .../prefer-optional-chain.test.ts | 66 ------------------- 1 file changed, 66 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 3a1b3199d11f..c05f6218b24c 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 @@ -1680,65 +1680,6 @@ describe('hand-crafted cases', () => { ], output: 'a?.prop;', }, - // check void - { - code: ` -declare const foo: void; -foo && foo(); - `, - errors: [{ messageId: 'preferOptionalChain' }], - output: ` -declare const foo: void; -foo?.(); - `, - }, - // check flag with obj - { - code: ` - declare const x: { y: boolean }; - x && x.y; - `, - errors: [ - { - messageId: 'preferOptionalChain', - suggestions: [ - { - messageId: 'optionalChainSuggest', - output: ` - declare const x: { y: boolean }; - x?.y; - `, - }, - ], - }, - ], - options: [{ checkBoolean: false }], - }, - // Exclude for everything else, an error occurs - { - code: noFormat`declare const foo: { x: { y: string } } | null; foo && foo.x;`, - errors: [ - { - messageId: 'preferOptionalChain', - suggestions: [ - { - messageId: 'optionalChainSuggest', - output: `declare const foo: { x: { y: string } } | null; foo?.x;`, - }, - ], - }, - ], - options: [ - { - checkAny: false, - checkBigInt: false, - checkBoolean: false, - checkNumber: false, - checkString: false, - checkUnknown: false, - }, - ], - }, ], valid: [ '!a || !b;', @@ -1916,13 +1857,6 @@ foo?.(); `, options: [{ checkBoolean: false }], }, - { - code: ` - declare const x: { y: boolean }; - x?.y && x?.y.length; - `, - options: [{ checkBoolean: false }], - }, { code: ` declare const x: number; From 433e6d479d53c921c3d047c2313d734f3ca30bdf Mon Sep 17 00:00:00 2001 From: nayounsang Date: Wed, 16 Jul 2025 17:35:41 +0900 Subject: [PATCH 17/18] fix: ignore isValidFalseBooleanCheckType if there is no more op --- .../gatherLogicalOperands.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.ts b/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.ts index 174f8982cad8..fdb6996c612d 100644 --- a/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.ts +++ b/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.ts @@ -248,12 +248,13 @@ export function gatherLogicalOperands( case AST_NODE_TYPES.UnaryExpression: if ( operand.operator === '!' && - isValidFalseBooleanCheckType( - operand.argument, - areMoreOperands && node.operator === '||', - parserServices, - options, - ) + (!areMoreOperands || + isValidFalseBooleanCheckType( + operand.argument, + node.operator === '||', + parserServices, + options, + )) ) { result.push({ comparedName: operand.argument, @@ -274,9 +275,10 @@ export function gatherLogicalOperands( default: if ( + !areMoreOperands || isValidFalseBooleanCheckType( operand, - areMoreOperands && node.operator === '&&', + node.operator === '&&', parserServices, options, ) From baf82db531984be08af90b56006c475ec02dd189 Mon Sep 17 00:00:00 2001 From: nayounsang Date: Wed, 16 Jul 2025 17:52:42 +0900 Subject: [PATCH 18/18] test: add test case --- .../prefer-optional-chain.test.ts | 44 +++++++++++++++++++ 1 file changed, 44 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 c05f6218b24c..a3d395e3cd37 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 @@ -1680,6 +1680,46 @@ describe('hand-crafted cases', () => { ], output: 'a?.prop;', }, + { + code: ` +declare const foo: { + bar: undefined | (() => void); +}; + +foo.bar && foo.bar(); + `, + errors: [{ messageId: 'preferOptionalChain' }], + output: ` +declare const foo: { + bar: undefined | (() => void); +}; + +foo.bar?.(); + `, + }, + { + code: ` +declare const foo: { bar: string }; + +const baz = foo && foo.bar; + `, + errors: [ + { + messageId: 'preferOptionalChain', + suggestions: [ + { + messageId: 'optionalChainSuggest', + output: ` +declare const foo: { bar: string }; + +const baz = foo?.bar; + `, + }, + ], + }, + ], + options: [{ checkString: false }], + }, ], valid: [ '!a || !b;', @@ -1917,6 +1957,10 @@ describe('hand-crafted cases', () => { !x || x.a; `, "typeof globalThis !== 'undefined' && globalThis.Array();", + ` + declare const x: void; + x && x(); + `, ], }); }); 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