11import { AST_NODE_TYPES , TSESLint , TSESTree } from "@typescript-eslint/utils" ;
22
3+ /**
4+ * Resolves the element of an array literal bound to `name` by an array
5+ * destructuring pattern (for example `const [cmd] = [command]`). Returns null
6+ * when the binding cannot be resolved precisely — a non-literal right-hand
7+ * side, a rest element before the binding, a spread element in the array
8+ * literal, a hole, or a default value.
9+ */
10+ function resolveArrayPatternElement ( pattern : TSESTree . ArrayPattern , init : TSESTree . Expression , name : string ) : TSESTree . Expression | null {
11+ if ( init . type !== AST_NODE_TYPES . ArrayExpression ) return null ;
12+ for ( let index = 0 ; index < pattern . elements . length ; index ++ ) {
13+ const element = pattern . elements [ index ] ;
14+ // A rest element consumes the remaining values, so positions after it no
15+ // longer line up with the array literal.
16+ if ( element !== null && element . type === AST_NODE_TYPES . RestElement ) return null ;
17+ if ( element === null || element . type !== AST_NODE_TYPES . Identifier || element . name !== name ) continue ;
18+ // A spread element at or before this position shifts the value positions.
19+ if ( init . elements . slice ( 0 , index + 1 ) . some ( value => value !== null && value . type === AST_NODE_TYPES . SpreadElement ) ) return null ;
20+ const value = init . elements [ index ] ;
21+ if ( value === null || value . type === AST_NODE_TYPES . SpreadElement ) return null ;
22+ return value ;
23+ }
24+ return null ;
25+ }
26+
27+ /**
28+ * Returns the static property name of a non-computed property key, or null
29+ * when the key is not statically known.
30+ */
31+ function getStaticPropertyName ( key : TSESTree . Node ) : string | null {
32+ if ( key . type === AST_NODE_TYPES . Identifier ) return key . name ;
33+ if ( key . type === AST_NODE_TYPES . Literal && ( typeof key . value === "string" || typeof key . value === "number" ) ) return String ( key . value ) ;
34+ return null ;
35+ }
36+
37+ /**
38+ * Narrows a property value to a plain expression, rejecting binding patterns
39+ * and other non-expression property values.
40+ */
41+ function asExpression ( value : TSESTree . Property [ "value" ] ) : TSESTree . Expression | null {
42+ switch ( value . type ) {
43+ case AST_NODE_TYPES . ArrayPattern :
44+ case AST_NODE_TYPES . AssignmentPattern :
45+ case AST_NODE_TYPES . ObjectPattern :
46+ case AST_NODE_TYPES . TSEmptyBodyFunctionExpression :
47+ return null ;
48+ default :
49+ return value ;
50+ }
51+ }
52+
53+ /**
54+ * Resolves the property value of an object literal bound to `name` by an
55+ * object destructuring pattern (for example `const { cmd } = { cmd: command }`).
56+ * Returns null when the binding cannot be resolved precisely — a non-literal
57+ * right-hand side, a spread element, a computed or accessor property, or a
58+ * default value.
59+ */
60+ function resolveObjectPatternProperty ( pattern : TSESTree . ObjectPattern , init : TSESTree . Expression , name : string ) : TSESTree . Expression | null {
61+ if ( init . type !== AST_NODE_TYPES . ObjectExpression ) return null ;
62+ // A spread can override any property, so the literal is no longer authoritative.
63+ if ( init . properties . some ( property => property . type === AST_NODE_TYPES . SpreadElement ) ) return null ;
64+
65+ let key : string | null = null ;
66+ for ( const property of pattern . properties ) {
67+ if ( property . type !== AST_NODE_TYPES . Property || property . computed ) continue ;
68+ if ( property . value . type !== AST_NODE_TYPES . Identifier || property . value . name !== name ) continue ;
69+ key = getStaticPropertyName ( property . key ) ;
70+ break ;
71+ }
72+ if ( key === null ) return null ;
73+
74+ let resolved : TSESTree . Expression | null = null ;
75+ for ( const property of init . properties ) {
76+ if ( property . type !== AST_NODE_TYPES . Property || property . computed || property . kind !== "init" ) continue ;
77+ if ( getStaticPropertyName ( property . key ) !== key ) continue ;
78+ // Later properties win over earlier duplicates.
79+ resolved = asExpression ( property . value ) ;
80+ }
81+ return resolved ;
82+ }
83+
384/**
485 * When `identifier` is a write-once local variable binding, returns its
586 * initializer expression so the caller can apply further checks. Returns null
687 * for parameters, imports, multiply-assigned vars, and vars with no
788 * initializer.
89+ *
90+ * Destructured bindings (for example `const [cmd] = [command]`) resolve to the
91+ * specific destructured value when it can be determined precisely, and to null
92+ * otherwise — never to the whole right-hand side expression.
893 */
994function resolveInitializer ( identifier : TSESTree . Identifier , sourceCode : TSESLint . SourceCode ) : TSESTree . Expression | null {
1095 const startScope = sourceCode . getScope ( identifier ) ;
@@ -28,7 +113,11 @@ function resolveInitializer(identifier: TSESTree.Identifier, sourceCode: TSESLin
28113 // Reject re-assigned bindings (write references that are not the initializer).
29114 if ( variable . references . some ( ref => ref . isWrite ( ) && ! ref . init ) ) return null ;
30115 const declarator = def . node as TSESTree . VariableDeclarator ;
31- return declarator . init ?? null ;
116+ if ( declarator . init === null || declarator . init === undefined ) return null ;
117+ if ( declarator . id . type === AST_NODE_TYPES . Identifier ) return declarator . init ;
118+ if ( declarator . id . type === AST_NODE_TYPES . ArrayPattern ) return resolveArrayPatternElement ( declarator . id , declarator . init , identifier . name ) ;
119+ if ( declarator . id . type === AST_NODE_TYPES . ObjectPattern ) return resolveObjectPatternProperty ( declarator . id , declarator . init , identifier . name ) ;
120+ return null ;
32121 }
33122 scope = scope . upper ;
34123 }
0 commit comments