1
1
import type { StaticSvelteConfig } from "." ;
2
- import { getEspree } from "../parser/espree" ;
3
- import type {
4
- Program ,
5
- ExportDefaultDeclaration ,
6
- Expression ,
7
- Identifier ,
8
- } from "estree" ;
9
- import { getFallbackKeys , traverseNodes } from "../traverse" ;
2
+ import type * as ESTree from "estree" ;
3
+ import type { Scope } from "eslint" ;
10
4
import type { ScopeManager } from "eslint-scope" ;
5
+ import { getFallbackKeys , traverseNodes } from "../traverse" ;
6
+ import { getEspree } from "../parser/espree" ;
11
7
import { analyze } from "eslint-scope" ;
12
8
import { findVariable } from "../scope" ;
13
9
@@ -72,11 +68,11 @@ class EvaluatedProperties {
72
68
}
73
69
74
70
function parseAst (
75
- ast : Program ,
71
+ ast : ESTree . Program ,
76
72
scopeManager : ScopeManager ,
77
73
) : StaticSvelteConfig {
78
74
const edd = ast . body . find (
79
- ( node ) : node is ExportDefaultDeclaration =>
75
+ ( node ) : node is ESTree . ExportDefaultDeclaration =>
80
76
node . type === "ExportDefaultDeclaration" ,
81
77
) ;
82
78
if ( ! edd ) return { } ;
@@ -87,10 +83,10 @@ function parseAst(
87
83
}
88
84
89
85
function parseSvelteConfigExpression (
90
- node : Expression ,
86
+ node : ESTree . Expression ,
91
87
scopeManager : ScopeManager ,
92
88
) : StaticSvelteConfig {
93
- const tracked = new Map < Identifier , Expression | null > ( ) ;
89
+ const tracked = new Map < ESTree . Identifier , Scope . Definition | null > ( ) ;
94
90
const parsed = parseExpression ( node ) ;
95
91
if ( parsed ?. type !== EvaluatedType . object ) return { } ;
96
92
const properties = parsed . properties ;
@@ -124,61 +120,161 @@ function parseSvelteConfigExpression(
124
120
}
125
121
return result ;
126
122
127
- function parseExpression ( node : Expression ) : Evaluated | null {
123
+ function parseExpression ( node : ESTree . Expression ) : Evaluated | null {
128
124
if ( node . type === "Literal" ) {
129
125
return { type : EvaluatedType . literal , value : node . value } ;
130
126
}
131
127
if ( node . type === "Identifier" ) {
132
- const expr = trackIdentifier ( node ) ;
133
- if ( ! expr ) return null ;
134
- return parseExpression ( expr ) ;
128
+ return parseIdentifier ( node ) ;
135
129
}
136
130
if ( node . type === "ObjectExpression" ) {
137
131
const reversedProperties = [ ...node . properties ] . reverse ( ) ;
138
132
return {
139
133
type : EvaluatedType . object ,
140
134
properties : new EvaluatedProperties ( ( key ) => {
135
+ let hasUnknown = false ;
141
136
for ( const prop of reversedProperties ) {
142
137
if ( prop . type === "Property" ) {
143
- if (
144
- ! prop . computed &&
145
- prop . key . type === "Identifier" &&
146
- prop . key . name === key
147
- ) {
148
- return parseExpression ( prop . value as Expression ) ;
149
- }
150
- const evaluatedKey = parseExpression ( prop . key as Expression ) ;
151
- if (
152
- evaluatedKey ?. type === EvaluatedType . literal &&
153
- String ( evaluatedKey . value ) === key
154
- ) {
155
- return parseExpression ( prop . value as Expression ) ;
138
+ if ( ! prop . computed && prop . key . type === "Identifier" ) {
139
+ if ( prop . key . name === key )
140
+ return parseExpression ( prop . value as ESTree . Expression ) ;
141
+ } else {
142
+ const evaluatedKey = parseExpression (
143
+ prop . key as ESTree . Expression ,
144
+ ) ;
145
+ if ( evaluatedKey ?. type === EvaluatedType . literal ) {
146
+ if ( String ( evaluatedKey . value ) === key )
147
+ return parseExpression ( prop . value as ESTree . Expression ) ;
148
+ } else {
149
+ hasUnknown = true ;
150
+ }
156
151
}
157
152
} else if ( prop . type === "SpreadElement" ) {
153
+ hasUnknown = true ;
158
154
const nesting = parseExpression ( prop . argument ) ;
159
155
if ( nesting ?. type === EvaluatedType . object ) {
160
156
const value = nesting . properties . get ( key ) ;
161
157
if ( value ) return value ;
162
158
}
163
159
}
164
160
}
165
- return null ;
161
+ return hasUnknown
162
+ ? null
163
+ : { type : EvaluatedType . literal , value : undefined } ;
166
164
} ) ,
167
165
} ;
168
166
}
169
167
170
168
return null ;
171
169
}
172
170
173
- function trackIdentifier ( node : Identifier ) : Expression | null {
171
+ function parseIdentifier ( node : ESTree . Identifier ) {
172
+ const def = getIdentifierDefinition ( node ) ;
173
+ if ( ! def ) return null ;
174
+ if ( def . type !== "Variable" ) return null ;
175
+ if ( def . parent . kind !== "const" || ! def . node . init ) return null ;
176
+ const evaluated = parseExpression ( def . node . init ) ;
177
+ if ( ! evaluated ) return null ;
178
+ const assigns = parsePatternAssign ( def . name , def . node . id ) ;
179
+ let result = evaluated ;
180
+ while ( assigns . length ) {
181
+ const assign = assigns . shift ( ) ! ;
182
+ if ( assign . type === "member" ) {
183
+ if ( result . type !== EvaluatedType . object ) return null ;
184
+ const next = result . properties . get ( assign . name ) ;
185
+ if ( ! next ) return null ;
186
+ result = next ;
187
+ } else if ( assign . type === "assignment" ) {
188
+ if (
189
+ result . type === EvaluatedType . literal &&
190
+ result . value === undefined
191
+ ) {
192
+ const next = parseExpression ( assign . node . right ) ;
193
+ if ( ! next ) return null ;
194
+ result = next ;
195
+ }
196
+ }
197
+ }
198
+ return result ;
199
+ }
200
+
201
+ function getIdentifierDefinition (
202
+ node : ESTree . Identifier ,
203
+ ) : Scope . Definition | null {
174
204
if ( tracked . has ( node ) ) return tracked . get ( node ) || null ;
175
205
tracked . set ( node , null ) ;
176
206
const variable = findVariable ( scopeManager , node ) ;
177
207
if ( ! variable || variable . defs . length !== 1 ) return null ;
178
208
const def = variable . defs [ 0 ] ;
179
- if ( def . type !== "Variable" || def . parent . kind !== "const" ) return null ;
180
- const init = def . node . init || null ;
181
- tracked . set ( node , init ) ;
182
- return init ;
209
+ tracked . set ( node , def ) ;
210
+ if (
211
+ def . type !== "Variable" ||
212
+ def . parent . kind !== "const" ||
213
+ def . node . id . type !== "Identifier" ||
214
+ def . node . init ?. type !== "Identifier"
215
+ ) {
216
+ return def ;
217
+ }
218
+ const newDef = getIdentifierDefinition ( def . node . init ) ;
219
+ tracked . set ( node , newDef ) ;
220
+ return newDef ;
221
+ }
222
+ }
223
+
224
+ function parsePatternAssign (
225
+ node : ESTree . Pattern ,
226
+ root : ESTree . Pattern ,
227
+ ) : (
228
+ | { type : "member" ; name : string }
229
+ | { type : "assignment" ; node : ESTree . AssignmentPattern }
230
+ ) [ ] {
231
+ return parse ( root ) || [ ] ;
232
+
233
+ function parse (
234
+ target : ESTree . Pattern ,
235
+ ) :
236
+ | (
237
+ | { type : "member" ; name : string }
238
+ | { type : "assignment" ; node : ESTree . AssignmentPattern }
239
+ ) [ ]
240
+ | null {
241
+ if ( node === target ) {
242
+ return [ ] ;
243
+ }
244
+ if ( target . type === "Identifier" ) {
245
+ return null ;
246
+ }
247
+ if ( target . type === "AssignmentPattern" ) {
248
+ const left = parse ( target . left ) ;
249
+ if ( ! left ) return null ;
250
+ return [ { type : "assignment" , node : target } , ...left ] ;
251
+ }
252
+ if ( target . type === "ObjectPattern" ) {
253
+ for ( const prop of target . properties ) {
254
+ if ( prop . type === "Property" ) {
255
+ const name =
256
+ ! prop . computed && prop . key . type === "Identifier"
257
+ ? prop . key . name
258
+ : prop . key . type === "Literal"
259
+ ? String ( prop . key . value )
260
+ : null ;
261
+ if ( ! name ) continue ;
262
+ const value = parse ( prop . value ) ;
263
+ if ( ! value ) return null ;
264
+ return [ { type : "member" , name } , ...value ] ;
265
+ }
266
+ }
267
+ return null ;
268
+ }
269
+ if ( target . type === "ArrayPattern" ) {
270
+ for ( const [ index , element ] of target . elements . entries ( ) ) {
271
+ if ( ! element ) continue ;
272
+ const value = parse ( element ) ;
273
+ if ( ! value ) return null ;
274
+ return [ { type : "member" , name : String ( index ) } , ...value ] ;
275
+ }
276
+ return null ;
277
+ }
278
+ return null ;
183
279
}
184
280
}
0 commit comments