1
- import type { RegExpContext } from "../utils"
2
- import { compositingVisitors , createRule , defineRegexpVisitor } from "../utils"
1
+ import type { RegExpContext , RegExpContextForSource } from "../utils"
2
+ import {
3
+ getFlagsRange ,
4
+ compositingVisitors ,
5
+ createRule ,
6
+ defineRegexpVisitor ,
7
+ getFlagsLocation ,
8
+ } from "../utils"
3
9
import type {
4
10
CallExpression ,
5
11
Expression ,
@@ -45,9 +51,11 @@ type RegExpMethodKind =
45
51
| "replaceAll"
46
52
47
53
class RegExpReference {
48
- public regExpContext : RegExpContext
54
+ public readonly regExpContext : RegExpContext
49
55
50
- public readonly defineNode : RegExpExpression
56
+ public get defineNode ( ) : RegExpExpression {
57
+ return this . regExpContext . regexpNode
58
+ }
51
59
52
60
public defineId ?: CodePathId
53
61
@@ -69,12 +77,8 @@ class RegExpReference {
69
77
track : true ,
70
78
}
71
79
72
- public constructor (
73
- regExpContext : RegExpContext ,
74
- defineNode : RegExpExpression ,
75
- ) {
80
+ public constructor ( regExpContext : RegExpContext ) {
76
81
this . regExpContext = regExpContext
77
- this . defineNode = defineNode
78
82
}
79
83
80
84
public addReadNode ( node : Expression ) {
@@ -165,31 +169,6 @@ class RegExpReference {
165
169
}
166
170
}
167
171
168
- /**
169
- * Gets the location for reporting the flag.
170
- */
171
- function getFlagLocation (
172
- context : Rule . RuleContext ,
173
- node : RegExpExpression ,
174
- flag : "i" | "m" | "s" | "g" | "y" ,
175
- ) {
176
- const sourceCode = context . getSourceCode ( )
177
- if ( node . type === "Literal" ) {
178
- const flagIndex =
179
- node . range ! [ 1 ] -
180
- node . regex . flags . length +
181
- node . regex . flags . indexOf ( flag )
182
- return {
183
- start : sourceCode . getLocFromIndex ( flagIndex ) ,
184
- end : sourceCode . getLocFromIndex ( flagIndex + 1 ) ,
185
- }
186
- }
187
- if ( node . arguments . length >= 2 ) {
188
- return node . arguments [ 1 ] . loc !
189
- }
190
- return context . getSourceCode ( ) . getTokenAfter ( node . arguments [ 0 ] ) ! . loc !
191
- }
192
-
193
172
/**
194
173
* Returns a fixer that removes the given flag.
195
174
*/
@@ -209,7 +188,13 @@ function fixRemoveFlag(
209
188
function createUselessIgnoreCaseFlagVisitor ( context : Rule . RuleContext ) {
210
189
return defineRegexpVisitor ( context , {
211
190
createVisitor ( regExpContext : RegExpContext ) {
212
- const { flags, regexpNode, toCharSet, ownsFlags } = regExpContext
191
+ const {
192
+ flags,
193
+ regexpNode,
194
+ toCharSet,
195
+ ownsFlags,
196
+ getFlagLocation,
197
+ } = regExpContext
213
198
214
199
if ( ! flags . ignoreCase || ! ownsFlags ) {
215
200
return { }
@@ -261,7 +246,7 @@ function createUselessIgnoreCaseFlagVisitor(context: Rule.RuleContext) {
261
246
if ( unnecessary ) {
262
247
context . report ( {
263
248
node : regexpNode ,
264
- loc : getFlagLocation ( context , regexpNode , "i" ) ,
249
+ loc : getFlagLocation ( "i" ) ,
265
250
messageId : "uselessIgnoreCaseFlag" ,
266
251
fix : fixRemoveFlag ( regExpContext , "i" ) ,
267
252
} )
@@ -278,7 +263,12 @@ function createUselessIgnoreCaseFlagVisitor(context: Rule.RuleContext) {
278
263
function createUselessMultilineFlagVisitor ( context : Rule . RuleContext ) {
279
264
return defineRegexpVisitor ( context , {
280
265
createVisitor ( regExpContext : RegExpContext ) {
281
- const { flags, regexpNode, ownsFlags } = regExpContext
266
+ const {
267
+ flags,
268
+ regexpNode,
269
+ ownsFlags,
270
+ getFlagLocation,
271
+ } = regExpContext
282
272
283
273
if ( ! flags . multiline || ! ownsFlags ) {
284
274
return { }
@@ -294,7 +284,7 @@ function createUselessMultilineFlagVisitor(context: Rule.RuleContext) {
294
284
if ( unnecessary ) {
295
285
context . report ( {
296
286
node : regexpNode ,
297
- loc : getFlagLocation ( context , regexpNode , "m" ) ,
287
+ loc : getFlagLocation ( "m" ) ,
298
288
messageId : "uselessMultilineFlag" ,
299
289
fix : fixRemoveFlag ( regExpContext , "m" ) ,
300
290
} )
@@ -311,7 +301,12 @@ function createUselessMultilineFlagVisitor(context: Rule.RuleContext) {
311
301
function createUselessDotAllFlagVisitor ( context : Rule . RuleContext ) {
312
302
return defineRegexpVisitor ( context , {
313
303
createVisitor ( regExpContext : RegExpContext ) {
314
- const { flags, regexpNode, ownsFlags } = regExpContext
304
+ const {
305
+ flags,
306
+ regexpNode,
307
+ ownsFlags,
308
+ getFlagLocation,
309
+ } = regExpContext
315
310
316
311
if ( ! flags . dotAll || ! ownsFlags ) {
317
312
return { }
@@ -327,7 +322,7 @@ function createUselessDotAllFlagVisitor(context: Rule.RuleContext) {
327
322
if ( unnecessary ) {
328
323
context . report ( {
329
324
node : regexpNode ,
330
- loc : getFlagLocation ( context , regexpNode , "s" ) ,
325
+ loc : getFlagLocation ( "s" ) ,
331
326
messageId : "uselessDotAllFlag" ,
332
327
fix : fixRemoveFlag ( regExpContext , "s" ) ,
333
328
} )
@@ -367,10 +362,12 @@ function createUselessGlobalFlagVisitor(
367
362
regExpReference : RegExpReference ,
368
363
data : ReportData ,
369
364
) {
365
+ const { getFlagLocation } = regExpReference . regExpContext
370
366
const node = regExpReference . defineNode
367
+
371
368
context . report ( {
372
369
node,
373
- loc : getFlagLocation ( context , node , "g" ) ,
370
+ loc : getFlagLocation ( "g" ) ,
374
371
messageId :
375
372
data . kind === ReportKind . usedOnlyInSplit
376
373
? "uselessGlobalFlagForSplit"
@@ -492,10 +489,12 @@ function createUselessStickyFlagVisitor(
492
489
regExpReference : RegExpReference ,
493
490
data : ReportData ,
494
491
) {
492
+ const { getFlagLocation } = regExpReference . regExpContext
495
493
const node = regExpReference . defineNode
494
+
496
495
context . report ( {
497
496
node,
498
- loc : getFlagLocation ( context , node , "y" ) ,
497
+ loc : getFlagLocation ( "y" ) ,
499
498
messageId : "uselessStickyFlag" ,
500
499
fix : data . fixable
501
500
? fixRemoveFlag ( regExpReference . regExpContext , "y" )
@@ -640,10 +639,7 @@ function createRegExpReferenceExtractVisitor(
640
639
createVisitor ( regExpContext : RegExpContext ) {
641
640
const { flags, regexpNode } = regExpContext
642
641
if ( flags [ flag ] ) {
643
- const regExpReference = new RegExpReference (
644
- regExpContext ,
645
- regexpNode ,
646
- )
642
+ const regExpReference = new RegExpReference ( regExpContext )
647
643
regExpReferenceList . push ( regExpReference )
648
644
regExpReferenceMap . set ( regexpNode , regExpReference )
649
645
for ( const ref of extractExpressionReferences (
@@ -772,6 +768,56 @@ function createRegExpReferenceExtractVisitor(
772
768
)
773
769
}
774
770
771
+ /**
772
+ * Create visitor for verify unnecessary flags of owned RegExp literals
773
+ */
774
+ function createOwnedRegExpFlagsVisitor ( context : Rule . RuleContext ) {
775
+ const sourceCode = context . getSourceCode ( )
776
+
777
+ /** Remove the flags of the given literal */
778
+ function removeFlags ( node : RegExpLiteral ) : void {
779
+ // The u flag is relevant for parsing the literal, so
780
+ // we can't just remove it and potentially create
781
+ // invalid source code.
782
+ const newFlags = node . regex . flags . replace ( / [ ^ u ] + / g, "" )
783
+ if ( newFlags === node . regex . flags ) {
784
+ return
785
+ }
786
+
787
+ context . report ( {
788
+ node,
789
+ loc : getFlagsLocation ( sourceCode , node , node ) ,
790
+ messageId : "uselessFlagsOwned" ,
791
+ fix ( fixer ) {
792
+ const range = getFlagsRange ( node )
793
+ return fixer . replaceTextRange ( range , newFlags )
794
+ } ,
795
+ } )
796
+ }
797
+
798
+ return defineRegexpVisitor ( context , {
799
+ createSourceVisitor ( regExpContext : RegExpContextForSource ) {
800
+ const { patternSource, regexpNode } = regExpContext
801
+
802
+ if ( patternSource . isStringValue ( ) ) {
803
+ // all regexp literals are used via `.source`
804
+ patternSource . getOwnedRegExpLiterals ( ) . forEach ( removeFlags )
805
+ } else {
806
+ // The source is copied from some other regex
807
+ if ( regexpNode . arguments . length >= 2 ) {
808
+ // and the flags are given using the second parameter
809
+ const ownedNode = patternSource . regexpValue ?. ownedNode
810
+ if ( ownedNode ) {
811
+ removeFlags ( ownedNode )
812
+ }
813
+ }
814
+ }
815
+
816
+ return { }
817
+ } ,
818
+ } )
819
+ }
820
+
775
821
/**
776
822
* Parse option
777
823
*/
@@ -844,6 +890,8 @@ export default createRule("no-useless-flag", {
844
890
"The 'g' flag is unnecessary because 'String.prototype.search' ignores the 'g' flag." ,
845
891
uselessStickyFlag :
846
892
"The 'y' flag is unnecessary because 'String.prototype.split' ignores the 'y' flag." ,
893
+ uselessFlagsOwned :
894
+ "The flags of this RegExp literal are useless because only the source of the regex is used." ,
847
895
} ,
848
896
type : "suggestion" , // "problem",
849
897
} ,
@@ -881,6 +929,10 @@ export default createRule("no-useless-flag", {
881
929
createUselessStickyFlagVisitor ( context , strictTypes ) ,
882
930
)
883
931
}
932
+ visitor = compositingVisitors (
933
+ visitor ,
934
+ createOwnedRegExpFlagsVisitor ( context ) ,
935
+ )
884
936
return visitor
885
937
} ,
886
938
} )
0 commit comments