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.
@@ -0,0 +1,312 @@
1
+ /* auto-generated by NAPI-RS */
2
+ /* eslint-disable */
3
+
4
+ import type { Program } from "goatlint-types";
5
+ import type { VisitorObject } from "./generated/visit/visitor.d.ts";
6
+
7
+ export * from "goatlint-types";
8
+
9
+ export { VisitorObject };
10
+
11
+ export const visitorKeys: Record<string, string[]>;
12
+
13
+ export class Visitor {
14
+ constructor(visitor: VisitorObject);
15
+ visit(program: Program): void;
16
+ }
17
+ export interface Comment {
18
+ type: 'Line' | 'Block'
19
+ value: string
20
+ start: number
21
+ end: number
22
+ }
23
+
24
+ export interface ErrorLabel {
25
+ message: string | null
26
+ start: number
27
+ end: number
28
+ }
29
+
30
+ export interface OxcError {
31
+ severity: Severity
32
+ message: string
33
+ labels: Array<ErrorLabel>
34
+ helpMessage: string | null
35
+ codeframe: string | null
36
+ }
37
+
38
+ export declare const enum Severity {
39
+ Error = 'Error',
40
+ Warning = 'Warning',
41
+ Advice = 'Advice'
42
+ }
43
+ export declare class ParseResult {
44
+ get program(): import("goatlint-types").Program
45
+ get module(): EcmaScriptModule
46
+ get comments(): Array<Comment>
47
+ get errors(): Array<OxcError>
48
+ }
49
+
50
+ export interface DynamicImport {
51
+ start: number
52
+ end: number
53
+ moduleRequest: Span
54
+ }
55
+
56
+ export interface EcmaScriptModule {
57
+ /**
58
+ * Has ESM syntax.
59
+ *
60
+ * i.e. `import` and `export` statements, and `import.meta`.
61
+ *
62
+ * Dynamic imports `import('foo')` are ignored since they can be used in non-ESM files.
63
+ */
64
+ hasModuleSyntax: boolean
65
+ /** Import statements. */
66
+ staticImports: Array<StaticImport>
67
+ /** Export statements. */
68
+ staticExports: Array<StaticExport>
69
+ /** Dynamic import expressions. */
70
+ dynamicImports: Array<DynamicImport>
71
+ /** Span positions` of `import.meta` */
72
+ importMetas: Array<Span>
73
+ }
74
+
75
+ export interface ExportExportName {
76
+ kind: ExportExportNameKind
77
+ name: string | null
78
+ start: number | null
79
+ end: number | null
80
+ }
81
+
82
+ export declare const enum ExportExportNameKind {
83
+ /** `export { name } */
84
+ Name = 'Name',
85
+ /** `export default expression` */
86
+ Default = 'Default',
87
+ /** `export * from "mod" */
88
+ None = 'None'
89
+ }
90
+
91
+ export interface ExportImportName {
92
+ kind: ExportImportNameKind
93
+ name: string | null
94
+ start: number | null
95
+ end: number | null
96
+ }
97
+
98
+ export declare const enum ExportImportNameKind {
99
+ /** `export { name } */
100
+ Name = 'Name',
101
+ /** `export * as ns from "mod"` */
102
+ All = 'All',
103
+ /** `export * from "mod"` */
104
+ AllButDefault = 'AllButDefault',
105
+ /** Does not have a specifier. */
106
+ None = 'None'
107
+ }
108
+
109
+ export interface ExportLocalName {
110
+ kind: ExportLocalNameKind
111
+ name: string | null
112
+ start: number | null
113
+ end: number | null
114
+ }
115
+
116
+ export declare const enum ExportLocalNameKind {
117
+ /** `export { name } */
118
+ Name = 'Name',
119
+ /** `export default expression` */
120
+ Default = 'Default',
121
+ /**
122
+ * If the exported value is not locally accessible from within the module.
123
+ * `export default function () {}`
124
+ */
125
+ None = 'None'
126
+ }
127
+
128
+ export interface ImportName {
129
+ kind: ImportNameKind
130
+ name: string | null
131
+ start: number | null
132
+ end: number | null
133
+ }
134
+
135
+ export declare const enum ImportNameKind {
136
+ /** `import { x } from "mod"` */
137
+ Name = 'Name',
138
+ /** `import * as ns from "mod"` */
139
+ NamespaceObject = 'NamespaceObject',
140
+ /** `import defaultExport from "mod"` */
141
+ Default = 'Default'
142
+ }
143
+
144
+ /**
145
+ * Parse JS/TS source asynchronously on a separate thread.
146
+ *
147
+ * Note that not all of the workload can happen on a separate thread.
148
+ * Parsing on Rust side does happen in a separate thread, but deserialization of the AST to JS objects
149
+ * has to happen on current thread. This synchronous deserialization work typically outweighs
150
+ * the asynchronous parsing by a factor of between 3 and 20.
151
+ *
152
+ * i.e. the majority of the workload cannot be parallelized by using this method.
153
+ *
154
+ * Generally `parseSync` is preferable to use as it does not have the overhead of spawning a thread.
155
+ * If you need to parallelize parsing multiple files, it is recommended to use worker threads.
156
+ */
157
+ export declare function parse(filename: string, sourceText: string, options?: ParserOptions | undefined | null): Promise<ParseResult>
158
+
159
+ export interface ParserOptions {
160
+ /** Treat the source text as `js`, `jsx`, `ts`, `tsx` or `dts`. */
161
+ lang?: 'js' | 'jsx' | 'ts' | 'tsx' | 'dts'
162
+ /** Treat the source text as `script` or `module` code. */
163
+ sourceType?: 'script' | 'module' | 'commonjs' | 'unambiguous' | undefined
164
+ /**
165
+ * Return an AST which includes TypeScript-related properties, or excludes them.
166
+ *
167
+ * `'js'` is default for JS / JSX files.
168
+ * `'ts'` is default for TS / TSX files.
169
+ * The type of the file is determined from `lang` option, or extension of provided `filename`.
170
+ */
171
+ astType?: 'js' | 'ts'
172
+ /**
173
+ * Controls whether the `range` property is included on AST nodes.
174
+ * The `range` property is a `[number, number]` which indicates the start/end offsets
175
+ * of the node in the file contents.
176
+ *
177
+ * @default false
178
+ */
179
+ range?: boolean
180
+ /**
181
+ * Emit `ParenthesizedExpression` and `TSParenthesizedType` in AST.
182
+ *
183
+ * If this option is true, parenthesized expressions are represented by
184
+ * (non-standard) `ParenthesizedExpression` and `TSParenthesizedType` nodes that
185
+ * have a single `expression` property containing the expression inside parentheses.
186
+ *
187
+ * @default true
188
+ */
189
+ preserveParens?: boolean
190
+ /**
191
+ * Produce semantic errors with an additional AST pass.
192
+ * Semantic errors depend on symbols and scopes, where the parser does not construct.
193
+ * This adds a small performance overhead.
194
+ *
195
+ * @default false
196
+ */
197
+ showSemanticErrors?: boolean
198
+ }
199
+
200
+ /**
201
+ * Parse JS/TS source synchronously on current thread.
202
+ *
203
+ * This is generally preferable over `parse` (async) as it does not have the overhead
204
+ * of spawning a thread, and the majority of the workload cannot be parallelized anyway
205
+ * (see `parse` documentation for details).
206
+ *
207
+ * If you need to parallelize parsing multiple files, it is recommended to use worker threads
208
+ * with `parseSync` rather than using `parse`.
209
+ */
210
+ export declare function parseSync(filename: string, sourceText: string, options?: ParserOptions | undefined | null): ParseResult
211
+
212
+ /** Returns `true` if raw transfer is supported on this platform. */
213
+ export declare function rawTransferSupported(): boolean
214
+
215
+ export interface Span {
216
+ start: number
217
+ end: number
218
+ }
219
+
220
+ export interface StaticExport {
221
+ start: number
222
+ end: number
223
+ entries: Array<StaticExportEntry>
224
+ }
225
+
226
+ export interface StaticExportEntry {
227
+ start: number
228
+ end: number
229
+ moduleRequest: ValueSpan | null
230
+ /** The name under which the desired binding is exported by the module`. */
231
+ importName: ExportImportName
232
+ /** The name used to export this binding by this module. */
233
+ exportName: ExportExportName
234
+ /** The name that is used to locally access the exported value from within the importing module. */
235
+ localName: ExportLocalName
236
+ /**
237
+ * Whether the export is a TypeScript `export type`.
238
+ *
239
+ * Examples:
240
+ *
241
+ * ```ts
242
+ * export type * from 'mod';
243
+ * export type * as ns from 'mod';
244
+ * export type { foo };
245
+ * export { type foo }:
246
+ * export type { foo } from 'mod';
247
+ * ```
248
+ */
249
+ isType: boolean
250
+ }
251
+
252
+ export interface StaticImport {
253
+ /** Start of import statement. */
254
+ start: number
255
+ /** End of import statement. */
256
+ end: number
257
+ /**
258
+ * Import source.
259
+ *
260
+ * ```js
261
+ * import { foo } from "mod";
262
+ * // ^^^
263
+ * ```
264
+ */
265
+ moduleRequest: ValueSpan
266
+ /**
267
+ * Import specifiers.
268
+ *
269
+ * Empty for `import "mod"`.
270
+ */
271
+ entries: Array<StaticImportEntry>
272
+ }
273
+
274
+ export interface StaticImportEntry {
275
+ /**
276
+ * The name under which the desired binding is exported by the module.
277
+ *
278
+ * ```js
279
+ * import { foo } from "mod";
280
+ * // ^^^
281
+ * import { foo as bar } from "mod";
282
+ * // ^^^
283
+ * ```
284
+ */
285
+ importName: ImportName
286
+ /**
287
+ * The name that is used to locally access the imported value from within the importing module.
288
+ * ```js
289
+ * import { foo } from "mod";
290
+ * // ^^^
291
+ * import { foo as bar } from "mod";
292
+ * // ^^^
293
+ * ```
294
+ */
295
+ localName: ValueSpan
296
+ /**
297
+ * Whether this binding is for a TypeScript type-only import.
298
+ *
299
+ * `true` for the following imports:
300
+ * ```ts
301
+ * import type { foo } from "mod";
302
+ * import { type foo } from "mod";
303
+ * ```
304
+ */
305
+ isType: boolean
306
+ }
307
+
308
+ export interface ValueSpan {
309
+ value: string
310
+ start: number
311
+ end: number
312
+ }
@@ -0,0 +1,108 @@
1
+ import { createRequire } from "node:module";
2
+ import { parse as parseBinding, parseSync as parseSyncBinding } from "./bindings.js";
3
+ import { wrap } from "./wrap.js";
4
+
5
+ export { default as visitorKeys } from "./generated/visit/keys.js";
6
+ export { Visitor } from "./visit/index.js";
7
+
8
+ export {
9
+ ExportExportNameKind,
10
+ ExportImportNameKind,
11
+ ExportLocalNameKind,
12
+ ImportNameKind,
13
+ ParseResult,
14
+ Severity,
15
+ } from "./bindings.js";
16
+ export { rawTransferSupported } from "./raw-transfer/supported.js";
17
+
18
+ const require = createRequire(import.meta.url);
19
+
20
+ // Lazily loaded as needed
21
+ let parseSyncRaw = null,
22
+ parseRaw,
23
+ parseSyncLazy = null,
24
+ parseLazy,
25
+ LazyVisitor;
26
+
27
+ /**
28
+ * Lazy-load code related to raw transfer.
29
+ * @returns {undefined}
30
+ */
31
+ function loadRawTransfer() {
32
+ if (parseSyncRaw === null) {
33
+ ({ parseSyncRaw, parse: parseRaw } = require("./raw-transfer/eager.js"));
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Lazy-load code related to raw transfer lazy deserialization.
39
+ * @returns {undefined}
40
+ */
41
+ function loadRawTransferLazy() {
42
+ if (parseSyncLazy === null) {
43
+ ({ parseSyncLazy, parse: parseLazy, Visitor: LazyVisitor } = require("./raw-transfer/lazy.js"));
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Parse JS/TS source synchronously on current thread.
49
+ *
50
+ * @param {string} filename - Filename
51
+ * @param {string} sourceText - Source text of file
52
+ * @param {Object|undefined} options - Parsing options
53
+ * @returns {Object} - Object with property getters for `program`, `module`, `comments`, and `errors`
54
+ * @throws {Error} - If `experimentalRawTransfer` or `experimentalLazy` option is enabled,
55
+ * and raw transfer is not supported on this platform
56
+ */
57
+ export function parseSync(filename, sourceText, options) {
58
+ if (options?.experimentalRawTransfer) {
59
+ loadRawTransfer();
60
+ return parseSyncRaw(filename, sourceText, options);
61
+ }
62
+ if (options?.experimentalLazy) {
63
+ loadRawTransferLazy();
64
+ return parseSyncLazy(filename, sourceText, options);
65
+ }
66
+ return wrap(parseSyncBinding(filename, sourceText, options));
67
+ }
68
+
69
+ /**
70
+ * Parse JS/TS source asynchronously on a separate thread.
71
+ *
72
+ * Note that not all of the workload can happen on a separate thread.
73
+ * Parsing on Rust side does happen in a separate thread, but deserialization of the AST to JS objects
74
+ * has to happen on current thread. This synchronous deserialization work typically outweighs
75
+ * the asynchronous parsing by a factor of between 3 and 20.
76
+ *
77
+ * i.e. the majority of the workload cannot be parallelized by using this method.
78
+ *
79
+ * Generally `parseSync` is preferable to use as it does not have the overhead of spawning a thread.
80
+ * If you need to parallelize parsing multiple files, it is recommended to use worker threads.
81
+ *
82
+ * @param {string} filename - Filename
83
+ * @param {string} sourceText - Source text of file
84
+ * @param {Object|undefined} options - Parsing options
85
+ * @returns {Object} - Object with property getters for `program`, `module`, `comments`, and `errors`
86
+ * @throws {Error} - If `experimentalRawTransfer` or `experimentalLazy` option is enabled,
87
+ * and raw transfer is not supported on this platform
88
+ */
89
+ export async function parse(filename, sourceText, options) {
90
+ if (options?.experimentalRawTransfer) {
91
+ loadRawTransfer();
92
+ return await parseRaw(filename, sourceText, options);
93
+ }
94
+ if (options?.experimentalLazy) {
95
+ loadRawTransferLazy();
96
+ return await parseLazy(filename, sourceText, options);
97
+ }
98
+ return wrap(await parseBinding(filename, sourceText, options));
99
+ }
100
+
101
+ /**
102
+ * Get `Visitor` class to construct visitors with.
103
+ * @returns {function} - `Visitor` class
104
+ */
105
+ export function experimentalGetLazyVisitor() {
106
+ loadRawTransferLazy();
107
+ return LazyVisitor;
108
+ }
@@ -0,0 +1,276 @@
1
+ import os from "node:os";
2
+ import { BUFFER_ALIGN, BUFFER_SIZE, IS_TS_FLAG_POS } from "../generated/constants.js";
3
+ import {
4
+ getBufferOffset,
5
+ parseRaw as parseRawBinding,
6
+ parseRawSync as parseRawSyncBinding,
7
+ } from "../bindings.js";
8
+ import { rawTransferSupported } from "./supported.js";
9
+
10
+ // Throw an error if running on a platform which raw transfer doesn't support.
11
+ //
12
+ // Note: This module is lazy-loaded only when user calls `parseSync` or `parseAsync` with
13
+ // `experimentalRawTransfer` or `experimentalLazy` options, or calls `experimentalGetLazyVisitor`.
14
+ if (!rawTransferSupported()) {
15
+ throw new Error(
16
+ "`experimentalRawTransfer` and `experimentalLazy` options are not supported " +
17
+ "on 32-bit or big-endian systems, versions of NodeJS prior to v22.0.0, " +
18
+ "versions of Deno prior to v2.0.0, or other runtimes",
19
+ );
20
+ }
21
+
22
+ /**
23
+ * Parse JS/TS source synchronously on current thread using raw transfer.
24
+ *
25
+ * Convert the buffer returned by Rust to a JS object with provided `convert` function.
26
+ *
27
+ * This function contains logic shared by both `parseSyncRaw` and `parseSyncLazy`.
28
+ *
29
+ * @param {string} filename - Filename
30
+ * @param {string} sourceText - Source text of file
31
+ * @param {Object} options - Parsing options
32
+ * @param {function} convert - Function to convert the buffer returned from Rust into a JS object
33
+ * @returns {Object} - The return value of `convert`
34
+ */
35
+ export function parseSyncRawImpl(filename, sourceText, options, convert) {
36
+ const { buffer, sourceByteLen } = prepareRaw(sourceText);
37
+ parseRawSyncBinding(filename, buffer, sourceByteLen, options);
38
+ return convert(buffer, sourceText, sourceByteLen, options);
39
+ }
40
+
41
+ // User should not schedule more async tasks than there are available CPUs, as it hurts performance,
42
+ // but it's a common mistake in async JS code to do exactly that.
43
+ //
44
+ // That anti-pattern looks like this when applied to Oxc:
45
+ //
46
+ // ```js
47
+ // const asts = await Promise.all(
48
+ // files.map(
49
+ // async (filename) => {
50
+ // const sourceText = await fs.readFile(filename, 'utf8');
51
+ // const ast = await oxc.parseAsync(filename, sourceText);
52
+ // return ast;
53
+ // }
54
+ // )
55
+ // );
56
+ // ```
57
+ //
58
+ // In most cases, that'd just result in a bit of degraded performance, and higher memory use because
59
+ // of loading sources into memory prematurely.
60
+ //
61
+ // However, raw transfer uses a 6 GiB buffer for each parsing operation.
62
+ // Most of the memory pages in those buffers are never touched, so this does not consume a huge amount
63
+ // of physical memory, but it does still consume virtual memory.
64
+ //
65
+ // If we allowed creating a large number of 6 GiB buffers simultaneously, it would quickly consume
66
+ // virtual memory space and risk memory exhaustion. The code above would exhaust all of bottom half
67
+ // (heap) of 48-bit virtual memory space if `files.length >= 21_845`. This is not a number which
68
+ // is unrealistic in real world code.
69
+ //
70
+ // To guard against this possibility, we implement a simple queue.
71
+ // No more than `os.availableParallelism()` files can be parsed simultaneously, and any further calls to
72
+ // `parseAsyncRaw` will be put in a queue, to execute once other tasks complete.
73
+ //
74
+ // Fallback to `os.cpus().length` on versions of NodeJS prior to v18.14.0, which do not support
75
+ // `os.availableParallelism`.
76
+ let availableCores = os.availableParallelism ? os.availableParallelism() : os.cpus().length;
77
+ const queue = [];
78
+
79
+ /**
80
+ * Parse JS/TS source asynchronously using raw transfer.
81
+ *
82
+ * Convert the buffer returned by Rust to a JS object with provided `convert` function.
83
+ *
84
+ * Queues up parsing operations if more calls than number of CPU cores (see above).
85
+ *
86
+ * This function contains logic shared by both `parseAsyncRaw` and `parseAsyncLazy`.
87
+ *
88
+ * @param {string} filename - Filename
89
+ * @param {string} sourceText - Source text of file
90
+ * @param {Object} options - Parsing options
91
+ * @param {function} convert - Function to convert the buffer returned from Rust into a JS object
92
+ * @returns {Object} - The return value of `convert`
93
+ */
94
+ export async function parseAsyncRawImpl(filename, sourceText, options, convert) {
95
+ // Wait for a free CPU core if all CPUs are currently busy.
96
+ //
97
+ // Note: `availableCores` is NOT decremented if have to wait in the queue first,
98
+ // and NOT incremented when parsing completes and it runs next task in the queue.
99
+ //
100
+ // This is to avoid a race condition if `parseAsyncRaw` is called during the microtick in between
101
+ // `resolve` being called below, and the promise resolving here. In that case the new task could
102
+ // start running, and then the promise resolves, and the queued task also starts running.
103
+ // We'd then have `availableParallelism() + 1` tasks running simultaneously. Potentially, this could
104
+ // happen repeatedly, with the number of tasks running simultaneously ever-increasing.
105
+ if (availableCores === 0) {
106
+ // All CPU cores are busy. Put this task in queue and wait for capacity to become available.
107
+ await new Promise((resolve, _) => {
108
+ queue.push(resolve);
109
+ });
110
+ } else {
111
+ // A CPU core is available. Mark core as busy, and run parsing now.
112
+ availableCores--;
113
+ }
114
+
115
+ // Parse
116
+ const { buffer, sourceByteLen } = prepareRaw(sourceText);
117
+ await parseRawBinding(filename, buffer, sourceByteLen, options);
118
+ const data = convert(buffer, sourceText, sourceByteLen, options);
119
+
120
+ // Free the CPU core
121
+ if (queue.length > 0) {
122
+ // Some further tasks waiting in queue. Run the next one.
123
+ // Do not increment `availableCores` (see above).
124
+ const resolve = queue.shift();
125
+ resolve();
126
+ } else {
127
+ // No tasks waiting in queue. This CPU is now free.
128
+ availableCores++;
129
+ }
130
+
131
+ return data;
132
+ }
133
+
134
+ const ARRAY_BUFFER_SIZE = BUFFER_SIZE + BUFFER_ALIGN;
135
+ const ONE_GIB = 1 << 30;
136
+
137
+ // We keep a cache of buffers for raw transfer, so we can reuse them as much as possible.
138
+ //
139
+ // When processing multiple files, it's ideal if can reuse an existing buffer, as it's more likely to
140
+ // be warm in CPU cache, it avoids allocations, and it saves work for the garbage collector.
141
+ //
142
+ // However, we also don't want to keep a load of large buffers around indefinitely using up memory,
143
+ // if they're not going to be used again.
144
+ //
145
+ // We have no knowledge of what pattern over time user may process files in (could be lots in quick
146
+ // succession, or more occasionally in a long-running process). So we try to use flexible caching
147
+ // strategy which is adaptable to many usage patterns.
148
+ //
149
+ // We use a 2-tier cache.
150
+ // Tier 1 uses strong references, tier 2 uses weak references.
151
+ //
152
+ // When parsing is complete and the buffer is no longer in use, push it to `buffers` (tier 1 cache).
153
+ // Set a timer to clear the cache when no activity for 10 seconds.
154
+ //
155
+ // When the timer expires, move all the buffers from tier 1 cache into `oldBuffers` (tier 2).
156
+ // They are stored there as `WeakRef`s, so the garbage collector is free to reclaim them.
157
+ //
158
+ // On the next call to `parseSync` or `parseAsync`, promote any buffers in tier 2 cache which were not
159
+ // already garbage collected back into tier 1 cache. This is on assumption that parsing one file
160
+ // indicates parsing as a whole is an ongoing process, and there will likely be further calls to
161
+ // `parseSync` / `parseAsync` in future.
162
+ //
163
+ // The weak tier 2 cache is because V8 does not necessarily free memory as soon as it's able to be
164
+ // freed. We don't want to block it from freeing memory, but if it's not done that yet, there's no
165
+ // point creating a new buffer, when one already exists.
166
+ const CLEAR_BUFFERS_TIMEOUT = 10_000; // 10 seconds
167
+ const buffers = [],
168
+ oldBuffers = [];
169
+ let clearBuffersTimeout = null;
170
+
171
+ const textEncoder = new TextEncoder();
172
+
173
+ /**
174
+ * Get a buffer (from cache if possible), and copy source text into it.
175
+ *
176
+ * @param {string} sourceText - Source text of file
177
+ * @returns {Object} - Object of form `{ buffer, sourceByteLen }`.
178
+ * - `buffer`: `Uint8Array` containing the AST in raw form.
179
+ * - `sourceByteLen`: Length of source text in UTF-8 bytes
180
+ * (which may not be equal to `sourceText.length` if source contains non-ASCII characters).
181
+ */
182
+ export function prepareRaw(sourceText) {
183
+ // Cancel timeout for clearing buffers
184
+ if (clearBuffersTimeout !== null) {
185
+ clearTimeout(clearBuffersTimeout);
186
+ clearBuffersTimeout = null;
187
+ }
188
+
189
+ // Revive any discarded buffers which have not yet been garbage collected
190
+ if (oldBuffers.length > 0) {
191
+ const revivedBuffers = [];
192
+ for (let oldBuffer of oldBuffers) {
193
+ oldBuffer = oldBuffer.deref();
194
+ if (oldBuffer !== undefined) revivedBuffers.push(oldBuffer);
195
+ }
196
+ oldBuffers.length = 0;
197
+ if (revivedBuffers.length > 0) buffers.unshift(...revivedBuffers);
198
+ }
199
+
200
+ // Reuse existing buffer, or create a new one
201
+ const buffer = buffers.length > 0 ? buffers.pop() : createBuffer();
202
+
203
+ // Write source into start of buffer.
204
+ // `TextEncoder` cannot write into a `Uint8Array` larger than 1 GiB,
205
+ // so create a view into buffer of this size to write into.
206
+ const sourceBuffer = new Uint8Array(buffer.buffer, buffer.byteOffset, ONE_GIB);
207
+ const { read, written: sourceByteLen } = textEncoder.encodeInto(sourceText, sourceBuffer);
208
+ if (read !== sourceText.length) throw new Error("Failed to write source text into buffer");
209
+
210
+ return { buffer, sourceByteLen };
211
+ }
212
+
213
+ /**
214
+ * Get if AST should be parsed as JS or TS.
215
+ * Rust side sets a `bool` in this position in buffer which is `true` if TS.
216
+ *
217
+ * @param {Uint8Array} buffer - Buffer containing AST in raw form
218
+ * @returns {boolean} - `true` if AST is JS, `false` if TS
219
+ */
220
+ export function isJsAst(buffer) {
221
+ return buffer[IS_TS_FLAG_POS] === 0;
222
+ }
223
+
224
+ /**
225
+ * Return buffer to cache, to be reused.
226
+ * Set a timer to clear buffers.
227
+ *
228
+ * @param {Uint8Array} buffer - Buffer
229
+ * @returns {undefined}
230
+ */
231
+ export function returnBufferToCache(buffer) {
232
+ buffers.push(buffer);
233
+
234
+ if (clearBuffersTimeout !== null) clearTimeout(clearBuffersTimeout);
235
+ clearBuffersTimeout = setTimeout(clearBuffersCache, CLEAR_BUFFERS_TIMEOUT);
236
+ clearBuffersTimeout.unref();
237
+ }
238
+
239
+ /**
240
+ * Downgrade buffers in tier 1 cache (`buffers`) to tier 2 (`oldBuffers`)
241
+ * so they can be garbage collected.
242
+ *
243
+ * @returns {undefined}
244
+ */
245
+ function clearBuffersCache() {
246
+ clearBuffersTimeout = null;
247
+
248
+ for (const buffer of buffers) {
249
+ oldBuffers.push(new WeakRef(buffer));
250
+ }
251
+ buffers.length = 0;
252
+ }
253
+
254
+ /**
255
+ * Create a `Uint8Array` which is 2 GiB in size, with its start aligned on 4 GiB.
256
+ *
257
+ * Achieve this by creating a 6 GiB `ArrayBuffer`, getting the offset within it that's aligned to 4 GiB,
258
+ * chopping off that number of bytes from the start, and shortening to 2 GiB.
259
+ *
260
+ * It's always possible to obtain a 2 GiB slice aligned on 4 GiB within a 6 GiB buffer,
261
+ * no matter how the 6 GiB buffer is aligned.
262
+ *
263
+ * Note: On systems with virtual memory, this only consumes 6 GiB of *virtual* memory.
264
+ * It does not consume physical memory until data is actually written to the `Uint8Array`.
265
+ * Physical memory consumed corresponds to the quantity of data actually written.
266
+ *
267
+ * @returns {Uint8Array} - Buffer
268
+ */
269
+ function createBuffer() {
270
+ const arrayBuffer = new ArrayBuffer(ARRAY_BUFFER_SIZE);
271
+ const offset = getBufferOffset(new Uint8Array(arrayBuffer));
272
+ const buffer = new Uint8Array(arrayBuffer, offset, BUFFER_SIZE);
273
+ buffer.int32 = new Int32Array(arrayBuffer, offset, BUFFER_SIZE / 4);
274
+ buffer.float64 = new Float64Array(arrayBuffer, offset, BUFFER_SIZE / 8);
275
+ return buffer;
276
+ }