1
1
import type { Expression } from "estree"
2
2
import type { RegExpVisitor } from "regexpp/visitor"
3
3
import type { Rule } from "eslint"
4
- import type {
5
- CharacterClass ,
6
- EscapeCharacterSet ,
7
- UnicodePropertyCharacterSet ,
8
- Node as RegExpNode ,
9
- CharacterClassRange ,
10
- } from "regexpp/ast"
4
+ import type { CharacterClass , Node as RegExpNode } from "regexpp/ast"
11
5
import {
12
6
createRule ,
13
7
defineRegexpVisitor ,
14
- FLAG_DOTALL ,
15
8
getRegexpLocation ,
16
9
getRegexpRange ,
17
10
fixerApplyEscape ,
11
+ parseFlags ,
18
12
} from "../utils"
13
+ import type { ReadonlyFlags } from "regexp-ast-analysis"
14
+ import { matchesAllCharacters } from "regexp-ast-analysis"
19
15
20
16
const OPTION_SS1 = "[\\s\\S]" as const
21
17
const OPTION_SS2 = "[\\S\\s]" as const
22
18
const OPTION_CARET = "[^]" as const
23
19
const OPTION_DOTALL = "dotAll" as const
24
-
25
- /**
26
- * Get the key of the given CharacterSet node
27
- * @param node
28
- */
29
- function getCharacterSetKey (
30
- node : EscapeCharacterSet | UnicodePropertyCharacterSet ,
31
- ) {
32
- if ( node . kind === "property" ) {
33
- return `\\p{${
34
- node . kind +
35
- ( node . value == null ? node . key : `${ node . key } =${ node . value } ` )
36
- } }`
37
- }
38
-
39
- return node . kind
40
- }
20
+ type Allowed =
21
+ | typeof OPTION_SS1
22
+ | typeof OPTION_SS2
23
+ | typeof OPTION_CARET
24
+ | typeof OPTION_DOTALL
41
25
42
26
export default createRule ( "match-any" , {
43
27
meta : {
@@ -75,18 +59,13 @@ export default createRule("match-any", {
75
59
} ,
76
60
create ( context ) {
77
61
const sourceCode = context . getSourceCode ( )
78
- const allowList : (
79
- | "[\\s\\S]"
80
- | "[\\S\\s]"
81
- | "[^]"
82
- | "dotAll"
83
- ) [ ] = context . options [ 0 ] ?. allows ?? [ OPTION_SS1 , OPTION_DOTALL ]
62
+ const allowList : Allowed [ ] = context . options [ 0 ] ?. allows ?? [
63
+ OPTION_SS1 ,
64
+ OPTION_DOTALL ,
65
+ ]
66
+ const allows = new Set < string > ( allowList )
84
67
85
- const allows : Set < "[\\s\\S]" | "[\\S\\s]" | "[^]" | "dotAll" > = new Set (
86
- allowList ,
87
- )
88
-
89
- const prefer = ( allowList [ 0 ] !== OPTION_DOTALL && allowList [ 0 ] ) || null
68
+ const preference : Allowed | null = allowList [ 0 ] || null
90
69
91
70
/**
92
71
* Fix source code
@@ -96,8 +75,13 @@ export default createRule("match-any", {
96
75
fixer : Rule . RuleFixer ,
97
76
node : Expression ,
98
77
regexpNode : RegExpNode ,
78
+ flags : ReadonlyFlags ,
99
79
) {
100
- if ( ! prefer ) {
80
+ if ( ! preference ) {
81
+ return null
82
+ }
83
+ if ( preference === OPTION_DOTALL && ! flags . dotAll ) {
84
+ // since we can't just add flags, we cannot fix this
101
85
return null
102
86
}
103
87
const range = getRegexpRange ( sourceCode , node , regexpNode )
@@ -107,16 +91,20 @@ export default createRule("match-any", {
107
91
108
92
if (
109
93
regexpNode . type === "CharacterClass" &&
110
- prefer . startsWith ( "[" ) &&
111
- prefer . endsWith ( "]" )
94
+ preference . startsWith ( "[" ) &&
95
+ preference . endsWith ( "]" )
112
96
) {
113
97
return fixer . replaceTextRange (
114
98
[ range [ 0 ] + 1 , range [ 1 ] - 1 ] ,
115
- fixerApplyEscape ( prefer . slice ( 1 , - 1 ) , node ) ,
99
+ fixerApplyEscape ( preference . slice ( 1 , - 1 ) , node ) ,
116
100
)
117
101
}
118
102
119
- return fixer . replaceTextRange ( range , fixerApplyEscape ( prefer , node ) )
103
+ const replacement = preference === OPTION_DOTALL ? "." : preference
104
+ return fixer . replaceTextRange (
105
+ range ,
106
+ fixerApplyEscape ( replacement , node ) ,
107
+ )
120
108
}
121
109
122
110
/**
@@ -126,150 +114,48 @@ export default createRule("match-any", {
126
114
function createVisitor (
127
115
node : Expression ,
128
116
_pattern : string ,
129
- flags : string ,
117
+ flagsStr : string ,
130
118
) : RegExpVisitor . Handlers {
131
- let characterClassData : {
132
- node : CharacterClass
133
- charSets : Map <
134
- string ,
135
- EscapeCharacterSet | UnicodePropertyCharacterSet
136
- >
137
- reported : boolean
138
- } | null = null
119
+ const flags = parseFlags ( flagsStr )
120
+
139
121
return {
140
122
onCharacterSetEnter ( csNode ) {
141
- if ( csNode . kind === "any" ) {
142
- if ( flags . includes ( FLAG_DOTALL ) ) {
143
- if ( ! allows . has ( OPTION_DOTALL ) ) {
144
- context . report ( {
145
- node,
146
- loc : getRegexpLocation (
147
- sourceCode ,
148
- node ,
149
- csNode ,
150
- ) ,
151
- messageId : "unexpected" ,
152
- data : {
153
- expr : "." ,
154
- } ,
155
- fix ( fixer ) {
156
- return fix ( fixer , node , csNode )
157
- } ,
158
- } )
159
- }
160
- }
161
- return
162
- }
163
123
if (
164
- characterClassData &&
165
- ! characterClassData . reported &&
166
- ! characterClassData . node . negate
124
+ csNode . kind === "any" &&
125
+ flags . dotAll &&
126
+ ! allows . has ( OPTION_DOTALL )
167
127
) {
168
- const key = getCharacterSetKey ( csNode )
169
- const alreadyCharSet = characterClassData . charSets . get (
170
- key ,
171
- )
172
- if (
173
- alreadyCharSet != null &&
174
- alreadyCharSet . negate === ! csNode . negate
175
- ) {
176
- const ccNode = characterClassData . node
177
- if (
178
- ! ccNode . negate &&
179
- ccNode . elements . length === 2 &&
180
- alreadyCharSet . kind === "space"
181
- ) {
182
- if ( ! alreadyCharSet . negate ) {
183
- if ( allows . has ( OPTION_SS1 ) ) {
184
- return
185
- }
186
- } else {
187
- if ( allows . has ( OPTION_SS2 ) ) {
188
- return
189
- }
190
- }
191
- }
192
- context . report ( {
193
- node,
194
- loc : getRegexpLocation (
195
- sourceCode ,
196
- node ,
197
- ccNode ,
198
- ) ,
199
- messageId : "unexpected" ,
200
- data : {
201
- expr : ccNode . raw ,
202
- } ,
203
- fix ( fixer ) {
204
- return fix ( fixer , node , ccNode )
205
- } ,
206
- } )
207
- characterClassData . reported = true
208
- } else {
209
- characterClassData . charSets . set ( key , csNode )
210
- }
128
+ context . report ( {
129
+ node,
130
+ loc : getRegexpLocation ( sourceCode , node , csNode ) ,
131
+ messageId : "unexpected" ,
132
+ data : {
133
+ expr : "." ,
134
+ } ,
135
+ fix ( fixer ) {
136
+ return fix ( fixer , node , csNode , flags )
137
+ } ,
138
+ } )
211
139
}
212
140
} ,
213
- onCharacterClassRangeEnter ( ccrNode : CharacterClassRange ) {
141
+ onCharacterClassEnter ( ccNode : CharacterClass ) {
214
142
if (
215
- ccrNode . min . value === 0 &&
216
- ccrNode . max . value === 65535
143
+ matchesAllCharacters ( ccNode , flags ) &&
144
+ ! allows . has ( ccNode . raw as never )
217
145
) {
218
- if (
219
- characterClassData &&
220
- ! characterClassData . reported &&
221
- ! characterClassData . node . negate
222
- ) {
223
- const ccNode = characterClassData . node
224
- context . report ( {
225
- node,
226
- loc : getRegexpLocation (
227
- sourceCode ,
228
- node ,
229
- ccNode ,
230
- ) ,
231
- messageId : "unexpected" ,
232
- data : {
233
- expr : ccNode . raw ,
234
- } ,
235
- fix ( fixer ) {
236
- return fix ( fixer , node , ccNode )
237
- } ,
238
- } )
239
- characterClassData . reported = true
240
- }
241
- }
242
- } ,
243
- onCharacterClassEnter ( ccNode : CharacterClass ) {
244
- if ( ccNode . elements . length === 0 ) {
245
- if ( ccNode . negate && ! allows . has ( OPTION_CARET ) ) {
246
- context . report ( {
247
- node,
248
- loc : getRegexpLocation (
249
- sourceCode ,
250
- node ,
251
- ccNode ,
252
- ) ,
253
- messageId : "unexpected" ,
254
- data : {
255
- expr : "[^]" ,
256
- } ,
257
- fix ( fixer ) {
258
- return fix ( fixer , node , ccNode )
259
- } ,
260
- } )
261
- }
262
- return
263
- }
264
- characterClassData = {
265
- node : ccNode ,
266
- charSets : new Map ( ) ,
267
- reported : false ,
146
+ context . report ( {
147
+ node,
148
+ loc : getRegexpLocation ( sourceCode , node , ccNode ) ,
149
+ messageId : "unexpected" ,
150
+ data : {
151
+ expr : ccNode . raw ,
152
+ } ,
153
+ fix ( fixer ) {
154
+ return fix ( fixer , node , ccNode , flags )
155
+ } ,
156
+ } )
268
157
}
269
158
} ,
270
- onCharacterClassLeave ( ) {
271
- characterClassData = null
272
- } ,
273
159
}
274
160
}
275
161
0 commit comments