@@ -5,6 +5,7 @@ import type ts from 'typescript';
5
5
import { findVariable } from '../utils/ast-utils.js' ;
6
6
import { toRegExp } from '../utils/regexp.js' ;
7
7
import { normalize } from 'path' ;
8
+ import type { AST as SvAST } from 'svelte-eslint-parser' ;
8
9
9
10
type PropertyPathArray = string [ ] ;
10
11
type DeclaredPropertyNames = Set < { originalName : string ; aliasName : string } > ;
@@ -130,49 +131,66 @@ export default createRule('no-unused-props', {
130
131
/**
131
132
* Extracts property paths from member expressions.
132
133
*/
133
- function getPropertyPath ( node : TSESTree . Identifier ) : PropertyPathArray {
134
+ function getPropertyPath ( node : TSESTree . Identifier ) : {
135
+ paths : PropertyPathArray ;
136
+ isSpread : boolean ;
137
+ } {
134
138
const paths : PropertyPathArray = [ ] ;
135
- let currentNode : TSESTree . Node = node ;
136
- let parentNode : TSESTree . Node | null = currentNode . parent ?? null ;
137
-
139
+ let isSpread = false ;
140
+ let currentNode : TSESTree . Node | SvAST . SvelteSpreadAttribute = node ;
141
+ let parentNode : TSESTree . Node | SvAST . SvelteSpreadAttribute | null =
142
+ currentNode . parent ?? null ;
138
143
while ( parentNode ) {
139
144
if ( parentNode . type === 'MemberExpression' && parentNode . object === currentNode ) {
140
145
const property = parentNode . property ;
141
146
if ( property . type === 'Identifier' ) {
142
147
paths . push ( property . name ) ;
143
148
} else if ( property . type === 'Literal' && typeof property . value === 'string' ) {
144
149
paths . push ( property . value ) ;
145
- } else {
146
- break ;
147
150
}
151
+ } else {
152
+ if ( parentNode . type === 'SpreadElement' || parentNode . type === 'SvelteSpreadAttribute' ) {
153
+ isSpread = true ;
154
+ }
155
+ break ;
148
156
}
157
+
149
158
currentNode = parentNode ;
150
- parentNode = currentNode . parent ?? null ;
159
+ parentNode = ( currentNode . parent as TSESTree . Node | SvAST . SvelteSpreadAttribute ) ?? null ;
151
160
}
152
161
153
- return paths ;
162
+ return { paths, isSpread } ;
154
163
}
155
164
156
165
/**
157
166
* Finds all property access paths for a given variable.
158
167
*/
159
- function getUsedNestedPropertyPathsArray ( node : TSESTree . Identifier ) : PropertyPathArray [ ] {
168
+ function getUsedNestedPropertyPathsArray ( node : TSESTree . Identifier ) : {
169
+ paths : PropertyPathArray [ ] ;
170
+ spreadPaths : PropertyPathArray [ ] ;
171
+ } {
160
172
const variable = findVariable ( context , node ) ;
161
- if ( ! variable ) return [ ] ;
173
+ if ( ! variable ) return { paths : [ ] , spreadPaths : [ ] } ;
162
174
163
175
const pathsArray : PropertyPathArray [ ] = [ ] ;
176
+ const spreadPathsArray : PropertyPathArray [ ] = [ ] ;
164
177
for ( const reference of variable . references ) {
165
178
if (
166
179
'identifier' in reference &&
167
180
reference . identifier . type === 'Identifier' &&
168
181
( reference . identifier . range [ 0 ] !== node . range [ 0 ] ||
169
182
reference . identifier . range [ 1 ] !== node . range [ 1 ] )
170
183
) {
171
- const referencePath = getPropertyPath ( reference . identifier ) ;
172
- pathsArray . push ( referencePath ) ;
184
+ const { paths, isSpread } = getPropertyPath ( reference . identifier ) ;
185
+ if ( isSpread ) {
186
+ spreadPathsArray . push ( paths ) ;
187
+ } else {
188
+ pathsArray . push ( paths ) ;
189
+ }
173
190
}
174
191
}
175
- return pathsArray ;
192
+
193
+ return { paths : pathsArray , spreadPaths : spreadPathsArray } ;
176
194
}
177
195
178
196
/**
@@ -239,6 +257,7 @@ export default createRule('no-unused-props', {
239
257
function checkUnusedProperties ( {
240
258
propsType,
241
259
usedPropertyPaths,
260
+ usedSpreadPropertyPaths,
242
261
declaredPropertyNames,
243
262
reportNode,
244
263
parentPath,
@@ -247,6 +266,7 @@ export default createRule('no-unused-props', {
247
266
} : {
248
267
propsType : ts . Type ;
249
268
usedPropertyPaths : string [ ] ;
269
+ usedSpreadPropertyPaths : string [ ] ;
250
270
declaredPropertyNames : DeclaredPropertyNames ;
251
271
reportNode : TSESTree . Node ;
252
272
parentPath : string [ ] ;
@@ -273,6 +293,7 @@ export default createRule('no-unused-props', {
273
293
checkUnusedProperties ( {
274
294
propsType : propsBaseType ,
275
295
usedPropertyPaths,
296
+ usedSpreadPropertyPaths,
276
297
declaredPropertyNames,
277
298
reportNode,
278
299
parentPath,
@@ -290,13 +311,17 @@ export default createRule('no-unused-props', {
290
311
if ( shouldIgnoreProperty ( propName ) ) continue ;
291
312
292
313
const currentPath = [ ...parentPath , propName ] ;
293
- const currentPathStr = [ ... parentPath , propName ] . join ( '.' ) ;
314
+ const currentPathStr = currentPath . join ( '.' ) ;
294
315
295
316
if ( reportedPropertyPaths . has ( currentPathStr ) ) continue ;
296
317
297
318
const propType = typeChecker . getTypeOfSymbol ( prop ) ;
298
319
299
- const isUsedThisInPath = usedPropertyPaths . includes ( currentPathStr ) ;
320
+ const isUsedThisInPath =
321
+ usedPropertyPaths . includes ( currentPathStr ) ||
322
+ usedSpreadPropertyPaths . some ( ( path ) => {
323
+ return path === '' || path === currentPathStr || path . startsWith ( `${ currentPathStr } .` ) ;
324
+ } ) ;
300
325
const isUsedInPath = usedPropertyPaths . some ( ( path ) => {
301
326
return path . startsWith ( `${ currentPathStr } .` ) ;
302
327
} ) ;
@@ -330,6 +355,7 @@ export default createRule('no-unused-props', {
330
355
checkUnusedProperties ( {
331
356
propsType : propType ,
332
357
usedPropertyPaths,
358
+ usedSpreadPropertyPaths,
333
359
declaredPropertyNames,
334
360
reportNode,
335
361
parentPath : currentPath ,
@@ -370,7 +396,6 @@ export default createRule('no-unused-props', {
370
396
) : PropertyPathArray [ ] {
371
397
const normalized : PropertyPathArray [ ] = [ ] ;
372
398
for ( const path of paths . sort ( ( a , b ) => a . length - b . length ) ) {
373
- if ( path . length === 0 ) continue ;
374
399
if ( normalized . some ( ( p ) => p . every ( ( part , idx ) => part === path [ idx ] ) ) ) {
375
400
continue ;
376
401
}
@@ -398,7 +423,8 @@ export default createRule('no-unused-props', {
398
423
if ( ! tsNode || ! tsNode . type ) return ;
399
424
400
425
const propsType = typeChecker . getTypeFromTypeNode ( tsNode . type ) ;
401
- let usedPropertyPathsArray : PropertyPathArray [ ] = [ ] ;
426
+ const usedPropertyPathsArray : PropertyPathArray [ ] = [ ] ;
427
+ const usedSpreadPropertyPathsArray : PropertyPathArray [ ] = [ ] ;
402
428
let declaredPropertyNames : DeclaredPropertyNames = new Set ( ) ;
403
429
404
430
if ( node . id . type === 'ObjectPattern' ) {
@@ -416,21 +442,28 @@ export default createRule('no-unused-props', {
416
442
}
417
443
}
418
444
for ( const identifier of identifiers ) {
419
- const paths = getUsedNestedPropertyPathsArray ( identifier ) ;
445
+ const { paths, spreadPaths } = getUsedNestedPropertyPathsArray ( identifier ) ;
420
446
usedPropertyPathsArray . push ( ...paths . map ( ( path ) => [ identifier . name , ...path ] ) ) ;
447
+ usedSpreadPropertyPathsArray . push (
448
+ ...spreadPaths . map ( ( path ) => [ identifier . name , ...path ] )
449
+ ) ;
421
450
}
422
451
} else if ( node . id . type === 'Identifier' ) {
423
- usedPropertyPathsArray = getUsedNestedPropertyPathsArray ( node . id ) ;
452
+ const { paths, spreadPaths } = getUsedNestedPropertyPathsArray ( node . id ) ;
453
+ usedPropertyPathsArray . push ( ...paths ) ;
454
+ usedSpreadPropertyPathsArray . push ( ...spreadPaths ) ;
455
+ }
456
+
457
+ function runNormalizeUsedPaths ( paths : PropertyPathArray [ ] ) {
458
+ return normalizeUsedPaths ( paths , options . allowUnusedNestedProperties ) . map ( ( pathArray ) => {
459
+ return pathArray . join ( '.' ) ;
460
+ } ) ;
424
461
}
425
462
426
463
checkUnusedProperties ( {
427
464
propsType,
428
- usedPropertyPaths : normalizeUsedPaths (
429
- usedPropertyPathsArray ,
430
- options . allowUnusedNestedProperties
431
- ) . map ( ( pathArray ) => {
432
- return pathArray . join ( '.' ) ;
433
- } ) ,
465
+ usedPropertyPaths : runNormalizeUsedPaths ( usedPropertyPathsArray ) ,
466
+ usedSpreadPropertyPaths : runNormalizeUsedPaths ( usedSpreadPropertyPathsArray ) ,
434
467
declaredPropertyNames,
435
468
reportNode : node . id ,
436
469
parentPath : [ ] ,
0 commit comments