feat(web): document the API

This commit is contained in:
Amaan Qureshi 2025-01-20 02:43:52 -05:00
parent a4b20c1c56
commit 09cb4c5729
17 changed files with 1155 additions and 198 deletions

View file

@ -4,21 +4,85 @@ import { marshalRange, unmarshalRange } from './marshal';
import { checkModule, initializeBinding } from './bindings';
import { Tree } from './tree';
/**
* Options for parsing
*
* The `includedRanges` property is an array of {@link Range} objects that
* represent the ranges of text that the parser should include when parsing.
*
* The `progressCallback` property is a function that is called periodically
* during parsing to check whether parsing should be cancelled.
*
* See {@link Parser#parse} for more information.
*/
export interface ParseOptions {
/**
* An array of {@link Range} objects that
* represent the ranges of text that the parser should include when parsing.
*
* This sets the ranges of text that the parser should include when parsing.
* By default, the parser will always include entire documents. This
* function allows you to parse only a *portion* of a document but
* still return a syntax tree whose ranges match up with the document
* as a whole. You can also pass multiple disjoint ranges.
* If `ranges` is empty, then the entire document will be parsed.
* Otherwise, the given ranges must be ordered from earliest to latest
* in the document, and they must not overlap. That is, the following
* must hold for all `i` < `length - 1`:
* ```text
* ranges[i].end_byte <= ranges[i + 1].start_byte
* ```
*/
includedRanges?: Range[];
/**
* A function that is called periodically during parsing to check
* whether parsing should be cancelled. If the progress callback returns
* `false`, then parsing will be cancelled. You can also use this to instrument
* parsing and check where the parser is at in the document. The progress callback
* takes a single argument, which is a {@link ParseState} representing the current
* state of the parser.
*/
progressCallback?: (state: ParseState) => void;
}
/**
* A stateful object that is passed into the progress callback {@link ParseOptions#progressCallback}
* to provide the current state of the parser.
*/
export interface ParseState {
/** The byte offset in the document that the parser is at. */
currentOffset: number;
}
// Global variable for transferring data across the FFI boundary
/**
* @internal
*
* Global variable for transferring data across the FFI boundary
*/
export let TRANSFER_BUFFER: number;
let VERSION: number;
let MIN_COMPATIBLE_VERSION: number;
/**
* The latest ABI version that is supported by the current version of the
* library.
*
* When Languages are generated by the Tree-sitter CLI, they are
* assigned an ABI version number that corresponds to the current CLI version.
* The Tree-sitter library is generally backwards-compatible with languages
* generated using older CLI versions, but is not forwards-compatible.
*/
export let LANGUAGE_VERSION: number;
/**
* The earliest ABI version that is supported by the current version of the
* library.
*/
export let MIN_COMPATIBLE_VERSION: number;
/**
* A stateful object that is used to produce a {@link Tree} based on some
* source code.
*/
export class Parser {
/** @internal */
private [0] = 0; // Internal handle for WASM
@ -26,26 +90,33 @@ export class Parser {
/** @internal */
private [1] = 0; // Internal handle for WASM
/** @internal */
private language: Language | null = null;
/** @internal */
private logCallback: LogCallback | null = null;
/** The parser's current language. */
language: Language | null = null;
/**
* This must always be called before creating a Parser.
*
* You can optionally pass in options to configure the WASM module, the most common
* one being `locateFile` to help the module find the `.wasm` file.
*/
static async init(moduleOptions?: EmscriptenModule) {
setModule(await initializeBinding(moduleOptions));
TRANSFER_BUFFER = C._ts_init();
VERSION = C.getValue(TRANSFER_BUFFER, 'i32');
LANGUAGE_VERSION = C.getValue(TRANSFER_BUFFER, 'i32');
MIN_COMPATIBLE_VERSION = C.getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32');
}
/**
* Create a new parser.
*/
constructor() {
this.initialize();
}
/** @internal */
initialize() {
if (!checkModule()) {
throw new Error("cannot construct a Parser before calling `init()`");
@ -55,6 +126,7 @@ export class Parser {
this[1] = C.getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32');
}
/** Delete the parser, freeing its resources. */
delete() {
C._ts_parser_delete(this[0]);
C._free(this[1]);
@ -62,6 +134,15 @@ export class Parser {
this[1] = 0;
}
/**
* Set the language that the parser should use for parsing.
*
* If the language was not successfully assigned, an error will be thrown.
* This happens if the language was generated with an incompatible
* version of the Tree-sitter CLI. Check the language's version using
* {@link Language#version} and compare it to this library's
* {@link LANGUAGE_VERSION} and {@link MIN_COMPATIBLE_VERSION} constants.
*/
setLanguage(language: Language | null): this {
let address: number;
if (!language) {
@ -70,10 +151,10 @@ export class Parser {
} else if (language.constructor === Language) {
address = language[0];
const version = C._ts_language_version(address);
if (version < MIN_COMPATIBLE_VERSION || VERSION < version) {
if (version < MIN_COMPATIBLE_VERSION || LANGUAGE_VERSION < version) {
throw new Error(
`Incompatible language version ${version}. ` +
`Compatibility range ${MIN_COMPATIBLE_VERSION} through ${VERSION}.`
`Compatibility range ${MIN_COMPATIBLE_VERSION} through ${LANGUAGE_VERSION}.`
);
}
this.language = language;
@ -85,15 +166,27 @@ export class Parser {
return this;
}
getLanguage(): Language | null {
return this.language;
}
/**
* Parse a slice of UTF8 text.
*
* @param {string | ParseCallback} callback - The UTF8-encoded text to parse or a callback function.
*
* @param {Tree | null} [oldTree] - A previous syntax tree parsed from the same document. If the text of the
* document has changed since `oldTree` was created, then you must edit `oldTree` to match
* the new text using {@link Tree#edit}.
*
* @param {ParseOptions} [options] - Options for parsing the text.
* This can be used to set the included ranges, or a progress callback.
*
* @returns {Tree | null} A {@link Tree} if parsing succeeded, or `null` if:
* - The parser has not yet had a language assigned with {@link Parser#setLanguage}.
* - The progress callback returned true.
*/
parse(
callback: string | ParseCallback,
oldTree?: Tree | null,
options: ParseOptions = {}
): Tree {
options?: ParseOptions,
): Tree | null {
if (typeof callback === 'string') {
C.currentParseCallback = (index: number) => callback.slice(index);
} else if (typeof callback === 'function') {
@ -102,7 +195,7 @@ export class Parser {
throw new Error('Argument must be a string or a function');
}
if (options.progressCallback) {
if (options?.progressCallback) {
C.currentProgressCallback = options.progressCallback;
} else {
C.currentProgressCallback = null;
@ -118,7 +211,7 @@ export class Parser {
let rangeCount = 0;
let rangeAddress = 0;
if (options.includedRanges) {
if (options?.includedRanges) {
rangeCount = options.includedRanges.length;
rangeAddress = C._calloc(rangeCount, SIZE_OF_RANGE);
let address = rangeAddress;
@ -140,7 +233,7 @@ export class Parser {
C.currentParseCallback = null;
C.currentLogCallback = null;
C.currentProgressCallback = null;
throw new Error('Parsing failed');
return null;
}
if (!this.language) {
@ -154,10 +247,20 @@ export class Parser {
return result;
}
/**
* Instruct the parser to start the next parse from the beginning.
*
* If the parser previously failed because of a timeout, cancellation,
* or callback, then by default, it will resume where it left off on the
* next call to {@link Parser#parse} or other parsing functions.
* If you don't want to resume, and instead intend to use this parser to
* parse some other document, you must call `reset` first.
*/
reset(): void {
C._ts_parser_reset(this[0]);
}
/** Get the ranges of text that the parser will include when parsing. */
getIncludedRanges(): Range[] {
C._ts_parser_included_ranges_wasm(this[0]);
const count = C.getValue(TRANSFER_BUFFER, 'i32');
@ -176,14 +279,31 @@ export class Parser {
return result;
}
/**
* @deprecated since version 0.25.0, prefer passing a progress callback to {@link Parser#parse}
*
* Get the duration in microseconds that parsing is allowed to take.
*
* This is set via {@link Parser#setTimeoutMicros}.
*/
getTimeoutMicros(): number {
return C._ts_parser_timeout_micros(this[0]);
}
/**
* @deprecated since version 0.25.0, prefer passing a progress callback to {@link Parser#parse}
*
* Set the maximum duration in microseconds that parsing should be allowed
* to take before halting.
*
* If parsing takes longer than this, it will halt early, returning `null`.
* See {@link Parser#parse} for more information.
*/
setTimeoutMicros(timeout: number): void {
C._ts_parser_set_timeout_micros(this[0], 0, timeout);
}
/** Set the logging callback that a parser should use during parsing. */
setLogger(callback: LogCallback | boolean | null): this {
if (!callback) {
this.logCallback = null;
@ -195,6 +315,7 @@ export class Parser {
return this;
}
/** Get the parser's current logger. */
getLogger(): LogCallback | null {
return this.logCallback;
}