@@ -136,6 +136,56 @@ type ExtractCapturingGroupReferencesContext = {
136
136
isString : ( node : Expression ) => boolean
137
137
}
138
138
139
+ type ArrayMethodName = Exclude < keyof unknown [ ] , "length" | symbol | number >
140
+ const WELL_KNOWN_ARRAY_METHODS : {
141
+ [ key in ArrayMethodName ] : {
142
+ // If specified, the method receives a function that iterates the element.
143
+ // Specify an array with the index of the argument that receives the element.
144
+ elementParameters ?: number [ ]
145
+ result ?:
146
+ | "element" // The method returns the element.
147
+ | "array" // The method returns an array with some of the elements.]
148
+ | "iterator" // The method returns an iterator with some of the elements.
149
+ }
150
+ } = {
151
+ toString : { } ,
152
+ toLocaleString : { } ,
153
+ pop : { result : "element" } ,
154
+ push : { } ,
155
+ concat : { result : "array" } ,
156
+ join : { } ,
157
+ reverse : { result : "array" } ,
158
+ shift : { result : "element" } ,
159
+ slice : { result : "array" } ,
160
+ sort : { elementParameters : [ 0 , 1 ] , result : "array" } ,
161
+ splice : { result : "array" } ,
162
+ unshift : { } ,
163
+ indexOf : { } ,
164
+ lastIndexOf : { } ,
165
+ every : { elementParameters : [ 0 ] } ,
166
+ some : { elementParameters : [ 0 ] } ,
167
+ forEach : { elementParameters : [ 0 ] } ,
168
+ map : { elementParameters : [ 0 ] } ,
169
+ filter : { elementParameters : [ 0 ] , result : "array" } ,
170
+ reduce : { elementParameters : [ 1 ] } ,
171
+ reduceRight : { elementParameters : [ 1 ] } ,
172
+ // ES2015
173
+ find : { elementParameters : [ 0 ] , result : "element" } ,
174
+ findIndex : { elementParameters : [ 0 ] } ,
175
+ fill : { } ,
176
+ copyWithin : { result : "array" } ,
177
+ entries : { } ,
178
+ keys : { } ,
179
+ values : { result : "iterator" } ,
180
+ // ES2016
181
+ includes : { } ,
182
+ // ES2019
183
+ flatMap : { elementParameters : [ 0 ] } ,
184
+ flat : { } ,
185
+ // ES2022
186
+ at : { result : "element" } ,
187
+ }
188
+
139
189
/**
140
190
* Extracts the usage of the capturing group.
141
191
*/
@@ -234,11 +284,9 @@ function* iterateForMember(
234
284
object : Expression ,
235
285
ctx : ExtractCapturingGroupReferencesContext ,
236
286
) : Iterable < CapturingGroupReference > {
237
- const parent = getParent ( memberExpression )
287
+ const parent = getCallExpressionFromCalleeExpression ( memberExpression )
238
288
if (
239
289
! parent ||
240
- parent . type !== "CallExpression" ||
241
- parent . callee !== memberExpression ||
242
290
! isKnownMethodCall ( parent , {
243
291
test : 1 ,
244
292
exec : 1 ,
@@ -335,7 +383,7 @@ function* iterateForStringReplace(
335
383
336
384
/** Iterate the capturing group references for String.prototype.matchAll(). */
337
385
function * iterateForStringMatchAll (
338
- node : KnownMethodCall ,
386
+ node : CallExpression ,
339
387
argument : Expression ,
340
388
ctx : ExtractCapturingGroupReferencesContext ,
341
389
) : Iterable < CapturingGroupReference > {
@@ -351,37 +399,41 @@ function* iterateForStringMatchAll(
351
399
return
352
400
}
353
401
if ( hasNameRef ( iterationRef ) ) {
402
+ if (
403
+ iterationRef . type === "member" &&
404
+ isWellKnownArrayMethodName ( iterationRef . name )
405
+ ) {
406
+ const call = getCallExpressionFromCalleeExpression (
407
+ iterationRef . node ,
408
+ )
409
+ if ( call ) {
410
+ for ( const cgRef of iterateForArrayMethodOfStringMatchAll (
411
+ call ,
412
+ iterationRef . name ,
413
+ argument ,
414
+ ctx ,
415
+ ) ) {
416
+ useRet = true
417
+ yield cgRef
418
+ if ( cgRef . type === "UnknownRef" ) {
419
+ return
420
+ }
421
+ }
422
+ }
423
+ continue
424
+ }
354
425
if ( Number . isNaN ( Number ( iterationRef . name ) ) ) {
355
426
// Not aimed to iteration.
356
427
continue
357
428
}
358
429
}
359
430
for ( const ref of iterationRef . extractPropertyReferences ( ) ) {
360
- if ( hasNameRef ( ref ) ) {
361
- if ( ref . name === "groups" ) {
362
- for ( const namedRef of ref . extractPropertyReferences ( ) ) {
363
- useRet = true
364
- yield getNamedArrayRef ( namedRef )
365
- }
366
- } else {
367
- if (
368
- ref . name === "input" ||
369
- ref . name === "index" ||
370
- ref . name === "indices"
371
- ) {
372
- continue
373
- }
374
- useRet = true
375
- yield getIndexArrayRef ( ref )
376
- }
377
- } else {
431
+ for ( const cgRef of iterateForRegExpMatchArrayReference ( ref ) ) {
378
432
useRet = true
379
- yield {
380
- type : "UnknownRef" ,
381
- kind : "array" ,
382
- prop : ref ,
433
+ yield cgRef
434
+ if ( cgRef . type === "UnknownRef" ) {
435
+ return
383
436
}
384
- return
385
437
}
386
438
}
387
439
}
@@ -420,28 +472,11 @@ function* iterateForExecResult(
420
472
ctx : ExtractCapturingGroupReferencesContext ,
421
473
) : Iterable < CapturingGroupReference > {
422
474
for ( const ref of extractPropertyReferences ( node , ctx . context ) ) {
423
- if ( hasNameRef ( ref ) ) {
424
- if ( ref . name === "groups" ) {
425
- for ( const namedRef of ref . extractPropertyReferences ( ) ) {
426
- yield getNamedArrayRef ( namedRef )
427
- }
428
- } else {
429
- if (
430
- ref . name === "input" ||
431
- ref . name === "index" ||
432
- ref . name === "indices"
433
- ) {
434
- continue
435
- }
436
- yield getIndexArrayRef ( ref )
437
- }
438
- } else {
439
- yield {
440
- type : "UnknownRef" ,
441
- kind : "array" ,
442
- prop : ref ,
475
+ for ( const cgRef of iterateForRegExpMatchArrayReference ( ref ) ) {
476
+ yield cgRef
477
+ if ( cgRef . type === "UnknownRef" ) {
478
+ return
443
479
}
444
- return
445
480
}
446
481
}
447
482
}
@@ -590,6 +625,75 @@ function* iterateForReplacerFunction(
590
625
}
591
626
}
592
627
628
+ /** Iterate the capturing group references for RegExpMatchArray reference. */
629
+ function * iterateForRegExpMatchArrayReference (
630
+ ref : PropertyReference ,
631
+ ) : Iterable < CapturingGroupReference > {
632
+ if ( hasNameRef ( ref ) ) {
633
+ if ( ref . name === "groups" ) {
634
+ for ( const namedRef of ref . extractPropertyReferences ( ) ) {
635
+ yield getNamedArrayRef ( namedRef )
636
+ }
637
+ } else {
638
+ if (
639
+ ref . name === "input" ||
640
+ ref . name === "index" ||
641
+ ref . name === "indices"
642
+ ) {
643
+ return
644
+ }
645
+ yield getIndexArrayRef ( ref )
646
+ }
647
+ } else {
648
+ yield {
649
+ type : "UnknownRef" ,
650
+ kind : "array" ,
651
+ prop : ref ,
652
+ }
653
+ }
654
+ }
655
+
656
+ /** Iterate the capturing group references for Array method of String.prototype.matchAll(). */
657
+ function * iterateForArrayMethodOfStringMatchAll (
658
+ node : CallExpression ,
659
+ methodsName : ArrayMethodName ,
660
+ argument : Expression ,
661
+ ctx : ExtractCapturingGroupReferencesContext ,
662
+ ) : Iterable < CapturingGroupReference > {
663
+ const arrayMethod = WELL_KNOWN_ARRAY_METHODS [ methodsName ]
664
+ if (
665
+ arrayMethod . elementParameters &&
666
+ node . arguments [ 0 ] &&
667
+ ( node . arguments [ 0 ] . type === "FunctionExpression" ||
668
+ node . arguments [ 0 ] . type === "ArrowFunctionExpression" )
669
+ ) {
670
+ const fnNode = node . arguments [ 0 ]
671
+ for ( const index of arrayMethod . elementParameters ) {
672
+ const param = fnNode . params [ index ]
673
+ if ( param ) {
674
+ for ( const ref of extractPropertyReferencesForPattern (
675
+ param ,
676
+ ctx . context ,
677
+ ) ) {
678
+ yield * iterateForRegExpMatchArrayReference ( ref )
679
+ }
680
+ }
681
+ }
682
+ }
683
+ if ( arrayMethod . result ) {
684
+ if ( arrayMethod . result === "element" ) {
685
+ for ( const ref of extractPropertyReferences ( node , ctx . context ) ) {
686
+ yield * iterateForRegExpMatchArrayReference ( ref )
687
+ }
688
+ } else if (
689
+ arrayMethod . result === "array" ||
690
+ arrayMethod . result === "iterator"
691
+ ) {
692
+ yield * iterateForStringMatchAll ( node , argument , ctx )
693
+ }
694
+ }
695
+ }
696
+
593
697
/** Checks whether the given reference is a named reference. */
594
698
function hasNameRef (
595
699
ref : PropertyReference ,
@@ -638,3 +742,23 @@ function getNamedArrayRef(
638
742
prop : namedRef ,
639
743
}
640
744
}
745
+
746
+ /** Gets the CallExpression from the given callee node. */
747
+ function getCallExpressionFromCalleeExpression (
748
+ expression : Expression ,
749
+ ) : CallExpression | null {
750
+ const parent = getParent ( expression )
751
+ if (
752
+ ! parent ||
753
+ parent . type !== "CallExpression" ||
754
+ parent . callee !== expression
755
+ ) {
756
+ return null
757
+ }
758
+ return parent
759
+ }
760
+
761
+ /** Checks whether the given name is a well known array method name. */
762
+ function isWellKnownArrayMethodName ( name : string ) : name is ArrayMethodName {
763
+ return Boolean ( WELL_KNOWN_ARRAY_METHODS [ name as ArrayMethodName ] )
764
+ }
0 commit comments