goatlint-parser 0.125.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 +167 -0
- package/package.json +129 -0
- package/src-js/bindings.js +601 -0
- package/src-js/generated/constants.js +95 -0
- package/src-js/generated/deserialize/js.js +5839 -0
- package/src-js/generated/deserialize/js_range.js +6380 -0
- package/src-js/generated/deserialize/ts.js +6131 -0
- package/src-js/generated/deserialize/ts_range.js +6700 -0
- package/src-js/generated/lazy/constructors.js +13864 -0
- package/src-js/generated/lazy/type_ids.js +191 -0
- package/src-js/generated/lazy/walk.js +5802 -0
- package/src-js/generated/visit/keys.js +220 -0
- package/src-js/generated/visit/type_ids.js +177 -0
- package/src-js/generated/visit/visitor.d.ts +387 -0
- package/src-js/generated/visit/walk.js +2455 -0
- package/src-js/index.d.ts +312 -0
- package/src-js/index.js +108 -0
- package/src-js/raw-transfer/common.js +276 -0
- package/src-js/raw-transfer/eager.js +254 -0
- package/src-js/raw-transfer/lazy-common.js +11 -0
- package/src-js/raw-transfer/lazy.js +153 -0
- package/src-js/raw-transfer/node-array.js +365 -0
- package/src-js/raw-transfer/supported.js +52 -0
- package/src-js/raw-transfer/visitor.js +127 -0
- package/src-js/visit/index.js +41 -0
- package/src-js/visit/visitor.js +405 -0
- package/src-js/wasm.js +11 -0
- package/src-js/webcontainer-fallback.cjs +21 -0
- package/src-js/wrap.js +57 -0
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
import { constructorError, TOKEN } from "./lazy-common.js";
|
|
2
|
+
|
|
3
|
+
// Internal symbol to get `NodeArray` from a proxy wrapping a `NodeArray`.
|
|
4
|
+
//
|
|
5
|
+
// Methods of `NodeArray` are called with `this` being the proxy, rather than the `NodeArray` itself.
|
|
6
|
+
// They can "unwrap" the proxy by getting `this[ARRAY]`, and the `get` proxy trap will return
|
|
7
|
+
// the actual `NodeArray`.
|
|
8
|
+
//
|
|
9
|
+
// This symbol is not exported, and it is not actually defined on `NodeArray`s, so user cannot obtain it
|
|
10
|
+
// via `Object.getOwnPropertySymbols` or `Reflect.ownKeys`. Therefore user code cannot unwrap the proxy.
|
|
11
|
+
const ARRAY = Symbol();
|
|
12
|
+
|
|
13
|
+
// Functions to get internal properties of a `NodeArray`. Initialized in class static block below.
|
|
14
|
+
let getInternalFromProxy, getLength, getElement;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* An array of AST nodes where elements are deserialized lazily upon access.
|
|
18
|
+
*
|
|
19
|
+
* Extends `Array` to make `Array.isArray` return `true` for a `NodeArray`.
|
|
20
|
+
*
|
|
21
|
+
* TODO: Other methods could maybe be more optimal, avoiding going via proxy multiple times
|
|
22
|
+
* e.g. `some`, `indexOf`.
|
|
23
|
+
*/
|
|
24
|
+
export class NodeArray extends Array {
|
|
25
|
+
#internal;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Create a `NodeArray`.
|
|
29
|
+
*
|
|
30
|
+
* Constructor does not actually return a `NodeArray`, but one wrapped in a `Proxy`.
|
|
31
|
+
* The proxy intercepts accesses to elements and lazily deserializes them,
|
|
32
|
+
* and blocks mutation of elements or `length` property.
|
|
33
|
+
*
|
|
34
|
+
* @class
|
|
35
|
+
* @param {number} pos - Buffer position of first element
|
|
36
|
+
* @param {number} length - Number of elements
|
|
37
|
+
* @param {number} stride - Element size in bytes
|
|
38
|
+
* @param {Function} construct - Function to deserialize element
|
|
39
|
+
* @param {Object} ast - AST object
|
|
40
|
+
* @returns {Proxy<NodeArray>} - `NodeArray` wrapped in a `Proxy`
|
|
41
|
+
*/
|
|
42
|
+
constructor(pos, length, stride, construct, ast) {
|
|
43
|
+
if (ast?.token !== TOKEN) constructorError();
|
|
44
|
+
|
|
45
|
+
super();
|
|
46
|
+
this.#internal = { pos, length, ast, stride, construct };
|
|
47
|
+
return new Proxy(this, PROXY_HANDLERS);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Allow `arr.filter`, `arr.map` etc.
|
|
51
|
+
static [Symbol.species] = Array;
|
|
52
|
+
|
|
53
|
+
// Override `values` method with a more efficient one that avoids going via proxy for every iteration.
|
|
54
|
+
// TODO: Benchmark to check that this is actually faster.
|
|
55
|
+
values() {
|
|
56
|
+
return new NodeArrayValuesIterator(this);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Override `keys` method with a more efficient one that avoids going via proxy for every iteration.
|
|
60
|
+
// TODO: Benchmark to check that this is actually faster.
|
|
61
|
+
keys() {
|
|
62
|
+
return new NodeArrayKeysIterator(this);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Override `entries` method with a more efficient one that avoids going via proxy for every iteration.
|
|
66
|
+
// TODO: Benchmark to check that this is actually faster.
|
|
67
|
+
entries() {
|
|
68
|
+
return new NodeArrayEntriesIterator(this);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// This method is overwritten with reference to `values` method below.
|
|
72
|
+
// Defining dummy method here to prevent the later assignment altering the shape of class prototype.
|
|
73
|
+
[Symbol.iterator]() {}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Override `slice` method to return a `NodeArray`.
|
|
77
|
+
*
|
|
78
|
+
* @this {NodeArray}
|
|
79
|
+
* @param {*} start - Start of slice
|
|
80
|
+
* @param {*} end - End of slice
|
|
81
|
+
* @returns {NodeArray} - `NodeArray` containing slice of this one
|
|
82
|
+
*/
|
|
83
|
+
slice(start, end) {
|
|
84
|
+
const internal = this[ARRAY].#internal,
|
|
85
|
+
{ length } = internal;
|
|
86
|
+
|
|
87
|
+
start = toInt(start);
|
|
88
|
+
if (start < 0) {
|
|
89
|
+
start = length + start;
|
|
90
|
+
if (start < 0) start = 0;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (end === void 0) {
|
|
94
|
+
end = length;
|
|
95
|
+
} else {
|
|
96
|
+
end = toInt(end);
|
|
97
|
+
if (end < 0) {
|
|
98
|
+
end += length;
|
|
99
|
+
if (end < 0) end = 0;
|
|
100
|
+
} else if (end > length) {
|
|
101
|
+
end = length;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
let sliceLength = end - start;
|
|
106
|
+
if (sliceLength <= 0 || start >= length) {
|
|
107
|
+
start = 0;
|
|
108
|
+
sliceLength = 0;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const { stride } = internal;
|
|
112
|
+
return new NodeArray(
|
|
113
|
+
internal.pos + start * stride,
|
|
114
|
+
sliceLength,
|
|
115
|
+
stride,
|
|
116
|
+
internal.construct,
|
|
117
|
+
internal.ast,
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Make `console.log` deserialize all elements.
|
|
122
|
+
[Symbol.for("nodejs.util.inspect.custom")]() {
|
|
123
|
+
const values = [...this.values()];
|
|
124
|
+
Object.setPrototypeOf(values, DebugNodeArray.prototype);
|
|
125
|
+
return values;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
static {
|
|
129
|
+
/**
|
|
130
|
+
* Get internal properties of `NodeArray`, given a proxy wrapping a `NodeArray`.
|
|
131
|
+
* @param {Proxy} proxy - Proxy wrapping `NodeArray` object
|
|
132
|
+
* @returns {Object} - Internal properties object
|
|
133
|
+
*/
|
|
134
|
+
getInternalFromProxy = (proxy) => proxy[ARRAY].#internal;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Get length of `NodeArray`.
|
|
138
|
+
* @param {NodeArray} arr - `NodeArray` object
|
|
139
|
+
* @returns {number} - Array length
|
|
140
|
+
*/
|
|
141
|
+
getLength = (arr) => arr.#internal.length;
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Get element of `NodeArray` at index `index`.
|
|
145
|
+
*
|
|
146
|
+
* @param {NodeArray} arr - `NodeArray` object
|
|
147
|
+
* @param {number} index - Index of element to get
|
|
148
|
+
* @returns {*} - Element at index `index`, or `undefined` if out of bounds
|
|
149
|
+
*/
|
|
150
|
+
getElement = (arr, index) => {
|
|
151
|
+
const internal = arr.#internal;
|
|
152
|
+
if (index >= internal.length) return void 0;
|
|
153
|
+
return (0, internal.construct)(internal.pos + index * internal.stride, internal.ast);
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// goatlint-disable-next-line typescript/unbound-method
|
|
159
|
+
NodeArray.prototype[Symbol.iterator] = NodeArray.prototype.values;
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Iterator over values of a `NodeArray`.
|
|
163
|
+
* Returned by `values` method, and also used as iterator for `for (const node of nodeArray) {}`.
|
|
164
|
+
*/
|
|
165
|
+
class NodeArrayValuesIterator {
|
|
166
|
+
#internal;
|
|
167
|
+
|
|
168
|
+
constructor(proxy) {
|
|
169
|
+
const internal = getInternalFromProxy(proxy),
|
|
170
|
+
{ pos, stride } = internal;
|
|
171
|
+
|
|
172
|
+
this.#internal = {
|
|
173
|
+
pos,
|
|
174
|
+
endPos: pos + internal.length * stride,
|
|
175
|
+
ast: internal.ast,
|
|
176
|
+
construct: internal.construct,
|
|
177
|
+
stride,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
next() {
|
|
182
|
+
const internal = this.#internal,
|
|
183
|
+
{ pos } = internal;
|
|
184
|
+
if (pos === internal.endPos) return { done: true, value: null };
|
|
185
|
+
internal.pos = pos + internal.stride;
|
|
186
|
+
return { done: false, value: (0, internal.construct)(pos, internal.ast) };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
[Symbol.iterator]() {
|
|
190
|
+
return this;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Iterator over keys of a `NodeArray`. Returned by `keys` method.
|
|
196
|
+
*/
|
|
197
|
+
class NodeArrayKeysIterator {
|
|
198
|
+
#internal;
|
|
199
|
+
|
|
200
|
+
constructor(proxy) {
|
|
201
|
+
const internal = getInternalFromProxy(proxy);
|
|
202
|
+
this.#internal = { index: 0, length: internal.length };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
next() {
|
|
206
|
+
const internal = this.#internal,
|
|
207
|
+
{ index } = internal;
|
|
208
|
+
if (index === internal.length) return { done: true, value: null };
|
|
209
|
+
internal.index = index + 1;
|
|
210
|
+
return { done: false, value: index };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
[Symbol.iterator]() {
|
|
214
|
+
return this;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Iterator over values of a `NodeArray`. Returned by `entries` method.
|
|
220
|
+
*/
|
|
221
|
+
class NodeArrayEntriesIterator {
|
|
222
|
+
#internal;
|
|
223
|
+
|
|
224
|
+
constructor(proxy) {
|
|
225
|
+
const internal = getInternalFromProxy(proxy);
|
|
226
|
+
|
|
227
|
+
this.#internal = {
|
|
228
|
+
index: 0,
|
|
229
|
+
length: internal.length,
|
|
230
|
+
pos: internal.pos,
|
|
231
|
+
ast: internal.ast,
|
|
232
|
+
construct: internal.construct,
|
|
233
|
+
stride: internal.stride,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
next() {
|
|
238
|
+
const internal = this.#internal,
|
|
239
|
+
{ index } = internal;
|
|
240
|
+
if (index === internal.length) return { done: true, value: null };
|
|
241
|
+
internal.index = index + 1;
|
|
242
|
+
return {
|
|
243
|
+
done: false,
|
|
244
|
+
value: [index, (0, internal.construct)(internal.pos + index * internal.stride, internal.ast)],
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
[Symbol.iterator]() {
|
|
249
|
+
return this;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Class used for `[Symbol.for('nodejs.util.inspect.custom')]` method (for `console.log`).
|
|
254
|
+
const DebugNodeArray = class NodeArray extends Array {};
|
|
255
|
+
|
|
256
|
+
// Proxy handlers.
|
|
257
|
+
//
|
|
258
|
+
// Every `NodeArray` returned to user is wrapped in a `Proxy`, using these handlers.
|
|
259
|
+
// They lazily deserialize array elements upon access, and block mutation of array elements / `length`.
|
|
260
|
+
const PROXY_HANDLERS = {
|
|
261
|
+
// Return `true` for indexes which are in bounds.
|
|
262
|
+
// e.g. `'0' in arr`.
|
|
263
|
+
has(arr, key) {
|
|
264
|
+
const index = toIndex(key);
|
|
265
|
+
if (index !== null) return index < getLength(arr);
|
|
266
|
+
return Reflect.has(arr, key);
|
|
267
|
+
},
|
|
268
|
+
|
|
269
|
+
// Get elements and length.
|
|
270
|
+
get(arr, key) {
|
|
271
|
+
// Methods of `NodeArray` are called with `this` being the proxy, rather than the `NodeArray` itself.
|
|
272
|
+
// They can "unwrap" the proxy by getting `this[ARRAY]`.
|
|
273
|
+
if (key === ARRAY) return arr;
|
|
274
|
+
if (key === "length") return getLength(arr);
|
|
275
|
+
const index = toIndex(key);
|
|
276
|
+
if (index !== null) return getElement(arr, index);
|
|
277
|
+
|
|
278
|
+
return Reflect.get(arr, key);
|
|
279
|
+
},
|
|
280
|
+
|
|
281
|
+
// Get descriptors for elements and length.
|
|
282
|
+
getOwnPropertyDescriptor(arr, key) {
|
|
283
|
+
if (key === "length") {
|
|
284
|
+
// Cannot return `writable: false` unfortunately
|
|
285
|
+
return { value: getLength(arr), writable: true, enumerable: false, configurable: false };
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const index = toIndex(key);
|
|
289
|
+
if (index !== null) {
|
|
290
|
+
const value = getElement(arr, index);
|
|
291
|
+
if (value === void 0) return void 0;
|
|
292
|
+
// Cannot return `configurable: false` unfortunately
|
|
293
|
+
return { value, writable: false, enumerable: true, configurable: true };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return Reflect.getOwnPropertyDescriptor(arr, key);
|
|
297
|
+
},
|
|
298
|
+
|
|
299
|
+
// Prevent setting `length` or entries.
|
|
300
|
+
// Catches:
|
|
301
|
+
// * `Object.defineProperty(arr, 0, {value: null})`.
|
|
302
|
+
// * `arr[1] = null`.
|
|
303
|
+
// * `arr.length = 0`.
|
|
304
|
+
// * `Object.defineProperty(arr, 'length', {value: 0})`.
|
|
305
|
+
// * Other operations which mutate entries e.g. `arr.push(123)`.
|
|
306
|
+
defineProperty(arr, key, descriptor) {
|
|
307
|
+
if (key === "length" || toIndex(key) !== null) return false;
|
|
308
|
+
return Reflect.defineProperty(arr, key, descriptor);
|
|
309
|
+
},
|
|
310
|
+
|
|
311
|
+
// Prevent deleting entries.
|
|
312
|
+
deleteProperty(arr, key) {
|
|
313
|
+
// Note: `Reflect.deleteProperty(arr, 'length')` already returns `false`
|
|
314
|
+
if (toIndex(key) !== null) return false;
|
|
315
|
+
return Reflect.deleteProperty(arr, key);
|
|
316
|
+
},
|
|
317
|
+
|
|
318
|
+
// Get keys, including element indexes.
|
|
319
|
+
ownKeys(arr) {
|
|
320
|
+
const keys = [],
|
|
321
|
+
length = getLength(arr);
|
|
322
|
+
for (let i = 0; i < length; i++) {
|
|
323
|
+
keys.push(i + "");
|
|
324
|
+
}
|
|
325
|
+
keys.push(...Reflect.ownKeys(arr));
|
|
326
|
+
return keys;
|
|
327
|
+
},
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Convert key to array index, if it is a valid array index.
|
|
332
|
+
*
|
|
333
|
+
* Only strings comprising a plain integer are valid indexes.
|
|
334
|
+
* e.g. `"-1"`, `"01"`, `"0xFF"`, `"1e1"`, `"1 "` are not valid indexes.
|
|
335
|
+
* Integers >= 4294967295 are not valid indexes.
|
|
336
|
+
*
|
|
337
|
+
* @param {string|Symbol} - Key used for property lookup.
|
|
338
|
+
* @returns {number|null} - `key` converted to integer, if it's a valid array index, otherwise `null`.
|
|
339
|
+
*/
|
|
340
|
+
function toIndex(key) {
|
|
341
|
+
if (typeof key === "string") {
|
|
342
|
+
if (key === "0") return 0;
|
|
343
|
+
if (INDEX_REGEX.test(key)) {
|
|
344
|
+
const index = +key;
|
|
345
|
+
if (index < 4294967295) return index;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return null;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const INDEX_REGEX = /^[1-9]\d*$/;
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Convert value to integer.
|
|
355
|
+
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number#integer_conversion
|
|
356
|
+
*
|
|
357
|
+
* @param {*} value - Value to convert to integer.
|
|
358
|
+
* @returns {number} - Integer
|
|
359
|
+
*/
|
|
360
|
+
function toInt(value) {
|
|
361
|
+
value = Math.trunc(+value);
|
|
362
|
+
// `value === 0` check is to convert -0 to 0
|
|
363
|
+
if (value === 0 || Number.isNaN(value)) return 0;
|
|
364
|
+
return value;
|
|
365
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { rawTransferSupported as rawTransferSupportedBinding } from "../bindings.js";
|
|
2
|
+
|
|
3
|
+
let rawTransferIsSupported = null;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Returns `true` if `experimentalRawTransfer` is option is supported.
|
|
7
|
+
*
|
|
8
|
+
* Raw transfer is only supported on 64-bit little-endian systems,
|
|
9
|
+
* and NodeJS >= v22.0.0 or Deno >= v2.0.0.
|
|
10
|
+
*
|
|
11
|
+
* Versions of NodeJS prior to v22.0.0 do not support creating an `ArrayBuffer` larger than 4 GiB.
|
|
12
|
+
* Bun (as at v1.2.4) also does not support creating an `ArrayBuffer` larger than 4 GiB.
|
|
13
|
+
* Support on Deno v1 is unknown and it's EOL, so treating Deno before v2.0.0 as unsupported.
|
|
14
|
+
*
|
|
15
|
+
* No easy way to determining pointer width (64 bit or 32 bit) in JS,
|
|
16
|
+
* so call a function on Rust side to find out.
|
|
17
|
+
*
|
|
18
|
+
* @returns {boolean} - `true` if raw transfer is supported on this platform
|
|
19
|
+
*/
|
|
20
|
+
export function rawTransferSupported() {
|
|
21
|
+
if (rawTransferIsSupported === null) {
|
|
22
|
+
rawTransferIsSupported = rawTransferRuntimeSupported() && rawTransferSupportedBinding();
|
|
23
|
+
}
|
|
24
|
+
return rawTransferIsSupported;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Checks copied from:
|
|
28
|
+
// https://github.com/unjs/std-env/blob/ab15595debec9e9115a9c1d31bc7597a8e71dbfd/src/runtimes.ts
|
|
29
|
+
// MIT license: https://github.com/unjs/std-env/blob/ab15595debec9e9115a9c1d31bc7597a8e71dbfd/LICENCE
|
|
30
|
+
function rawTransferRuntimeSupported() {
|
|
31
|
+
let global;
|
|
32
|
+
try {
|
|
33
|
+
global = globalThis;
|
|
34
|
+
} catch {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const isBun = !!global.Bun || !!global.process?.versions?.bun;
|
|
39
|
+
if (isBun) return false;
|
|
40
|
+
|
|
41
|
+
const isDeno = !!global.Deno;
|
|
42
|
+
if (isDeno) {
|
|
43
|
+
const match = Deno.version?.deno?.match(/^(\d+)\./);
|
|
44
|
+
return !!match && match[1] * 1 >= 2;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const isNode = global.process?.release?.name === "node";
|
|
48
|
+
if (!isNode) return false;
|
|
49
|
+
|
|
50
|
+
const match = process.version?.match(/^v(\d+)\./);
|
|
51
|
+
return !!match && match[1] * 1 >= 22;
|
|
52
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LEAF_NODE_TYPES_COUNT,
|
|
3
|
+
NODE_TYPE_IDS_MAP,
|
|
4
|
+
NODE_TYPES_COUNT,
|
|
5
|
+
} from "../generated/lazy/type_ids.js";
|
|
6
|
+
|
|
7
|
+
// Getter for private `#visitorsArr` property of `Visitor` class. Initialized in class body below.
|
|
8
|
+
let getVisitorsArrTemp;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Visitor class, used to visit an AST.
|
|
12
|
+
*/
|
|
13
|
+
export class Visitor {
|
|
14
|
+
#visitorsArr;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Create `Visitor`.
|
|
18
|
+
*
|
|
19
|
+
* Provide an object where keys are names of AST nodes you want to visit,
|
|
20
|
+
* and values are visitor functions which receive AST node objects of that type.
|
|
21
|
+
*
|
|
22
|
+
* Keys can also be postfixed with `:exit` to visit when exiting the node, rather than entering.
|
|
23
|
+
*
|
|
24
|
+
* ```js
|
|
25
|
+
* const visitor = new Visitor({
|
|
26
|
+
* BinaryExpression(binExpr) {
|
|
27
|
+
* // Do stuff when entering a `BinaryExpression`
|
|
28
|
+
* },
|
|
29
|
+
* 'BinaryExpression:exit'(binExpr) {
|
|
30
|
+
* // Do stuff when exiting a `BinaryExpression`
|
|
31
|
+
* },
|
|
32
|
+
* });
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* @class
|
|
36
|
+
* @param {Object} visitor - Object defining visit functions for AST nodes
|
|
37
|
+
* @returns {Visitor}
|
|
38
|
+
*/
|
|
39
|
+
constructor(visitor) {
|
|
40
|
+
this.#visitorsArr = createVisitorsArr(visitor);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
static {
|
|
44
|
+
getVisitorsArrTemp = (visitor) => visitor.#visitorsArr;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const getVisitorsArr = getVisitorsArrTemp;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Create array of visitors, keyed by node type ID.
|
|
52
|
+
*
|
|
53
|
+
* Each element of array is one of:
|
|
54
|
+
*
|
|
55
|
+
* * No visitor for this type = `null`.
|
|
56
|
+
* * Visitor for leaf node = visit function.
|
|
57
|
+
* * Visitor for non-leaf node = object of form `{ enter, exit }`,
|
|
58
|
+
* where each property is either a visitor function or `null`.
|
|
59
|
+
*
|
|
60
|
+
* @param {Object} visitor - Visitors object from user
|
|
61
|
+
* @returns {Array<Object|Function|null>} - Array of visitors
|
|
62
|
+
*/
|
|
63
|
+
function createVisitorsArr(visitor) {
|
|
64
|
+
if (visitor === null || typeof visitor !== "object") {
|
|
65
|
+
throw new Error("`visitor` must be an object");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Create empty visitors array
|
|
69
|
+
const visitorsArr = [];
|
|
70
|
+
for (let i = NODE_TYPES_COUNT; i !== 0; i--) {
|
|
71
|
+
visitorsArr.push(null);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Populate visitors array from provided object
|
|
75
|
+
for (let name of Object.keys(visitor)) {
|
|
76
|
+
const visitFn = visitor[name];
|
|
77
|
+
if (typeof visitFn !== "function") {
|
|
78
|
+
throw new Error(`'${name}' property of \`visitor\` object is not a function`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const isExit = name.endsWith(":exit");
|
|
82
|
+
if (isExit) name = name.slice(0, -5);
|
|
83
|
+
|
|
84
|
+
const typeId = NODE_TYPE_IDS_MAP.get(name);
|
|
85
|
+
if (typeId === void 0) throw new Error(`Unknown node type '${name}' in \`visitor\` object`);
|
|
86
|
+
|
|
87
|
+
if (typeId < LEAF_NODE_TYPES_COUNT) {
|
|
88
|
+
// Leaf node. Store just 1 function.
|
|
89
|
+
const existingVisitFn = visitorsArr[typeId];
|
|
90
|
+
if (existingVisitFn === null) {
|
|
91
|
+
visitorsArr[typeId] = visitFn;
|
|
92
|
+
} else if (isExit) {
|
|
93
|
+
visitorsArr[typeId] = combineVisitFunctions(existingVisitFn, visitFn);
|
|
94
|
+
} else {
|
|
95
|
+
visitorsArr[typeId] = combineVisitFunctions(visitFn, existingVisitFn);
|
|
96
|
+
}
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let enterExit = visitorsArr[typeId];
|
|
101
|
+
if (enterExit === null) {
|
|
102
|
+
enterExit = visitorsArr[typeId] = { enter: null, exit: null };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (isExit) {
|
|
106
|
+
enterExit.exit = visitFn;
|
|
107
|
+
} else {
|
|
108
|
+
enterExit.enter = visitFn;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return visitorsArr;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Combine 2 visitor functions into 1.
|
|
117
|
+
*
|
|
118
|
+
* @param {function} visit1 - 1st visitor function
|
|
119
|
+
* @param {function} visit2 - 2nd visitor function
|
|
120
|
+
* @returns {function} - Combined visitor function
|
|
121
|
+
*/
|
|
122
|
+
function combineVisitFunctions(visit1, visit2) {
|
|
123
|
+
return function (node) {
|
|
124
|
+
visit1(node);
|
|
125
|
+
visit2(node);
|
|
126
|
+
};
|
|
127
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
|
|
3
|
+
// Lazy-loaded when first construct a `Visitor`
|
|
4
|
+
let walkProgram = null,
|
|
5
|
+
addVisitorToCompiled,
|
|
6
|
+
createCompiledVisitor,
|
|
7
|
+
finalizeCompiledVisitor;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Visitor class for traversing AST.
|
|
11
|
+
*/
|
|
12
|
+
export class Visitor {
|
|
13
|
+
#compiledVisitor = null;
|
|
14
|
+
|
|
15
|
+
constructor(visitor) {
|
|
16
|
+
if (walkProgram === null) {
|
|
17
|
+
const require = createRequire(import.meta.url);
|
|
18
|
+
({ walkProgram } = require("../generated/visit/walk.js"));
|
|
19
|
+
({
|
|
20
|
+
addVisitorToCompiled,
|
|
21
|
+
createCompiledVisitor,
|
|
22
|
+
finalizeCompiledVisitor,
|
|
23
|
+
} = require("./visitor.js"));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const compiledVisitor = createCompiledVisitor();
|
|
27
|
+
addVisitorToCompiled(visitor);
|
|
28
|
+
const needsVisit = finalizeCompiledVisitor();
|
|
29
|
+
if (needsVisit) this.#compiledVisitor = compiledVisitor;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Visit AST.
|
|
34
|
+
* @param program - The AST to visit.
|
|
35
|
+
* @returns {undefined}
|
|
36
|
+
*/
|
|
37
|
+
visit(program) {
|
|
38
|
+
const compiledVisitor = this.#compiledVisitor;
|
|
39
|
+
if (compiledVisitor !== null) walkProgram(program, compiledVisitor);
|
|
40
|
+
}
|
|
41
|
+
}
|