oxc-parser 0.73.2 → 0.74.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.
@@ -1,14 +1,74 @@
1
1
  'use strict';
2
2
 
3
- const { parseSyncRawImpl, parseAsyncRawImpl, returnBufferToCache } = require('./index.js');
3
+ const { parseSyncRawImpl, parseAsyncRawImpl, returnBufferToCache } = require('./common.js'),
4
+ { TOKEN } = require('./lazy-common.js'),
5
+ constructLazyData = require('../generated/deserialize/lazy.js').construct,
6
+ walkProgram = require('../generated/deserialize/lazy-visit.js'),
7
+ { Visitor, getVisitorsArr } = require('./visitor.js');
4
8
 
5
- module.exports = { parseSyncLazy, parseAsyncLazy };
9
+ module.exports = { parseSyncLazy, parseAsyncLazy, Visitor };
6
10
 
11
+ /**
12
+ * Parse JS/TS source synchronously on current thread.
13
+ *
14
+ * The data in buffer is not deserialized. Is deserialized to JS objects lazily, when accessing the
15
+ * properties of objects.
16
+ *
17
+ * e.g. `program` in returned object is an instance of `Program` class, with getters for `start`, `end`,
18
+ * `body` etc.
19
+ *
20
+ * Returned object contains a `visit` function which can be used to visit the AST with a `Visitor`
21
+ * (`Visitor` class can be obtained by calling `experimentalGetLazyVisitor()`).
22
+ *
23
+ * Returned object contains a `dispose` method. When finished with this AST, it's advisable to call
24
+ * `dispose`, to return the buffer to the cache, so it can be reused.
25
+ * Garbage collector should do this anyway at some point, but on an unpredictable schedule,
26
+ * so it's preferable to call `dispose` manually, to ensure the buffer can be reused immediately.
27
+ *
28
+ * @param {string} filename - Filename
29
+ * @param {string} sourceText - Source text of file
30
+ * @param {Object} options - Parsing options
31
+ * @returns {Object} - Object with property getters for `program`, `module`, `comments`, and `errors`,
32
+ * and `dispose` and `visit` methods
33
+ * @throws {Error} - If raw transfer is not supported on this platform
34
+ */
7
35
  function parseSyncLazy(filename, sourceText, options) {
36
+ let _;
37
+ ({ experimentalLazy: _, ...options } = options);
8
38
  return parseSyncRawImpl(filename, sourceText, options, construct);
9
39
  }
10
40
 
41
+ /**
42
+ * Parse JS/TS source asynchronously on a separate thread.
43
+ *
44
+ * The data in buffer is not deserialized. Is deserialized to JS objects lazily, when accessing the
45
+ * properties of objects.
46
+ *
47
+ * e.g. `program` in returned object is an instance of `Program` class, with getters for `start`, `end`,
48
+ * `body` etc.
49
+ *
50
+ * Because this function does not deserialize the AST, unlike `parseAsyncRaw`, very little work happens
51
+ * on current thread in this function. Deserialization work only occurs when properties of the objects
52
+ * are accessed.
53
+ *
54
+ * Returned object contains a `visit` function which can be used to visit the AST with a `Visitor`
55
+ * (`Visitor` class can be obtained by calling `experimentalGetLazyVisitor()`).
56
+ *
57
+ * Returned object contains a `dispose` method. When finished with this AST, it's advisable to call
58
+ * `dispose`, to return the buffer to the cache, so it can be reused.
59
+ * Garbage collector should do this anyway at some point, but on an unpredictable schedule,
60
+ * so it's preferable to call `dispose` manually, to ensure the buffer can be reused immediately.
61
+ *
62
+ * @param {string} filename - Filename
63
+ * @param {string} sourceText - Source text of file
64
+ * @param {Object} options - Parsing options
65
+ * @returns {Object} - Object with property getters for `program`, `module`, `comments`, and `errors`,
66
+ * and `dispose` and `visit` methods
67
+ * @throws {Error} - If raw transfer is not supported on this platform
68
+ */
11
69
  function parseAsyncLazy(filename, sourceText, options) {
70
+ let _;
71
+ ({ experimentalLazy: _, ...options } = options);
12
72
  return parseAsyncRawImpl(filename, sourceText, options, construct);
13
73
  }
14
74
 
@@ -23,15 +83,18 @@ const bufferRecycleRegistry = typeof FinalizationRegistry === 'undefined'
23
83
  ? null
24
84
  : new FinalizationRegistry(returnBufferToCache);
25
85
 
26
- let constructLazyData = null, TOKEN;
27
-
28
- // Get an object with getters which lazy deserialize AST from buffer
86
+ /**
87
+ * Get an object with getters which lazy deserialize AST and other data from buffer.
88
+ *
89
+ * Object also includes `dispose` and `visit` functions.
90
+ *
91
+ * @param {Uint8Array} buffer - Buffer containing AST in raw form
92
+ * @param {string} sourceText - Source for the file
93
+ * @param {number} sourceByteLen - Length of source text in UTF-8 bytes
94
+ * @returns {Object} - Object with property getters for `program`, `module`, `comments`, and `errors`,
95
+ * and `dispose` and `visit` methods
96
+ */
29
97
  function construct(buffer, sourceText, sourceLen) {
30
- // Lazy load deserializer, and get `TOKEN` to store in `ast` objects
31
- if (constructLazyData === null) {
32
- ({ construct: constructLazyData, TOKEN } = require('../generated/deserialize/lazy.js'));
33
- }
34
-
35
98
  // Create AST object
36
99
  const sourceIsAscii = sourceText.length === sourceLen;
37
100
  const ast = { buffer, sourceText, sourceLen, sourceIsAscii, nodes: new Map(), token: TOKEN };
@@ -57,21 +120,32 @@ function construct(buffer, sourceText, sourceLen) {
57
120
  return data.errors;
58
121
  },
59
122
  dispose: dispose.bind(null, ast),
123
+ visit(visitor) {
124
+ // (2 * 1024 * 1024 * 1024 - 16) >> 2
125
+ const metadataPos32 = 536870908;
126
+ const pos = buffer.uint32[metadataPos32];
127
+ walkProgram(pos, ast, getVisitorsArr(visitor));
128
+ },
60
129
  };
61
130
  }
62
131
 
63
- // Dispose of this AST.
64
- //
65
- // After calling this method, trying to read any nodes from this AST may cause an error.
66
- //
67
- // Buffer is returned to the cache to be reused.
68
- //
69
- // The buffer would be returned to the cache anyway, once all nodes of the AST are garbage collected,
70
- // but calling `dispose` is preferable, as it will happen immediately.
71
- // Otherwise, garbage collector may take time to collect the `ast` object, and new buffers may be created
72
- // in the meantime, when we could have reused this one.
132
+ /**
133
+ * Dispose of this AST.
134
+ *
135
+ * After calling this method, trying to read any nodes from this AST may cause an error.
136
+ *
137
+ * Buffer is returned to the cache to be reused.
138
+ *
139
+ * The buffer would be returned to the cache anyway, once all nodes of the AST are garbage collected,
140
+ * but calling `dispose` is preferable, as it will happen immediately.
141
+ * Otherwise, garbage collector may take time to collect the `ast` object, and new buffers may be created
142
+ * in the meantime, when we could have reused this one.
143
+ *
144
+ * @param {Object} ast - AST object containing buffer etc
145
+ * @returns {undefined}
146
+ */
73
147
  function dispose(ast) {
74
- // Return buffer to cache to be reused
148
+ // Return buffer to cache, to be reused
75
149
  returnBufferToCache(ast.buffer);
76
150
 
77
151
  // Remove connection between `ast` and the buffer
@@ -12,12 +12,14 @@ const nodeArrays = new WeakMap();
12
12
  // Function to get element from an array. Initialized in class static block below.
13
13
  let getElement;
14
14
 
15
- // An array of AST nodes where elements are deserialized lazily upon access.
16
- //
17
- // Extends `Array` to make `Array.isArray` return `true` for a `NodeArray`.
18
- //
19
- // TODO: Other methods could maybe be more optimal, avoiding going via proxy multiple times
20
- // e.g. `some`, `indexOf`.
15
+ /**
16
+ * An array of AST nodes where elements are deserialized lazily upon access.
17
+ *
18
+ * Extends `Array` to make `Array.isArray` return `true` for a `NodeArray`.
19
+ *
20
+ * TODO: Other methods could maybe be more optimal, avoiding going via proxy multiple times
21
+ * e.g. `some`, `indexOf`.
22
+ */
21
23
  class NodeArray extends Array {
22
24
  #internal;
23
25
 
@@ -80,7 +82,14 @@ class NodeArray extends Array {
80
82
  // Defining dummy method here to prevent the later assignment altering the shape of class prototype.
81
83
  [Symbol.iterator]() {}
82
84
 
83
- // Override `slice` method to return a `NodeArray`.
85
+ /**
86
+ * Override `slice` method to return a `NodeArray`.
87
+ *
88
+ * @this {NodeArray}
89
+ * @param {*} start - Start of slice
90
+ * @param {*} end - End of slice
91
+ * @returns {NodeArray} - `NodeArray` containing slice of this one
92
+ */
84
93
  slice(start, end) {
85
94
  // Get actual `NodeArray`. `this` is a proxy.
86
95
  const arr = nodeArrays.get(this);
@@ -142,8 +151,10 @@ NodeArray.prototype[Symbol.iterator] = NodeArray.prototype.values;
142
151
 
143
152
  module.exports = NodeArray;
144
153
 
145
- // Iterator over values of a `NodeArray`.
146
- // Returned by `values` method, and also used as iterator for `for (const node of nodeArray) {}`.
154
+ /**
155
+ * Iterator over values of a `NodeArray`.
156
+ * Returned by `values` method, and also used as iterator for `for (const node of nodeArray) {}`.
157
+ */
147
158
  class NodeArrayValuesIterator {
148
159
  #internal;
149
160
 
@@ -173,7 +184,9 @@ class NodeArrayValuesIterator {
173
184
  }
174
185
  }
175
186
 
176
- // Iterator over keys of a `NodeArray`. Returned by `keys` method.
187
+ /**
188
+ * Iterator over keys of a `NodeArray`. Returned by `keys` method.
189
+ */
177
190
  class NodeArrayKeysIterator {
178
191
  #internal;
179
192
 
@@ -196,7 +209,9 @@ class NodeArrayKeysIterator {
196
209
  }
197
210
  }
198
211
 
199
- // Iterator over values of a `NodeArray`. Returned by `entries` method.
212
+ /**
213
+ * Iterator over values of a `NodeArray`. Returned by `entries` method.
214
+ */
200
215
  class NodeArrayEntriesIterator {
201
216
  #internal;
202
217
 
@@ -0,0 +1,56 @@
1
+ 'use strict';
2
+
3
+ const rawTransferSupportedBinding = require('../bindings.js').rawTransferSupported;
4
+
5
+ module.exports = rawTransferSupported;
6
+
7
+ let rawTransferIsSupported = null;
8
+
9
+ /**
10
+ * Returns `true` if `experimentalRawTransfer` is option is supported.
11
+ *
12
+ * Raw transfer is only supported on 64-bit little-endian systems,
13
+ * and NodeJS >= v22.0.0 or Deno >= v2.0.0.
14
+ *
15
+ * Versions of NodeJS prior to v22.0.0 do not support creating an `ArrayBuffer` larger than 4 GiB.
16
+ * Bun (as at v1.2.4) also does not support creating an `ArrayBuffer` larger than 4 GiB.
17
+ * Support on Deno v1 is unknown and it's EOL, so treating Deno before v2.0.0 as unsupported.
18
+ *
19
+ * No easy way to determining pointer width (64 bit or 32 bit) in JS,
20
+ * so call a function on Rust side to find out.
21
+ *
22
+ * @returns {boolean} - `true` if raw transfer is supported on this platform
23
+ */
24
+ function rawTransferSupported() {
25
+ if (rawTransferIsSupported === null) {
26
+ rawTransferIsSupported = rawTransferRuntimeSupported() && rawTransferSupportedBinding();
27
+ }
28
+ return rawTransferIsSupported;
29
+ }
30
+
31
+ // Checks copied from:
32
+ // https://github.com/unjs/std-env/blob/ab15595debec9e9115a9c1d31bc7597a8e71dbfd/src/runtimes.ts
33
+ // MIT license: https://github.com/unjs/std-env/blob/ab15595debec9e9115a9c1d31bc7597a8e71dbfd/LICENCE
34
+ function rawTransferRuntimeSupported() {
35
+ let global;
36
+ try {
37
+ global = globalThis;
38
+ } catch (e) {
39
+ return false;
40
+ }
41
+
42
+ const isBun = !!global.Bun || !!global.process?.versions?.bun;
43
+ if (isBun) return false;
44
+
45
+ const isDeno = !!global.Deno;
46
+ if (isDeno) {
47
+ const match = Deno.version?.deno?.match(/^(\d+)\./);
48
+ return !!match && match[1] * 1 >= 2;
49
+ }
50
+
51
+ const isNode = global.process?.release?.name === 'node';
52
+ if (!isNode) return false;
53
+
54
+ const match = process.version?.match(/^v(\d+)\./);
55
+ return !!match && match[1] * 1 >= 22;
56
+ }
@@ -0,0 +1,127 @@
1
+ 'use strict';
2
+
3
+ const { NODE_TYPE_IDS_MAP, LEAF_NODES_COUNT } = require('../generated/deserialize/lazy-types.js');
4
+
5
+ const NODE_TYPES_COUNT = NODE_TYPE_IDS_MAP.size;
6
+
7
+ // Getter for private `#visitorsArr` property of `Visitor` class. Initialized in class body below.
8
+ let getVisitorsArr;
9
+
10
+ /**
11
+ * Visitor class, used to visit an AST.
12
+ */
13
+ 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
+ * @constructor
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
+ getVisitorsArr = visitor => visitor.#visitorsArr;
45
+ }
46
+ }
47
+
48
+ module.exports = { Visitor, getVisitorsArr };
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('`visitors` must be an object');
66
+ }
67
+
68
+ // Create empty visitors array
69
+ const visitorsArr = [];
70
+ for (let i = 0; i < NODE_TYPES_COUNT; 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 \`visitors\` 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 \`visitors\` object`);
86
+
87
+ if (typeId < LEAF_NODES_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
+ }