@@ -8,6 +8,9 @@ import type {
8
8
CharacterClassRange ,
9
9
CharacterSet ,
10
10
AnyCharacterSet ,
11
+ UnicodeSetsCharacterClass ,
12
+ ClassStringDisjunction ,
13
+ ExpressionCharacterClass ,
11
14
} from "@eslint-community/regexpp/ast"
12
15
import type { RegExpContext } from "../utils"
13
16
import {
@@ -20,8 +23,9 @@ import {
20
23
import type { CharRange , CharSet } from "refa"
21
24
import { JS } from "refa"
22
25
import type { ReadonlyFlags } from "regexp-ast-analysis"
23
- import { toCharSet } from "regexp-ast-analysis"
26
+ import { toCharSet , toUnicodeSet } from "regexp-ast-analysis"
24
27
import { mentionChar } from "../utils/mention"
28
+ import { assertNever } from "../utils/util"
25
29
26
30
interface Grouping {
27
31
duplicates : {
@@ -30,7 +34,13 @@ interface Grouping {
30
34
} [ ]
31
35
characters : Character [ ]
32
36
characterRanges : CharacterClassRange [ ]
33
- characterSets : ( EscapeCharacterSet | UnicodePropertyCharacterSet ) [ ]
37
+ characterSetAndClasses : (
38
+ | EscapeCharacterSet
39
+ | UnicodePropertyCharacterSet
40
+ | UnicodeSetsCharacterClass
41
+ | ClassStringDisjunction
42
+ | ExpressionCharacterClass
43
+ ) [ ]
34
44
}
35
45
36
46
/**
@@ -44,9 +54,13 @@ function groupElements(
44
54
const duplicates : Grouping [ "duplicates" ] = [ ]
45
55
const characters = new Map < number , Character > ( )
46
56
const characterRanges = new Map < string , CharacterClassRange > ( )
47
- const characterSets = new Map <
57
+ const characterSetAndClasses = new Map <
48
58
string ,
49
- EscapeCharacterSet | UnicodePropertyCharacterSet
59
+ | EscapeCharacterSet
60
+ | UnicodePropertyCharacterSet
61
+ | UnicodeSetsCharacterClass
62
+ | ClassStringDisjunction
63
+ | ExpressionCharacterClass
50
64
> ( )
51
65
52
66
/**
@@ -68,27 +82,32 @@ function groupElements(
68
82
}
69
83
70
84
for ( const e of elements ) {
71
- // FIXME: TS Error
72
- // @ts -expect-error -- FIXME
73
- const charSet = toCharSet ( e , flags )
74
-
75
85
if ( e . type === "Character" ) {
86
+ const charSet = toCharSet ( e , flags )
76
87
const key = charSet . ranges [ 0 ] . min
77
88
addToGroup ( characters , key , e )
78
89
} else if ( e . type === "CharacterClassRange" ) {
90
+ const charSet = toCharSet ( e , flags )
79
91
const key = buildRangeKey ( charSet )
80
92
addToGroup ( characterRanges , key , e )
81
- } else if ( e . type === "CharacterSet" ) {
93
+ } else if (
94
+ e . type === "CharacterSet" ||
95
+ e . type === "CharacterClass" ||
96
+ e . type === "ClassStringDisjunction" ||
97
+ e . type === "ExpressionCharacterClass"
98
+ ) {
82
99
const key = e . raw
83
- addToGroup ( characterSets , key , e )
100
+ addToGroup ( characterSetAndClasses , key , e )
101
+ } else {
102
+ assertNever ( e )
84
103
}
85
104
}
86
105
87
106
return {
88
107
duplicates,
89
108
characters : [ ...characters . values ( ) ] ,
90
109
characterRanges : [ ...characterRanges . values ( ) ] ,
91
- characterSets : [ ...characterSets . values ( ) ] ,
110
+ characterSetAndClasses : [ ...characterSetAndClasses . values ( ) ] ,
92
111
}
93
112
94
113
function buildRangeKey ( rangeCharSet : CharSet ) {
@@ -197,7 +216,10 @@ export default createRule("no-dupe-characters-character-class", {
197
216
subsetElement : CharacterClassElement ,
198
217
element :
199
218
| Exclude < CharacterSet , AnyCharacterSet >
200
- | CharacterClassRange ,
219
+ | CharacterClassRange
220
+ | UnicodeSetsCharacterClass
221
+ | ClassStringDisjunction
222
+ | ExpressionCharacterClass ,
201
223
) {
202
224
const { node, getRegexpLocation } = regexpContext
203
225
@@ -254,9 +276,12 @@ export default createRule("no-dupe-characters-character-class", {
254
276
duplicates,
255
277
characters,
256
278
characterRanges,
257
- characterSets ,
279
+ characterSetAndClasses ,
258
280
} = groupElements ( ccNode . elements , flags )
259
- const rangesAndSets = [ ...characterRanges , ...characterSets ]
281
+ const elementsOtherThanCharacter = [
282
+ ...characterRanges ,
283
+ ...characterSetAndClasses ,
284
+ ]
260
285
261
286
// keep track of all reported subset elements
262
287
const subsets = new Set < CharacterClassElement > ( )
@@ -269,10 +294,10 @@ export default createRule("no-dupe-characters-character-class", {
269
294
270
295
// report characters that are already matched by some range or set
271
296
for ( const char of characters ) {
272
- for ( const other of rangesAndSets ) {
273
- // FIXME: TS Error
274
- // @ts -expect-error -- FIXME
275
- if ( toCharSet ( other , flags ) . has ( char . value ) ) {
297
+ for ( const other of elementsOtherThanCharacter ) {
298
+ if (
299
+ toUnicodeSet ( other , flags ) . chars . has ( char . value )
300
+ ) {
276
301
reportSubset ( regexpContext , char , other )
277
302
subsets . add ( char )
278
303
break
@@ -281,19 +306,15 @@ export default createRule("no-dupe-characters-character-class", {
281
306
}
282
307
283
308
// report character ranges and sets that are already matched by some range or set
284
- for ( const element of rangesAndSets ) {
285
- for ( const other of rangesAndSets ) {
309
+ for ( const element of elementsOtherThanCharacter ) {
310
+ for ( const other of elementsOtherThanCharacter ) {
286
311
if ( element === other || subsets . has ( other ) ) {
287
312
continue
288
313
}
289
314
290
315
if (
291
- // FIXME: TS Error
292
- // @ts -expect-error -- FIXME
293
- toCharSet ( element , flags ) . isSubsetOf (
294
- // FIXME: TS Error
295
- // @ts -expect-error -- FIXME
296
- toCharSet ( other , flags ) ,
316
+ toUnicodeSet ( element , flags ) . isSubsetOf (
317
+ toUnicodeSet ( other , flags ) ,
297
318
)
298
319
) {
299
320
reportSubset ( regexpContext , element , other )
@@ -305,34 +326,28 @@ export default createRule("no-dupe-characters-character-class", {
305
326
306
327
// character ranges and sets might be a subset of a combination of other elements
307
328
// e.g. `b-d` is a subset of `a-cd-f`
308
- const characterTotal = toCharSet (
329
+ const characterTotal = toUnicodeSet (
309
330
characters . filter ( ( c ) => ! subsets . has ( c ) ) ,
310
331
flags ,
311
332
)
312
- for ( const element of rangesAndSets ) {
333
+ for ( const element of elementsOtherThanCharacter ) {
313
334
if ( subsets . has ( element ) ) {
314
335
continue
315
336
}
316
337
317
338
const totalOthers = characterTotal . union (
318
- ...rangesAndSets
339
+ ...elementsOtherThanCharacter
319
340
. filter ( ( e ) => ! subsets . has ( e ) && e !== element )
320
- // FIXME: TS Error
321
- // @ts -expect-error -- FIXME
322
- . map ( ( e ) => toCharSet ( e , flags ) ) ,
341
+ . map ( ( e ) => toUnicodeSet ( e , flags ) ) ,
323
342
)
324
343
325
- // FIXME: TS Error
326
- // @ts -expect-error -- FIXME
327
- const elementCharSet = toCharSet ( element , flags )
344
+ const elementCharSet = toUnicodeSet ( element , flags )
328
345
if ( elementCharSet . isSubsetOf ( totalOthers ) ) {
329
346
const superSetElements = ccNode . elements
330
347
. filter ( ( e ) => ! subsets . has ( e ) && e !== element )
331
348
. filter (
332
349
( e ) =>
333
- // FIXME: TS Error
334
- // @ts -expect-error -- FIXME
335
- ! toCharSet ( e , flags ) . isDisjointWith (
350
+ ! toUnicodeSet ( e , flags ) . isDisjointWith (
336
351
elementCharSet ,
337
352
) ,
338
353
)
@@ -354,18 +369,20 @@ export default createRule("no-dupe-characters-character-class", {
354
369
continue
355
370
}
356
371
357
- for ( let j = i + 1 ; j < rangesAndSets . length ; j ++ ) {
358
- const other = rangesAndSets [ j ]
372
+ for (
373
+ let j = i + 1 ;
374
+ j < elementsOtherThanCharacter . length ;
375
+ j ++
376
+ ) {
377
+ const other = elementsOtherThanCharacter [ j ]
359
378
if ( range === other || subsets . has ( other ) ) {
360
379
continue
361
380
}
362
381
363
- const intersection = toCharSet (
382
+ const intersection = toUnicodeSet (
364
383
range ,
365
384
flags ,
366
- // FIXME: TS Error
367
- // @ts -expect-error -- FIXME
368
- ) . intersect ( toCharSet ( other , flags ) )
385
+ ) . intersect ( toUnicodeSet ( other , flags ) )
369
386
if ( intersection . isEmpty ) {
370
387
continue
371
388
}
@@ -375,7 +392,8 @@ export default createRule("no-dupe-characters-character-class", {
375
392
// character range.
376
393
// there is no point in reporting overlaps that can't be fixed.
377
394
const interestingRanges =
378
- intersection . ranges . filter (
395
+ // Range and string never intersect, so we can only check `chars`.
396
+ intersection . chars . ranges . filter (
379
397
( r ) =>
380
398
inRange ( r , range . min . value ) ||
381
399
inRange ( r , range . max . value ) ,
0 commit comments