@@ -7,6 +7,7 @@ import type {
7
7
EscapeCharacterSet ,
8
8
UnicodePropertyCharacterSet ,
9
9
CharacterClassRange ,
10
+ CharacterSet ,
10
11
} from "regexpp/ast"
11
12
import {
12
13
createRule ,
@@ -15,14 +16,16 @@ import {
15
16
CP_LOW_LINE ,
16
17
CP_RANGE_DIGIT ,
17
18
CP_RANGE_SPACES ,
18
- CPS_SINGLE_SPACES ,
19
+ CPS_SINGLE_SPACES as CPS_SINGLE_SPACES_SET ,
19
20
CP_RANGES_WORDS ,
20
21
isDigit ,
21
22
isSpace ,
22
23
isWord ,
23
24
invisibleEscape ,
24
25
} from "../utils"
25
26
27
+ const CPS_SINGLE_SPACES = [ ...CPS_SINGLE_SPACES_SET ]
28
+
26
29
/**
27
30
* Checks if the given character is within the character class range.
28
31
* @param char The character to check.
@@ -102,10 +105,13 @@ function getCharacterClassRangesIntersection(
102
105
function getCharacterClassRangeAndCharacterSetIntersections (
103
106
range : CharacterClassRange ,
104
107
set : EscapeCharacterSet | UnicodePropertyCharacterSet ,
105
- ) {
108
+ ) : {
109
+ intersections : ( number | [ number , number ] ) [ ]
110
+ set ?: EscapeCharacterSet | UnicodePropertyCharacterSet
111
+ } {
106
112
if ( set . negate ) {
107
113
// It does not check negate character set.
108
- return [ ]
114
+ return { intersections : [ ] }
109
115
}
110
116
const codePointRange = [ range . min . value , range . max . value ] as const
111
117
@@ -120,6 +126,22 @@ function getCharacterClassRangeAndCharacterSetIntersections(
120
126
)
121
127
}
122
128
129
+ /**
130
+ * Checks if the given code point is in code point range.
131
+ */
132
+ function isCodePointInRange ( codePoint : number ) {
133
+ return codePointRange [ 0 ] <= codePoint && codePoint <= codePointRange [ 1 ]
134
+ }
135
+
136
+ /**
137
+ * Checks if the given code point range is in code point range.
138
+ */
139
+ function isRangeInRange ( cpRange : readonly [ number , number ] ) {
140
+ return (
141
+ codePointRange [ 0 ] <= cpRange [ 0 ] && cpRange [ 1 ] <= codePointRange [ 1 ]
142
+ )
143
+ }
144
+
123
145
/**
124
146
* Gets the intersections that do not separate the range.
125
147
* @param otherRange the range to check
@@ -146,33 +168,59 @@ function getCharacterClassRangeAndCharacterSetIntersections(
146
168
codePointRange ,
147
169
CP_RANGE_DIGIT ,
148
170
)
149
- return intersection ? [ intersection ] : [ ]
171
+ return { intersections : intersection ? [ intersection ] : [ ] }
150
172
}
151
173
if ( set . kind === "space" ) {
174
+ if (
175
+ isRangeInRange ( CP_RANGE_SPACES ) &&
176
+ CPS_SINGLE_SPACES . every ( ( codePoint ) =>
177
+ isCodePointInRange ( codePoint ) ,
178
+ )
179
+ ) {
180
+ // EscapeCharacterSet in range
181
+ return {
182
+ intersections : [ ] ,
183
+ set,
184
+ }
185
+ }
152
186
const result : number [ ] = [ ]
153
187
for ( const codePoint of CPS_SINGLE_SPACES ) {
154
188
if ( isCodePointIsRangeEdge ( codePoint ) ) {
155
189
result . push ( codePoint )
156
190
}
157
191
}
158
192
const intersection = getIntersectionAndNotSeparate ( CP_RANGE_SPACES )
159
- return intersection ? [ ...result , intersection ] : result
193
+ return {
194
+ intersections : intersection ? [ ...result , intersection ] : result ,
195
+ }
160
196
}
161
197
if ( set . kind === "word" ) {
198
+ if (
199
+ isCodePointInRange ( CP_LOW_LINE ) &&
200
+ CP_RANGES_WORDS . every ( ( r ) => isRangeInRange ( r ) )
201
+ ) {
202
+ // EscapeCharacterSet in range
203
+ return {
204
+ intersections : [ ] ,
205
+ set,
206
+ }
207
+ }
162
208
const intersections : [ number , number ] [ ] = [ ]
163
209
for ( const wordRange of CP_RANGES_WORDS ) {
164
210
const intersection = getIntersectionAndNotSeparate ( wordRange )
165
211
if ( intersection ) {
166
212
intersections . push ( intersection )
167
213
}
168
214
}
169
- return isCodePointIsRangeEdge ( CP_LOW_LINE )
170
- ? [ ...intersections , CP_LOW_LINE ]
171
- : intersections
215
+ return {
216
+ intersections : isCodePointIsRangeEdge ( CP_LOW_LINE )
217
+ ? [ ...intersections , CP_LOW_LINE ]
218
+ : intersections ,
219
+ }
172
220
}
173
221
174
222
// It does not check Unicode properties.
175
- return [ ]
223
+ return { intersections : [ ] }
176
224
}
177
225
178
226
/**
@@ -216,9 +264,9 @@ function groupingElements(elements: CharacterClassElement[]) {
216
264
}
217
265
218
266
return {
219
- characters,
220
- characterClassRanges,
221
- characterSets,
267
+ characters : [ ... characters . values ( ) ] ,
268
+ characterClassRanges : [ ... characterClassRanges . values ( ) ] ,
269
+ characterSets : [ ... characterSets . values ( ) ] ,
222
270
}
223
271
224
272
/**
@@ -259,6 +307,7 @@ export default createRule("no-dupe-characters-character-class", {
259
307
messages : {
260
308
duplicates : "Unexpected element '{{element}}' duplication." ,
261
309
charIsIncluded : "The '{{char}}' is included in '{{element}}'." ,
310
+ charSetIsInRange : "The '{{charSet}}' is included in '{{range}}'." ,
262
311
intersect :
263
312
"Unexpected intersection of '{{elementA}}' and '{{elementB}}' was found '{{intersection}}'." ,
264
313
} ,
@@ -346,6 +395,25 @@ export default createRule("no-dupe-characters-character-class", {
346
395
}
347
396
}
348
397
398
+ /**
399
+ * Report the character set included in the range.
400
+ */
401
+ function reportCharSetInRange (
402
+ node : Expression ,
403
+ set : CharacterSet ,
404
+ range : CharacterClassRange ,
405
+ ) {
406
+ context . report ( {
407
+ node,
408
+ loc : getRegexpLocation ( sourceCode , node , set ) ,
409
+ messageId : "charSetIsInRange" ,
410
+ data : {
411
+ charSet : set . raw ,
412
+ range : range . raw ,
413
+ } ,
414
+ } )
415
+ }
416
+
349
417
/**
350
418
* Create visitor
351
419
* @param node
@@ -359,12 +427,12 @@ export default createRule("no-dupe-characters-character-class", {
359
427
characterSets,
360
428
} = groupingElements ( ccNode . elements )
361
429
362
- for ( const [ char , ...dupeChars ] of characters . values ( ) ) {
430
+ for ( const [ char , ...dupeChars ] of characters ) {
363
431
if ( dupeChars . length ) {
364
432
reportDuplicates ( node , [ char , ...dupeChars ] )
365
433
}
366
434
367
- for ( const [ range ] of characterClassRanges . values ( ) ) {
435
+ for ( const [ range ] of characterClassRanges ) {
368
436
if ( isCharacterInCharacterClassRange ( char , range ) ) {
369
437
reportCharIncluded (
370
438
node ,
@@ -373,7 +441,7 @@ export default createRule("no-dupe-characters-character-class", {
373
441
)
374
442
}
375
443
}
376
- for ( const [ set ] of characterSets . values ( ) ) {
444
+ for ( const [ set ] of characterSets ) {
377
445
if ( isCharacterInCharacterSet ( char , set ) ) {
378
446
reportCharIncluded (
379
447
node ,
@@ -384,21 +452,14 @@ export default createRule("no-dupe-characters-character-class", {
384
452
}
385
453
}
386
454
387
- for ( const [
388
- key ,
389
- [ range , ...dupeRanges ] ,
390
- ] of characterClassRanges ) {
455
+ for ( const [ range , ...dupeRanges ] of characterClassRanges ) {
391
456
if ( dupeRanges . length ) {
392
457
reportDuplicates ( node , [ range , ...dupeRanges ] )
393
458
}
394
459
395
- for ( const [
396
- keyOther ,
397
- [ rangeOther ] ,
398
- ] of characterClassRanges ) {
399
- if ( keyOther === key ) {
400
- continue
401
- }
460
+ for ( const [ rangeOther ] of characterClassRanges . filter (
461
+ ( [ ccr ] ) => ccr !== range ,
462
+ ) ) {
402
463
const intersection = getCharacterClassRangesIntersection (
403
464
range ,
404
465
rangeOther ,
@@ -413,11 +474,17 @@ export default createRule("no-dupe-characters-character-class", {
413
474
}
414
475
}
415
476
416
- for ( const [ set ] of characterSets . values ( ) ) {
417
- const intersections = getCharacterClassRangeAndCharacterSetIntersections (
477
+ for ( const [ set ] of characterSets ) {
478
+ const {
479
+ set : reportSet ,
480
+ intersections,
481
+ } = getCharacterClassRangeAndCharacterSetIntersections (
418
482
range ,
419
483
set ,
420
484
)
485
+ if ( reportSet ) {
486
+ reportCharSetInRange ( node , reportSet , range )
487
+ }
421
488
for ( const intersection of intersections ) {
422
489
reportIntersect (
423
490
node ,
@@ -428,7 +495,7 @@ export default createRule("no-dupe-characters-character-class", {
428
495
}
429
496
}
430
497
}
431
- for ( const [ set , ...dupeSets ] of characterSets . values ( ) ) {
498
+ for ( const [ set , ...dupeSets ] of characterSets ) {
432
499
if ( dupeSets . length ) {
433
500
reportDuplicates ( node , [ set , ...dupeSets ] )
434
501
}
0 commit comments