From be7716dfa7b92ccec4869aa81473d2aeb0652a5e Mon Sep 17 00:00:00 2001 From: Amaan Qureshi Date: Sun, 19 Jan 2025 15:15:01 -0500 Subject: [PATCH] feat(web)!: use the WASM module in the bindings, and not the other way around Parser is no longer the default export, but you *must* call `Parser.init()` before doing anything still --- lib/binding_web/.gitattributes | 1 + lib/binding_web/.gitignore | 3 + lib/binding_web/{wasm => lib}/exports.txt | 0 lib/binding_web/{wasm => lib}/imports.js | 14 +- lib/binding_web/lib/prefix.js | 4 + lib/binding_web/lib/tree-sitter.d.ts | 217 ++++++++++++++ lib/binding_web/package.json | 7 +- lib/binding_web/script/build-sourcemap.ts | 64 ---- .../script/check-artifacts-fresh.ts | 6 +- lib/binding_web/src/bindings.ts | 14 + lib/binding_web/src/constants.ts | 185 +----------- lib/binding_web/src/language.ts | 58 ++-- lib/binding_web/src/marshal.ts | 62 ++-- lib/binding_web/src/node.ts | 38 ++- lib/binding_web/src/parser.ts | 66 +++-- lib/binding_web/src/query.ts | 45 ++- lib/binding_web/src/tree.ts | 10 +- lib/binding_web/src/tree_cursor.ts | 2 +- lib/binding_web/test/helper.ts | 23 +- lib/binding_web/test/language.test.ts | 12 +- lib/binding_web/test/node.test.ts | 278 +++++++++--------- lib/binding_web/test/parser.test.ts | 33 ++- lib/binding_web/test/query.test.ts | 7 +- lib/binding_web/test/tree.test.ts | 2 +- lib/binding_web/wasm/prefix.js | 19 -- lib/binding_web/wasm/suffix.js | 23 -- xtask/src/build_wasm.rs | 76 ++--- xtask/src/check_wasm_exports.rs | 2 +- xtask/src/main.rs | 4 + 29 files changed, 613 insertions(+), 662 deletions(-) create mode 100644 lib/binding_web/.gitattributes rename lib/binding_web/{wasm => lib}/exports.txt (100%) rename lib/binding_web/{wasm => lib}/imports.js (64%) create mode 100644 lib/binding_web/lib/prefix.js create mode 100644 lib/binding_web/lib/tree-sitter.d.ts delete mode 100644 lib/binding_web/script/build-sourcemap.ts create mode 100644 lib/binding_web/src/bindings.ts delete mode 100644 lib/binding_web/wasm/prefix.js delete mode 100644 lib/binding_web/wasm/suffix.js diff --git a/lib/binding_web/.gitattributes b/lib/binding_web/.gitattributes new file mode 100644 index 00000000..3fa60d26 --- /dev/null +++ b/lib/binding_web/.gitattributes @@ -0,0 +1 @@ +/lib/tree-sitter.d.ts linguist-generated diff --git a/lib/binding_web/.gitignore b/lib/binding_web/.gitignore index bdb90ed4..50f811fc 100644 --- a/lib/binding_web/.gitignore +++ b/lib/binding_web/.gitignore @@ -1,5 +1,8 @@ debug/ dist/ +/lib/tree-sitter.js +/lib/tree-sitter.wasm +/lib/tree-sitter.wasm.map /tree-sitter.js /tree-sitter.js.map /tree-sitter.wasm diff --git a/lib/binding_web/wasm/exports.txt b/lib/binding_web/lib/exports.txt similarity index 100% rename from lib/binding_web/wasm/exports.txt rename to lib/binding_web/lib/exports.txt diff --git a/lib/binding_web/wasm/imports.js b/lib/binding_web/lib/imports.js similarity index 64% rename from lib/binding_web/wasm/imports.js rename to lib/binding_web/lib/imports.js index 89ed6d93..6e06eece 100644 --- a/lib/binding_web/wasm/imports.js +++ b/lib/binding_web/lib/imports.js @@ -7,7 +7,7 @@ mergeInto(LibraryManager.library, { lengthAddress, ) { const INPUT_BUFFER_SIZE = 10 * 1024; - const string = currentParseCallback(index, { row, column }); + const string = Module.currentParseCallback(index, { row, column }); if (typeof string === 'string') { setValue(lengthAddress, string.length, 'i32'); stringToUTF16(string, inputBufferAddress, INPUT_BUFFER_SIZE); @@ -17,22 +17,22 @@ mergeInto(LibraryManager.library, { }, tree_sitter_log_callback(isLexMessage, messageAddress) { - if (currentLogCallback) { + if (Module.currentLogCallback) { const message = UTF8ToString(messageAddress); - currentLogCallback(message, isLexMessage !== 0); + Module.currentLogCallback(message, isLexMessage !== 0); } }, tree_sitter_progress_callback(currentOffset) { - if (currentProgressCallback) { - return currentProgressCallback({ currentOffset }); + if (Module.currentProgressCallback) { + return Module.currentProgressCallback({ currentOffset }); } return false; }, tree_sitter_query_progress_callback(currentOffset) { - if (currentQueryProgressCallback) { - return currentQueryProgressCallback({ currentOffset }); + if (Module.currentQueryProgressCallback) { + return Module.currentQueryProgressCallback({ currentOffset }); } return false; }, diff --git a/lib/binding_web/lib/prefix.js b/lib/binding_web/lib/prefix.js new file mode 100644 index 00000000..bd953d5d --- /dev/null +++ b/lib/binding_web/lib/prefix.js @@ -0,0 +1,4 @@ +Module.currentQueryProgressCallback = null; +Module.currentProgressCallback = null; +Module.currentLogCallback = null; +Module.currentParseCallback = null; diff --git a/lib/binding_web/lib/tree-sitter.d.ts b/lib/binding_web/lib/tree-sitter.d.ts new file mode 100644 index 00000000..55e217e4 --- /dev/null +++ b/lib/binding_web/lib/tree-sitter.d.ts @@ -0,0 +1,217 @@ +// TypeScript bindings for emscripten-generated code. Automatically generated at compile time. +declare namespace RuntimeExports { + function AsciiToString(ptr: number): string; + function stringToUTF8(str: string, outPtr: number, maxBytesToWrite: number): number; + /** + * Given a pointer 'ptr' to a null-terminated UTF8-encoded string in the + * emscripten HEAP, returns a copy of that string as a Javascript String object. + * + * @param {number} ptr + * @param {number=} maxBytesToRead - An optional length that specifies the + * maximum number of bytes to read. You can omit this parameter to scan the + * string until the first 0 byte. If maxBytesToRead is passed, and the string + * at [ptr, ptr+maxBytesToReadr[ contains a null byte in the middle, then the + * string will cut short at that byte index (i.e. maxBytesToRead will not + * produce a string of exact length [ptr, ptr+maxBytesToRead[) N.B. mixing + * frequent uses of UTF8ToString() with and without maxBytesToRead may throw + * JS JIT optimizations off, so it is worth to consider consistently using one + * @return {string} + */ + function UTF8ToString(ptr: number, maxBytesToRead?: number): string; + function lengthBytesUTF8(str: string): number; + function stringToUTF16(str: string, outPtr: number, maxBytesToWrite: number): number; + /** + * @param {string=} libName + * @param {Object=} localScope + * @param {number=} handle + */ + function loadWebAssemblyModule( + binary: Uint8Array, + flags: { + allowUndefined?: boolean, + loadAsync?: boolean, + global?: boolean, + nodelete?: boolean; + }, + libName?: string, + localScope?: Record, + handle?: number + ): Promise number>>; + /** + * @param {number} ptr + * @param {string} type + */ + function getValue(ptr: number, type?: string): number; + /** + * @param {number} ptr + * @param {number} value + * @param {string} type + */ + function setValue(ptr: number, value: number, type?: string): void; + let currentParseCallback: ((index: number, position: {row: number, column: number}) => string | undefined) | null; + let currentLogCallback: ((message: string, isLex: boolean) => void) | null; + let currentProgressCallback: ((state: {currentOffset: number}) => void) | null; + let currentQueryProgressCallback: ((state: {currentOffset: number}) => void) | null; + let HEAPF32: Float32Array; + let HEAPF64: Float64Array; + let HEAP_DATA_VIEW: DataView; + let HEAP8: Int8Array + let HEAPU8: Uint8Array; + let HEAP16: Int16Array; + let HEAPU16: Uint16Array; + let HEAP32: Int32Array; + let HEAPU32: Uint32Array; + let HEAP64: BigInt64Array; + let HEAPU64: BigUint64Array; +} +interface WasmModule { + _malloc(_0: number): number; + _calloc(_0: number, _1: number): number; + _realloc(_0: number, _1: number): number; + _free(_0: number): void; + _ts_language_symbol_count(_0: number): number; + _ts_language_state_count(_0: number): number; + _ts_language_version(_0: number): number; + _ts_language_name(_0: number): number; + _ts_language_field_count(_0: number): number; + _ts_language_next_state(_0: number, _1: number, _2: number): number; + _ts_language_symbol_name(_0: number, _1: number): number; + _ts_language_symbol_for_name(_0: number, _1: number, _2: number, _3: number): number; + _strncmp(_0: number, _1: number, _2: number): number; + _ts_language_symbol_type(_0: number, _1: number): number; + _ts_language_field_name_for_id(_0: number, _1: number): number; + _ts_lookahead_iterator_new(_0: number, _1: number): number; + _ts_lookahead_iterator_delete(_0: number): void; + _ts_lookahead_iterator_reset_state(_0: number, _1: number): number; + _ts_lookahead_iterator_reset(_0: number, _1: number, _2: number): number; + _ts_lookahead_iterator_next(_0: number): number; + _ts_lookahead_iterator_current_symbol(_0: number): number; + _memset(_0: number, _1: number, _2: number): number; + _memcpy(_0: number, _1: number, _2: number): number; + _ts_parser_delete(_0: number): void; + _ts_parser_reset(_0: number): void; + _ts_parser_set_language(_0: number, _1: number): number; + _ts_parser_timeout_micros(_0: number): number; + _ts_parser_set_timeout_micros(_0: number, _1: number, _2: number): void; + _ts_parser_set_included_ranges(_0: number, _1: number, _2: number): number; + _memmove(_0: number, _1: number, _2: number): number; + _memcmp(_0: number, _1: number, _2: number): number; + _ts_query_new(_0: number, _1: number, _2: number, _3: number, _4: number): number; + _ts_query_delete(_0: number): void; + _iswspace(_0: number): number; + _iswalnum(_0: number): number; + _ts_query_pattern_count(_0: number): number; + _ts_query_capture_count(_0: number): number; + _ts_query_string_count(_0: number): number; + _ts_query_capture_name_for_id(_0: number, _1: number, _2: number): number; + _ts_query_capture_quantifier_for_id(_0: number, _1: number, _2: number): number; + _ts_query_string_value_for_id(_0: number, _1: number, _2: number): number; + _ts_query_predicates_for_pattern(_0: number, _1: number, _2: number): number; + _ts_query_start_byte_for_pattern(_0: number, _1: number): number; + _ts_query_end_byte_for_pattern(_0: number, _1: number): number; + _ts_query_is_pattern_rooted(_0: number, _1: number): number; + _ts_query_is_pattern_non_local(_0: number, _1: number): number; + _ts_query_is_pattern_guaranteed_at_step(_0: number, _1: number): number; + _ts_query_disable_capture(_0: number, _1: number, _2: number): void; + _ts_query_disable_pattern(_0: number, _1: number): void; + _ts_tree_copy(_0: number): number; + _ts_tree_delete(_0: number): void; + _ts_init(): number; + _ts_parser_new_wasm(): void; + _ts_parser_enable_logger_wasm(_0: number, _1: number): void; + _ts_parser_parse_wasm(_0: number, _1: number, _2: number, _3: number, _4: number): number; + _ts_parser_included_ranges_wasm(_0: number): void; + _ts_language_type_is_named_wasm(_0: number, _1: number): number; + _ts_language_type_is_visible_wasm(_0: number, _1: number): number; + _ts_language_supertypes_wasm(_0: number): void; + _ts_language_subtypes_wasm(_0: number, _1: number): void; + _ts_tree_root_node_wasm(_0: number): void; + _ts_tree_root_node_with_offset_wasm(_0: number): void; + _ts_tree_edit_wasm(_0: number): void; + _ts_tree_included_ranges_wasm(_0: number): void; + _ts_tree_get_changed_ranges_wasm(_0: number, _1: number): void; + _ts_tree_cursor_new_wasm(_0: number): void; + _ts_tree_cursor_copy_wasm(_0: number): void; + _ts_tree_cursor_delete_wasm(_0: number): void; + _ts_tree_cursor_reset_wasm(_0: number): void; + _ts_tree_cursor_reset_to_wasm(_0: number, _1: number): void; + _ts_tree_cursor_goto_first_child_wasm(_0: number): number; + _ts_tree_cursor_goto_last_child_wasm(_0: number): number; + _ts_tree_cursor_goto_first_child_for_index_wasm(_0: number): number; + _ts_tree_cursor_goto_first_child_for_position_wasm(_0: number): number; + _ts_tree_cursor_goto_next_sibling_wasm(_0: number): number; + _ts_tree_cursor_goto_previous_sibling_wasm(_0: number): number; + _ts_tree_cursor_goto_descendant_wasm(_0: number, _1: number): void; + _ts_tree_cursor_goto_parent_wasm(_0: number): number; + _ts_tree_cursor_current_node_type_id_wasm(_0: number): number; + _ts_tree_cursor_current_node_state_id_wasm(_0: number): number; + _ts_tree_cursor_current_node_is_named_wasm(_0: number): number; + _ts_tree_cursor_current_node_is_missing_wasm(_0: number): number; + _ts_tree_cursor_current_node_id_wasm(_0: number): number; + _ts_tree_cursor_start_position_wasm(_0: number): void; + _ts_tree_cursor_end_position_wasm(_0: number): void; + _ts_tree_cursor_start_index_wasm(_0: number): number; + _ts_tree_cursor_end_index_wasm(_0: number): number; + _ts_tree_cursor_current_field_id_wasm(_0: number): number; + _ts_tree_cursor_current_depth_wasm(_0: number): number; + _ts_tree_cursor_current_descendant_index_wasm(_0: number): number; + _ts_tree_cursor_current_node_wasm(_0: number): void; + _ts_node_symbol_wasm(_0: number): number; + _ts_node_field_name_for_child_wasm(_0: number, _1: number): number; + _ts_node_field_name_for_named_child_wasm(_0: number, _1: number): number; + _ts_node_children_by_field_id_wasm(_0: number, _1: number): void; + _ts_node_first_child_for_byte_wasm(_0: number): void; + _ts_node_first_named_child_for_byte_wasm(_0: number): void; + _ts_node_grammar_symbol_wasm(_0: number): number; + _ts_node_child_count_wasm(_0: number): number; + _ts_node_named_child_count_wasm(_0: number): number; + _ts_node_child_wasm(_0: number, _1: number): void; + _ts_node_named_child_wasm(_0: number, _1: number): void; + _ts_node_child_by_field_id_wasm(_0: number, _1: number): void; + _ts_node_next_sibling_wasm(_0: number): void; + _ts_node_prev_sibling_wasm(_0: number): void; + _ts_node_next_named_sibling_wasm(_0: number): void; + _ts_node_prev_named_sibling_wasm(_0: number): void; + _ts_node_descendant_count_wasm(_0: number): number; + _ts_node_parent_wasm(_0: number): void; + _ts_node_descendant_for_index_wasm(_0: number): void; + _ts_node_named_descendant_for_index_wasm(_0: number): void; + _ts_node_descendant_for_position_wasm(_0: number): void; + _ts_node_named_descendant_for_position_wasm(_0: number): void; + _ts_node_start_point_wasm(_0: number): void; + _ts_node_end_point_wasm(_0: number): void; + _ts_node_start_index_wasm(_0: number): number; + _ts_node_end_index_wasm(_0: number): number; + _ts_node_to_string_wasm(_0: number): number; + _ts_node_children_wasm(_0: number): void; + _ts_node_named_children_wasm(_0: number): void; + _ts_node_descendants_of_type_wasm(_0: number, _1: number, _2: number, _3: number, _4: number, _5: number, _6: number): void; + _ts_node_is_named_wasm(_0: number): number; + _ts_node_has_changes_wasm(_0: number): number; + _ts_node_has_error_wasm(_0: number): number; + _ts_node_is_error_wasm(_0: number): number; + _ts_node_is_missing_wasm(_0: number): number; + _ts_node_is_extra_wasm(_0: number): number; + _ts_node_parse_state_wasm(_0: number): number; + _ts_node_next_parse_state_wasm(_0: number): number; + _ts_query_matches_wasm(_0: number, _1: number, _2: number, _3: number, _4: number, _5: number, _6: number, _7: number, _8: number, _9: number, _10: number): void; + _ts_query_captures_wasm(_0: number, _1: number, _2: number, _3: number, _4: number, _5: number, _6: number, _7: number, _8: number, _9: number, _10: number): void; + _iswalpha(_0: number): number; + _iswblank(_0: number): number; + _iswdigit(_0: number): number; + _iswlower(_0: number): number; + _iswupper(_0: number): number; + _iswxdigit(_0: number): number; + _memchr(_0: number, _1: number, _2: number): number; + _strlen(_0: number): number; + _strcmp(_0: number, _1: number): number; + _strncat(_0: number, _1: number, _2: number): number; + _strncpy(_0: number, _1: number, _2: number): number; + _towlower(_0: number): number; + _towupper(_0: number): number; + _orig$ts_parser_timeout_micros(_0: number): bigint; + _orig$ts_parser_set_timeout_micros(_0: number, _1: bigint): void; +} + +export type MainModule = WasmModule & typeof RuntimeExports; +export default function MainModuleFactory (options?: EmscriptenModule): Promise; diff --git a/lib/binding_web/package.json b/lib/binding_web/package.json index 8425a274..f62556f3 100644 --- a/lib/binding_web/package.json +++ b/lib/binding_web/package.json @@ -50,12 +50,11 @@ "vitest": "^3.0.2" }, "scripts": { - "build:ts": "esbuild src/index.ts --bundle --platform=neutral --format=cjs --global-name=TreeSitterImpl --outfile=dist/tree-sitter.js --external:fs/* --external:fs/promises --sourcemap --sources-content=true --keep-names", + "build:ts": "esbuild src/index.ts --bundle --format=esm --platform=node --global-name=TreeSitterImpl --outfile=tree-sitter.js --external:fs/* --external:fs/promises --sourcemap --sources-content=true --keep-names && cp lib/*wasm* .", "build:wasm": "cd ../../ && cargo xtask build-wasm", "build:wasm:debug": "cd ../../ && cargo xtask build-wasm --debug", - "build:sourcemap": "tsx script/build-sourcemap.ts", - "build": "npm run build:ts && npm run build:wasm && npm run build:sourcemap", - "build:debug": "npm run build:ts && npm run build:wasm:debug && cp debug/* . && npm run build:sourcemap", + "build": "npm run build:wasm && npm run build:ts", + "build:debug": "npm run build:wasm:debug && npm run build:ts && cp debug/* .", "lint": "eslint src/*.ts script/*.ts", "lint:fix": "eslint src/*.ts script/*.ts --fix", "test": "vitest run", diff --git a/lib/binding_web/script/build-sourcemap.ts b/lib/binding_web/script/build-sourcemap.ts deleted file mode 100644 index e35e0d31..00000000 --- a/lib/binding_web/script/build-sourcemap.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { readFileSync, writeFileSync } from 'fs'; -import { SourceMapGenerator, SourceMapConsumer, RawSourceMap } from 'source-map'; - -async function fixSourceMap() { - const distMap = JSON.parse(readFileSync('dist/tree-sitter.js.map', 'utf8')) as RawSourceMap; - const distJs = readFileSync('dist/tree-sitter.js', 'utf8').split('\n'); - const finalJs = readFileSync('tree-sitter.js', 'utf8').split('\n'); - - const lineMap = new Map(); - - let currentFinalLine = 0; - for (let distLine = 0; distLine < distJs.length; distLine++) { - const line = distJs[distLine].trim(); - if (!line) continue; - - for (let finalLine = currentFinalLine; finalLine < finalJs.length; finalLine++) { - if (finalJs[finalLine].trim() === line) { - lineMap.set(distLine + 1, finalLine + 1); - currentFinalLine = finalLine; - break; - } - } - } - - const consumer = await new SourceMapConsumer(distMap); - const generator = new SourceMapGenerator({ - file: 'tree-sitter.js', - sourceRoot: '', - }); - - consumer.eachMapping(mapping => { - const finalLine = lineMap.get(mapping.generatedLine); - if (finalLine) { - generator.addMapping({ - generated: { - line: finalLine, - column: mapping.generatedColumn, - }, - original: { - line: mapping.originalLine, - column: mapping.originalColumn, - }, - // Fix the source path to be relative to binding_web - source: `src/${mapping.source.split('/').pop()}`, - name: mapping.name, - }); - } - }); - - for (const source of consumer.sources) { - const content = consumer.sourceContentFor(source); - if (content) { - generator.setSourceContent( - `src/${source.split('/').pop()}`, - content, - ); - } - } - - consumer.destroy(); - writeFileSync('tree-sitter.js.map', generator.toString()); -} - -fixSourceMap().catch(console.error); diff --git a/lib/binding_web/script/check-artifacts-fresh.ts b/lib/binding_web/script/check-artifacts-fresh.ts index 7cf91788..89c61939 100755 --- a/lib/binding_web/script/check-artifacts-fresh.ts +++ b/lib/binding_web/script/check-artifacts-fresh.ts @@ -17,9 +17,9 @@ const inputFiles = [ '../src/tree.ts', '../src/tree_cursor.ts', '../dist/tree-sitter.js', - '../wasm/exports.txt', - '../wasm/imports.js', - '../wasm/prefix.js', + '../lib/exports.txt', + '../lib/imports.js', + '../lib/prefix.js', ...listFiles('../../include/tree_sitter'), ...listFiles('../../src'), ]; diff --git a/lib/binding_web/src/bindings.ts b/lib/binding_web/src/bindings.ts new file mode 100644 index 00000000..65940a93 --- /dev/null +++ b/lib/binding_web/src/bindings.ts @@ -0,0 +1,14 @@ +import createModule, { type MainModule } from '../lib/tree-sitter'; + +export let Module: MainModule | null = null; + +export async function initializeBinding(moduleOptions: EmscriptenModule): Promise { + if (!Module) { + Module = await createModule(moduleOptions); + } + return Module; +} + +export function checkModule(): boolean { + return !!Module; +} diff --git a/lib/binding_web/src/constants.ts b/lib/binding_web/src/constants.ts index a0681946..9b7aa086 100644 --- a/lib/binding_web/src/constants.ts +++ b/lib/binding_web/src/constants.ts @@ -1,4 +1,4 @@ -import { CaptureQuantifier } from './query'; +import { type MainModule } from '../lib/tree-sitter'; export interface Point { row: number; @@ -56,183 +56,8 @@ export function isPoint(point?: Point): point is Point { ); } -// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -export const C: EmscriptenModule & { - // Global - _ts_init(): number; +export function setModule(module: MainModule) { + C = module; +} - // Libc - _malloc(size: number): number; - _calloc(count: number, size: number): number; - _free(ptr: number): void; - - // Parser - _ts_parser_new_wasm(): void; - _ts_parser_delete(address: number): void; - _ts_parser_set_language(parserAddress: number, languageAddress: number): void; - _ts_parser_enable_logger_wasm(address: number, enabled: number): void; - _ts_parser_parse_wasm( - address: number, - payload: number, - oldTreeAddress: number, - rangeAddress: number, - rangeCount: number - ): number; - _ts_parser_reset(address: number): void; - _ts_parser_timeout_micros(address: number): number; - _ts_parser_set_timeout_micros(address: number, timeout: number): void; - _ts_parser_included_ranges_wasm(address: number): void; - - // Language - _ts_language_symbol_count(address: number): number; - _ts_language_symbol_name(address: number, index: number): number; - _ts_language_symbol_type(address: number, index: number): number; - _ts_language_field_count(address: number): number; - _ts_language_field_name_for_id(address: number, id: number): number; - _ts_language_name(address: number): number; - _ts_language_version(address: number): number; - _ts_language_state_count(address: number): number; - _ts_language_symbol_for_name(address: number, typeAddress: number, typeLength: number, named: boolean): number; - _ts_language_type_is_named_wasm(address: number, typeId: number): number; - _ts_language_type_is_visible_wasm(address: number, typeId: number): number; - _ts_language_next_state(address: number, stateId: number, typeId: number): number; - _ts_language_supertypes_wasm(address: number): void; - _ts_language_subtypes_wasm(address: number, supertype: number): void; - - // Tree - _ts_tree_copy(tree: number): number; - _ts_tree_delete(tree: number): void; - _ts_tree_edit_wasm(tree: number): void; - _ts_tree_root_node_wasm(tree: number): void; - _ts_tree_root_node_with_offset_wasm(tree: number): void; - _ts_tree_get_changed_ranges_wasm(self: number, other: number): void; - _ts_tree_included_ranges_wasm(self: number): void; - - // Node - _ts_node_symbol_wasm(tree: number): number; - _ts_node_grammar_symbol_wasm(tree: number): number; - _ts_node_end_point_wasm(tree: number): void; - _ts_node_end_index_wasm(tree: number): number; - _ts_node_parse_state_wasm(tree: number): number; - _ts_node_next_parse_state_wasm(tree: number): number; - _ts_node_is_named_wasm(tree: number): number; - _ts_node_has_error_wasm(tree: number): number; - _ts_node_has_changes_wasm(tree: number): number; - _ts_node_is_error_wasm(tree: number): number; - _ts_node_is_missing_wasm(tree: number): number; - _ts_node_is_extra_wasm(tree: number): number; - _ts_node_child_wasm(tree: number, index: number): void; - _ts_node_named_child_wasm(tree: number, index: number): void; - _ts_node_child_by_field_id_wasm(tree: number, fieldId: number): void; - _ts_node_field_name_for_child_wasm(tree: number, index: number): number; - _ts_node_field_name_for_named_child_wasm(tree: number, index: number): number; - _ts_node_children_by_field_id_wasm(tree: number, fieldId: number): void; - _ts_node_first_child_for_byte_wasm(tree: number): void; - _ts_node_first_named_child_for_byte_wasm(tree: number): void; - _ts_node_child_count_wasm(tree: number): number; - _ts_node_named_child_count_wasm(tree: number): number; - _ts_node_children_wasm(tree: number): void; - _ts_node_named_children_wasm(tree: number): void; - _ts_node_descendants_of_type_wasm( - tree: number, - symbolsAddress: number, - symbolCount: number, - startRow: number, - startColumn: number, - endRow: number, - endColumn: number - ): void; - _ts_node_next_sibling_wasm(tree: number): void; - _ts_node_prev_sibling_wasm(tree: number): void; - _ts_node_next_named_sibling_wasm(tree: number): void; - _ts_node_prev_named_sibling_wasm(tree: number): void; - _ts_node_descendant_count_wasm(tree: number): number; - _ts_node_parent_wasm(tree: number): void; - _ts_node_descendant_for_index_wasm(tree: number): void; - _ts_node_named_descendant_for_index_wasm(tree: number): void; - _ts_node_descendant_for_position_wasm(tree: number): void; - _ts_node_named_descendant_for_position_wasm(tree: number): void; - _ts_tree_cursor_new_wasm(tree: number): void; - _ts_node_to_string_wasm(tree: number): number; - - // TreeCursor - _ts_tree_cursor_copy_wasm(cursor: number): void; - _ts_tree_cursor_delete_wasm(cursor: number): void; - _ts_tree_cursor_reset_wasm(cursor: number): void; - _ts_tree_cursor_reset_to_wasm(cursor: number, other: number): void; - _ts_tree_cursor_current_node_type_id_wasm(cursor: number): number; - _ts_tree_cursor_current_node_state_id_wasm(cursor: number): number; - _ts_tree_cursor_current_node_id_wasm(cursor: number): number; - _ts_tree_cursor_current_node_is_named_wasm(cursor: number): number; - _ts_tree_cursor_current_node_is_missing_wasm(cursor: number): number; - _ts_tree_cursor_start_index_wasm(cursor: number): number; - _ts_tree_cursor_end_index_wasm(cursor: number): number; - _ts_tree_cursor_start_position_wasm(cursor: number): void; - _ts_tree_cursor_end_position_wasm(cursor: number): void; - _ts_tree_cursor_current_node_wasm(cursor: number): void; - _ts_tree_cursor_current_field_id_wasm(cursor: number): number; - _ts_tree_cursor_current_depth_wasm(cursor: number): number; - _ts_tree_cursor_current_descendant_index_wasm(cursor: number): number; - _ts_tree_cursor_goto_first_child_wasm(cursor: number): number; - _ts_tree_cursor_goto_last_child_wasm(cursor: number): number; - _ts_tree_cursor_goto_first_child_for_index_wasm(cursor: number): number; - _ts_tree_cursor_goto_first_child_for_position_wasm(cursor: number): number; - _ts_tree_cursor_goto_next_sibling_wasm(cursor: number): number; - _ts_tree_cursor_goto_previous_sibling_wasm(cursor: number): number; - _ts_tree_cursor_goto_descendant_wasm(cursor: number, index: number): void; - _ts_tree_cursor_goto_parent_wasm(cursor: number): number; - - // Query - _ts_query_new(languageAddress: number, sourceAddress: number, sourceLength: number, errorOffset: number, errorType: number): number; - _ts_query_string_count(address: number): number; - _ts_query_capture_count(address: number): number; - _ts_query_pattern_count(address: number): number; - _ts_query_capture_name_for_id(address: number, id: number, buffer: number): number; - _ts_query_capture_quantifier_for_id(address: number, patternId: number, captureId: number): CaptureQuantifier; - _ts_query_string_value_for_id(address: number, id: number, buffer: number): number; - _ts_query_predicates_for_pattern(address: number, patternId: number, buffer: number): number; - _ts_query_delete(address: number): void; - _ts_query_matches_wasm( - address: number, - treeAddress: number, - startRow: number, - startColumn: number, - endRow: number, - endColumn: number, - startIndex: number, - endIndex: number, - matchLimit: number, - maxStartDepth: number, - timeoutMicros: number - ): void; - _ts_query_captures_wasm( - address: number, - treeAddress: number, - startRow: number, - startColumn: number, - endRow: number, - endColumn: number, - startIndex: number, - endIndex: number, - matchLimit: number, - maxStartDepth: number, - timeoutMicros: number - ): void; - _ts_query_disable_capture(address: number, nameAddress: number, nameLength: number): void; - _ts_query_disable_pattern(address: number, patternIndex: number): void; - _ts_query_start_byte_for_pattern(address: number, patternIndex: number): number; - _ts_query_end_byte_for_pattern(address: number, patternIndex: number): number; - _ts_query_is_pattern_non_local(address: number, patternIndex: number): number; - _ts_query_is_pattern_rooted(address: number, patternIndex: number): number; - _ts_query_is_pattern_guaranteed_at_step(address: number, patternIndex: number, stepIndex: number): number; - - // LookaheadIterator - _ts_lookahead_iterator_new(address: number, stateId: number): number; - _ts_lookahead_iterator_current_symbol(address: number): number; - _ts_lookahead_iterator_delete(address: number): void; - _ts_lookahead_iterator_reset_state(address: number, stateId: number): boolean; - _ts_lookahead_iterator_reset(address: number, languageAddress: number, stateId: number): boolean; - _ts_lookahead_iterator_next(address: number): boolean; - - // @ts-expect-error Module is defined after compilation -} = Module; +export let C: MainModule; diff --git a/lib/binding_web/src/language.ts b/lib/binding_web/src/language.ts index 5eacc968..80d351f8 100644 --- a/lib/binding_web/src/language.ts +++ b/lib/binding_web/src/language.ts @@ -1,11 +1,9 @@ -import { INTERNAL, Internal, assertInternal, SIZE_OF_INT, SIZE_OF_SHORT, C } from './constants'; +import { C, INTERNAL, Internal, assertInternal, SIZE_OF_INT, SIZE_OF_SHORT } from './constants'; import { LookaheadIterator } from './lookahead_iterator'; import { Node } from './node'; import { TRANSFER_BUFFER } from './parser'; import { CaptureQuantifier, Predicate, PredicateStep, Properties, Query, TextPredicate } from './query'; -declare const loadWebAssemblyModule: (bytes: Uint8Array, options: { loadAsync: boolean }) => Promise number>>; - const PREDICATE_STEP_TYPE_CAPTURE = 1; const PREDICATE_STEP_TYPE_STRING = 2; @@ -23,14 +21,14 @@ export class Language { this.types = new Array(C._ts_language_symbol_count(this[0])); for (let i = 0, n = this.types.length; i < n; i++) { if (C._ts_language_symbol_type(this[0], i) < 2) { - this.types[i] = UTF8ToString(C._ts_language_symbol_name(this[0], i)); + this.types[i] = C.UTF8ToString(C._ts_language_symbol_name(this[0], i)); } } this.fields = new Array(C._ts_language_field_count(this[0]) + 1); for (let i = 0, n = this.fields.length; i < n; i++) { const fieldName = C._ts_language_field_name_for_id(this[0], i); if (fieldName !== 0) { - this.fields[i] = UTF8ToString(fieldName); + this.fields[i] = C.UTF8ToString(fieldName); } else { this.fields[i] = null; } @@ -40,7 +38,7 @@ export class Language { get name(): string | null { const ptr = C._ts_language_name(this[0]); if (ptr === 0) return null; - return UTF8ToString(ptr); + return C.UTF8ToString(ptr); } get version(): number { @@ -65,10 +63,10 @@ export class Language { } idForNodeType(type: string, named: boolean): number | null { - const typeLength = lengthBytesUTF8(type); + const typeLength = C.lengthBytesUTF8(type); const typeAddress = C._malloc(typeLength + 1); - stringToUTF8(type, typeAddress, typeLength + 1); - const result = C._ts_language_symbol_for_name(this[0], typeAddress, typeLength, named); + C.stringToUTF8(type, typeAddress, typeLength + 1); + const result = C._ts_language_symbol_for_name(this[0], typeAddress, typeLength, named ? 1 : 0); C._free(typeAddress); return result || null; } @@ -79,7 +77,7 @@ export class Language { nodeTypeForId(typeId: number): string | null { const name = C._ts_language_symbol_name(this[0], typeId); - return name ? UTF8ToString(name) : null; + return name ? C.UTF8ToString(name) : null; } nodeTypeIsNamed(typeId: number): boolean { @@ -92,14 +90,14 @@ export class Language { get supertypes(): number[] { C._ts_language_supertypes_wasm(this[0]); - const count = getValue(TRANSFER_BUFFER, 'i32'); - const buffer = getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32'); + const count = C.getValue(TRANSFER_BUFFER, 'i32'); + const buffer = C.getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32'); const result = new Array(count); if (count > 0) { let address = buffer; for (let i = 0; i < count; i++) { - result[i] = getValue(address, 'i16'); + result[i] = C.getValue(address, 'i16'); address += SIZE_OF_SHORT; } } @@ -109,14 +107,14 @@ export class Language { subtypes(supertype: number): number[] { C._ts_language_subtypes_wasm(this[0], supertype); - const count = getValue(TRANSFER_BUFFER, 'i32'); - const buffer = getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32'); + const count = C.getValue(TRANSFER_BUFFER, 'i32'); + const buffer = C.getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32'); const result = new Array(count); if (count > 0) { let address = buffer; for (let i = 0; i < count; i++) { - result[i] = getValue(address, 'i16'); + result[i] = C.getValue(address, 'i16'); address += SIZE_OF_SHORT; } } @@ -135,9 +133,9 @@ export class Language { } query(source: string): Query { - const sourceLength = lengthBytesUTF8(source); + const sourceLength = C.lengthBytesUTF8(source); const sourceAddress = C._malloc(sourceLength + 1); - stringToUTF8(source, sourceAddress, sourceLength + 1); + C.stringToUTF8(source, sourceAddress, sourceLength + 1); const address = C._ts_query_new( this[0], sourceAddress, @@ -147,9 +145,9 @@ export class Language { ); if (!address) { - const errorId = getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32'); - const errorByte = getValue(TRANSFER_BUFFER, 'i32'); - const errorIndex = UTF8ToString(sourceAddress, errorByte).length; + const errorId = C.getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32'); + const errorByte = C.getValue(TRANSFER_BUFFER, 'i32'); + const errorIndex = C.UTF8ToString(sourceAddress, errorByte).length; const suffix = source.slice(errorIndex, errorIndex + 100).split('\n')[0]; let word = suffix.match(QUERY_WORD_REGEX)?.[0] ?? ''; let error: Error; @@ -195,15 +193,15 @@ export class Language { i, TRANSFER_BUFFER ); - const nameLength = getValue(TRANSFER_BUFFER, 'i32'); - captureNames[i] = UTF8ToString(nameAddress, nameLength); + const nameLength = C.getValue(TRANSFER_BUFFER, 'i32'); + captureNames[i] = C.UTF8ToString(nameAddress, nameLength); } for (let i = 0; i < patternCount; i++) { const captureQuantifiersArray = new Array(captureCount); for (let j = 0; j < captureCount; j++) { const quantifier = C._ts_query_capture_quantifier_for_id(address, i, j); - captureQuantifiersArray[j] = quantifier; + captureQuantifiersArray[j] = quantifier as CaptureQuantifier; } captureQuantifiers[i] = captureQuantifiersArray; } @@ -214,8 +212,8 @@ export class Language { i, TRANSFER_BUFFER ); - const nameLength = getValue(TRANSFER_BUFFER, 'i32'); - stringValues[i] = UTF8ToString(valueAddress, nameLength); + const nameLength = C.getValue(TRANSFER_BUFFER, 'i32'); + stringValues[i] = C.UTF8ToString(valueAddress, nameLength); } const setProperties = new Array(patternCount); @@ -230,7 +228,7 @@ export class Language { i, TRANSFER_BUFFER ); - const stepCount = getValue(TRANSFER_BUFFER, 'i32'); + const stepCount = C.getValue(TRANSFER_BUFFER, 'i32'); predicates[i] = []; textPredicates[i] = []; @@ -238,9 +236,9 @@ export class Language { const steps: PredicateStep[] = []; let stepAddress = predicatesAddress; for (let j = 0; j < stepCount; j++) { - const stepType = getValue(stepAddress, 'i32'); + const stepType = C.getValue(stepAddress, 'i32'); stepAddress += SIZE_OF_INT; - const stepValueId: number = getValue(stepAddress, 'i32'); + const stepValueId: number = C.getValue(stepAddress, 'i32'); stepAddress += SIZE_OF_INT; if (stepType === PREDICATE_STEP_TYPE_CAPTURE) { @@ -469,7 +467,7 @@ export class Language { } } - const mod = await loadWebAssemblyModule(await bytes, { loadAsync: true }); + const mod = await C.loadWebAssemblyModule(await bytes, { loadAsync: true }); const symbolNames = Object.keys(mod); const functionName = symbolNames.find((key) => LANGUAGE_FUNCTION_REGEX.test(key) && !key.includes('external_scanner_')); diff --git a/lib/binding_web/src/marshal.ts b/lib/binding_web/src/marshal.ts index 0661cc8c..44efa233 100644 --- a/lib/binding_web/src/marshal.ts +++ b/lib/binding_web/src/marshal.ts @@ -1,4 +1,4 @@ -import { Edit, INTERNAL, Point, Range, SIZE_OF_INT, SIZE_OF_NODE, SIZE_OF_POINT } from "./constants"; +import { Edit, INTERNAL, Point, Range, SIZE_OF_INT, SIZE_OF_NODE, SIZE_OF_POINT, C } from "./constants"; import { Node } from "./node"; import { Tree } from "./tree"; import { Query } from "./query"; @@ -7,7 +7,7 @@ import { TRANSFER_BUFFER } from "./parser"; export function unmarshalCaptures(query: Query, tree: Tree, address: number, result: {name: string, node: Node}[]) { for (let i = 0, n = result.length; i < n; i++) { - const captureIndex = getValue(address, 'i32'); + const captureIndex = C.getValue(address, 'i32'); address += SIZE_OF_INT; const node = unmarshalNode(tree, address)!; address += SIZE_OF_NODE; @@ -18,29 +18,29 @@ export function unmarshalCaptures(query: Query, tree: Tree, address: number, res export function marshalNode(node: Node) { let address = TRANSFER_BUFFER; - setValue(address, node.id, 'i32'); + C.setValue(address, node.id, 'i32'); address += SIZE_OF_INT; - setValue(address, node.startIndex, 'i32'); + C.setValue(address, node.startIndex, 'i32'); address += SIZE_OF_INT; - setValue(address, node.startPosition.row, 'i32'); + C.setValue(address, node.startPosition.row, 'i32'); address += SIZE_OF_INT; - setValue(address, node.startPosition.column, 'i32'); + C.setValue(address, node.startPosition.column, 'i32'); address += SIZE_OF_INT; - setValue(address, node[0], 'i32'); + C.setValue(address, node[0], 'i32'); } export function unmarshalNode(tree: Tree, address = TRANSFER_BUFFER): Node | null { - const id = getValue(address, 'i32'); + const id = C.getValue(address, 'i32'); address += SIZE_OF_INT; if (id === 0) return null; - const index = getValue(address, 'i32'); + const index = C.getValue(address, 'i32'); address += SIZE_OF_INT; - const row = getValue(address, 'i32'); + const row = C.getValue(address, 'i32'); address += SIZE_OF_INT; - const column = getValue(address, 'i32'); + const column = C.getValue(address, 'i32'); address += SIZE_OF_INT; - const other = getValue(address, 'i32'); + const other = C.getValue(address, 'i32'); const result = new Node(INTERNAL, { id, @@ -54,28 +54,28 @@ export function unmarshalNode(tree: Tree, address = TRANSFER_BUFFER): Node | nul } export function marshalTreeCursor(cursor: TreeCursor, address = TRANSFER_BUFFER) { - setValue(address + 0 * SIZE_OF_INT, cursor[0], 'i32'); - setValue(address + 1 * SIZE_OF_INT, cursor[1], 'i32'); - setValue(address + 2 * SIZE_OF_INT, cursor[2], 'i32'); - setValue(address + 3 * SIZE_OF_INT, cursor[3], 'i32'); + C.setValue(address + 0 * SIZE_OF_INT, cursor[0], 'i32'); + C.setValue(address + 1 * SIZE_OF_INT, cursor[1], 'i32'); + C.setValue(address + 2 * SIZE_OF_INT, cursor[2], 'i32'); + C.setValue(address + 3 * SIZE_OF_INT, cursor[3], 'i32'); } export function unmarshalTreeCursor(cursor: TreeCursor) { - cursor[0] = getValue(TRANSFER_BUFFER + 0 * SIZE_OF_INT, 'i32'); - cursor[1] = getValue(TRANSFER_BUFFER + 1 * SIZE_OF_INT, 'i32'); - cursor[2] = getValue(TRANSFER_BUFFER + 2 * SIZE_OF_INT, 'i32'); - cursor[3] = getValue(TRANSFER_BUFFER + 3 * SIZE_OF_INT, 'i32'); + cursor[0] = C.getValue(TRANSFER_BUFFER + 0 * SIZE_OF_INT, 'i32'); + cursor[1] = C.getValue(TRANSFER_BUFFER + 1 * SIZE_OF_INT, 'i32'); + cursor[2] = C.getValue(TRANSFER_BUFFER + 2 * SIZE_OF_INT, 'i32'); + cursor[3] = C.getValue(TRANSFER_BUFFER + 3 * SIZE_OF_INT, 'i32'); } export function marshalPoint(address: number, point: Point): void { - setValue(address, point.row, 'i32'); - setValue(address + SIZE_OF_INT, point.column, 'i32'); + C.setValue(address, point.row, 'i32'); + C.setValue(address + SIZE_OF_INT, point.column, 'i32'); } export function unmarshalPoint(address: number): Point { const result = { - row: getValue(address, 'i32') >>> 0, - column: getValue(address + SIZE_OF_INT, 'i32') >>> 0, + row: C.getValue(address, 'i32') >>> 0, + column: C.getValue(address + SIZE_OF_INT, 'i32') >>> 0, }; return result; } @@ -83,16 +83,16 @@ export function unmarshalPoint(address: number): Point { export function marshalRange(address: number, range: Range): void { marshalPoint(address, range.startPosition); address += SIZE_OF_POINT; marshalPoint(address, range.endPosition); address += SIZE_OF_POINT; - setValue(address, range.startIndex, 'i32'); address += SIZE_OF_INT; - setValue(address, range.endIndex, 'i32'); address += SIZE_OF_INT; + C.setValue(address, range.startIndex, 'i32'); address += SIZE_OF_INT; + C.setValue(address, range.endIndex, 'i32'); address += SIZE_OF_INT; } export function unmarshalRange(address: number): Range { const result = {} as Range; result.startPosition = unmarshalPoint(address); address += SIZE_OF_POINT; result.endPosition = unmarshalPoint(address); address += SIZE_OF_POINT; - result.startIndex = getValue(address, 'i32') >>> 0; address += SIZE_OF_INT; - result.endIndex = getValue(address, 'i32') >>> 0; + result.startIndex = C.getValue(address, 'i32') >>> 0; address += SIZE_OF_INT; + result.endIndex = C.getValue(address, 'i32') >>> 0; return result; } @@ -100,7 +100,7 @@ export function marshalEdit(edit: Edit, address = TRANSFER_BUFFER) { marshalPoint(address, edit.startPosition); address += SIZE_OF_POINT; marshalPoint(address, edit.oldEndPosition); address += SIZE_OF_POINT; marshalPoint(address, edit.newEndPosition); address += SIZE_OF_POINT; - setValue(address, edit.startIndex, 'i32'); address += SIZE_OF_INT; - setValue(address, edit.oldEndIndex, 'i32'); address += SIZE_OF_INT; - setValue(address, edit.newEndIndex, 'i32'); address += SIZE_OF_INT; + C.setValue(address, edit.startIndex, 'i32'); address += SIZE_OF_INT; + C.setValue(address, edit.oldEndIndex, 'i32'); address += SIZE_OF_INT; + C.setValue(address, edit.newEndIndex, 'i32'); address += SIZE_OF_INT; } diff --git a/lib/binding_web/src/node.ts b/lib/binding_web/src/node.ts index aed0ead8..d6c6e68a 100644 --- a/lib/binding_web/src/node.ts +++ b/lib/binding_web/src/node.ts @@ -4,8 +4,6 @@ import { TreeCursor } from './tree_cursor'; import { marshalNode, marshalPoint, unmarshalNode, unmarshalPoint } from './marshal'; import { TRANSFER_BUFFER } from './parser'; -declare const AsciiToString: (ptr: number) => string; - export class Node { private [0]: number; // Internal handle for WASM private _children?: (Node | null)[]; @@ -145,14 +143,14 @@ export class Node { marshalNode(this); const address = C._ts_node_field_name_for_child_wasm(this.tree[0], index); if (!address) return null; - return AsciiToString(address); + return C.AsciiToString(address); } fieldNameForNamedChild(index: number): string | null { marshalNode(this); const address = C._ts_node_field_name_for_named_child_wasm(this.tree[0], index); if (!address) return null; - return AsciiToString(address); + return C.AsciiToString(address); } childrenForFieldName(fieldName: string): (Node | null)[] { @@ -164,8 +162,8 @@ export class Node { childrenForFieldId(fieldId: number): (Node | null)[] { marshalNode(this); C._ts_node_children_by_field_id_wasm(this.tree[0], fieldId); - const count = getValue(TRANSFER_BUFFER, 'i32'); - const buffer = getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32'); + const count = C.getValue(TRANSFER_BUFFER, 'i32'); + const buffer = C.getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32'); const result = new Array(count); if (count > 0) { @@ -182,7 +180,7 @@ export class Node { firstChildForIndex(index: number): Node | null { marshalNode(this); const address = TRANSFER_BUFFER + SIZE_OF_NODE; - setValue(address, index, 'i32'); + C.setValue(address, index, 'i32'); C._ts_node_first_child_for_byte_wasm(this.tree[0]); return unmarshalNode(this.tree); } @@ -190,7 +188,7 @@ export class Node { firstNamedChildForIndex(index: number): Node | null { marshalNode(this); const address = TRANSFER_BUFFER + SIZE_OF_NODE; - setValue(address, index, 'i32'); + C.setValue(address, index, 'i32'); C._ts_node_first_named_child_for_byte_wasm(this.tree[0]); return unmarshalNode(this.tree); } @@ -225,8 +223,8 @@ export class Node { if (!this._children) { marshalNode(this); C._ts_node_children_wasm(this.tree[0]); - const count = getValue(TRANSFER_BUFFER, 'i32'); - const buffer = getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32'); + const count = C.getValue(TRANSFER_BUFFER, 'i32'); + const buffer = C.getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32'); this._children = new Array(count); if (count > 0) { let address = buffer; @@ -244,8 +242,8 @@ export class Node { if (!this._namedChildren) { marshalNode(this); C._ts_node_named_children_wasm(this.tree[0]); - const count = getValue(TRANSFER_BUFFER, 'i32'); - const buffer = getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32'); + const count = C.getValue(TRANSFER_BUFFER, 'i32'); + const buffer = C.getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32'); this._namedChildren = new Array(count); if (count > 0) { let address = buffer; @@ -278,7 +276,7 @@ export class Node { // Copy the array of symbols to the WASM heap const symbolsAddress = C._malloc(SIZE_OF_INT * symbols.length); for (let i = 0, n = symbols.length; i < n; i++) { - setValue(symbolsAddress + i * SIZE_OF_INT, symbols[i], 'i32'); + C.setValue(symbolsAddress + i * SIZE_OF_INT, symbols[i], 'i32'); } // Call the C API to compute the descendants @@ -294,8 +292,8 @@ export class Node { ); // Instantiate the nodes based on the data returned - const descendantCount = getValue(TRANSFER_BUFFER, 'i32'); - const descendantAddress = getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32'); + const descendantCount = C.getValue(TRANSFER_BUFFER, 'i32'); + const descendantAddress = C.getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32'); const result = new Array(descendantCount); if (descendantCount > 0) { let address = descendantAddress; @@ -353,8 +351,8 @@ export class Node { marshalNode(this); const address = TRANSFER_BUFFER + SIZE_OF_NODE; - setValue(address, start, 'i32'); - setValue(address + SIZE_OF_INT, end, 'i32'); + C.setValue(address, start, 'i32'); + C.setValue(address + SIZE_OF_INT, end, 'i32'); C._ts_node_descendant_for_index_wasm(this.tree[0]); return unmarshalNode(this.tree); } @@ -366,8 +364,8 @@ export class Node { marshalNode(this); const address = TRANSFER_BUFFER + SIZE_OF_NODE; - setValue(address, start, 'i32'); - setValue(address + SIZE_OF_INT, end, 'i32'); + C.setValue(address, start, 'i32'); + C.setValue(address + SIZE_OF_INT, end, 'i32'); C._ts_node_named_descendant_for_index_wasm(this.tree[0]); return unmarshalNode(this.tree); } @@ -437,7 +435,7 @@ export class Node { toString() { marshalNode(this); const address = C._ts_node_to_string_wasm(this.tree[0]); - const result = AsciiToString(address); + const result = C.AsciiToString(address); C._free(address); return result; } diff --git a/lib/binding_web/src/parser.ts b/lib/binding_web/src/parser.ts index 700c36a5..1016821e 100644 --- a/lib/binding_web/src/parser.ts +++ b/lib/binding_web/src/parser.ts @@ -1,6 +1,7 @@ -import { C, INTERNAL, Point, Range, SIZE_OF_INT, SIZE_OF_RANGE } from './constants'; +import { C, INTERNAL, Point, Range, SIZE_OF_INT, SIZE_OF_RANGE, setModule } from './constants'; import { Language } from './language'; import { marshalRange, unmarshalRange } from './marshal'; +import { checkModule, initializeBinding } from './bindings'; import { Tree } from './tree'; interface ParseOptions { @@ -17,29 +18,38 @@ export let TRANSFER_BUFFER: number; let VERSION: number; let MIN_COMPATIBLE_VERSION: number; -let currentParseCallback: ((index: number, position: Point) => string) | null = null; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -let currentLogCallback: LogCallback = null; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -let currentProgressCallback: ((percent: number) => void) | null = null; +// declare let currentParseCallback: ((index: number, position: Point) => string) | null; +// // eslint-disable-next-line @typescript-eslint/no-unused-vars +// declare let currentLogCallback: LogCallback; +// // eslint-disable-next-line @typescript-eslint/no-unused-vars +// declare let currentProgressCallback: ((percent: number) => void) | null; -export class ParserImpl { +export class Parser { protected [0] = 0; protected [1] = 0; protected language: Language | null = null; protected logCallback: LogCallback = null; static Language: typeof Language; - static init() { + // This must always be called before creating a Parser. + static async init(moduleOptions: EmscriptenModule) { + setModule(await initializeBinding(moduleOptions)); TRANSFER_BUFFER = C._ts_init(); - VERSION = getValue(TRANSFER_BUFFER, 'i32'); - MIN_COMPATIBLE_VERSION = getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32'); + VERSION = C.getValue(TRANSFER_BUFFER, 'i32'); + MIN_COMPATIBLE_VERSION = C.getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32'); + } + + constructor() { + this.initialize(); } initialize() { + if (!checkModule()) { + throw new Error("cannot construct a Parser before calling `init()`"); + } C._ts_parser_new_wasm(); - this[0] = getValue(TRANSFER_BUFFER, 'i32'); - this[1] = getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32'); + this[0] = C.getValue(TRANSFER_BUFFER, 'i32'); + this[1] = C.getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32'); } delete() { @@ -82,24 +92,24 @@ export class ParserImpl { options: ParseOptions = {} ): Tree { if (typeof callback === 'string') { - currentParseCallback = (index: number) => callback.slice(index); + C.currentParseCallback = (index: number) => callback.slice(index); } else if (typeof callback === 'function') { - currentParseCallback = callback; + C.currentParseCallback = callback; } else { throw new Error('Argument must be a string or a function'); } if (options.progressCallback) { - currentProgressCallback = options.progressCallback; + C.currentProgressCallback = options.progressCallback; } else { - currentProgressCallback = null; + C.currentProgressCallback = null; } if (this.logCallback) { - currentLogCallback = this.logCallback; + C.currentLogCallback = this.logCallback; C._ts_parser_enable_logger_wasm(this[0], 1); } else { - currentLogCallback = null; + C.currentLogCallback = null; C._ts_parser_enable_logger_wasm(this[0], 0); } @@ -124,9 +134,9 @@ export class ParserImpl { ); if (!treeAddress) { - currentParseCallback = null; - currentLogCallback = null; - currentProgressCallback = null; + C.currentParseCallback = null; + C.currentLogCallback = null; + C.currentProgressCallback = null; throw new Error('Parsing failed'); } @@ -134,10 +144,10 @@ export class ParserImpl { throw new Error('Parser must have a language to parse'); } - const result = new Tree(INTERNAL, treeAddress, this.language, currentParseCallback); - currentParseCallback = null; - currentLogCallback = null; - currentProgressCallback = null; + const result = new Tree(INTERNAL, treeAddress, this.language, C.currentParseCallback); + C.currentParseCallback = null; + C.currentLogCallback = null; + C.currentProgressCallback = null; return result; } @@ -147,8 +157,8 @@ export class ParserImpl { getIncludedRanges(): Range[] { C._ts_parser_included_ranges_wasm(this[0]); - const count = getValue(TRANSFER_BUFFER, 'i32'); - const buffer = getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32'); + const count = C.getValue(TRANSFER_BUFFER, 'i32'); + const buffer = C.getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32'); const result = new Array(count); if (count > 0) { @@ -168,7 +178,7 @@ export class ParserImpl { } setTimeoutMicros(timeout: number): void { - C._ts_parser_set_timeout_micros(this[0], timeout); + C._ts_parser_set_timeout_micros(this[0], 0, timeout); } setLogger(callback: LogCallback): this { diff --git a/lib/binding_web/src/query.ts b/lib/binding_web/src/query.ts index 7c055dbf..4861d995 100644 --- a/lib/binding_web/src/query.ts +++ b/lib/binding_web/src/query.ts @@ -3,8 +3,7 @@ import { Node } from './node'; import { marshalNode, unmarshalCaptures } from './marshal'; import { TRANSFER_BUFFER } from './parser'; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -let currentQueryProgressCallback: ((percent: number) => void) | null = null; +// let currentQueryProgressCallback: ((percent: number) => void) | null = null; interface QueryOptions { startPosition?: Point; @@ -127,7 +126,7 @@ export class Query { } if (progressCallback) { - currentQueryProgressCallback = progressCallback; + C.currentQueryProgressCallback = progressCallback; } marshalNode(node); @@ -146,18 +145,18 @@ export class Query { timeoutMicros, ); - const rawCount = getValue(TRANSFER_BUFFER, 'i32'); - const startAddress = getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32'); - const didExceedMatchLimit = getValue(TRANSFER_BUFFER + 2 * SIZE_OF_INT, 'i32'); + const rawCount = C.getValue(TRANSFER_BUFFER, 'i32'); + const startAddress = C.getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32'); + const didExceedMatchLimit = C.getValue(TRANSFER_BUFFER + 2 * SIZE_OF_INT, 'i32'); const result = new Array(rawCount); this.exceededMatchLimit = Boolean(didExceedMatchLimit); let filteredCount = 0; let address = startAddress; for (let i = 0; i < rawCount; i++) { - const pattern = getValue(address, 'i32'); + const pattern = C.getValue(address, 'i32'); address += SIZE_OF_INT; - const captureCount = getValue(address, 'i32'); + const captureCount = C.getValue(address, 'i32'); address += SIZE_OF_INT; const captures = new Array(captureCount); @@ -177,7 +176,7 @@ export class Query { result.length = filteredCount; C._free(startAddress); - currentQueryProgressCallback = null; + C.currentQueryProgressCallback = null; return result; } @@ -211,7 +210,7 @@ export class Query { } if (progressCallback) { - currentQueryProgressCallback = progressCallback; + C.currentQueryProgressCallback = progressCallback; } marshalNode(node); @@ -230,20 +229,20 @@ export class Query { timeoutMicros, ); - const count = getValue(TRANSFER_BUFFER, 'i32'); - const startAddress = getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32'); - const didExceedMatchLimit = getValue(TRANSFER_BUFFER + 2 * SIZE_OF_INT, 'i32'); + const count = C.getValue(TRANSFER_BUFFER, 'i32'); + const startAddress = C.getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32'); + const didExceedMatchLimit = C.getValue(TRANSFER_BUFFER + 2 * SIZE_OF_INT, 'i32'); const result: Capture[] = []; this.exceededMatchLimit = Boolean(didExceedMatchLimit); const captures: Capture[] = []; let address = startAddress; for (let i = 0; i < count; i++) { - const pattern = getValue(address, 'i32'); + const pattern = C.getValue(address, 'i32'); address += SIZE_OF_INT; - const captureCount = getValue(address, 'i32'); + const captureCount = C.getValue(address, 'i32'); address += SIZE_OF_INT; - const captureIndex = getValue(address, 'i32'); + const captureIndex = C.getValue(address, 'i32'); address += SIZE_OF_INT; captures.length = captureCount; @@ -262,7 +261,7 @@ export class Query { } C._free(startAddress); - currentQueryProgressCallback = null; + C.currentQueryProgressCallback = null; return result; } @@ -271,9 +270,9 @@ export class Query { } disableCapture(captureName: string): void { - const captureNameLength = lengthBytesUTF8(captureName); + const captureNameLength = C.lengthBytesUTF8(captureName); const captureNameAddress = C._malloc(captureNameLength + 1); - stringToUTF8(captureName, captureNameAddress, captureNameLength + 1); + C.stringToUTF8(captureName, captureNameAddress, captureNameLength + 1); C._ts_query_disable_capture(this[0], captureNameAddress, captureNameLength); C._free(captureNameAddress); } @@ -317,11 +316,7 @@ export class Query { return C._ts_query_is_pattern_rooted(this[0], patternIndex) === 1; } - isPatternGuaranteedAtStep(patternIndex: number, stepIndex: number): boolean { - return C._ts_query_is_pattern_guaranteed_at_step( - this[0], - patternIndex, - stepIndex - ) === 1; + isPatternGuaranteedAtStep(byteIndex: number): boolean { + return C._ts_query_is_pattern_guaranteed_at_step(this[0], byteIndex) === 1; } } diff --git a/lib/binding_web/src/tree.ts b/lib/binding_web/src/tree.ts index 27fe3d16..d2043942 100644 --- a/lib/binding_web/src/tree.ts +++ b/lib/binding_web/src/tree.ts @@ -61,7 +61,7 @@ export class Tree { rootNodeWithOffset(offsetBytes: number, offsetExtent: Point): Node { const address = TRANSFER_BUFFER + SIZE_OF_NODE; - setValue(address, offsetBytes, 'i32'); + C.setValue(address, offsetBytes, 'i32'); marshalPoint(address + SIZE_OF_INT, offsetExtent); C._ts_tree_root_node_with_offset_wasm(this[0]); return unmarshalNode(this)!; @@ -81,8 +81,8 @@ export class Tree { } C._ts_tree_get_changed_ranges_wasm(this[0], other[0]); - const count = getValue(TRANSFER_BUFFER, 'i32'); - const buffer = getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32'); + const count = C.getValue(TRANSFER_BUFFER, 'i32'); + const buffer = C.getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32'); const result = new Array(count); if (count > 0) { @@ -98,8 +98,8 @@ export class Tree { getIncludedRanges(): Range[] { C._ts_tree_included_ranges_wasm(this[0]); - const count = getValue(TRANSFER_BUFFER, 'i32'); - const buffer = getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32'); + const count = C.getValue(TRANSFER_BUFFER, 'i32'); + const buffer = C.getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32'); const result = new Array(count); if (count > 0) { diff --git a/lib/binding_web/src/tree_cursor.ts b/lib/binding_web/src/tree_cursor.ts index 2b9395c6..7d1118bc 100644 --- a/lib/binding_web/src/tree_cursor.ts +++ b/lib/binding_web/src/tree_cursor.ts @@ -150,7 +150,7 @@ export class TreeCursor { gotoFirstChildForIndex(goalIndex: number): boolean { marshalTreeCursor(this); - setValue(TRANSFER_BUFFER + SIZE_OF_CURSOR, goalIndex, 'i32'); + C.setValue(TRANSFER_BUFFER + SIZE_OF_CURSOR, goalIndex, 'i32'); const result = C._ts_tree_cursor_goto_first_child_for_index_wasm(this.tree[0]); unmarshalTreeCursor(this); return result === 1; diff --git a/lib/binding_web/test/helper.ts b/lib/binding_web/test/helper.ts index 74210385..2c4ecdbd 100644 --- a/lib/binding_web/test/helper.ts +++ b/lib/binding_web/test/helper.ts @@ -1,9 +1,11 @@ -import type { default as ParserType } from 'web-tree-sitter'; +import { type Parser as ParserType, type Language as LanguageType } from '../src'; import path from 'path'; // @ts-expect-error We're intentionally importing ../tree-sitter.js -// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access -const Parser: typeof ParserType = await import('..').then(m => m.default); +import { Parser as ParserImpl, Language as LanguageImpl } from '..'; + +const Parser = ParserImpl as typeof ParserType; +const Language = LanguageImpl as typeof LanguageType; // https://github.com/tree-sitter/tree-sitter/blob/master/xtask/src/fetch.rs#L15 export type LanguageName = 'bash' | 'c' | 'cpp' | 'embedded-template' | 'go' | 'html' | 'java' | 'javascript' | 'jsdoc' | 'json' | 'php' | 'python' | 'ruby' | 'rust' | 'typescript' | 'tsx'; @@ -15,12 +17,13 @@ function languageURL(name: LanguageName): string { export default Parser.init().then(async () => ({ Parser, + Language, languageURL, - C: await Parser.Language.load(languageURL('c')), - EmbeddedTemplate: await Parser.Language.load(languageURL('embedded-template')), - HTML: await Parser.Language.load(languageURL('html')), - JavaScript: await Parser.Language.load(languageURL('javascript')), - JSON: await Parser.Language.load(languageURL('json')), - Python: await Parser.Language.load(languageURL('python')), - Rust: await Parser.Language.load(languageURL('rust')), + C: await Language.load(languageURL('c')), + EmbeddedTemplate: await Language.load(languageURL('embedded-template')), + HTML: await Language.load(languageURL('html')), + JavaScript: await Language.load(languageURL('javascript')), + JSON: await Language.load(languageURL('json')), + Python: await Language.load(languageURL('python')), + Rust: await Language.load(languageURL('rust')), })); diff --git a/lib/binding_web/test/language.test.ts b/lib/binding_web/test/language.test.ts index 2a1f0ae1..126aa872 100644 --- a/lib/binding_web/test/language.test.ts +++ b/lib/binding_web/test/language.test.ts @@ -1,7 +1,8 @@ import { describe, it, expect, beforeAll, afterAll } from 'vitest'; import helper from './helper'; -import type { default as ParserType, LookaheadIterable, Language } from 'web-tree-sitter'; +import type { Parser as ParserType, LookaheadIterator, Language } from '../src'; +let Parser: typeof ParserType; let JavaScript: Language; let Rust: Language; @@ -35,8 +36,8 @@ describe('Language', () => { describe('.idForNodeType, .nodeTypeForId, .nodeTypeIsNamed', () => { it('converts between the string and integer representations of a node type', () => { - const exportStatementId = JavaScript.idForNodeType('export_statement', true); - const starId = JavaScript.idForNodeType('*', false); + const exportStatementId = JavaScript.idForNodeType('export_statement', true)!; + const starId = JavaScript.idForNodeType('*', false)!; expect(exportStatementId).toBeLessThan(JavaScript.nodeTypeCount); expect(starId).toBeLessThan(JavaScript.nodeTypeCount); @@ -131,12 +132,11 @@ describe('Language', () => { }); describe('Lookahead iterator', () => { - let lookahead: LookaheadIterable; + let lookahead: LookaheadIterator; let state: number; beforeAll(async () => { - let Parser: typeof ParserType; - ({ JavaScript, Parser } = await helper); + ({ Parser, JavaScript } = await helper); const parser = new Parser(); parser.setLanguage(JavaScript); const tree = parser.parse('function fn() {}'); diff --git a/lib/binding_web/test/node.test.ts b/lib/binding_web/test/node.test.ts index baa301ad..a5d08d3b 100644 --- a/lib/binding_web/test/node.test.ts +++ b/lib/binding_web/test/node.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeAll, beforeEach, afterEach } from 'vitest'; -import type { default as ParserType, Language, Tree, SyntaxNode } from 'web-tree-sitter'; +import type { Parser as ParserType, Language, Tree, Node } from '../src'; import helper from './helper'; let Parser: typeof ParserType; @@ -19,8 +19,8 @@ const JSON_EXAMPLE = ` ] `; -function getAllNodes(tree: Tree): SyntaxNode[] { - const result: SyntaxNode[] = []; +function getAllNodes(tree: Tree): Node[] { + const result: Node[] = []; let visitedChildren = false; const cursor = tree.walk(); @@ -55,15 +55,15 @@ describe('Node', () => { afterEach(() => { parser.delete(); - tree!.delete(); + tree?.delete(); }); describe('.children', () => { it('returns an array of child nodes', () => { tree = parser.parse('x10 + 1000'); expect(tree.rootNode.children).toHaveLength(1); - const sumNode = tree.rootNode.firstChild!.firstChild; - expect(sumNode!.children.map(child => child.type)).toEqual([ + const sumNode = tree.rootNode.firstChild?.firstChild; + expect(sumNode?.children.map(child => child?.type)).toEqual([ 'identifier', '+', 'number' @@ -74,9 +74,9 @@ describe('Node', () => { describe('.namedChildren', () => { it('returns an array of named child nodes', () => { tree = parser.parse('x10 + 1000'); - const sumNode = tree.rootNode.firstChild!.firstChild; + const sumNode = tree.rootNode.firstChild?.firstChild; expect(tree.rootNode.namedChildren).toHaveLength(1); - expect(sumNode!.namedChildren.map(child => child.type)).toEqual([ + expect(sumNode?.namedChildren.map(child => child?.type)).toEqual([ 'identifier', 'number' ]); @@ -98,11 +98,11 @@ describe('Node', () => { tree = parser.parse(source); const node = tree.rootNode.firstChild; - expect(node!.type).toBe('if_statement'); - const alternatives = node!.childrenForFieldName('alternative'); - const alternativeTexts = alternatives.map(n => { - const condition = n.childForFieldName('condition'); - return source.slice(condition!.startIndex, condition!.endIndex); + expect(node?.type).toBe('if_statement'); + const alternatives = node?.childrenForFieldName('alternative'); + const alternativeTexts = alternatives?.map(n => { + const condition = n?.childForFieldName('condition'); + return source.slice(condition?.startIndex, condition?.endIndex); }); expect(alternativeTexts).toEqual(['two', 'three', 'four']); }); @@ -111,29 +111,29 @@ describe('Node', () => { describe('.startIndex and .endIndex', () => { it('returns the character index where the node starts/ends in the text', () => { tree = parser.parse('a👍👎1 / b👎c👎'); - const quotientNode = tree.rootNode.firstChild!.firstChild; + const quotientNode = tree.rootNode.firstChild?.firstChild; - expect(quotientNode!.startIndex).toBe(0); - expect(quotientNode!.endIndex).toBe(15); - expect(quotientNode!.children.map(child => child.startIndex)).toEqual([0, 7, 9]); - expect(quotientNode!.children.map(child => child.endIndex)).toEqual([6, 8, 15]); + expect(quotientNode?.startIndex).toBe(0); + expect(quotientNode?.endIndex).toBe(15); + expect(quotientNode?.children.map(child => child?.startIndex)).toEqual([0, 7, 9]); + expect(quotientNode?.children.map(child => child?.endIndex)).toEqual([6, 8, 15]); }); }); describe('.startPosition and .endPosition', () => { it('returns the row and column where the node starts/ends in the text', () => { tree = parser.parse('x10 + 1000'); - const sumNode = tree.rootNode.firstChild!.firstChild!; - expect(sumNode.type).toBe('binary_expression'); + const sumNode = tree.rootNode.firstChild?.firstChild; + expect(sumNode?.type).toBe('binary_expression'); - expect(sumNode.startPosition).toEqual({ row: 0, column: 0 }); - expect(sumNode.endPosition).toEqual({ row: 0, column: 10 }); - expect(sumNode.children.map((child) => child.startPosition)).toEqual([ + expect(sumNode?.startPosition).toEqual({ row: 0, column: 0 }); + expect(sumNode?.endPosition).toEqual({ row: 0, column: 10 }); + expect(sumNode?.children.map((child) => child?.startPosition)).toEqual([ { row: 0, column: 0 }, { row: 0, column: 4 }, { row: 0, column: 6 }, ]); - expect(sumNode.children.map((child) => child.endPosition)).toEqual([ + expect(sumNode?.children.map((child) => child?.endPosition)).toEqual([ { row: 0, column: 3 }, { row: 0, column: 5 }, { row: 0, column: 10 }, @@ -142,8 +142,8 @@ describe('Node', () => { it('handles characters that occupy two UTF16 code units', () => { tree = parser.parse('a👍👎1 /\n b👎c👎'); - const sumNode = tree.rootNode.firstChild!.firstChild; - expect(sumNode!.children.map(child => [child.startPosition, child.endPosition])).toEqual([ + const sumNode = tree.rootNode.firstChild?.firstChild; + expect(sumNode?.children.map(child => [child?.startPosition, child?.endPosition])).toEqual([ [{ row: 0, column: 0 }, { row: 0, column: 6 }], [{ row: 0, column: 7 }, { row: 0, column: 8 }], [{ row: 1, column: 1 }, { row: 1, column: 7 }] @@ -155,23 +155,23 @@ describe('Node', () => { it('returns the node\'s parent', () => { tree = parser.parse('x10 + 1000'); const sumNode = tree.rootNode.firstChild; - const variableNode = sumNode!.firstChild; - expect(sumNode!.id).not.toBe(variableNode!.id); - expect(sumNode!.id).toBe(variableNode!.parent!.id); - expect(tree.rootNode.id).toBe(sumNode!.parent!.id); + const variableNode = sumNode?.firstChild; + expect(sumNode?.id).not.toBe(variableNode?.id); + expect(sumNode?.id).toBe(variableNode?.parent?.id); + expect(tree.rootNode.id).toBe(sumNode?.parent?.id); }); }); describe('.child(), .firstChild, .lastChild', () => { it('returns null when the node has no children', () => { tree = parser.parse('x10 + 1000'); - const sumNode = tree.rootNode.firstChild!.firstChild; - const variableNode = sumNode!.firstChild; - expect(variableNode!.firstChild).toBeNull(); - expect(variableNode!.lastChild).toBeNull(); - expect(variableNode!.firstNamedChild).toBeNull(); - expect(variableNode!.lastNamedChild).toBeNull(); - expect(variableNode!.child(1)).toBeNull(); + const sumNode = tree.rootNode.firstChild?.firstChild; + const variableNode = sumNode?.firstChild; + expect(variableNode?.firstChild).toBeNull(); + expect(variableNode?.lastChild).toBeNull(); + expect(variableNode?.firstNamedChild).toBeNull(); + expect(variableNode?.lastNamedChild).toBeNull(); + expect(variableNode?.child(1)).toBeNull(); }); }); @@ -180,57 +180,57 @@ describe('Node', () => { tree = parser.parse('class A { b() {} }'); const classNode = tree.rootNode.firstChild; - expect(classNode!.type).toBe('class_declaration'); + expect(classNode?.type).toBe('class_declaration'); - const classNameNode = classNode!.childForFieldName('name'); - expect(classNameNode!.type).toBe('identifier'); - expect(classNameNode!.text).toBe('A'); + const classNameNode = classNode?.childForFieldName('name'); + expect(classNameNode?.type).toBe('identifier'); + expect(classNameNode?.text).toBe('A'); - const bodyNode = classNode!.childForFieldName('body'); - expect(bodyNode!.type).toBe('class_body'); - expect(bodyNode!.text).toBe('{ b() {} }'); + const bodyNode = classNode?.childForFieldName('body'); + expect(bodyNode?.type).toBe('class_body'); + expect(bodyNode?.text).toBe('{ b() {} }'); - const methodNode = bodyNode!.firstNamedChild; - expect(methodNode!.type).toBe('method_definition'); - expect(methodNode!.text).toBe('b() {}'); + const methodNode = bodyNode?.firstNamedChild; + expect(methodNode?.type).toBe('method_definition'); + expect(methodNode?.text).toBe('b() {}'); }); }); describe('.nextSibling and .previousSibling', () => { it('returns the node\'s next and previous sibling', () => { tree = parser.parse('x10 + 1000'); - const sumNode = tree.rootNode.firstChild!.firstChild; - expect(sumNode!.children[1].id).toBe(sumNode!.children[0].nextSibling!.id); - expect(sumNode!.children[2].id).toBe(sumNode!.children[1].nextSibling!.id); - expect(sumNode!.children[0].id).toBe(sumNode!.children[1].previousSibling!.id); - expect(sumNode!.children[1].id).toBe(sumNode!.children[2].previousSibling!.id); + const sumNode = tree.rootNode.firstChild?.firstChild; + expect(sumNode?.children[1]?.id).toBe(sumNode?.children[0]?.nextSibling?.id); + expect(sumNode?.children[2]?.id).toBe(sumNode?.children[1]?.nextSibling?.id); + expect(sumNode?.children[0]?.id).toBe(sumNode?.children[1]?.previousSibling?.id); + expect(sumNode?.children[1]?.id).toBe(sumNode?.children[2]?.previousSibling?.id); }); }); describe('.nextNamedSibling and .previousNamedSibling', () => { it('returns the node\'s next and previous named sibling', () => { tree = parser.parse('x10 + 1000'); - const sumNode = tree.rootNode.firstChild!.firstChild; - expect(sumNode!.namedChildren[1].id).toBe(sumNode!.namedChildren[0].nextNamedSibling!.id); - expect(sumNode!.namedChildren[0].id).toBe(sumNode!.namedChildren[1].previousNamedSibling!.id); + const sumNode = tree.rootNode.firstChild?.firstChild; + expect(sumNode?.namedChildren[1]?.id).toBe(sumNode?.namedChildren[0]?.nextNamedSibling?.id); + expect(sumNode?.namedChildren[0]?.id).toBe(sumNode?.namedChildren[1]?.previousNamedSibling?.id); }); }); describe('.descendantForIndex(min, max)', () => { it('returns the smallest node that spans the given range', () => { tree = parser.parse('x10 + 1000'); - const sumNode = tree.rootNode.firstChild!.firstChild; - expect(sumNode!.descendantForIndex(1, 2).type).toBe('identifier'); - expect(sumNode!.descendantForIndex(4, 4).type).toBe('+'); + const sumNode = tree.rootNode.firstChild?.firstChild; + expect(sumNode?.descendantForIndex(1, 2)?.type).toBe('identifier'); + expect(sumNode?.descendantForIndex(4, 4)?.type).toBe('+'); expect(() => { // @ts-expect-error Testing invalid arguments - sumNode!.descendantForIndex(1, {}); + sumNode.descendantForIndex(1, {}); }).toThrow('Arguments must be numbers'); expect(() => { // @ts-expect-error Testing invalid arguments - sumNode!.descendantForIndex(undefined); + sumNode.descendantForIndex(undefined); }).toThrow('Arguments must be numbers'); }); }); @@ -238,36 +238,36 @@ describe('Node', () => { describe('.namedDescendantForIndex', () => { it('returns the smallest named node that spans the given range', () => { tree = parser.parse('x10 + 1000'); - const sumNode = tree.rootNode.firstChild; - expect(sumNode!.descendantForIndex(1, 2).type).toBe('identifier'); - expect(sumNode!.descendantForIndex(4, 4).type).toBe('+'); + const sumNode = tree.rootNode.firstChild!; + expect(sumNode.descendantForIndex(1, 2)?.type).toBe('identifier'); + expect(sumNode.descendantForIndex(4, 4)?.type).toBe('+'); }); }); describe('.descendantForPosition', () => { it('returns the smallest node that spans the given range', () => { tree = parser.parse('x10 + 1000'); - const sumNode = tree.rootNode.firstChild; + const sumNode = tree.rootNode.firstChild!; expect( - sumNode!.descendantForPosition( + sumNode.descendantForPosition( { row: 0, column: 1 }, { row: 0, column: 2 } - ).type + )?.type ).toBe('identifier'); expect( - sumNode!.descendantForPosition({ row: 0, column: 4 }).type + sumNode.descendantForPosition({ row: 0, column: 4 })?.type ).toBe('+'); expect(() => { // @ts-expect-error Testing invalid arguments - sumNode!.descendantForPosition(1, {}); + sumNode.descendantForPosition(1, {}); }).toThrow('Arguments must be {row, column} objects'); expect(() => { // @ts-expect-error Testing invalid arguments - sumNode!.descendantForPosition(undefined); + sumNode.descendantForPosition(undefined); }).toThrow('Arguments must be {row, column} objects'); }); }); @@ -281,11 +281,11 @@ describe('Node', () => { sumNode.namedDescendantForPosition( { row: 0, column: 1 }, { row: 0, column: 2 }, - ).type + )?.type ).toBe('identifier') expect( - sumNode.namedDescendantForPosition({ row: 0, column: 4 }).type + sumNode.namedDescendantForPosition({ row: 0, column: 4 })?.type ).toBe('binary_expression'); }); }); @@ -298,11 +298,11 @@ describe('Node', () => { '(program (expression_statement (binary_expression left: (number) right: (binary_expression left: (number) (ERROR) right: (number)))))' ); - const sum = node.firstChild!.firstChild; - expect(sum!.hasError).toBe(true); - expect(sum!.children[0].hasError).toBe(false); - expect(sum!.children[1].hasError).toBe(false); - expect(sum!.children[2].hasError).toBe(true); + const sum = node.firstChild?.firstChild; + expect(sum?.hasError).toBe(true); + expect(sum?.children[0]?.hasError).toBe(false); + expect(sum?.children[1]?.hasError).toBe(false); + expect(sum?.children[2]?.hasError).toBe(true); }); }); @@ -314,12 +314,12 @@ describe('Node', () => { '(program (expression_statement (binary_expression left: (number) (ERROR) right: (number))))' ); - const multi = node.firstChild!.firstChild; - expect(multi!.hasError).toBe(true); - expect(multi!.children[0].isError).toBe(false); - expect(multi!.children[1].isError).toBe(false); - expect(multi!.children[2].isError).toBe(true); - expect(multi!.children[3].isError).toBe(false); + const multi = node.firstChild?.firstChild; + expect(multi?.hasError).toBe(true); + expect(multi?.children[0]?.isError).toBe(false); + expect(multi?.children[1]?.isError).toBe(false); + expect(multi?.children[2]?.isError).toBe(true); + expect(multi?.children[3]?.isError).toBe(false); }); }); @@ -331,12 +331,12 @@ describe('Node', () => { '(program (expression_statement (parenthesized_expression (binary_expression left: (number) right: (MISSING identifier)))))' ); - const sum = node.firstChild!.firstChild!.firstNamedChild; - expect(sum!.type).toBe('binary_expression'); - expect(sum!.hasError).toBe(true); - expect(sum!.children[0].isMissing).toBe(false); - expect(sum!.children[1].isMissing).toBe(false); - expect(sum!.children[2].isMissing).toBe(true); + const sum = node.firstChild?.firstChild?.firstNamedChild; + expect(sum?.type).toBe('binary_expression'); + expect(sum?.hasError).toBe(true); + expect(sum?.children[0]?.isMissing).toBe(false); + expect(sum?.children[1]?.isMissing).toBe(false); + expect(sum?.children[2]?.isMissing).toBe(true); }); }); @@ -344,7 +344,7 @@ describe('Node', () => { it('returns true if the node is an extra node like comments', () => { tree = parser.parse('foo(/* hi */);'); const node = tree.rootNode; - const commentNode = node.descendantForIndex(7, 7); + const commentNode = node.descendantForIndex(7, 7)!; expect(node.type).toBe('program'); expect(commentNode.type).toBe('comment'); @@ -363,14 +363,14 @@ describe('Node', () => { it(`returns the text of a node generated by ${method}`, () => { const [numeratorSrc, denominatorSrc] = text.split(/\s*\/\s+/); tree = parser.parse(_parse); - const quotientNode = tree.rootNode.firstChild!.firstChild!; - const [numerator, slash, denominator] = quotientNode.children; + const quotientNode = tree.rootNode.firstChild?.firstChild; + const [numerator, slash, denominator] = quotientNode!.children; expect(tree.rootNode.text).toBe(text); - expect(denominator.text).toBe(denominatorSrc); - expect(quotientNode.text).toBe(text); - expect(numerator.text).toBe(numeratorSrc); - expect(slash.text).toBe('/'); + expect(denominator?.text).toBe(denominatorSrc); + expect(quotientNode?.text).toBe(text); + expect(numerator?.text).toBe(numeratorSrc); + expect(slash?.text).toBe('/'); }); }); }); @@ -427,12 +427,12 @@ describe('Node', () => { expect(node.startPosition).toEqual({ row: 2, column: 4 }); expect(node.endPosition).toEqual({ row: 2, column: 12 }); - let child = node.firstChild!.child(2); - expect(child!.type).toBe('expression_statement'); - expect(child!.startIndex).toBe(15); - expect(child!.endIndex).toBe(16); - expect(child!.startPosition).toEqual({ row: 2, column: 11 }); - expect(child!.endPosition).toEqual({ row: 2, column: 12 }); + let child = node.firstChild?.child(2); + expect(child?.type).toBe('expression_statement'); + expect(child?.startIndex).toBe(15); + expect(child?.endIndex).toBe(16); + expect(child?.startPosition).toEqual({ row: 2, column: 11 }); + expect(child?.endPosition).toEqual({ row: 2, column: 12 }); const cursor = node.walk(); cursor.gotoFirstChild(); @@ -452,23 +452,23 @@ describe('Node', () => { it('returns node parse state ids', () => { tree = parser.parse(text); - const quotientNode = tree.rootNode.firstChild!.firstChild; + const quotientNode = tree.rootNode.firstChild?.firstChild; const [numerator, slash, denominator] = quotientNode!.children; expect(tree.rootNode.parseState).toBe(0); // parse states will change on any change to the grammar so test that it // returns something instead - expect(numerator.parseState).toBeGreaterThan(0); - expect(slash.parseState).toBeGreaterThan(0); - expect(denominator.parseState).toBeGreaterThan(0); + expect(numerator?.parseState).toBeGreaterThan(0); + expect(slash?.parseState).toBeGreaterThan(0); + expect(denominator?.parseState).toBeGreaterThan(0); }); it('returns next parse state equal to the language', () => { tree = parser.parse(text); - const quotientNode = tree.rootNode.firstChild!.firstChild; - quotientNode!.children.forEach((node) => { - expect(node.nextParseState).toBe( - JavaScript.nextState(node.parseState, node.grammarId) + const quotientNode = tree.rootNode.firstChild?.firstChild; + quotientNode?.children.forEach((node) => { + expect(node?.nextParseState).toBe( + JavaScript.nextState(node!.parseState, node!.grammarId) ); }); }); @@ -477,11 +477,11 @@ describe('Node', () => { describe('.descendantsOfType', () => { it('finds all descendants of a given type in the given range', () => { tree = parser.parse('a + 1 * b * 2 + c + 3'); - const outerSum = tree.rootNode.firstChild!.firstChild; + const outerSum = tree.rootNode.firstChild?.firstChild; - const descendants = outerSum!.descendantsOfType('number', { row: 0, column: 2 }, { row: 0, column: 15 }); - expect(descendants.map(node => node.startIndex)).toEqual([4, 12]); - expect(descendants.map(node => node.endPosition)).toEqual([ + const descendants = outerSum?.descendantsOfType('number', { row: 0, column: 2 }, { row: 0, column: 15 }) ?? []; + expect(descendants.map(node => node?.startIndex)).toEqual([4, 12]); + expect(descendants.map(node => node?.endPosition)).toEqual([ { row: 0, column: 5 }, { row: 0, column: 13 }, ]); @@ -493,23 +493,23 @@ describe('Node', () => { describe('.firstChildForIndex(index)', () => { it('returns the first child that contains or starts after the given index', () => { tree = parser.parse('x10 + 1000'); - const sumNode = tree.rootNode.firstChild!.firstChild; + const sumNode = tree.rootNode.firstChild?.firstChild; - expect(sumNode!.firstChildForIndex(0)!.type).toBe('identifier'); - expect(sumNode!.firstChildForIndex(1)!.type).toBe('identifier'); - expect(sumNode!.firstChildForIndex(3)!.type).toBe('+'); - expect(sumNode!.firstChildForIndex(5)!.type).toBe('number'); + expect(sumNode?.firstChildForIndex(0)?.type).toBe('identifier'); + expect(sumNode?.firstChildForIndex(1)?.type).toBe('identifier'); + expect(sumNode?.firstChildForIndex(3)?.type).toBe('+'); + expect(sumNode?.firstChildForIndex(5)?.type).toBe('number'); }); }); describe('.firstNamedChildForIndex(index)', () => { it('returns the first child that contains or starts after the given index', () => { tree = parser.parse('x10 + 1000'); - const sumNode = tree.rootNode.firstChild!.firstChild; + const sumNode = tree.rootNode.firstChild?.firstChild; - expect(sumNode!.firstNamedChildForIndex(0)!.type).toBe('identifier'); - expect(sumNode!.firstNamedChildForIndex(1)!.type).toBe('identifier'); - expect(sumNode!.firstNamedChildForIndex(3)!.type).toBe('number'); + expect(sumNode?.firstNamedChildForIndex(0)?.type).toBe('identifier'); + expect(sumNode?.firstNamedChildForIndex(1)?.type).toBe('identifier'); + expect(sumNode?.firstNamedChildForIndex(3)?.type).toBe('number'); }); }); @@ -517,19 +517,19 @@ describe('Node', () => { it('returns true if the nodes are the same', () => { tree = parser.parse('1 + 2'); - const sumNode = tree.rootNode.firstChild!.firstChild; - const node1 = sumNode!.firstChild; - const node2 = sumNode!.firstChild; - expect(node1!.equals(node2!)).toBe(true); + const sumNode = tree.rootNode.firstChild?.firstChild; + const node1 = sumNode?.firstChild; + const node2 = sumNode?.firstChild; + expect(node1?.equals(node2!)).toBe(true); }); it('returns false if the nodes are not the same', () => { tree = parser.parse('1 + 2'); - const sumNode = tree.rootNode.firstChild!.firstChild; - const node1 = sumNode!.firstChild; - const node2 = node1!.nextSibling; - expect(node1!.equals(node2!)).toBe(false); + const sumNode = tree.rootNode.firstChild?.firstChild; + const node1 = sumNode?.firstChild; + const node2 = node1?.nextSibling; + expect(node1?.equals(node2!)).toBe(false); }); }); @@ -551,13 +551,13 @@ describe('Node', () => { // right: (identifier) 3 // ------------------- - expect(binaryExpressionNode!.fieldNameForChild(0)).toBe('left'); - expect(binaryExpressionNode!.fieldNameForChild(1)).toBe('operator'); + expect(binaryExpressionNode?.fieldNameForChild(0)).toBe('left'); + expect(binaryExpressionNode?.fieldNameForChild(1)).toBe('operator'); // The comment should not have a field name, as it's just an extra - expect(binaryExpressionNode!.fieldNameForChild(2)).toBeNull(); - expect(binaryExpressionNode!.fieldNameForChild(3)).toBe('right'); + expect(binaryExpressionNode?.fieldNameForChild(2)).toBeNull(); + expect(binaryExpressionNode?.fieldNameForChild(3)).toBe('right'); // Negative test - Not a valid child index - expect(binaryExpressionNode!.fieldNameForChild(4)).toBeNull(); + expect(binaryExpressionNode?.fieldNameForChild(4)).toBeNull(); }); }); @@ -579,13 +579,13 @@ describe('Node', () => { // right: (identifier) 2 // ------------------- - expect(binaryExpressionNode!.fieldNameForNamedChild(0)).toBe('left'); + expect(binaryExpressionNode?.fieldNameForNamedChild(0)).toBe('left'); // The comment should not have a field name, as it's just an extra - expect(binaryExpressionNode!.fieldNameForNamedChild(1)).toBeNull(); + expect(binaryExpressionNode?.fieldNameForNamedChild(1)).toBeNull(); // The operator is not a named child, so the named child at index 2 is the right child - expect(binaryExpressionNode!.fieldNameForNamedChild(2)).toBe('right'); + expect(binaryExpressionNode?.fieldNameForNamedChild(2)).toBe('right'); // Negative test - Not a valid child index - expect(binaryExpressionNode!.fieldNameForNamedChild(3)).toBeNull(); + expect(binaryExpressionNode?.fieldNameForNamedChild(3)).toBeNull(); }); }); }); diff --git a/lib/binding_web/test/parser.test.ts b/lib/binding_web/test/parser.test.ts index 13f69416..1c42abee 100644 --- a/lib/binding_web/test/parser.test.ts +++ b/lib/binding_web/test/parser.test.ts @@ -1,18 +1,19 @@ import { describe, it, expect, beforeAll, beforeEach, afterEach } from 'vitest'; import helper, { type LanguageName } from './helper'; -import type { default as ParserType, Language } from 'web-tree-sitter'; +import type { ParseState, Tree, Parser as ParserType, Language as LanguageType } from '../src'; let Parser: typeof ParserType; -let JavaScript: Language; -let HTML: Language; -let JSON: Language; +let Language: typeof LanguageType; +let JavaScript: LanguageType; +let HTML: LanguageType; +let JSON: LanguageType; let languageURL: (name: LanguageName) => string; describe('Parser', () => { let parser: ParserType; beforeAll(async () => { - ({ Parser, JavaScript, HTML, JSON, languageURL } = await helper); + ({ Parser, Language, JavaScript, HTML, JSON, languageURL } = await helper); }); beforeEach(() => { @@ -25,7 +26,7 @@ describe('Parser', () => { describe('.setLanguage', () => { it('allows setting the language to null', () => { - expect(parser.getLanguage()).toBeUndefined(); + expect(parser.getLanguage()).toBeNull(); parser.setLanguage(JavaScript); expect(parser.getLanguage()).toBe(JavaScript); parser.setLanguage(null); @@ -134,7 +135,7 @@ describe('Parser', () => { const templateStringNode = jsTree.rootNode.descendantForIndex( sourceCode.indexOf('`<'), sourceCode.indexOf('>`') - ); + )!; expect(templateStringNode.type).toBe('template_string'); const openQuoteNode = templateStringNode.child(0)!; @@ -222,7 +223,7 @@ describe('Parser', () => { }); describe('.parse', () => { - let tree: ParserType.Tree | null; + let tree: Tree | null; beforeEach(() => { tree = null; @@ -266,7 +267,7 @@ describe('Parser', () => { }); it('can use the bash parser', { timeout: 5000 }, async () => { - parser.setLanguage(await Parser.Language.load(languageURL('bash'))); + parser.setLanguage(await Language.load(languageURL('bash'))); tree = parser.parse('FOO=bar echo < err.txt > hello.txt \nhello${FOO}\nEOF'); expect(tree.rootNode.toString()).toBe( '(program ' + @@ -283,7 +284,7 @@ describe('Parser', () => { }); it('can use the c++ parser', { timeout: 5000 }, async () => { - parser.setLanguage(await Parser.Language.load(languageURL('cpp'))); + parser.setLanguage(await Language.load(languageURL('cpp'))); tree = parser.parse('const char *s = R"EOF(HELLO WORLD)EOF";'); expect(tree.rootNode.toString()).toBe( '(translation_unit (declaration ' + @@ -296,7 +297,7 @@ describe('Parser', () => { }); it('can use the HTML parser', { timeout: 5000 }, async () => { - parser.setLanguage(await Parser.Language.load(languageURL('html'))); + parser.setLanguage(await Language.load(languageURL('html'))); tree = parser.parse('
'); expect(tree.rootNode.toString()).toBe( '(document (element (start_tag (tag_name)) (element (start_tag (tag_name)) ' + @@ -305,7 +306,7 @@ describe('Parser', () => { }); it('can use the python parser', { timeout: 5000 }, async () => { - parser.setLanguage(await Parser.Language.load(languageURL('python'))); + parser.setLanguage(await Language.load(languageURL('python'))); tree = parser.parse('class A:\n def b():\n c()'); expect(tree.rootNode.toString()).toBe( '(module (class_definition ' + @@ -321,7 +322,7 @@ describe('Parser', () => { }); it('can use the rust parser', { timeout: 5000 }, async () => { - parser.setLanguage(await Parser.Language.load(languageURL('rust'))); + parser.setLanguage(await Language.load(languageURL('rust'))); tree = parser.parse('const x: &\'static str = r###"hello"###;'); expect(tree.rootNode.toString()).toBe( '(source_file (const_item ' + @@ -332,7 +333,7 @@ describe('Parser', () => { }); it('can use the typescript parser', { timeout: 5000 }, async () => { - parser.setLanguage(await Parser.Language.load(languageURL('typescript'))); + parser.setLanguage(await Language.load(languageURL('typescript'))); tree = parser.parse('a()\nb()\n[c]'); expect(tree.rootNode.toString()).toBe( '(program ' + @@ -346,7 +347,7 @@ describe('Parser', () => { }); it('can use the tsx parser', { timeout: 5000 }, async () => { - parser.setLanguage(await Parser.Language.load(languageURL('tsx'))); + parser.setLanguage(await Language.load(languageURL('tsx'))); tree = parser.parse('a()\nb()\n[c]'); expect(tree.rootNode.toString()).toBe( '(program ' + @@ -397,7 +398,7 @@ describe('Parser', () => { const startTime = performance.now(); let currentByteOffset = 0; - const progressCallback = (state: ParserType.State) => { + const progressCallback = (state: ParseState) => { expect(state.currentOffset).toBeGreaterThanOrEqual(currentByteOffset); currentByteOffset = state.currentOffset; diff --git a/lib/binding_web/test/query.test.ts b/lib/binding_web/test/query.test.ts index 5cda3ad9..f0bd96c1 100644 --- a/lib/binding_web/test/query.test.ts +++ b/lib/binding_web/test/query.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeAll, beforeEach, afterEach } from 'vitest'; -import type { default as ParserType, Language, Tree, Query, QueryCapture, QueryMatch } from 'web-tree-sitter'; +import type { Parser as ParserType, Language, Tree, Query, QueryMatch, QueryCapture } from '../src'; import helper from './helper'; let Parser: typeof ParserType; @@ -576,12 +576,11 @@ function formatMatches(matches: QueryMatch[]): QueryMatch[] { })); } -function formatCaptures(captures: QueryCapture[]): QueryCapture[] { +function formatCaptures(captures: QueryCapture[]): (QueryCapture & { text: string })[] { return captures.map((c) => { const node = c.node; // @ts-expect-error We're not interested in the node object for these tests delete c.node; - c.text = node.text; - return c; + return { ...c, text: node.text }; }); } diff --git a/lib/binding_web/test/tree.test.ts b/lib/binding_web/test/tree.test.ts index 0718e2b7..711a039f 100644 --- a/lib/binding_web/test/tree.test.ts +++ b/lib/binding_web/test/tree.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeAll, beforeEach, afterEach } from 'vitest'; -import type { default as ParserType, Language, Tree, TreeCursor, Edit, Point } from 'web-tree-sitter'; +import type { Parser as ParserType, Point, Language, Tree, Edit, TreeCursor } from '../src'; import helper from './helper'; let Parser: typeof ParserType; diff --git a/lib/binding_web/wasm/prefix.js b/lib/binding_web/wasm/prefix.js deleted file mode 100644 index c6bc7c22..00000000 --- a/lib/binding_web/wasm/prefix.js +++ /dev/null @@ -1,19 +0,0 @@ -var TreeSitter = function() { - var initPromise; - var document = typeof window == 'object' - ? {currentScript: window.document.currentScript} - : null; - - class Parser { - constructor() { - this.initialize(); - } - - initialize() { - throw new Error("cannot construct a Parser before calling `init()`"); - } - - static init(moduleOptions) { - if (initPromise) return initPromise; - Module = Object.assign({}, Module, moduleOptions); - return initPromise = new Promise((resolveInitPromise) => { diff --git a/lib/binding_web/wasm/suffix.js b/lib/binding_web/wasm/suffix.js deleted file mode 100644 index 8e096f61..00000000 --- a/lib/binding_web/wasm/suffix.js +++ /dev/null @@ -1,23 +0,0 @@ - for (const name of Object.getOwnPropertyNames(ParserImpl.prototype)) { - Object.defineProperty(Parser.prototype, name, { - value: ParserImpl.prototype[name], - enumerable: false, - writable: false, - }) - } - - Parser.Language = Language; - Module.onRuntimeInitialized = () => { - ParserImpl.init(); - resolveInitPromise(); - }; - }); - } - } - - return Parser; -}(); - -if (typeof exports === 'object') { - module.exports = TreeSitter; -} diff --git a/xtask/src/build_wasm.rs b/xtask/src/build_wasm.rs index 5650cde7..abc8b14e 100644 --- a/xtask/src/build_wasm.rs +++ b/xtask/src/build_wasm.rs @@ -24,19 +24,18 @@ enum EmccSource { Podman, } -pub fn run_wasm(args: &BuildWasm) -> Result<()> { - let npm = if cfg!(target_os = "windows") { - "npm.cmd" - } else { - "npm" - }; - let npm = Command::new(npm) - .current_dir("lib/binding_web") - .args(["run", "build:ts"]) - .output() - .expect("Failed to run npm run build:ts"); - bail_on_err(&npm, "Failed to run npm run build:ts")?; +const EXPORTED_RUNTIME_METHODS: [&str; 8] = [ + "AsciiToString", + "stringToUTF8", + "UTF8ToString", + "lengthBytesUTF8", + "stringToUTF16", + "loadWebAssemblyModule", + "getValue", + "setValue", +]; +pub fn run_wasm(args: &BuildWasm) -> Result<()> { let mut emscripten_flags = vec!["-O3", "--minify", "0"]; if args.debug { @@ -118,7 +117,7 @@ pub fn run_wasm(args: &BuildWasm) -> Result<()> { let exported_functions = format!( "{}{}", fs::read_to_string("lib/src/wasm/stdlib-symbols.txt")?, - fs::read_to_string("lib/binding_web/wasm/exports.txt")? + fs::read_to_string("lib/binding_web/lib/exports.txt")? ) .replace('"', "") .lines() @@ -130,7 +129,10 @@ pub fn run_wasm(args: &BuildWasm) -> Result<()> { .to_string(); let exported_functions = format!("EXPORTED_FUNCTIONS={exported_functions}"); - let exported_runtime_methods = "EXPORTED_RUNTIME_METHODS=stringToUTF16,AsciiToString"; + let exported_runtime_methods = format!( + "EXPORTED_RUNTIME_METHODS={}", + EXPORTED_RUNTIME_METHODS.join(",") + ); std::env::set_var("EMCC_DEBUG_SAVE", "1"); @@ -138,7 +140,11 @@ pub fn run_wasm(args: &BuildWasm) -> Result<()> { emscripten_flags.extend([ "-gsource-map", "--source-map-base", ".", + "-fno-exceptions", + "-std=c11", "-s", "WASM=1", + "-s", "EXPORT_ES6", + "-s", "MODULARIZE=1", "-s", "INITIAL_MEMORY=33554432", "-s", "ALLOW_MEMORY_GROWTH=1", "-s", "SUPPORT_BIG_ENDIAN=1", @@ -147,60 +153,40 @@ pub fn run_wasm(args: &BuildWasm) -> Result<()> { "-s", "NODEJS_CATCH_EXIT=0", "-s", "NODEJS_CATCH_REJECTION=0", "-s", &exported_functions, - "-s", exported_runtime_methods, - "-fno-exceptions", - "-std=c11", + "-s", &exported_runtime_methods, "-D", "fprintf(...)=", "-D", "NDEBUG=", "-D", "_POSIX_C_SOURCE=200112L", "-D", "_DEFAULT_SOURCE=", "-I", "lib/src", "-I", "lib/include", - "--js-library", "lib/binding_web/wasm/imports.js", - "--pre-js", "lib/binding_web/wasm/prefix.js", - "--post-js", "lib/binding_web/dist/tree-sitter.js", - "--post-js", "lib/binding_web/wasm/suffix.js", - "-o", "target/scratch/tree-sitter.js", + "--js-library", "lib/binding_web/lib/imports.js", + "--pre-js", "lib/binding_web/lib/prefix.js", + "-o", "lib/binding_web/lib/tree-sitter.js", "lib/src/lib.c", "lib/binding_web/lib/tree-sitter.c", ]); + if args.emit_tsd { + emscripten_flags.extend(["--emit-tsd", "tree-sitter.d.ts"]); + } + let command = command.args(&emscripten_flags); if args.watch { - watch_wasm!(|| build_wasm(command, args.debug)); + watch_wasm!(|| build_wasm(command)); } else { - build_wasm(command, args.debug)?; + build_wasm(command)?; } Ok(()) } -fn build_wasm(cmd: &mut Command, debug: bool) -> Result<()> { +fn build_wasm(cmd: &mut Command) -> Result<()> { bail_on_err( &cmd.spawn()?.wait_with_output()?, "Failed to compile the Tree-sitter WASM library", )?; - let dir = if debug { - PathBuf::from("lib/binding_web/debug") - } else { - PathBuf::from("lib/binding_web") - }; - - fs::create_dir_all(&dir)?; - - fs::rename("target/scratch/tree-sitter.js", dir.join("tree-sitter.js"))?; - - fs::rename( - "target/scratch/tree-sitter.wasm", - dir.join("tree-sitter.wasm"), - )?; - - fs::rename( - "target/scratch/tree-sitter.wasm.map", - dir.join("tree-sitter.wasm.map"), - )?; - Ok(()) } diff --git a/xtask/src/check_wasm_exports.rs b/xtask/src/check_wasm_exports.rs index 4d6fa943..4a09f8a8 100644 --- a/xtask/src/check_wasm_exports.rs +++ b/xtask/src/check_wasm_exports.rs @@ -60,7 +60,7 @@ pub fn run(args: &CheckWasmExports) -> Result<()> { } fn check_wasm_exports() -> Result<()> { - let mut wasm_exports = std::fs::read_to_string("lib/binding_web/wasm/exports.txt")? + let mut wasm_exports = std::fs::read_to_string("lib/binding_web/lib/exports.txt")? .lines() .map(|s| s.replace("_wasm", "").replace("byte", "index")) // remove leading and trailing quotes, trailing comma diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 73b0b6b7..5ab23a8e 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -85,6 +85,10 @@ struct BuildWasm { /// Rebuild when relevant files are changed. #[arg(long, short)] watch: bool, + /// Emit TypeScript type definitions for the generated bindings, + /// requires `tsc` to be available. + #[arg(long, short)] + emit_tsd: bool, } #[derive(Args)]