@@ -13,6 +13,20 @@ import {
13
13
import type { Rule , AST , SourceCode } from "eslint"
14
14
export * from "./unicode"
15
15
16
+ type RegexpRule = {
17
+ createLiteralVisitor ?: (
18
+ node : ESTree . RegExpLiteral ,
19
+ pattern : string ,
20
+ flags : string ,
21
+ ) => RegExpVisitor . Handlers
22
+ createSourceVisitor ?: (
23
+ node : ESTree . Expression ,
24
+ pattern : string ,
25
+ flags : string ,
26
+ ) => RegExpVisitor . Handlers
27
+ }
28
+ const regexpRules = new WeakMap < ESTree . Program , RegexpRule [ ] > ( )
29
+
16
30
export const FLAG_GLOBAL = "g"
17
31
export const FLAG_DOTALL = "s"
18
32
export const FLAG_IGNORECASE = "i"
@@ -49,46 +63,64 @@ export function createRule(
49
63
export function defineRegexpVisitor (
50
64
context : Rule . RuleContext ,
51
65
rule :
52
- | {
53
- createLiteralVisitor ?: (
54
- node : ESTree . RegExpLiteral ,
55
- pattern : string ,
56
- flags : string ,
57
- ) => RegExpVisitor . Handlers
58
- createSourceVisitor ?: (
59
- node : ESTree . Expression ,
60
- pattern : string ,
61
- flags : string ,
62
- ) => RegExpVisitor . Handlers
63
- }
66
+ | RegexpRule
64
67
| {
65
68
createVisitor ?: (
66
69
node : ESTree . Expression ,
67
70
pattern : string ,
68
71
flags : string ,
69
72
) => RegExpVisitor . Handlers
70
73
} ,
74
+ ) : RuleListener {
75
+ const programNode = context . getSourceCode ( ) . ast
76
+
77
+ let visitor : RuleListener
78
+ let rules = regexpRules . get ( programNode )
79
+ if ( ! rules ) {
80
+ rules = [ ]
81
+ regexpRules . set ( programNode , rules )
82
+ visitor = buildRegexpVisitor ( context , rules , ( ) => {
83
+ regexpRules . delete ( programNode )
84
+ } )
85
+ } else {
86
+ visitor = { }
87
+ }
88
+
89
+ const createLiteralVisitor =
90
+ "createVisitor" in rule
91
+ ? rule . createVisitor
92
+ : "createLiteralVisitor" in rule
93
+ ? rule . createLiteralVisitor
94
+ : undefined
95
+ const createSourceVisitor =
96
+ "createVisitor" in rule
97
+ ? rule . createVisitor
98
+ : "createSourceVisitor" in rule
99
+ ? rule . createSourceVisitor
100
+ : undefined
101
+
102
+ rules . push ( { createLiteralVisitor, createSourceVisitor } )
103
+
104
+ return visitor
105
+ }
106
+
107
+ /** Build RegExp visitor */
108
+ function buildRegexpVisitor (
109
+ context : Rule . RuleContext ,
110
+ rules : RegexpRule [ ] ,
111
+ programExit : ( node : ESTree . Program ) => void ,
71
112
) : RuleListener {
72
113
const parser = new RegExpParser ( )
73
114
74
115
/**
75
116
* Verify a given regular expression.
76
- * @param node The node to report.
77
117
* @param pattern The regular expression pattern to verify.
78
118
* @param flags The flags of the regular expression.
79
119
*/
80
- function verify < T extends ESTree . Expression > (
81
- node : T ,
120
+ function verify (
82
121
pattern : string ,
83
122
flags : string ,
84
- createVisitor : (
85
- // eslint-disable-next-line no-shadow -- ignore
86
- node : T ,
87
- // eslint-disable-next-line no-shadow -- ignore
88
- pattern : string ,
89
- // eslint-disable-next-line no-shadow -- ignore
90
- flags : string ,
91
- ) => RegExpVisitor . Handlers ,
123
+ createVisitors : ( ) => IterableIterator < RegExpVisitor . Handlers > ,
92
124
) {
93
125
let patternNode
94
126
@@ -104,98 +136,102 @@ export function defineRegexpVisitor(
104
136
return
105
137
}
106
138
107
- const visitor = createVisitor ( node , pattern , flags )
108
- if ( Object . keys ( visitor ) . length === 0 ) {
139
+ const handler : RegExpVisitor . Handlers = { }
140
+ for ( const visitor of createVisitors ( ) ) {
141
+ for ( const [ key , fn ] of Object . entries ( visitor ) as [
142
+ keyof RegExpVisitor . Handlers ,
143
+ RegExpVisitor . Handlers [ keyof RegExpVisitor . Handlers ] ,
144
+ ] [ ] ) {
145
+ const orig = handler [ key ]
146
+ if ( orig ) {
147
+ handler [ key ] = (
148
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- ignore
149
+ node : any ,
150
+ ) => {
151
+ orig ( node )
152
+ fn ! ( node )
153
+ }
154
+ } else {
155
+ // @ts -expect-error -- ignore
156
+ handler [ key ] = fn
157
+ }
158
+ }
159
+ }
160
+ if ( Object . keys ( handler ) . length === 0 ) {
109
161
return
110
162
}
111
163
112
- visitRegExpAST ( patternNode , visitor )
164
+ visitRegExpAST ( patternNode , handler )
113
165
}
114
166
115
- const createLiteralVisitor =
116
- "createVisitor" in rule
117
- ? rule . createVisitor
118
- : "createLiteralVisitor" in rule
119
- ? rule . createLiteralVisitor
120
- : null
121
- const createSourceVisitor =
122
- "createVisitor" in rule
123
- ? rule . createVisitor
124
- : "createSourceVisitor" in rule
125
- ? rule . createSourceVisitor
126
- : null
127
-
128
167
return {
129
- ... ( createLiteralVisitor
130
- ? {
131
- "Literal[regex]" ( node : ESTree . RegExpLiteral ) {
132
- verify (
133
- node ,
134
- node . regex . pattern ,
135
- node . regex . flags ,
136
- createLiteralVisitor ,
137
- )
138
- } ,
139
- }
140
- : null ) ,
141
- ... ( createSourceVisitor
142
- ? {
143
- Program ( ) {
144
- const scope = context . getScope ( )
145
- const tracker = new ReferenceTracker ( scope )
168
+ "Program:exit" : programExit ,
169
+ "Literal[regex]" ( node : ESTree . RegExpLiteral ) {
170
+ verify ( node . regex . pattern , node . regex . flags , function * ( ) {
171
+ for ( const rule of rules ) {
172
+ if ( rule . createLiteralVisitor ) {
173
+ yield rule . createLiteralVisitor (
174
+ node ,
175
+ node . regex . pattern ,
176
+ node . regex . flags ,
177
+ )
178
+ }
179
+ }
180
+ } )
181
+ } ,
182
+ Program ( ) {
183
+ const scope = context . getScope ( )
184
+ const tracker = new ReferenceTracker ( scope )
146
185
147
- // Iterate calls of RegExp.
148
- // E.g., `new RegExp()`, `RegExp()`, `new window.RegExp()`,
149
- // `const {RegExp: a} = window; new a()`, etc...
150
- for ( const { node } of tracker . iterateGlobalReferences ( {
151
- RegExp : { [ CALL ] : true , [ CONSTRUCT ] : true } ,
152
- } ) ) {
153
- const newOrCall = node as
154
- | ESTree . NewExpression
155
- | ESTree . CallExpression
156
- const [ patternNode , flagsNode ] = newOrCall . arguments
157
- if (
158
- ! patternNode ||
159
- patternNode . type === "SpreadElement"
160
- ) {
161
- continue
162
- }
163
- const pattern = getStringIfConstant (
164
- patternNode ,
165
- scope ,
166
- )
167
- const flags = getStringIfConstant ( flagsNode , scope )
186
+ // Iterate calls of RegExp.
187
+ // E.g., `new RegExp()`, `RegExp()`, `new window.RegExp()`,
188
+ // `const {RegExp: a} = window; new a()`, etc...
189
+ for ( const { node } of tracker . iterateGlobalReferences ( {
190
+ RegExp : { [ CALL ] : true , [ CONSTRUCT ] : true } ,
191
+ } ) ) {
192
+ const newOrCall = node as
193
+ | ESTree . NewExpression
194
+ | ESTree . CallExpression
195
+ const [ patternNode , flagsNode ] = newOrCall . arguments
196
+ if ( ! patternNode || patternNode . type === "SpreadElement" ) {
197
+ continue
198
+ }
199
+ const pattern = getStringIfConstant ( patternNode , scope )
200
+ const flags = getStringIfConstant ( flagsNode , scope )
168
201
169
- if ( typeof pattern === "string" ) {
170
- let verifyPatternNode = patternNode
171
- if ( patternNode . type === "Identifier" ) {
172
- const variable = findVariable (
173
- context . getScope ( ) ,
174
- patternNode ,
175
- )
176
- if ( variable && variable . defs . length === 1 ) {
177
- const def = variable . defs [ 0 ]
178
- if (
179
- def . type === "Variable" &&
180
- def . parent . kind === "const" &&
181
- def . node . init &&
182
- def . node . init . type === "Literal"
183
- ) {
184
- verifyPatternNode = def . node . init
185
- }
186
- }
187
- }
188
- verify (
189
- verifyPatternNode ,
190
- pattern ,
191
- flags || "" ,
192
- createSourceVisitor ,
193
- )
194
- }
195
- }
196
- } ,
197
- }
198
- : null ) ,
202
+ if ( typeof pattern === "string" ) {
203
+ let verifyPatternNode = patternNode
204
+ if ( patternNode . type === "Identifier" ) {
205
+ const variable = findVariable (
206
+ context . getScope ( ) ,
207
+ patternNode ,
208
+ )
209
+ if ( variable && variable . defs . length === 1 ) {
210
+ const def = variable . defs [ 0 ]
211
+ if (
212
+ def . type === "Variable" &&
213
+ def . parent . kind === "const" &&
214
+ def . node . init &&
215
+ def . node . init . type === "Literal"
216
+ ) {
217
+ verifyPatternNode = def . node . init
218
+ }
219
+ }
220
+ }
221
+ verify ( pattern , flags || "" , function * ( ) {
222
+ for ( const rule of rules ) {
223
+ if ( rule . createSourceVisitor ) {
224
+ yield rule . createSourceVisitor (
225
+ verifyPatternNode ,
226
+ pattern ,
227
+ flags || "" ,
228
+ )
229
+ }
230
+ }
231
+ } )
232
+ }
233
+ }
234
+ } ,
199
235
}
200
236
}
201
237
0 commit comments