@@ -6,12 +6,24 @@ import type { RuleContext } from '../../types';
6
6
7
7
type ASTNode = TSESTree . Node ;
8
8
type CheckedNode = NonNullable < ASTNode & { parent : NonNullable < ASTNode > } > ;
9
+ type VariableDeclaration =
10
+ | TSESTree . LetOrConstOrVarDeclaration
11
+ | TSESTree . UsingInForOfDeclaration
12
+ | TSESTree . UsingInNormalContextDeclaration ;
13
+ type VariableDeclarator =
14
+ | TSESTree . LetOrConstOrVarDeclarator
15
+ | TSESTree . UsingInForOfDeclarator
16
+ | TSESTree . UsingInNomalConextDeclarator ;
9
17
10
18
const PATTERN_TYPE =
11
19
/ ^ (?: .+ ?P a t t e r n | R e s t E l e m e n t | S p r e a d P r o p e r t y | E x p e r i m e n t a l R e s t P r o p e r t y | P r o p e r t y ) $ / u;
12
20
const DECLARATION_HOST_TYPE = / ^ (?: P r o g r a m | B l o c k S t a t e m e n t | S t a t i c B l o c k | S w i t c h C a s e ) $ / u;
13
21
const DESTRUCTURING_HOST_TYPE = / ^ (?: V a r i a b l e D e c l a r a t o r | A s s i g n m e n t E x p r e s s i o n ) $ / u;
14
22
23
+ /**
24
+ * Finds the callee of a `CallExpression` if the given node is part of a
25
+ * `VariableDeclarator` or an ObjectPattern within a `VariableDeclarator`.
26
+ */
15
27
function findIdentifierCallee ( node : CheckedNode ) {
16
28
const { parent } = node ;
17
29
if ( parent . type === 'VariableDeclarator' && parent . init ?. type === 'CallExpression' ) {
@@ -70,7 +82,8 @@ function skipReactiveValues(identifier: ASTNode | null) {
70
82
}
71
83
72
84
/**
73
- * Checks whether a given Identifier node becomes a VariableDeclaration or not.
85
+ * Checks whether a given `Identifier` node becomes a `VariableDeclaration` or
86
+ * not.
74
87
*/
75
88
function canBecomeVariableDeclaration ( identifier : ASTNode ) {
76
89
let node = identifier . parent ;
@@ -91,8 +104,8 @@ function canBecomeVariableDeclaration(identifier: ASTNode) {
91
104
}
92
105
93
106
/**
94
- * Checks if an property or element is from outer scope or export function parameters
95
- * in destructing pattern.
107
+ * Checks if an property or element is from outer scope or export function
108
+ * parameters in destructing pattern.
96
109
*/
97
110
function isOuterVariableInDestructing ( name : string , initScope : Scope ) {
98
111
if ( initScope . through . some ( ( ref ) => ref . resolved && ref . resolved . name === name ) ) {
@@ -104,10 +117,9 @@ function isOuterVariableInDestructing(name: string, initScope: Scope) {
104
117
}
105
118
106
119
/**
107
- * Gets the VariableDeclarator/AssignmentExpression node that a given reference
108
- * belongs to.
109
- * This is used to detect a mix of reassigned and never reassigned in a
110
- * destructuring.
120
+ * Gets the `VariableDeclarator/AssignmentExpression` node that a given
121
+ * reference belongs to. It is used to detect a mix of reassigned and never
122
+ * reassigned in a destructuring.
111
123
*/
112
124
function getDestructuringHost ( reference : Reference ) {
113
125
if ( ! reference . isWrite ( ) ) {
@@ -127,10 +139,10 @@ function getDestructuringHost(reference: Reference) {
127
139
}
128
140
129
141
/**
130
- * Determines if a destructuring assignment node contains
131
- * any MemberExpression nodes. This is used to determine if a
132
- * variable that is only written once using destructuring can be
133
- * safely converted into a const declaration.
142
+ * Determines if a destructuring assignment node contains any
143
+ * ` MemberExpression` nodes. is used to determine if a variable that is only
144
+ * written once using destructuring can be safely converted into a const
145
+ * declaration.
134
146
*/
135
147
function hasMemberExpressionAssignment ( node : ASTNode ) : boolean {
136
148
if ( node . type === 'MemberExpression' ) {
@@ -153,6 +165,10 @@ function hasMemberExpressionAssignment(node: ASTNode): boolean {
153
165
return false ;
154
166
}
155
167
168
+ /**
169
+ * Validates an object pattern to determine if it contains outer variables or
170
+ * non-identifier assignments.
171
+ */
156
172
function validateObjectPattern ( node : TSESTree . ObjectPattern , variable : Variable ) {
157
173
const properties = node . properties ;
158
174
const hasOuterVariables = properties
@@ -170,6 +186,10 @@ function validateObjectPattern(node: TSESTree.ObjectPattern, variable: Variable)
170
186
return hasOuterVariables || hasNonIdentifiers ;
171
187
}
172
188
189
+ /**
190
+ * Validates an array pattern to determine if it contains outer variables or
191
+ * non-identifier elements.
192
+ */
173
193
function validateArrayPattern ( node : TSESTree . ArrayPattern , variable : Variable ) : boolean {
174
194
const elements = node . elements ;
175
195
const hasOuterVariables = elements
@@ -187,15 +207,15 @@ function validateArrayPattern(node: TSESTree.ArrayPattern, variable: Variable):
187
207
* the first assignment, the identifier node is the node of the declaration.
188
208
* Otherwise, the identifier node is the node of the first assignment.
189
209
*
190
- * If the variable should not change to const, this export function returns null.
210
+ * If the variable should not change to const, export function returns null.
191
211
* - If the variable is reassigned.
192
212
* - If the variable is never initialized nor assigned.
193
213
* - If the variable is initialized in a different scope from the declaration.
194
214
* - If the unique assignment of the variable cannot change to a declaration.
195
215
* e.g. `if (a) b = 1` / `return (b = 1)`
196
216
* - If the variable is declared in the global scope and `eslintUsed` is `true`.
197
- * `/*exported foo` directive comment makes such variables. This rule does not
198
- * warn such variables because this rule cannot distinguish whether the
217
+ * `/*exported foo` directive comment makes such variables. rule does not
218
+ * warn such variables because rule cannot distinguish whether the
199
219
* exported variables are reassigned or not.
200
220
*/
201
221
function getIdentifierIfShouldBeConst ( variable : Variable , ignoreReadBeforeAssign : boolean ) {
@@ -247,6 +267,7 @@ function getIdentifierIfShouldBeConst(variable: Variable, ignoreReadBeforeAssign
247
267
if ( ! shouldBeConst ) {
248
268
return null ;
249
269
}
270
+
250
271
if ( isReadBeforeInit ) {
251
272
return variable . defs [ 0 ] . name ;
252
273
}
@@ -295,7 +316,7 @@ export function isInitOfForStatement(node: ASTNode): boolean {
295
316
/**
296
317
* Groups by the VariableDeclarator/AssignmentExpression node that each
297
318
* reference of given variables belongs to.
298
- * This is used to detect a mix of reassigned and never reassigned in a
319
+ * is used to detect a mix of reassigned and never reassigned in a
299
320
* destructuring.
300
321
*/
301
322
export function groupByDestructuring (
@@ -336,6 +357,13 @@ export function groupByDestructuring(
336
357
return identifierMap ;
337
358
}
338
359
360
+ /**
361
+ * Calculates the retained text range by comparing the given range with an
362
+ * optional retained range. If a retained range is provided, it merges the two
363
+ * ranges to form an actual range. It then returns two ranges: one from the
364
+ * start of the actual range to the start of the given range, and another from
365
+ * the end of the given range to the end of the actual range.
366
+ */
339
367
function calculateRetainedTextRange (
340
368
range : TSESTree . Range ,
341
369
retainedRange : TSESTree . Range | null
@@ -350,33 +378,32 @@ function calculateRetainedTextRange(
350
378
] ;
351
379
}
352
380
353
- type CheckOptions = { destructuring : 'any' | 'all' } ;
354
- type VariableDeclaration =
355
- | TSESTree . LetOrConstOrVarDeclaration
356
- | TSESTree . UsingInForOfDeclaration
357
- | TSESTree . UsingInNormalContextDeclaration ;
358
- type VariableDeclarator =
359
- | TSESTree . LetOrConstOrVarDeclarator
360
- | TSESTree . UsingInForOfDeclarator
361
- | TSESTree . UsingInNomalConextDeclarator ;
362
- export class GroupChecker {
363
- private reportCount = 0 ;
364
-
365
- private checkedId : TSESTree . BindingName | null = null ;
366
-
367
- private checkedName = '' ;
368
-
369
- private readonly shouldMatchAnyDestructuredVariable : boolean ;
381
+ type NodeReporterOptions = { destructuring : 'any' | 'all' } ;
382
+ type NodeReporter = { report : ( nodes : ASTNode [ ] ) => void } ;
370
383
371
- private readonly context : RuleContext ;
372
-
373
- public constructor ( context : RuleContext , { destructuring } : CheckOptions ) {
374
- this . context = context ;
375
- this . shouldMatchAnyDestructuredVariable = destructuring !== 'all' ;
376
- }
377
-
378
- public checkAndReportNodes ( nodes : ASTNode [ ] ) : void {
379
- const shouldCheckGroup = nodes . length && this . shouldMatchAnyDestructuredVariable ;
384
+ /**
385
+ * Creates a node reporter function that checks and reports nodes based on the
386
+ * provided context and options.
387
+ *
388
+ * @remarks The `report` function checks and reports nodes that should be
389
+ * converted to `const` declarations. It performs various checks to ensure that
390
+ * the nodes meet the criteria for reporting and fixing.
391
+ *
392
+ * The function also handles cases where variables are declared using
393
+ * destructuring patterns and ensures that the correct number of nodes are
394
+ * reported and fixed.
395
+ */
396
+ export function createNodeReporter (
397
+ context : RuleContext ,
398
+ { destructuring } : NodeReporterOptions
399
+ ) : NodeReporter {
400
+ let reportCount = 0 ;
401
+ let checkedId : TSESTree . BindingName | null = null ;
402
+ let checkedName = '' ;
403
+ const shouldMatchAnyDestructuredVariable = destructuring !== 'all' ;
404
+
405
+ function checkAndReportNodes ( nodes : ASTNode [ ] ) : void {
406
+ const shouldCheckGroup = nodes . length && shouldMatchAnyDestructuredVariable ;
380
407
if ( ! shouldCheckGroup ) {
381
408
return ;
382
409
}
@@ -396,28 +423,28 @@ export class GroupChecker {
396
423
}
397
424
398
425
const dec = variableDeclarationParent . declarations [ 0 ] ;
399
- this . checkDeclarator ( dec ) ;
426
+ checkDeclarator ( dec ) ;
400
427
401
- const shouldFix = this . checkShouldFix ( variableDeclarationParent , nodes . length ) ;
428
+ const shouldFix = checkShouldFix ( variableDeclarationParent , nodes . length ) ;
402
429
if ( ! shouldFix ) {
403
430
return ;
404
431
}
405
432
406
- const sourceCode = getSourceCode ( this . context ) ;
433
+ const sourceCode = getSourceCode ( context ) ;
407
434
nodes . filter ( skipReactiveValues ) . forEach ( ( node ) => {
408
- this . report ( sourceCode , node , variableDeclarationParent ) ;
435
+ report ( sourceCode , node , variableDeclarationParent ) ;
409
436
} ) ;
410
437
}
411
438
412
- private report (
439
+ function report (
413
440
sourceCode : ReturnType < typeof getSourceCode > ,
414
441
node : ASTNode ,
415
442
nodeParent : VariableDeclaration
416
443
) {
417
- this . context . report ( {
444
+ context . report ( {
418
445
node,
419
446
messageId : 'useConst' ,
420
- // @ts -expect-error Name will exist at this point
447
+ // @ts -expect-error Name will exist at point
421
448
data : { name : node . name } ,
422
449
fix : ( fixer ) => {
423
450
const letKeywordToken = sourceCode . getFirstToken ( nodeParent , {
@@ -440,23 +467,23 @@ export class GroupChecker {
440
467
} ) ;
441
468
}
442
469
443
- private checkShouldFix ( declaration : VariableDeclaration , totalNodes : number ) {
470
+ function checkShouldFix ( declaration : VariableDeclaration , totalNodes : number ) {
444
471
const shouldFix =
445
472
declaration &&
446
473
( declaration . parent . type === 'ForInStatement' ||
447
474
declaration . parent . type === 'ForOfStatement' ||
448
475
( 'declarations' in declaration &&
449
476
declaration . declarations . every ( ( declaration ) => declaration . init ) ) ) ;
450
477
451
- const totalDeclarationCount = this . checkDestructuredDeclaration ( declaration , totalNodes ) ;
478
+ const totalDeclarationCount = checkDestructuredDeclaration ( declaration , totalNodes ) ;
452
479
if ( totalDeclarationCount === - 1 ) {
453
480
return shouldFix ;
454
481
}
455
482
456
- return shouldFix && this . reportCount === totalDeclarationCount ;
483
+ return shouldFix && reportCount === totalDeclarationCount ;
457
484
}
458
485
459
- private checkDestructuredDeclaration ( declaration : VariableDeclaration , totalNodes : number ) {
486
+ function checkDestructuredDeclaration ( declaration : VariableDeclaration , totalNodes : number ) {
460
487
const hasMultipleDeclarations =
461
488
declaration !== null &&
462
489
'declarations' in declaration &&
@@ -471,7 +498,7 @@ export class GroupChecker {
471
498
return - 1 ;
472
499
}
473
500
474
- this . reportCount += totalNodes ;
501
+ reportCount += totalNodes ;
475
502
476
503
return declaration . declarations . reduce ( ( total , declaration ) => {
477
504
if ( declaration . id . type === 'ObjectPattern' ) {
@@ -486,7 +513,7 @@ export class GroupChecker {
486
513
} , 0 ) ;
487
514
}
488
515
489
- private checkDeclarator ( declarator : VariableDeclarator ) {
516
+ function checkDeclarator ( declarator : VariableDeclarator ) {
490
517
if ( ! declarator . init ) {
491
518
return ;
492
519
}
@@ -497,22 +524,28 @@ export class GroupChecker {
497
524
}
498
525
499
526
const { id } = firstDecParent ;
500
- if ( 'name' in id && id . name !== this . checkedName ) {
501
- this . checkedName = id . name ;
502
- this . reportCount = 0 ;
527
+ if ( 'name' in id && id . name !== checkedName ) {
528
+ checkedName = id . name ;
529
+ reportCount = 0 ;
503
530
}
504
531
505
532
if ( firstDecParent . id . type === 'ObjectPattern' ) {
506
533
const { init } = firstDecParent ;
507
- if ( init && 'name' in init && init . name !== this . checkedName ) {
508
- this . checkedName = init . name ;
509
- this . reportCount = 0 ;
534
+ if ( init && 'name' in init && init . name !== checkedName ) {
535
+ checkedName = init . name ;
536
+ reportCount = 0 ;
510
537
}
511
538
}
512
539
513
- if ( firstDecParent . id !== this . checkedId ) {
514
- this . checkedId = firstDecParent . id ;
515
- this . reportCount = 0 ;
540
+ if ( firstDecParent . id !== checkedId ) {
541
+ checkedId = firstDecParent . id ;
542
+ reportCount = 0 ;
516
543
}
517
544
}
545
+
546
+ return {
547
+ report ( group ) {
548
+ checkAndReportNodes ( group ) ;
549
+ }
550
+ } ;
518
551
}
0 commit comments