oxc-parser
Version:
Oxc Parser Node API
156 lines (144 loc) • 6.39 kB
JavaScript
import { DATA_POINTER_POS_32, PROGRAM_OFFSET } from '../generated/constants.mjs';
import { RawTransferData } from '../generated/lazy/constructors.mjs';
import { walkProgram } from '../generated/lazy/walk.mjs';
import { parseAsyncRawImpl, parseSyncRawImpl, returnBufferToCache } from './common.mjs';
import { TOKEN } from './lazy-common.mjs';
import { getVisitorsArr } from './visitor.mjs';
export { Visitor } from './visitor.mjs';
/**
* Parse JS/TS source synchronously on current thread.
*
* The data in buffer is not deserialized. Is deserialized to JS objects lazily, when accessing the
* properties of objects.
*
* e.g. `program` in returned object is an instance of `Program` class, with getters for `start`, `end`,
* `body` etc.
*
* Returned object contains a `visit` function which can be used to visit the AST with a `Visitor`
* (`Visitor` class can be obtained by calling `experimentalGetLazyVisitor()`).
*
* Returned object contains a `dispose` method. When finished with this AST, it's advisable to call
* `dispose`, to return the buffer to the cache, so it can be reused.
* Garbage collector should do this anyway at some point, but on an unpredictable schedule,
* so it's preferable to call `dispose` manually, to ensure the buffer can be reused immediately.
*
* @param {string} filename - Filename
* @param {string} sourceText - Source text of file
* @param {Object} options - Parsing options
* @returns {Object} - Object with property getters for `program`, `module`, `comments`, and `errors`,
* and `dispose` and `visit` methods
*/
export function parseSyncLazy(filename, sourceText, options) {
let _;
({ experimentalLazy: _, ...options } = options);
return parseSyncRawImpl(filename, sourceText, options, construct);
}
/**
* Parse JS/TS source asynchronously on a separate thread.
*
* The data in buffer is not deserialized. Is deserialized to JS objects lazily, when accessing the
* properties of objects.
*
* e.g. `program` in returned object is an instance of `Program` class, with getters for `start`, `end`,
* `body` etc.
*
* Because this function does not deserialize the AST, unlike `parseAsyncRaw`, very little work happens
* on current thread in this function. Deserialization work only occurs when properties of the objects
* are accessed.
*
* Returned object contains a `visit` function which can be used to visit the AST with a `Visitor`
* (`Visitor` class can be obtained by calling `experimentalGetLazyVisitor()`).
*
* Returned object contains a `dispose` method. When finished with this AST, it's advisable to call
* `dispose`, to return the buffer to the cache, so it can be reused.
* Garbage collector should do this anyway at some point, but on an unpredictable schedule,
* so it's preferable to call `dispose` manually, to ensure the buffer can be reused immediately.
*
* @param {string} filename - Filename
* @param {string} sourceText - Source text of file
* @param {Object} options - Parsing options
* @returns {Object} - Object with property getters for `program`, `module`, `comments`, and `errors`,
* and `dispose` and `visit` methods
*/
export function parseAsyncLazy(filename, sourceText, options) {
let _;
({ experimentalLazy: _, ...options } = options);
return parseAsyncRawImpl(filename, sourceText, options, construct);
}
// Registry for buffers which are held by lazily-deserialized ASTs.
// Returns buffer to cache when the `ast` wrapper is garbage collected.
//
// Check for existence of `FinalizationRegistry`, to avoid errors on old versions of NodeJS
// which don't support it. e.g. Prettier supports NodeJS v14.
// Raw transfer is disabled on NodeJS before v22, so it doesn't matter if this is `null` on old NodeJS
// - it'll never be accessed in that case.
const bufferRecycleRegistry = typeof FinalizationRegistry === 'undefined'
? null
: new FinalizationRegistry(returnBufferToCache);
/**
* Get an object with getters which lazy deserialize AST and other data from buffer.
*
* Object also includes `dispose` and `visit` functions.
*
* @param {Uint8Array} buffer - Buffer containing AST in raw form
* @param {string} sourceText - Source for the file
* @param {number} sourceByteLen - Length of source text in UTF-8 bytes
* @returns {Object} - Object with property getters for `program`, `module`, `comments`, and `errors`,
* and `dispose` and `visit` methods
*/
function construct(buffer, sourceText, sourceByteLen) {
// Create AST object
const sourceIsAscii = sourceText.length === sourceByteLen;
const ast = { buffer, sourceText, sourceByteLen, sourceIsAscii, nodes: new Map(), token: TOKEN };
// Register `ast` with the recycle registry so buffer is returned to cache
// when `ast` is garbage collected
bufferRecycleRegistry.register(ast, buffer, ast);
// Get root data class instance
const rawDataPos = buffer.uint32[DATA_POINTER_POS_32];
const data = new RawTransferData(rawDataPos, ast);
return {
get program() {
return data.program;
},
get module() {
return data.module;
},
get comments() {
return data.comments;
},
get errors() {
return data.errors;
},
dispose: dispose.bind(null, ast),
visit(visitor) {
walkProgram(rawDataPos + PROGRAM_OFFSET, ast, getVisitorsArr(visitor));
},
};
}
/**
* Dispose of this AST.
*
* After calling this method, trying to read any nodes from this AST may cause an error.
*
* Buffer is returned to the cache to be reused.
*
* The buffer would be returned to the cache anyway, once all nodes of the AST are garbage collected,
* but calling `dispose` is preferable, as it will happen immediately.
* Otherwise, garbage collector may take time to collect the `ast` object, and new buffers may be created
* in the meantime, when we could have reused this one.
*
* @param {Object} ast - AST object containing buffer etc
* @returns {undefined}
*/
function dispose(ast) {
// Return buffer to cache, to be reused
returnBufferToCache(ast.buffer);
// Remove connection between `ast` and the buffer
ast.buffer = null;
// Clear other contents of `ast`, so they can be garbage collected
ast.sourceText = null;
ast.nodes = null;
// Remove `ast` from recycling register.
// When `ast` is garbage collected, there's no longer any action to be taken.
bufferRecycleRegistry.unregister(ast);
}