1
1
/**
2
- * @fileoverview The event generator for AST nodes .
3
- * @author Toru Nagashima
2
+ * @fileoverview Traverser for SourceCode objects .
3
+ * @author Nicholas C. Zakas
4
4
*/
5
5
6
6
"use strict" ;
10
10
//------------------------------------------------------------------------------
11
11
12
12
const { parse, matches } = require ( "./esquery" ) ;
13
+ const vk = require ( "eslint-visitor-keys" ) ;
13
14
14
15
//-----------------------------------------------------------------------------
15
16
// Typedefs
16
17
//-----------------------------------------------------------------------------
17
18
18
19
/**
19
20
* @import { ESQueryParsedSelector } from "./esquery.js";
21
+ * @import { Language, SourceCode } from "@eslint/core";
20
22
*/
21
23
22
24
//-----------------------------------------------------------------------------
23
25
// Helpers
24
26
//-----------------------------------------------------------------------------
25
27
28
+ const STEP_KIND_VISIT = 1 ;
29
+ const STEP_KIND_CALL = 2 ;
30
+
26
31
/**
27
32
* Compares two ESQuery selectors by specificity.
28
33
* @param {ESQueryParsedSelector } a The first selector to compare.
@@ -33,69 +38,10 @@ function compareSpecificity(a, b) {
33
38
return a . compare ( b ) ;
34
39
}
35
40
36
- //------------------------------------------------------------------------------
37
- // Public Interface
38
- //------------------------------------------------------------------------------
39
-
40
41
/**
41
- * The event generator for AST nodes.
42
- * This implements below interface.
43
- *
44
- * ```ts
45
- * interface EventGenerator {
46
- * emitter: SafeEmitter;
47
- * enterNode(node: ASTNode): void;
48
- * leaveNode(node: ASTNode): void;
49
- * }
50
- * ```
42
+ * Helper to wrap ESQuery operations.
51
43
*/
52
- class NodeEventGenerator {
53
- /**
54
- * The emitter to use during traversal.
55
- * @type {SafeEmitter }
56
- */
57
- emitter ;
58
-
59
- /**
60
- * The options for `esquery` to use during matching.
61
- * @type {ESQueryOptions }
62
- */
63
- esqueryOptions ;
64
-
65
- /**
66
- * The ancestry of the currently visited node.
67
- * @type {ASTNode[] }
68
- */
69
- currentAncestry = [ ] ;
70
-
71
- /**
72
- * A map of node type to selectors targeting that node type on the
73
- * enter phase of traversal.
74
- * @type {Map<string, ESQueryParsedSelector[]> }
75
- */
76
- enterSelectorsByNodeType = new Map ( ) ;
77
-
78
- /**
79
- * A map of node type to selectors targeting that node type on the
80
- * exit phase of traversal.
81
- * @type {Map<string, ESQueryParsedSelector[]> }
82
- */
83
- exitSelectorsByNodeType = new Map ( ) ;
84
-
85
- /**
86
- * An array of selectors that match any node type on the
87
- * enter phase of traversal.
88
- * @type {ESQueryParsedSelector[] }
89
- */
90
- anyTypeEnterSelectors = [ ] ;
91
-
92
- /**
93
- * An array of selectors that match any node type on the
94
- * exit phase of traversal.
95
- * @type {ESQueryParsedSelector[] }
96
- */
97
- anyTypeExitSelectors = [ ] ;
98
-
44
+ class ESQueryHelper {
99
45
/**
100
46
* @param {SafeEmitter } emitter
101
47
* An SafeEmitter which is the destination of events. This emitter must already
@@ -105,9 +51,46 @@ class NodeEventGenerator {
105
51
* @returns {NodeEventGenerator } new instance
106
52
*/
107
53
constructor ( emitter , esqueryOptions ) {
54
+ /**
55
+ * The emitter to use during traversal.
56
+ * @type {SafeEmitter }
57
+ */
108
58
this . emitter = emitter ;
59
+
60
+ /**
61
+ * The options for `esquery` to use during matching.
62
+ * @type {ESQueryOptions }
63
+ */
109
64
this . esqueryOptions = esqueryOptions ;
110
65
66
+ /**
67
+ * A map of node type to selectors targeting that node type on the
68
+ * enter phase of traversal.
69
+ * @type {Map<string, ESQueryParsedSelector[]> }
70
+ */
71
+ this . enterSelectorsByNodeType = new Map ( ) ;
72
+
73
+ /**
74
+ * A map of node type to selectors targeting that node type on the
75
+ * exit phase of traversal.
76
+ * @type {Map<string, ESQueryParsedSelector[]> }
77
+ */
78
+ this . exitSelectorsByNodeType = new Map ( ) ;
79
+
80
+ /**
81
+ * An array of selectors that match any node type on the
82
+ * enter phase of traversal.
83
+ * @type {ESQueryParsedSelector[] }
84
+ */
85
+ this . anyTypeEnterSelectors = [ ] ;
86
+
87
+ /**
88
+ * An array of selectors that match any node type on the
89
+ * exit phase of traversal.
90
+ * @type {ESQueryParsedSelector[] }
91
+ */
92
+ this . anyTypeExitSelectors = [ ] ;
93
+
111
94
emitter . eventNames ( ) . forEach ( rawSelector => {
112
95
const selector = parse ( rawSelector ) ;
113
96
@@ -156,29 +139,24 @@ class NodeEventGenerator {
156
139
/**
157
140
* Checks a selector against a node, and emits it if it matches
158
141
* @param {ASTNode } node The node to check
142
+ * @param {ASTNode[] } ancestry The ancestry of the node being checked.
159
143
* @param {ESQueryParsedSelector } selector An AST selector descriptor
160
144
* @returns {void }
161
145
*/
162
- applySelector ( node , selector ) {
163
- if (
164
- matches (
165
- node ,
166
- selector . root ,
167
- this . currentAncestry ,
168
- this . esqueryOptions ,
169
- )
170
- ) {
146
+ #applySelector( node , ancestry , selector ) {
147
+ if ( matches ( node , selector . root , ancestry , this . esqueryOptions ) ) {
171
148
this . emitter . emit ( selector . source , node ) ;
172
149
}
173
150
}
174
151
175
152
/**
176
153
* Applies all appropriate selectors to a node, in specificity order
177
154
* @param {ASTNode } node The node to check
155
+ * @param {ASTNode[] } ancestry The ancestry of the node being checked.
178
156
* @param {boolean } isExit `false` if the node is currently being entered, `true` if it's currently being exited
179
157
* @returns {void }
180
158
*/
181
- applySelectors ( node , isExit ) {
159
+ applySelectors ( node , ancestry , isExit ) {
182
160
const nodeTypeKey = this . esqueryOptions ?. nodeTypeKey || "type" ;
183
161
184
162
/*
@@ -218,39 +196,117 @@ class NodeEventGenerator {
218
196
selectorsByNodeType [ selectorsByNodeTypeIndex ] ,
219
197
) < 0 )
220
198
) {
221
- this . applySelector (
199
+ this . # applySelector(
222
200
node ,
201
+ ancestry ,
223
202
anyTypeSelectors [ anyTypeSelectorsIndex ++ ] ,
224
203
) ;
225
204
} else {
226
205
// otherwise apply the node type selector
227
- this . applySelector (
206
+ this . # applySelector(
228
207
node ,
208
+ ancestry ,
229
209
selectorsByNodeType [ selectorsByNodeTypeIndex ++ ] ,
230
210
) ;
231
211
}
232
212
}
233
213
}
214
+ }
215
+
216
+ //------------------------------------------------------------------------------
217
+ // Public Interface
218
+ //------------------------------------------------------------------------------
234
219
220
+ /**
221
+ * Traverses source code and ensures that visitor methods are called when
222
+ * entering and leaving each node.
223
+ */
224
+ class SourceCodeTraverser {
235
225
/**
236
- * Emits an event of entering AST node.
237
- * @param {ASTNode } node A node which was entered.
238
- * @returns {void }
226
+ * The language of the source code being traversed.
227
+ * @type {Language }
228
+ */
229
+ #language;
230
+
231
+ /**
232
+ * Map of languages to instances of this class.
233
+ * @type {WeakMap<Language, SourceCodeTraverser> }
234
+ */
235
+ static instances = new WeakMap ( ) ;
236
+
237
+ /**
238
+ * Creates a new instance.
239
+ * @param {Language } language The language of the source code being traversed.
239
240
*/
240
- enterNode ( node ) {
241
- this . applySelectors ( node , false ) ;
242
- this . currentAncestry . unshift ( node ) ;
241
+ constructor ( language ) {
242
+ this . #language = language ;
243
+ }
244
+
245
+ static getInstance ( language ) {
246
+ if ( ! this . instances . has ( language ) ) {
247
+ this . instances . set ( language , new this ( language ) ) ;
248
+ }
249
+
250
+ return this . instances . get ( language ) ;
243
251
}
244
252
245
253
/**
246
- * Emits an event of leaving AST node.
247
- * @param {ASTNode } node A node which was left.
254
+ * Traverses the given source code synchronously.
255
+ * @param {SourceCode } sourceCode The source code to traverse.
256
+ * @param {SafeEmitter } emitter The emitter to use for events.
257
+ * @param {Object } options Options for traversal.
258
+ * @param {ReturnType<SourceCode["traverse"]> } options.steps The steps to take during traversal.
248
259
* @returns {void }
260
+ * @throws {Error } If an error occurs during traversal.
249
261
*/
250
- leaveNode ( node ) {
251
- this . currentAncestry . shift ( ) ;
252
- this . applySelectors ( node , true ) ;
262
+ traverseSync ( sourceCode , emitter , { steps } = { } ) {
263
+ const esquery = new ESQueryHelper ( emitter , {
264
+ visitorKeys : sourceCode . visitorKeys ?? this . #language. visitorKeys ,
265
+ fallback : vk . getKeys ,
266
+ matchClass : this . #language. matchesSelectorClass ?? ( ( ) => false ) ,
267
+ nodeTypeKey : this . #language. nodeTypeKey ,
268
+ } ) ;
269
+
270
+ const currentAncestry = [ ] ;
271
+
272
+ for ( const step of steps ?? sourceCode . traverse ( ) ) {
273
+ switch ( step . kind ) {
274
+ case STEP_KIND_VISIT : {
275
+ try {
276
+ if ( step . phase === 1 ) {
277
+ esquery . applySelectors (
278
+ step . target ,
279
+ currentAncestry ,
280
+ false ,
281
+ ) ;
282
+ currentAncestry . unshift ( step . target ) ;
283
+ } else {
284
+ currentAncestry . shift ( ) ;
285
+ esquery . applySelectors (
286
+ step . target ,
287
+ currentAncestry ,
288
+ true ,
289
+ ) ;
290
+ }
291
+ } catch ( err ) {
292
+ err . currentNode = step . target ;
293
+ throw err ;
294
+ }
295
+ break ;
296
+ }
297
+
298
+ case STEP_KIND_CALL : {
299
+ emitter . emit ( step . target , ...step . args ) ;
300
+ break ;
301
+ }
302
+
303
+ default :
304
+ throw new Error (
305
+ `Invalid traversal step found: "${ step . kind } ".` ,
306
+ ) ;
307
+ }
308
+ }
253
309
}
254
310
}
255
311
256
- module . exports = NodeEventGenerator ;
312
+ module . exports = { SourceCodeTraverser } ;
0 commit comments