oxc-parser 0.90.0 → 0.91.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -4
- package/generated/deserialize/js.mjs +10 -6
- package/generated/deserialize/ts.mjs +10 -6
- package/generated/lazy/constructors.mjs +2 -2
- package/generated/visit/keys.mjs +172 -0
- package/generated/visit/types.mjs +176 -0
- package/generated/visit/visitor.d.ts +383 -0
- package/generated/visit/walk.mjs +2460 -0
- package/package.json +44 -37
- package/{bindings.mjs → src-js/bindings.mjs} +50 -50
- package/{index.d.ts → src-js/index.d.ts} +10 -0
- package/{index.mjs → src-js/index.mjs} +6 -3
- package/{raw-transfer → src-js/raw-transfer}/common.mjs +1 -1
- package/{raw-transfer → src-js/raw-transfer}/eager.mjs +2 -2
- package/{raw-transfer → src-js/raw-transfer}/lazy.mjs +3 -3
- package/{raw-transfer → src-js/raw-transfer}/visitor.mjs +1 -1
- package/src-js/visit/index.mjs +34 -0
- package/src-js/visit/visitor.mjs +402 -0
- package/{wrap.mjs → src-js/wrap.mjs} +0 -3
- /package/{raw-transfer → src-js/raw-transfer}/lazy-common.mjs +0 -0
- /package/{raw-transfer → src-js/raw-transfer}/node-array.mjs +0 -0
- /package/{raw-transfer → src-js/raw-transfer}/supported.mjs +0 -0
- /package/{wasm.mjs → src-js/wasm.mjs} +0 -0
- /package/{webcontainer-fallback.js → src-js/webcontainer-fallback.js} +0 -0
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
// Functions to compile 1 or more visitor objects into a single compiled visitor.
|
|
2
|
+
//
|
|
3
|
+
// # Visitor objects
|
|
4
|
+
//
|
|
5
|
+
// Visitor objects which are generated by rules' `create` functions have keys being either:
|
|
6
|
+
// * Name of an AST type. or
|
|
7
|
+
// * Name of an AST type postfixed with `:exit`.
|
|
8
|
+
//
|
|
9
|
+
// Each property value must be a function that handles that AST node.
|
|
10
|
+
//
|
|
11
|
+
// e.g.:
|
|
12
|
+
//
|
|
13
|
+
// ```
|
|
14
|
+
// {
|
|
15
|
+
// BinaryExpression(node) {
|
|
16
|
+
// // Do stuff on enter
|
|
17
|
+
// },
|
|
18
|
+
// 'BinaryExpression:exit'(node) {
|
|
19
|
+
// // Do stuff on exit
|
|
20
|
+
// },
|
|
21
|
+
// }
|
|
22
|
+
// ```
|
|
23
|
+
//
|
|
24
|
+
// # Compiled visitor
|
|
25
|
+
//
|
|
26
|
+
// Compiled visitor is an array with `NODE_TYPES_COUNT` length, keyed by the ID of the node type.
|
|
27
|
+
// `NODE_TYPE_IDS_MAP` maps from type name to ID.
|
|
28
|
+
//
|
|
29
|
+
// Each element of compiled array is one of:
|
|
30
|
+
// * No visitor for this type = `null`.
|
|
31
|
+
// * Visitor for leaf node = visit function.
|
|
32
|
+
// * Visitor for non-leaf node = object of form `{ enter, exit }`,
|
|
33
|
+
// where each property is either a visitor function or `null`.
|
|
34
|
+
//
|
|
35
|
+
// e.g.:
|
|
36
|
+
//
|
|
37
|
+
// ```
|
|
38
|
+
// [
|
|
39
|
+
// // Leaf nodes
|
|
40
|
+
// function(node) { /* do stuff */ },
|
|
41
|
+
// // ...
|
|
42
|
+
//
|
|
43
|
+
// // Non-leaf nodes
|
|
44
|
+
// {
|
|
45
|
+
// enter: function(node) { /* do stuff */ },
|
|
46
|
+
// exit: null,
|
|
47
|
+
// },
|
|
48
|
+
// // ...
|
|
49
|
+
// ]
|
|
50
|
+
// ```
|
|
51
|
+
//
|
|
52
|
+
// # Object reuse
|
|
53
|
+
//
|
|
54
|
+
// No more than 1 compiled visitor exists at any time, so we reuse a single array `compiledVisitor`,
|
|
55
|
+
// rather than creating a new array for each file being linted.
|
|
56
|
+
//
|
|
57
|
+
// To compile visitors, call:
|
|
58
|
+
// * `initCompiledVisitor` once.
|
|
59
|
+
// * `addVisitorToCompiled` with each visitor object.
|
|
60
|
+
// * `finalizeCompiledVisitor` once.
|
|
61
|
+
//
|
|
62
|
+
// After this sequence of calls, `compiledVisitor` is ready to be used to walk the AST.
|
|
63
|
+
//
|
|
64
|
+
// We also recycle:
|
|
65
|
+
//
|
|
66
|
+
// * `{ enter, exit }` objects which are stored in compiled visitor.
|
|
67
|
+
// * Temporary arrays used to store multiple visit functions, which are merged into a single function
|
|
68
|
+
// in `finalizeCompiledVisitor`.
|
|
69
|
+
//
|
|
70
|
+
// The aim is to reduce pressure on the garbage collector. All these recycled objects are long-lived
|
|
71
|
+
// and will graduate to "old space", which leaves as much capacity as possible in "new space"
|
|
72
|
+
// for objects created by user code in visitors. If ephemeral user-created objects all fit in new space,
|
|
73
|
+
// it will avoid full GC runs, which should greatly improve performance.
|
|
74
|
+
|
|
75
|
+
// TODO(camc314): we need to generate `.d.ts` file for this module.
|
|
76
|
+
// @ts-expect-error
|
|
77
|
+
import { LEAF_NODE_TYPES_COUNT, NODE_TYPE_IDS_MAP, NODE_TYPES_COUNT } from '../../generated/visit/types.mjs';
|
|
78
|
+
|
|
79
|
+
const { isArray } = Array;
|
|
80
|
+
|
|
81
|
+
// Compiled visitor used for visiting each file.
|
|
82
|
+
// Same array is reused for each file.
|
|
83
|
+
//
|
|
84
|
+
// Initialized with `.push()` to ensure V8 treats the array as "packed" (linear array),
|
|
85
|
+
// not "holey" (hash map). This is critical, as looking up elements in this array is a very hot path
|
|
86
|
+
// during AST visitation, and holey arrays are much slower.
|
|
87
|
+
// https://v8.dev/blog/elements-kinds
|
|
88
|
+
let compiledVisitor;
|
|
89
|
+
|
|
90
|
+
export function createCompiledVisitor() {
|
|
91
|
+
// Create a new compiled visitor array
|
|
92
|
+
compiledVisitor = [];
|
|
93
|
+
for (let i = NODE_TYPES_COUNT; i !== 0; i--) {
|
|
94
|
+
compiledVisitor.push(null);
|
|
95
|
+
}
|
|
96
|
+
return compiledVisitor;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Arrays containing type IDs of types which have multiple visit functions defined for them.
|
|
100
|
+
//
|
|
101
|
+
// Filled with `0` initially up to maximum size they could ever need to be so:
|
|
102
|
+
// 1. These arrays never need to grow.
|
|
103
|
+
// 2. V8 treats these arrays as "PACKED_SMI_ELEMENTS".
|
|
104
|
+
const mergedLeafVisitorTypeIds = [],
|
|
105
|
+
mergedEnterVisitorTypeIds = [],
|
|
106
|
+
mergedExitVisitorTypeIds = [];
|
|
107
|
+
|
|
108
|
+
for (let i = LEAF_NODE_TYPES_COUNT; i !== 0; i--) {
|
|
109
|
+
mergedLeafVisitorTypeIds.push(0);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
for (let i = NODE_TYPES_COUNT - LEAF_NODE_TYPES_COUNT; i !== 0; i--) {
|
|
113
|
+
mergedEnterVisitorTypeIds.push(0);
|
|
114
|
+
mergedExitVisitorTypeIds.push(0);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
mergedLeafVisitorTypeIds.length = 0;
|
|
118
|
+
mergedEnterVisitorTypeIds.length = 0;
|
|
119
|
+
mergedExitVisitorTypeIds.length = 0;
|
|
120
|
+
|
|
121
|
+
// `true` if `addVisitor` has been called with a visitor which visits at least one AST type
|
|
122
|
+
let hasActiveVisitors = false;
|
|
123
|
+
|
|
124
|
+
// Enter+exit object cache.
|
|
125
|
+
//
|
|
126
|
+
// `compiledVisitor` may contain many `{ enter, exit }` objects.
|
|
127
|
+
// Use this cache to reuse those objects across all visitor compilations.
|
|
128
|
+
//
|
|
129
|
+
// `enterExitObjectCacheNextIndex` is the index of first object in cache which is currently unused.
|
|
130
|
+
// It may point to the end of the cache array.
|
|
131
|
+
const enterExitObjectCache = [];
|
|
132
|
+
let enterExitObjectCacheNextIndex = 0;
|
|
133
|
+
|
|
134
|
+
function getEnterExitObject() {
|
|
135
|
+
if (enterExitObjectCacheNextIndex < enterExitObjectCache.length) {
|
|
136
|
+
return enterExitObjectCache[enterExitObjectCacheNextIndex++];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const enterExit = { enter: null, exit: null };
|
|
140
|
+
enterExitObjectCache.push(enterExit);
|
|
141
|
+
enterExitObjectCacheNextIndex++;
|
|
142
|
+
return enterExit;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Visit function arrays cache.
|
|
146
|
+
//
|
|
147
|
+
// During compilation, many arrays may be used temporarily to store multiple visit functions for same AST type.
|
|
148
|
+
// The functions in each array are merged into a single function in `finalizeCompiledVisitor`,
|
|
149
|
+
// after which these arrays aren't used again.
|
|
150
|
+
//
|
|
151
|
+
// Use this cache to reuse these arrays across each visitor compilation.
|
|
152
|
+
//
|
|
153
|
+
// `visitFnArrayCacheNextIndex` is the index of first array in cache which is currently unused.
|
|
154
|
+
// It may point to the end of the cache array.
|
|
155
|
+
const visitFnArrayCache = [];
|
|
156
|
+
let visitFnArrayCacheNextIndex = 0;
|
|
157
|
+
|
|
158
|
+
function createVisitFnArray(visit1, visit2) {
|
|
159
|
+
if (visitFnArrayCacheNextIndex < visitFnArrayCache.length) {
|
|
160
|
+
const arr = visitFnArrayCache[visitFnArrayCacheNextIndex++];
|
|
161
|
+
arr.push(visit1, visit2);
|
|
162
|
+
return arr;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const arr = [visit1, visit2];
|
|
166
|
+
visitFnArrayCache.push(arr);
|
|
167
|
+
visitFnArrayCacheNextIndex++;
|
|
168
|
+
return arr;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Initialize compiled visitor, ready for calls to `addVisitor`.
|
|
173
|
+
*/
|
|
174
|
+
export function initCompiledVisitor() {
|
|
175
|
+
// Reset `compiledVisitor` array after previous compilation
|
|
176
|
+
for (let i = 0; i < NODE_TYPES_COUNT; i++) {
|
|
177
|
+
compiledVisitor[i] = null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Reset enter+exit objects which were used in previous compilation
|
|
181
|
+
for (let i = 0; i < enterExitObjectCacheNextIndex; i++) {
|
|
182
|
+
const enterExit = enterExitObjectCache[i];
|
|
183
|
+
enterExit.enter = null;
|
|
184
|
+
enterExit.exit = null;
|
|
185
|
+
}
|
|
186
|
+
enterExitObjectCacheNextIndex = 0;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Add a visitor to compiled visitor.
|
|
191
|
+
*
|
|
192
|
+
* @param visitor - Visitor object
|
|
193
|
+
*/
|
|
194
|
+
export function addVisitorToCompiled(visitor) {
|
|
195
|
+
if (visitor === null || typeof visitor !== 'object') throw new TypeError('Visitor must be an object');
|
|
196
|
+
|
|
197
|
+
// Exit if is empty visitor
|
|
198
|
+
const keys = Object.keys(visitor),
|
|
199
|
+
keysLen = keys.length;
|
|
200
|
+
if (keysLen === 0) return;
|
|
201
|
+
|
|
202
|
+
hasActiveVisitors = true;
|
|
203
|
+
|
|
204
|
+
// Populate visitors array from provided object
|
|
205
|
+
for (let i = 0; i < keysLen; i++) {
|
|
206
|
+
let name = keys[i];
|
|
207
|
+
|
|
208
|
+
const visitFn = visitor[name];
|
|
209
|
+
if (typeof visitFn !== 'function') {
|
|
210
|
+
throw new TypeError(`'${name}' property of visitor object is not a function`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const isExit = name.endsWith(':exit');
|
|
214
|
+
if (isExit) name = name.slice(0, -5);
|
|
215
|
+
|
|
216
|
+
const typeId = NODE_TYPE_IDS_MAP.get(name);
|
|
217
|
+
if (typeId === void 0) throw new Error(`Unknown node type '${name}' in visitor object`);
|
|
218
|
+
|
|
219
|
+
const existing = compiledVisitor[typeId];
|
|
220
|
+
if (typeId < LEAF_NODE_TYPES_COUNT) {
|
|
221
|
+
// Leaf node - store just 1 function, not enter+exit pair
|
|
222
|
+
if (existing === null) {
|
|
223
|
+
compiledVisitor[typeId] = visitFn;
|
|
224
|
+
} else if (isArray(existing)) {
|
|
225
|
+
if (isExit) {
|
|
226
|
+
existing.push(visitFn);
|
|
227
|
+
} else {
|
|
228
|
+
// Insert before last in array in case last was enter visit function from the current rule,
|
|
229
|
+
// to ensure enter is called before exit.
|
|
230
|
+
// It could also be either an enter or exit visitor function for another rule, but the order
|
|
231
|
+
// rules are called in doesn't matter. We only need to make sure that a rule's exit visitor
|
|
232
|
+
// isn't called before enter visitor *for that same rule*.
|
|
233
|
+
existing.splice(existing.length - 1, 0, visitFn);
|
|
234
|
+
}
|
|
235
|
+
} else {
|
|
236
|
+
// Same as above, enter visitor is put to front of list to make sure enter is called before exit
|
|
237
|
+
compiledVisitor[typeId] = isExit
|
|
238
|
+
? createVisitFnArray(existing, visitFn)
|
|
239
|
+
: createVisitFnArray(visitFn, existing);
|
|
240
|
+
mergedLeafVisitorTypeIds.push(typeId);
|
|
241
|
+
}
|
|
242
|
+
} else {
|
|
243
|
+
// Not leaf node - store enter+exit pair
|
|
244
|
+
if (existing === null) {
|
|
245
|
+
const enterExit = compiledVisitor[typeId] = getEnterExitObject();
|
|
246
|
+
if (isExit) {
|
|
247
|
+
enterExit.exit = visitFn;
|
|
248
|
+
} else {
|
|
249
|
+
enterExit.enter = visitFn;
|
|
250
|
+
}
|
|
251
|
+
} else if (isExit) {
|
|
252
|
+
const { exit } = existing;
|
|
253
|
+
if (exit === null) {
|
|
254
|
+
existing.exit = visitFn;
|
|
255
|
+
} else if (isArray(exit)) {
|
|
256
|
+
exit.push(visitFn);
|
|
257
|
+
} else {
|
|
258
|
+
existing.exit = createVisitFnArray(exit, visitFn);
|
|
259
|
+
mergedExitVisitorTypeIds.push(typeId);
|
|
260
|
+
}
|
|
261
|
+
} else {
|
|
262
|
+
const { enter } = existing;
|
|
263
|
+
if (enter === null) {
|
|
264
|
+
existing.enter = visitFn;
|
|
265
|
+
} else if (isArray(enter)) {
|
|
266
|
+
enter.push(visitFn);
|
|
267
|
+
} else {
|
|
268
|
+
existing.enter = createVisitFnArray(enter, visitFn);
|
|
269
|
+
mergedEnterVisitorTypeIds.push(typeId);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Finalize compiled visitor.
|
|
278
|
+
*
|
|
279
|
+
* After calling this function, `compiledVisitor` is ready to be used to walk the AST.
|
|
280
|
+
*
|
|
281
|
+
* @returns {boolean} - `true` if compiled visitor visits at least 1 AST type
|
|
282
|
+
*/
|
|
283
|
+
export function finalizeCompiledVisitor() {
|
|
284
|
+
if (hasActiveVisitors === false) return false;
|
|
285
|
+
|
|
286
|
+
// Merge visit functions for node types which have multiple visitors from different rules,
|
|
287
|
+
// or enter+exit functions for leaf nodes
|
|
288
|
+
for (let i = mergedLeafVisitorTypeIds.length - 1; i >= 0; i--) {
|
|
289
|
+
const typeId = mergedLeafVisitorTypeIds[i];
|
|
290
|
+
compiledVisitor[typeId] = mergeVisitFns(compiledVisitor[typeId]);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
for (let i = mergedEnterVisitorTypeIds.length - 1; i >= 0; i--) {
|
|
294
|
+
const typeId = mergedEnterVisitorTypeIds[i];
|
|
295
|
+
const enterExit = compiledVisitor[typeId];
|
|
296
|
+
enterExit.enter = mergeVisitFns(enterExit.enter);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
for (let i = mergedExitVisitorTypeIds.length - 1; i >= 0; i--) {
|
|
300
|
+
const typeId = mergedExitVisitorTypeIds[i];
|
|
301
|
+
const enterExit = compiledVisitor[typeId];
|
|
302
|
+
enterExit.exit = mergeVisitFns(enterExit.exit);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Reset state, ready for next time
|
|
306
|
+
mergedLeafVisitorTypeIds.length = 0;
|
|
307
|
+
mergedEnterVisitorTypeIds.length = 0;
|
|
308
|
+
mergedExitVisitorTypeIds.length = 0;
|
|
309
|
+
|
|
310
|
+
// Note: Visit function arrays have been emptied in `mergeVisitFns`, so all arrays in `visitFnArrayCache`
|
|
311
|
+
// are now empty and ready for reuse. We just need to reset the index.
|
|
312
|
+
visitFnArrayCacheNextIndex = 0;
|
|
313
|
+
|
|
314
|
+
hasActiveVisitors = false;
|
|
315
|
+
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Merge array of visit functions into a single function, which calls each of input functions in turn.
|
|
321
|
+
*
|
|
322
|
+
* The array passed is cleared (length set to 0), so the array can be reused.
|
|
323
|
+
*
|
|
324
|
+
* The merged function is statically defined and does not contain a loop, to hopefully allow
|
|
325
|
+
* JS engine to heavily optimize it.
|
|
326
|
+
*
|
|
327
|
+
* `mergers` contains pre-defined functions to merge up to 5 visit functions.
|
|
328
|
+
* Merger functions for merging more than 5 visit functions are created dynamically on demand.
|
|
329
|
+
*
|
|
330
|
+
* @param visitFns - Array of visit functions
|
|
331
|
+
* @returns Function which calls all of `visitFns` in turn.
|
|
332
|
+
*/
|
|
333
|
+
function mergeVisitFns(visitFns) {
|
|
334
|
+
const numVisitFns = visitFns.length;
|
|
335
|
+
|
|
336
|
+
// Get or create merger for merging `numVisitFns` functions
|
|
337
|
+
let merger;
|
|
338
|
+
if (mergers.length <= numVisitFns) {
|
|
339
|
+
while (mergers.length < numVisitFns) {
|
|
340
|
+
mergers.push(null);
|
|
341
|
+
}
|
|
342
|
+
merger = createMerger(numVisitFns);
|
|
343
|
+
mergers.push(merger);
|
|
344
|
+
} else {
|
|
345
|
+
merger = mergers[numVisitFns];
|
|
346
|
+
if (merger === null) merger = mergers[numVisitFns] = createMerger(numVisitFns);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Merge functions
|
|
350
|
+
const mergedFn = merger(...visitFns);
|
|
351
|
+
|
|
352
|
+
// Empty `visitFns` array, so it can be reused
|
|
353
|
+
visitFns.length = 0;
|
|
354
|
+
|
|
355
|
+
return mergedFn;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Create a merger function that merges `fnCount` functions.
|
|
360
|
+
*
|
|
361
|
+
* @param fnCount - Number of functions to be merged
|
|
362
|
+
* @returns Function to merge `fnCount` functions
|
|
363
|
+
*/
|
|
364
|
+
function createMerger(fnCount) {
|
|
365
|
+
const args = [];
|
|
366
|
+
let body = 'return node=>{';
|
|
367
|
+
for (let i = 1; i <= fnCount; i++) {
|
|
368
|
+
args.push(`visit${i}`);
|
|
369
|
+
body += `visit${i}(node);`;
|
|
370
|
+
}
|
|
371
|
+
body += '}';
|
|
372
|
+
args.push(body);
|
|
373
|
+
return new Function(...args);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Pre-defined mergers for merging up to 5 functions
|
|
377
|
+
const mergers = [
|
|
378
|
+
null, // No merger for 0 functions
|
|
379
|
+
null, // No merger for 1 function
|
|
380
|
+
(visit1, visit2) => (node) => {
|
|
381
|
+
visit1(node);
|
|
382
|
+
visit2(node);
|
|
383
|
+
},
|
|
384
|
+
(visit1, visit2, visit3) => (node) => {
|
|
385
|
+
visit1(node);
|
|
386
|
+
visit2(node);
|
|
387
|
+
visit3(node);
|
|
388
|
+
},
|
|
389
|
+
(visit1, visit2, visit3, visit4) => (node) => {
|
|
390
|
+
visit1(node);
|
|
391
|
+
visit2(node);
|
|
392
|
+
visit3(node);
|
|
393
|
+
visit4(node);
|
|
394
|
+
},
|
|
395
|
+
(visit1, visit2, visit3, visit4, visit5) => (node) => {
|
|
396
|
+
visit1(node);
|
|
397
|
+
visit2(node);
|
|
398
|
+
visit3(node);
|
|
399
|
+
visit4(node);
|
|
400
|
+
visit5(node);
|
|
401
|
+
},
|
|
402
|
+
];
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|