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 73ce6d9255a..986b2e5f857 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 @@ -3,6 +3,7 @@ import type { TSESTree, } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; +import type { SourceCode } from '@typescript-eslint/utils/ts-eslint'; import { isBigIntLiteralType, isBooleanLiteralType, @@ -122,6 +123,7 @@ function isValidFalseBooleanCheckType( export function gatherLogicalOperands( node: TSESTree.LogicalExpression, parserServices: ParserServicesWithTypeInformation, + sourceCode: Readonly, options: PreferOptionalChainOptions, ): { operands: Operand[]; @@ -157,7 +159,20 @@ export function gatherLogicalOperands( comparedExpression.type === AST_NODE_TYPES.UnaryExpression && comparedExpression.operator === 'typeof' ) { - // typeof x === 'undefined' + const argument = comparedExpression.argument; + if (argument.type === AST_NODE_TYPES.Identifier) { + const reference = sourceCode + .getScope(argument) + .references.find(ref => ref.identifier.name === argument.name); + + if (!reference?.resolved?.defs.length) { + // typeof window === 'undefined' + result.push({ type: OperandValidity.Invalid }); + continue; + } + } + + // typeof x.y === 'undefined' result.push({ type: OperandValidity.Valid, comparedName: comparedExpression.argument, diff --git a/packages/eslint-plugin/src/rules/prefer-optional-chain.ts b/packages/eslint-plugin/src/rules/prefer-optional-chain.ts index 24791c5841e..605045b99fd 100644 --- a/packages/eslint-plugin/src/rules/prefer-optional-chain.ts +++ b/packages/eslint-plugin/src/rules/prefer-optional-chain.ts @@ -179,6 +179,7 @@ export default createRule< const { operands, newlySeenLogicals } = gatherLogicalOperands( node, parserServices, + context.sourceCode, 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 852296721a7..cee379d45ac 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 @@ -907,6 +907,7 @@ describe('hand-crafted cases', () => { declare const x: 0n | { a: string }; !x || x.a; `, + "typeof globalThis !== 'undefined' && globalThis.Array();", ], invalid: [ // two errors @@ -1915,6 +1916,42 @@ describe('hand-crafted cases', () => { }, ], }, + { + code: ` + function foo(globalThis?: { Array: Function }) { + typeof globalThis !== 'undefined' && globalThis.Array(); + } + `, + output: ` + function foo(globalThis?: { Array: Function }) { + globalThis?.Array(); + } + `, + errors: [ + { + messageId: 'preferOptionalChain', + }, + ], + }, + { + code: ` + typeof globalThis !== 'undefined' && globalThis.Array && globalThis.Array(); + `, + output: null, + errors: [ + { + messageId: 'preferOptionalChain', + suggestions: [ + { + messageId: 'optionalChainSuggest', + output: ` + typeof globalThis !== 'undefined' && globalThis.Array?.(); + `, + }, + ], + }, + ], + }, ], }); });