diff --git a/.gitignore b/.gitignore index 25738984..333b718a 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,6 @@ test/fuzz/out test/fixtures/grammars/* !test/fixtures/grammars/.gitkeep -package-lock.json node_modules docs/assets/js/tree-sitter.js diff --git a/lib/binding_web/.eslintrc.js b/lib/binding_web/.eslintrc.js deleted file mode 100644 index 38709eb8..00000000 --- a/lib/binding_web/.eslintrc.js +++ /dev/null @@ -1,22 +0,0 @@ -module.exports = { - 'env': { - 'commonjs': true, - 'es2021': true, - }, - 'extends': 'google', - 'overrides': [ - ], - 'parserOptions': { - 'ecmaVersion': 'latest', - 'sourceType': 'module', - }, - 'rules': { - 'indent': ['error', 2, {'SwitchCase': 1}], - 'max-len': [ - 'error', - {'code': 120, 'ignoreComments': true, 'ignoreUrls': true, 'ignoreStrings': true, 'ignoreTemplateLiterals': true}, - ], - 'require-jsdoc': 0, - 'new-cap': 0, - }, -}; diff --git a/lib/binding_web/.gitignore b/lib/binding_web/.gitignore index eec0cfe6..5da2c102 100644 --- a/lib/binding_web/.gitignore +++ b/lib/binding_web/.gitignore @@ -1,6 +1,8 @@ +dist/ /tree-sitter.js +/tree-sitter.js.map /tree-sitter.wasm -package-lock.json +/tree-sitter.wasm.map node_modules *.tgz LICENSE diff --git a/lib/binding_web/README.md b/lib/binding_web/README.md index 8d79605d..23bbd92e 100644 --- a/lib/binding_web/README.md +++ b/lib/binding_web/README.md @@ -50,6 +50,14 @@ await Parser.init(); // the library is ready ``` +To install a debug version of the library, pass in `--debug` when running `npm install`: + +```sh +npm install web-tree-sitter --debug +``` + +This will load the debug version of the `.wasm` file, which includes sourcemaps for both the JS and WASM files, debug symbols, and assertions. + ### Basic Usage First, create a parser: diff --git a/lib/binding_web/binding.js b/lib/binding_web/binding.js deleted file mode 100644 index e01addaf..00000000 --- a/lib/binding_web/binding.js +++ /dev/null @@ -1,1735 +0,0 @@ -/* eslint-disable-next-line spaced-comment */ -/// -/* eslint-disable-next-line spaced-comment */ -/// - -const C = Module; -const INTERNAL = {}; -const SIZE_OF_SHORT = 2; -const SIZE_OF_INT = 4; -const SIZE_OF_CURSOR = 4 * SIZE_OF_INT; -const SIZE_OF_NODE = 5 * SIZE_OF_INT; -const SIZE_OF_POINT = 2 * SIZE_OF_INT; -const SIZE_OF_RANGE = 2 * SIZE_OF_INT + 2 * SIZE_OF_POINT; -const ZERO_POINT = {row: 0, column: 0}; -const QUERY_WORD_REGEX = /[\w-.]*/g; - -const PREDICATE_STEP_TYPE_CAPTURE = 1; -const PREDICATE_STEP_TYPE_STRING = 2; - -const LANGUAGE_FUNCTION_REGEX = /^_?tree_sitter_\w+/; - -let VERSION; -let MIN_COMPATIBLE_VERSION; -let TRANSFER_BUFFER; -let currentParseCallback; -// eslint-disable-next-line no-unused-vars -let currentProgressCallback; -// eslint-disable-next-line no-unused-vars -let currentQueryProgressCallback; -// eslint-disable-next-line no-unused-vars -let currentLogCallback; - -// eslint-disable-next-line no-unused-vars -class ParserImpl { - static init() { - TRANSFER_BUFFER = C._ts_init(); - VERSION = getValue(TRANSFER_BUFFER, 'i32'); - MIN_COMPATIBLE_VERSION = getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32'); - } - - initialize() { - C._ts_parser_new_wasm(); - this[0] = getValue(TRANSFER_BUFFER, 'i32'); - this[1] = getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32'); - } - - delete() { - C._ts_parser_delete(this[0]); - C._free(this[1]); - this[0] = 0; - this[1] = 0; - } - - setLanguage(language) { - let address; - if (!language) { - address = 0; - language = null; - } else if (language.constructor === Language) { - address = language[0]; - const version = C._ts_language_version(address); - if (version < MIN_COMPATIBLE_VERSION || VERSION < version) { - throw new Error( - `Incompatible language version ${version}. ` + - `Compatibility range ${MIN_COMPATIBLE_VERSION} through ${VERSION}.`, - ); - } - } else { - throw new Error('Argument must be a Language'); - } - this.language = language; - C._ts_parser_set_language(this[0], address); - return this; - } - - getLanguage() { - return this.language; - } - - parse(callback, oldTree, options) { - if (typeof callback === 'string') { - currentParseCallback = (index, _) => callback.slice(index); - } else if (typeof callback === 'function') { - currentParseCallback = callback; - } else { - throw new Error('Argument must be a string or a function'); - } - - if (options?.progressCallback) { - currentProgressCallback = options.progressCallback; - } else { - currentProgressCallback = null; - } - - if (this.logCallback) { - currentLogCallback = this.logCallback; - C._ts_parser_enable_logger_wasm(this[0], 1); - } else { - currentLogCallback = null; - C._ts_parser_enable_logger_wasm(this[0], 0); - } - - let rangeCount = 0; - let rangeAddress = 0; - if (options?.includedRanges) { - rangeCount = options.includedRanges.length; - rangeAddress = C._calloc(rangeCount, SIZE_OF_RANGE); - let address = rangeAddress; - for (let i = 0; i < rangeCount; i++) { - marshalRange(address, options.includedRanges[i]); - address += SIZE_OF_RANGE; - } - } - - const treeAddress = C._ts_parser_parse_wasm( - this[0], - this[1], - oldTree ? oldTree[0] : 0, - rangeAddress, - rangeCount, - ); - - if (!treeAddress) { - currentParseCallback = null; - currentLogCallback = null; - currentProgressCallback = null; - throw new Error('Parsing failed'); - } - - const result = new Tree(INTERNAL, treeAddress, this.language, currentParseCallback); - currentParseCallback = null; - currentLogCallback = null; - currentProgressCallback = null; - return result; - } - - reset() { - C._ts_parser_reset(this[0]); - } - - getIncludedRanges() { - C._ts_parser_included_ranges_wasm(this[0]); - const count = getValue(TRANSFER_BUFFER, 'i32'); - const buffer = 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] = unmarshalRange(address); - address += SIZE_OF_RANGE; - } - C._free(buffer); - } - return result; - } - - getTimeoutMicros() { - return C._ts_parser_timeout_micros(this[0]); - } - - setTimeoutMicros(timeout) { - C._ts_parser_set_timeout_micros(this[0], timeout); - } - - setLogger(callback) { - if (!callback) { - callback = null; - } else if (typeof callback !== 'function') { - throw new Error('Logger callback must be a function'); - } - this.logCallback = callback; - return this; - } - - getLogger() { - return this.logCallback; - } -} - -class Tree { - constructor(internal, address, language, textCallback) { - assertInternal(internal); - this[0] = address; - this.language = language; - this.textCallback = textCallback; - } - - copy() { - const address = C._ts_tree_copy(this[0]); - return new Tree(INTERNAL, address, this.language, this.textCallback); - } - - delete() { - C._ts_tree_delete(this[0]); - this[0] = 0; - } - - edit(edit) { - marshalEdit(edit); - C._ts_tree_edit_wasm(this[0]); - } - - get rootNode() { - C._ts_tree_root_node_wasm(this[0]); - return unmarshalNode(this); - } - - rootNodeWithOffset(offsetBytes, offsetExtent) { - const address = TRANSFER_BUFFER + SIZE_OF_NODE; - setValue(address, offsetBytes, 'i32'); - marshalPoint(address + SIZE_OF_INT, offsetExtent); - C._ts_tree_root_node_with_offset_wasm(this[0]); - return unmarshalNode(this); - } - - getLanguage() { - return this.language; - } - - walk() { - return this.rootNode.walk(); - } - - getChangedRanges(other) { - if (other.constructor !== Tree) { - throw new TypeError('Argument must be a 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 result = new Array(count); - if (count > 0) { - let address = buffer; - for (let i = 0; i < count; i++) { - result[i] = unmarshalRange(address); - address += SIZE_OF_RANGE; - } - C._free(buffer); - } - return result; - } - - getIncludedRanges() { - C._ts_tree_included_ranges_wasm(this[0]); - const count = getValue(TRANSFER_BUFFER, 'i32'); - const buffer = 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] = unmarshalRange(address); - address += SIZE_OF_RANGE; - } - C._free(buffer); - } - return result; - } -} - -class Node { - constructor(internal, tree) { - assertInternal(internal); - this.tree = tree; - } - - get typeId() { - marshalNode(this); - return C._ts_node_symbol_wasm(this.tree[0]); - } - - get grammarId() { - marshalNode(this); - return C._ts_node_grammar_symbol_wasm(this.tree[0]); - } - - get type() { - return this.tree.language.types[this.typeId] || 'ERROR'; - } - - get grammarType() { - return this.tree.language.types[this.grammarId] || 'ERROR'; - } - - get endPosition() { - marshalNode(this); - C._ts_node_end_point_wasm(this.tree[0]); - return unmarshalPoint(TRANSFER_BUFFER); - } - - get endIndex() { - marshalNode(this); - return C._ts_node_end_index_wasm(this.tree[0]); - } - - get text() { - return getText(this.tree, this.startIndex, this.endIndex); - } - - get parseState() { - marshalNode(this); - return C._ts_node_parse_state_wasm(this.tree[0]); - } - - get nextParseState() { - marshalNode(this); - return C._ts_node_next_parse_state_wasm(this.tree[0]); - } - - get isNamed() { - marshalNode(this); - return C._ts_node_is_named_wasm(this.tree[0]) === 1; - } - - get hasError() { - marshalNode(this); - return C._ts_node_has_error_wasm(this.tree[0]) === 1; - } - - get hasChanges() { - marshalNode(this); - return C._ts_node_has_changes_wasm(this.tree[0]) === 1; - } - - get isError() { - marshalNode(this); - return C._ts_node_is_error_wasm(this.tree[0]) === 1; - } - - get isMissing() { - marshalNode(this); - return C._ts_node_is_missing_wasm(this.tree[0]) === 1; - } - - get isExtra() { - marshalNode(this); - return C._ts_node_is_extra_wasm(this.tree[0]) === 1; - } - - equals(other) { - return this.tree === other.tree && this.id === other.id; - } - - child(index) { - marshalNode(this); - C._ts_node_child_wasm(this.tree[0], index); - return unmarshalNode(this.tree); - } - - namedChild(index) { - marshalNode(this); - C._ts_node_named_child_wasm(this.tree[0], index); - return unmarshalNode(this.tree); - } - - childForFieldId(fieldId) { - marshalNode(this); - C._ts_node_child_by_field_id_wasm(this.tree[0], fieldId); - return unmarshalNode(this.tree); - } - - childForFieldName(fieldName) { - const fieldId = this.tree.language.fields.indexOf(fieldName); - if (fieldId !== -1) return this.childForFieldId(fieldId); - return null; - } - - fieldNameForChild(index) { - marshalNode(this); - const address = C._ts_node_field_name_for_child_wasm(this.tree[0], index); - if (!address) { - return null; - } - const result = AsciiToString(address); - // must not free, the string memory is owned by the language - return result; - } - - fieldNameForNamedChild(index) { - marshalNode(this); - const address = C._ts_node_field_name_for_named_child_wasm( - this.tree[0], - index, - ); - if (!address) { - return null; - } - const result = AsciiToString(address); - // must not free, the string memory is owned by the language - return result; - } - - childrenForFieldName(fieldName) { - const fieldId = this.tree.language.fields.indexOf(fieldName); - if (fieldId !== -1 && fieldId !== 0) return this.childrenForFieldId(fieldId); - return []; - } - - childrenForFieldId(fieldId) { - 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 result = new Array(count); - if (count > 0) { - let address = buffer; - for (let i = 0; i < count; i++) { - result[i] = unmarshalNode(this.tree, address); - address += SIZE_OF_NODE; - } - C._free(buffer); - } - return result; - } - - firstChildForIndex(index) { - marshalNode(this); - const address = TRANSFER_BUFFER + SIZE_OF_NODE; - setValue(address, index, 'i32'); - C._ts_node_first_child_for_byte_wasm(this.tree[0]); - return unmarshalNode(this.tree); - } - - firstNamedChildForIndex(index) { - marshalNode(this); - const address = TRANSFER_BUFFER + SIZE_OF_NODE; - setValue(address, index, 'i32'); - C._ts_node_first_named_child_for_byte_wasm(this.tree[0]); - return unmarshalNode(this.tree); - } - - get childCount() { - marshalNode(this); - return C._ts_node_child_count_wasm(this.tree[0]); - } - - get namedChildCount() { - marshalNode(this); - return C._ts_node_named_child_count_wasm(this.tree[0]); - } - - get firstChild() { - return this.child(0); - } - - get firstNamedChild() { - return this.namedChild(0); - } - - get lastChild() { - return this.child(this.childCount - 1); - } - - get lastNamedChild() { - return this.namedChild(this.namedChildCount - 1); - } - - get children() { - 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'); - this._children = new Array(count); - if (count > 0) { - let address = buffer; - for (let i = 0; i < count; i++) { - this._children[i] = unmarshalNode(this.tree, address); - address += SIZE_OF_NODE; - } - C._free(buffer); - } - } - return this._children; - } - - get namedChildren() { - 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'); - this._namedChildren = new Array(count); - if (count > 0) { - let address = buffer; - for (let i = 0; i < count; i++) { - this._namedChildren[i] = unmarshalNode(this.tree, address); - address += SIZE_OF_NODE; - } - C._free(buffer); - } - } - return this._namedChildren; - } - - descendantsOfType(types, startPosition, endPosition) { - if (!Array.isArray(types)) types = [types]; - if (!startPosition) startPosition = ZERO_POINT; - if (!endPosition) endPosition = ZERO_POINT; - - // Convert the type strings to numeric type symbols. - const symbols = []; - const typesBySymbol = this.tree.language.types; - for (let i = 0; i < types.length; i++) { - if (types[i] == "ERROR") { - symbols.push(65535); // Internally, ts_builtin_sym_error is -1, which is UINT_16MAX - } - } - for (let i = 0, n = typesBySymbol.length; i < n; i++) { - if (types.includes(typesBySymbol[i])) { - symbols.push(i); - } - } - - // 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'); - } - - // Call the C API to compute the descendants. - marshalNode(this); - C._ts_node_descendants_of_type_wasm( - this.tree[0], - symbolsAddress, - symbols.length, - startPosition.row, - startPosition.column, - endPosition.row, - endPosition.column, - ); - - // Instantiate the nodes based on the data returned. - const descendantCount = getValue(TRANSFER_BUFFER, 'i32'); - const descendantAddress = getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32'); - const result = new Array(descendantCount); - if (descendantCount > 0) { - let address = descendantAddress; - for (let i = 0; i < descendantCount; i++) { - result[i] = unmarshalNode(this.tree, address); - address += SIZE_OF_NODE; - } - } - - // Free the intermediate buffers - C._free(descendantAddress); - C._free(symbolsAddress); - return result; - } - - get nextSibling() { - marshalNode(this); - C._ts_node_next_sibling_wasm(this.tree[0]); - return unmarshalNode(this.tree); - } - - get previousSibling() { - marshalNode(this); - C._ts_node_prev_sibling_wasm(this.tree[0]); - return unmarshalNode(this.tree); - } - - get nextNamedSibling() { - marshalNode(this); - C._ts_node_next_named_sibling_wasm(this.tree[0]); - return unmarshalNode(this.tree); - } - - get previousNamedSibling() { - marshalNode(this); - C._ts_node_prev_named_sibling_wasm(this.tree[0]); - return unmarshalNode(this.tree); - } - - get descendantCount() { - marshalNode(this); - return C._ts_node_descendant_count_wasm(this.tree[0]); - } - - get parent() { - marshalNode(this); - C._ts_node_parent_wasm(this.tree[0]); - return unmarshalNode(this.tree); - } - - descendantForIndex(start, end = start) { - if (typeof start !== 'number' || typeof end !== 'number') { - throw new Error('Arguments must be numbers'); - } - - marshalNode(this); - const address = TRANSFER_BUFFER + SIZE_OF_NODE; - setValue(address, start, 'i32'); - setValue(address + SIZE_OF_INT, end, 'i32'); - C._ts_node_descendant_for_index_wasm(this.tree[0]); - return unmarshalNode(this.tree); - } - - namedDescendantForIndex(start, end = start) { - if (typeof start !== 'number' || typeof end !== 'number') { - throw new Error('Arguments must be numbers'); - } - - marshalNode(this); - const address = TRANSFER_BUFFER + SIZE_OF_NODE; - setValue(address, start, 'i32'); - setValue(address + SIZE_OF_INT, end, 'i32'); - C._ts_node_named_descendant_for_index_wasm(this.tree[0]); - return unmarshalNode(this.tree); - } - - descendantForPosition(start, end = start) { - if (!isPoint(start) || !isPoint(end)) { - throw new Error('Arguments must be {row, column} objects'); - } - - marshalNode(this); - const address = TRANSFER_BUFFER + SIZE_OF_NODE; - marshalPoint(address, start); - marshalPoint(address + SIZE_OF_POINT, end); - C._ts_node_descendant_for_position_wasm(this.tree[0]); - return unmarshalNode(this.tree); - } - - namedDescendantForPosition(start, end = start) { - if (!isPoint(start) || !isPoint(end)) { - throw new Error('Arguments must be {row, column} objects'); - } - - marshalNode(this); - const address = TRANSFER_BUFFER + SIZE_OF_NODE; - marshalPoint(address, start); - marshalPoint(address + SIZE_OF_POINT, end); - C._ts_node_named_descendant_for_position_wasm(this.tree[0]); - return unmarshalNode(this.tree); - } - - walk() { - marshalNode(this); - C._ts_tree_cursor_new_wasm(this.tree[0]); - return new TreeCursor(INTERNAL, this.tree); - } - - edit(edit) { - if (this.startIndex >= edit.oldEndIndex) { - this.startIndex = edit.newEndIndex + (this.startIndex - edit.oldEndIndex); - let subbedPointRow; - let subbedPointColumn; - if (this.startPosition.row > edit.oldEndPosition.row) { - subbedPointRow = this.startPosition.row - edit.oldEndPosition.row; - subbedPointColumn = this.startPosition.column; - } else { - subbedPointRow = 0; - if (this.startPosition.column >= edit.oldEndPosition.column) { - subbedPointColumn = - this.startPosition.column - edit.oldEndPosition.column; - } - } - - if (subbedPointRow > 0) { - this.startPosition.row += subbedPointRow; - this.startPosition.column = subbedPointColumn; - } else { - this.startPosition.column += subbedPointColumn; - } - } else if (this.startIndex > edit.startIndex) { - this.startIndex = edit.newEndIndex; - this.startPosition.row = edit.newEndPosition.row; - this.startPosition.column = edit.newEndPosition.column; - } - } - - toString() { - marshalNode(this); - const address = C._ts_node_to_string_wasm(this.tree[0]); - const result = AsciiToString(address); - C._free(address); - return result; - } -} - -class TreeCursor { - constructor(internal, tree) { - assertInternal(internal); - this.tree = tree; - unmarshalTreeCursor(this); - } - - copy() { - const copy = new TreeCursor(INTERNAL, this.tree); - C._ts_tree_cursor_copy_wasm(this.tree[0]); - unmarshalTreeCursor(copy); - return copy; - } - - delete() { - marshalTreeCursor(this); - C._ts_tree_cursor_delete_wasm(this.tree[0]); - this[0] = this[1] = this[2] = 0; - } - - reset(node) { - marshalNode(node); - marshalTreeCursor(this, TRANSFER_BUFFER + SIZE_OF_NODE); - C._ts_tree_cursor_reset_wasm(this.tree[0]); - unmarshalTreeCursor(this); - } - - resetTo(cursor) { - marshalTreeCursor(this, TRANSFER_BUFFER); - marshalTreeCursor(cursor, TRANSFER_BUFFER + SIZE_OF_CURSOR); - C._ts_tree_cursor_reset_to_wasm(this.tree[0], cursor.tree[0]); - unmarshalTreeCursor(this); - } - - get nodeType() { - return this.tree.language.types[this.nodeTypeId] || 'ERROR'; - } - - get nodeTypeId() { - marshalTreeCursor(this); - return C._ts_tree_cursor_current_node_type_id_wasm(this.tree[0]); - } - - get nodeStateId() { - marshalTreeCursor(this); - return C._ts_tree_cursor_current_node_state_id_wasm(this.tree[0]); - } - - get nodeId() { - marshalTreeCursor(this); - return C._ts_tree_cursor_current_node_id_wasm(this.tree[0]); - } - - get nodeIsNamed() { - marshalTreeCursor(this); - return C._ts_tree_cursor_current_node_is_named_wasm(this.tree[0]) === 1; - } - - get nodeIsMissing() { - marshalTreeCursor(this); - return C._ts_tree_cursor_current_node_is_missing_wasm(this.tree[0]) === 1; - } - - get nodeText() { - marshalTreeCursor(this); - const startIndex = C._ts_tree_cursor_start_index_wasm(this.tree[0]); - const endIndex = C._ts_tree_cursor_end_index_wasm(this.tree[0]); - return getText(this.tree, startIndex, endIndex); - } - - get startPosition() { - marshalTreeCursor(this); - C._ts_tree_cursor_start_position_wasm(this.tree[0]); - return unmarshalPoint(TRANSFER_BUFFER); - } - - get endPosition() { - marshalTreeCursor(this); - C._ts_tree_cursor_end_position_wasm(this.tree[0]); - return unmarshalPoint(TRANSFER_BUFFER); - } - - get startIndex() { - marshalTreeCursor(this); - return C._ts_tree_cursor_start_index_wasm(this.tree[0]); - } - - get endIndex() { - marshalTreeCursor(this); - return C._ts_tree_cursor_end_index_wasm(this.tree[0]); - } - - get currentNode() { - marshalTreeCursor(this); - C._ts_tree_cursor_current_node_wasm(this.tree[0]); - return unmarshalNode(this.tree); - } - - get currentFieldId() { - marshalTreeCursor(this); - return C._ts_tree_cursor_current_field_id_wasm(this.tree[0]); - } - - get currentFieldName() { - return this.tree.language.fields[this.currentFieldId]; - } - - get currentDepth() { - marshalTreeCursor(this); - return C._ts_tree_cursor_current_depth_wasm(this.tree[0]); - } - - get currentDescendantIndex() { - marshalTreeCursor(this); - return C._ts_tree_cursor_current_descendant_index_wasm(this.tree[0]); - } - - gotoFirstChild() { - marshalTreeCursor(this); - const result = C._ts_tree_cursor_goto_first_child_wasm(this.tree[0]); - unmarshalTreeCursor(this); - return result === 1; - } - - gotoLastChild() { - marshalTreeCursor(this); - const result = C._ts_tree_cursor_goto_last_child_wasm(this.tree[0]); - unmarshalTreeCursor(this); - return result === 1; - } - - gotoFirstChildForIndex(goalIndex) { - marshalTreeCursor(this); - 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; - } - - gotoFirstChildForPosition(goalPosition) { - marshalTreeCursor(this); - marshalPoint(TRANSFER_BUFFER + SIZE_OF_CURSOR, goalPosition); - const result = C._ts_tree_cursor_goto_first_child_for_position_wasm(this.tree[0]); - unmarshalTreeCursor(this); - return result === 1; - } - - gotoNextSibling() { - marshalTreeCursor(this); - const result = C._ts_tree_cursor_goto_next_sibling_wasm(this.tree[0]); - unmarshalTreeCursor(this); - return result === 1; - } - - gotoPreviousSibling() { - marshalTreeCursor(this); - const result = C._ts_tree_cursor_goto_previous_sibling_wasm(this.tree[0]); - unmarshalTreeCursor(this); - return result === 1; - } - - gotoDescendant(goalDescendantindex) { - marshalTreeCursor(this); - C._ts_tree_cursor_goto_descendant_wasm(this.tree[0], goalDescendantindex); - unmarshalTreeCursor(this); - } - - gotoParent() { - marshalTreeCursor(this); - const result = C._ts_tree_cursor_goto_parent_wasm(this.tree[0]); - unmarshalTreeCursor(this); - return result === 1; - } -} - -class Language { - constructor(internal, address) { - assertInternal(internal); - this[0] = address; - 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.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); - } else { - this.fields[i] = null; - } - } - } - - get name() { - return UTF8ToString(C._ts_language_name(this[0])); - } - - get version() { - return C._ts_language_version(this[0]); - } - - get fieldCount() { - return this.fields.length - 1; - } - - get stateCount() { - return C._ts_language_state_count(this[0]); - } - - fieldIdForName(fieldName) { - const result = this.fields.indexOf(fieldName); - if (result !== -1) { - return result; - } else { - return null; - } - } - - fieldNameForId(fieldId) { - return this.fields[fieldId] || null; - } - - idForNodeType(type, named) { - const typeLength = 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._free(typeAddress); - return result || null; - } - - get nodeTypeCount() { - return C._ts_language_symbol_count(this[0]); - } - - nodeTypeForId(typeId) { - const name = C._ts_language_symbol_name(this[0], typeId); - return name ? UTF8ToString(name) : null; - } - - nodeTypeIsNamed(typeId) { - return C._ts_language_type_is_named_wasm(this[0], typeId) ? true : false; - } - - nodeTypeIsVisible(typeId) { - return C._ts_language_type_is_visible_wasm(this[0], typeId) ? true : false; - } - - get supertypes() { - C._ts_language_supertypes_wasm(this[0]); - const count = getValue(TRANSFER_BUFFER, 'i32'); - const buffer = 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'); - address += SIZE_OF_SHORT; - } - } - - return result; - } - - subtypes(supertype) { - C._ts_language_subtypes_wasm(this[0], supertype); - const count = getValue(TRANSFER_BUFFER, 'i32'); - const buffer = 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'); - address += SIZE_OF_SHORT; - } - } - - return result; - } - - nextState(stateId, typeId) { - return C._ts_language_next_state(this[0], stateId, typeId); - } - - lookaheadIterator(stateId) { - const address = C._ts_lookahead_iterator_new(this[0], stateId); - if (address) return new LookaheadIterable(INTERNAL, address, this); - return null; - } - - query(source) { - const sourceLength = lengthBytesUTF8(source); - const sourceAddress = C._malloc(sourceLength + 1); - stringToUTF8(source, sourceAddress, sourceLength + 1); - const address = C._ts_query_new( - this[0], - sourceAddress, - sourceLength, - TRANSFER_BUFFER, - TRANSFER_BUFFER + SIZE_OF_INT, - ); - - if (!address) { - const errorId = getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32'); - const errorByte = getValue(TRANSFER_BUFFER, 'i32'); - const errorIndex = UTF8ToString(sourceAddress, errorByte).length; - const suffix = source.substr(errorIndex, 100).split('\n')[0]; - let word = suffix.match(QUERY_WORD_REGEX)[0]; - let error; - switch (errorId) { - case 2: - error = new RangeError(`Bad node name '${word}'`); - break; - case 3: - error = new RangeError(`Bad field name '${word}'`); - break; - case 4: - error = new RangeError(`Bad capture name @${word}`); - break; - case 5: - error = new TypeError(`Bad pattern structure at offset ${errorIndex}: '${suffix}'...`); - word = ''; - break; - default: - error = new SyntaxError(`Bad syntax at offset ${errorIndex}: '${suffix}'...`); - word = ''; - break; - } - error.index = errorIndex; - error.length = word.length; - C._free(sourceAddress); - throw error; - } - - const stringCount = C._ts_query_string_count(address); - const captureCount = C._ts_query_capture_count(address); - const patternCount = C._ts_query_pattern_count(address); - const captureNames = new Array(captureCount); - const captureQuantifiers = new Array(patternCount); - const stringValues = new Array(stringCount); - - for (let i = 0; i < captureCount; i++) { - const nameAddress = C._ts_query_capture_name_for_id( - address, - i, - TRANSFER_BUFFER, - ); - const nameLength = getValue(TRANSFER_BUFFER, 'i32'); - captureNames[i] = 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; - } - captureQuantifiers[i] = captureQuantifiersArray; - } - - for (let i = 0; i < stringCount; i++) { - const valueAddress = C._ts_query_string_value_for_id( - address, - i, - TRANSFER_BUFFER, - ); - const nameLength = getValue(TRANSFER_BUFFER, 'i32'); - stringValues[i] = UTF8ToString(valueAddress, nameLength); - } - - const setProperties = new Array(patternCount); - const assertedProperties = new Array(patternCount); - const refutedProperties = new Array(patternCount); - const predicates = new Array(patternCount); - const textPredicates = new Array(patternCount); - - for (let i = 0; i < patternCount; i++) { - const predicatesAddress = C._ts_query_predicates_for_pattern( - address, - i, - TRANSFER_BUFFER, - ); - const stepCount = getValue(TRANSFER_BUFFER, 'i32'); - - predicates[i] = []; - textPredicates[i] = []; - - const steps = []; - let stepAddress = predicatesAddress; - for (let j = 0; j < stepCount; j++) { - const stepType = getValue(stepAddress, 'i32'); - stepAddress += SIZE_OF_INT; - const stepValueId = getValue(stepAddress, 'i32'); - stepAddress += SIZE_OF_INT; - if (stepType === PREDICATE_STEP_TYPE_CAPTURE) { - steps.push({type: 'capture', name: captureNames[stepValueId]}); - } else if (stepType === PREDICATE_STEP_TYPE_STRING) { - steps.push({type: 'string', value: stringValues[stepValueId]}); - } else if (steps.length > 0) { - if (steps[0].type !== 'string') { - throw new Error('Predicates must begin with a literal value'); - } - const operator = steps[0].value; - let isPositive = true; - let matchAll = true; - let captureName; - switch (operator) { - case 'any-not-eq?': - case 'not-eq?': - isPositive = false; - case 'any-eq?': - case 'eq?': - if (steps.length !== 3) { - throw new Error( - `Wrong number of arguments to \`#${operator}\` predicate. Expected 2, got ${steps.length - 1}`, - ); - } - if (steps[1].type !== 'capture') { - throw new Error( - `First argument of \`#${operator}\` predicate must be a capture. Got "${steps[1].value}"`, - ); - } - matchAll = !operator.startsWith('any-'); - if (steps[2].type === 'capture') { - const captureName1 = steps[1].name; - const captureName2 = steps[2].name; - textPredicates[i].push((captures) => { - const nodes1 = []; - const nodes2 = []; - for (const c of captures) { - if (c.name === captureName1) nodes1.push(c.node); - if (c.name === captureName2) nodes2.push(c.node); - } - const compare = (n1, n2, positive) => { - return positive ? - n1.text === n2.text : - n1.text !== n2.text; - }; - return matchAll ? - nodes1.every((n1) => nodes2.some((n2) => compare(n1, n2, isPositive))) : - nodes1.some((n1) => nodes2.some((n2) => compare(n1, n2, isPositive))); - }); - } else { - captureName = steps[1].name; - const stringValue = steps[2].value; - const matches = (n) => n.text === stringValue; - const doesNotMatch = (n) => n.text !== stringValue; - textPredicates[i].push((captures) => { - const nodes = []; - for (const c of captures) { - if (c.name === captureName) nodes.push(c.node); - } - const test = isPositive ? matches : doesNotMatch; - return matchAll ? - nodes.every(test) : - nodes.some(test); - }); - } - break; - - case 'any-not-match?': - case 'not-match?': - isPositive = false; - case 'any-match?': - case 'match?': - if (steps.length !== 3) { - throw new Error( - `Wrong number of arguments to \`#${operator}\` predicate. Expected 2, got ${steps.length - 1}.`, - ); - } - if (steps[1].type !== 'capture') { - throw new Error( - `First argument of \`#${operator}\` predicate must be a capture. Got "${steps[1].value}".`, - ); - } - if (steps[2].type !== 'string') { - throw new Error( - `Second argument of \`#${operator}\` predicate must be a string. Got @${steps[2].value}.`, - ); - } - captureName = steps[1].name; - const regex = new RegExp(steps[2].value); - matchAll = !operator.startsWith('any-'); - textPredicates[i].push((captures) => { - const nodes = []; - for (const c of captures) { - if (c.name === captureName) nodes.push(c.node.text); - } - const test = (text, positive) => { - return positive ? - regex.test(text) : - !regex.test(text); - }; - if (nodes.length === 0) return !isPositive; - return matchAll ? - nodes.every((text) => test(text, isPositive)) : - nodes.some((text) => test(text, isPositive)); - }); - break; - - case 'set!': - if (steps.length < 2 || steps.length > 3) { - throw new Error( - `Wrong number of arguments to \`#set!\` predicate. Expected 1 or 2. Got ${steps.length - 1}.`, - ); - } - if (steps.some((s) => s.type !== 'string')) { - throw new Error( - `Arguments to \`#set!\` predicate must be a strings.".`, - ); - } - if (!setProperties[i]) setProperties[i] = {}; - setProperties[i][steps[1].value] = steps[2] ? steps[2].value : null; - break; - - case 'is?': - case 'is-not?': - if (steps.length < 2 || steps.length > 3) { - throw new Error( - `Wrong number of arguments to \`#${operator}\` predicate. Expected 1 or 2. Got ${steps.length - 1}.`, - ); - } - if (steps.some((s) => s.type !== 'string')) { - throw new Error( - `Arguments to \`#${operator}\` predicate must be a strings.".`, - ); - } - const properties = operator === 'is?' ? assertedProperties : refutedProperties; - if (!properties[i]) properties[i] = {}; - properties[i][steps[1].value] = steps[2] ? steps[2].value : null; - break; - - case 'not-any-of?': - isPositive = false; - case 'any-of?': - if (steps.length < 2) { - throw new Error( - `Wrong number of arguments to \`#${operator}\` predicate. Expected at least 1. Got ${steps.length - 1}.`, - ); - } - if (steps[1].type !== 'capture') { - throw new Error( - `First argument of \`#${operator}\` predicate must be a capture. Got "${steps[1].value}".`, - ); - } - for (let i = 2; i < steps.length; i++) { - if (steps[i].type !== 'string') { - throw new Error( - `Arguments to \`#${operator}\` predicate must be a strings.".`, - ); - } - } - captureName = steps[1].name; - const values = steps.slice(2).map((s) => s.value); - textPredicates[i].push((captures) => { - const nodes = []; - for (const c of captures) { - if (c.name === captureName) nodes.push(c.node.text); - } - if (nodes.length === 0) return !isPositive; - return nodes.every((text) => values.includes(text)) === isPositive; - }); - break; - - default: - predicates[i].push({operator, operands: steps.slice(1)}); - } - - steps.length = 0; - } - } - - Object.freeze(setProperties[i]); - Object.freeze(assertedProperties[i]); - Object.freeze(refutedProperties[i]); - } - - C._free(sourceAddress); - return new Query( - INTERNAL, - address, - captureNames, - captureQuantifiers, - textPredicates, - predicates, - Object.freeze(setProperties), - Object.freeze(assertedProperties), - Object.freeze(refutedProperties), - ); - } - - static load(input) { - let bytes; - if (input instanceof Uint8Array) { - bytes = Promise.resolve(input); - } else { - if (globalThis.process?.versions?.node) { - const fs = require('fs/promises'); - bytes = fs.readFile(input); - } else { - bytes = fetch(input) - .then((response) => response.arrayBuffer() - .then((buffer) => { - if (response.ok) { - return new Uint8Array(buffer); - } else { - const body = new TextDecoder('utf-8').decode(buffer); - throw new Error(`Language.load failed with status ${response.status}.\n\n${body}`); - } - })); - } - } - - return bytes - .then((bytes) => loadWebAssemblyModule(bytes, {loadAsync: true})) - .then((mod) => { - const symbolNames = Object.keys(mod); - const functionName = symbolNames.find((key) => - LANGUAGE_FUNCTION_REGEX.test(key) && - !key.includes('external_scanner_'), - ); - if (!functionName) { - console.log(`Couldn't find language function in WASM file. Symbols:\n${JSON.stringify(symbolNames, null, 2)}`); - } - const languageAddress = mod[functionName](); - return new Language(INTERNAL, languageAddress); - }); - } -} - -class LookaheadIterable { - constructor(internal, address, language) { - assertInternal(internal); - this[0] = address; - this.language = language; - } - - get currentTypeId() { - return C._ts_lookahead_iterator_current_symbol(this[0]); - } - - get currentType() { - return this.language.types[this.currentTypeId] || 'ERROR'; - } - - delete() { - C._ts_lookahead_iterator_delete(this[0]); - this[0] = 0; - } - - resetState(stateId) { - return C._ts_lookahead_iterator_reset_state(this[0], stateId); - } - - reset(language, stateId) { - if (C._ts_lookahead_iterator_reset(this[0], language[0], stateId)) { - this.language = language; - return true; - } - - return false; - } - - [Symbol.iterator]() { - const self = this; - return { - next() { - if (C._ts_lookahead_iterator_next(self[0])) { - return {done: false, value: self.currentType}; - } - - return {done: true, value: ''}; - }, - }; - } -} - -class Query { - constructor( - internal, address, - captureNames, captureQuantifiers, textPredicates, predicates, - setProperties, assertedProperties, refutedProperties, - ) { - assertInternal(internal); - this[0] = address; - this.captureNames = captureNames; - this.captureQuantifiers = captureQuantifiers; - this.textPredicates = textPredicates; - this.predicates = predicates; - this.setProperties = setProperties; - this.assertedProperties = assertedProperties; - this.refutedProperties = refutedProperties; - this.exceededMatchLimit = false; - } - - delete() { - C._ts_query_delete(this[0]); - this[0] = 0; - } - - matches( - node, - { - startPosition = ZERO_POINT, - endPosition = ZERO_POINT, - startIndex = 0, - endIndex = 0, - matchLimit = 0xFFFFFFFF, - maxStartDepth = 0xFFFFFFFF, - timeoutMicros = 0, - progressCallback, - } = {}, - ) { - if (typeof matchLimit !== 'number') { - throw new Error('Arguments must be numbers'); - } - if (endIndex != 0 && startIndex > endIndex) { - throw new Error('`startIndex` cannot be greater than `endIndex`'); - } - if (endPosition != ZERO_POINT && (startPosition.row > endPosition.row || - (startPosition.row == endPosition.row && startPosition.column > endPosition.row))) { - throw new Error('`startPosition` cannot be greater than `endPosition`'); - } - - if (progressCallback) { - currentQueryProgressCallback = progressCallback; - } - - marshalNode(node); - - C._ts_query_matches_wasm( - this[0], - node.tree[0], - startPosition.row, - startPosition.column, - endPosition.row, - endPosition.column, - startIndex, - endIndex, - matchLimit, - maxStartDepth, - 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 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'); - address += SIZE_OF_INT; - const captureCount = getValue(address, 'i32'); - address += SIZE_OF_INT; - - const captures = new Array(captureCount); - address = unmarshalCaptures(this, node.tree, address, captures); - if (this.textPredicates[pattern].every((p) => p(captures))) { - result[filteredCount] = {pattern, captures}; - const setProperties = this.setProperties[pattern]; - if (setProperties) result[filteredCount].setProperties = setProperties; - const assertedProperties = this.assertedProperties[pattern]; - if (assertedProperties) result[filteredCount].assertedProperties = assertedProperties; - const refutedProperties = this.refutedProperties[pattern]; - if (refutedProperties) result[filteredCount].refutedProperties = refutedProperties; - filteredCount++; - } - } - result.length = filteredCount; - - C._free(startAddress); - currentQueryProgressCallback = null; - return result; - } - - captures( - node, - { - startPosition = ZERO_POINT, - endPosition = ZERO_POINT, - startIndex = 0, - endIndex = 0, - matchLimit = 0xFFFFFFFF, - maxStartDepth = 0xFFFFFFFF, - timeoutMicros = 0, - progressCallback, - } = {}, - ) { - if (typeof matchLimit !== 'number') { - throw new Error('Arguments must be numbers'); - } - if (endIndex != 0 && startIndex > endIndex) { - throw new Error('`startIndex` cannot be greater than `endIndex`'); - } - if (endPosition != ZERO_POINT && (startPosition.row > endPosition.row || - (startPosition.row == endPosition.row && startPosition.column > endPosition.row))) { - throw new Error('`startPosition` cannot be greater than `endPosition`'); - } - - if (progressCallback) { - currentQueryProgressCallback = progressCallback; - } - - marshalNode(node); - - C._ts_query_captures_wasm( - this[0], - node.tree[0], - startPosition.row, - startPosition.column, - endPosition.row, - endPosition.column, - startIndex, - endIndex, - matchLimit, - maxStartDepth, - 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 result = []; - this.exceededMatchLimit = Boolean(didExceedMatchLimit); - - const captures = []; - let address = startAddress; - for (let i = 0; i < count; i++) { - const pattern = getValue(address, 'i32'); - address += SIZE_OF_INT; - const captureCount = getValue(address, 'i32'); - address += SIZE_OF_INT; - const captureIndex = getValue(address, 'i32'); - address += SIZE_OF_INT; - - captures.length = captureCount; - address = unmarshalCaptures(this, node.tree, address, captures); - - if (this.textPredicates[pattern].every((p) => p(captures))) { - const capture = captures[captureIndex]; - const setProperties = this.setProperties[pattern]; - if (setProperties) capture.setProperties = setProperties; - const assertedProperties = this.assertedProperties[pattern]; - if (assertedProperties) capture.assertedProperties = assertedProperties; - const refutedProperties = this.refutedProperties[pattern]; - if (refutedProperties) capture.refutedProperties = refutedProperties; - result.push(capture); - } - } - - C._free(startAddress); - currentQueryProgressCallback = null; - return result; - } - - predicatesForPattern(patternIndex) { - return this.predicates[patternIndex]; - } - - disableCapture(captureName) { - const captureNameLength = lengthBytesUTF8(captureName); - const captureNameAddress = C._malloc(captureNameLength + 1); - stringToUTF8(captureName, captureNameAddress, captureNameLength + 1); - C._ts_query_disable_capture(this[0], captureNameAddress, captureNameLength); - C._free(captureNameAddress); - } - - disablePattern(patternIndex) { - if (patternIndex >= this.predicates.length) { - throw new Error( - `Pattern index is ${patternIndex} but the pattern count is ${this.predicates.length}`, - ); - } - C._ts_query_disable_pattern(this[0], patternIndex); - } - - didExceedMatchLimit() { - return this.exceededMatchLimit; - } - - startIndexForPattern(patternIndex) { - if (patternIndex >= this.predicates.length) { - throw new Error( - `Pattern index is ${patternIndex} but the pattern count is ${this.predicates.length}`, - ); - } - return C._ts_query_start_byte_for_pattern(this[0], patternIndex); - } - - endIndexForPattern(patternIndex) { - if (patternIndex >= this.predicates.length) { - throw new Error( - `Pattern index is ${patternIndex} but the pattern count is ${this.predicates.length}`, - ); - } - return C._ts_query_end_byte_for_pattern(this[0], patternIndex); - } - - isPatternNonLocal(patternIndex) { - return C._ts_query_is_pattern_non_local(this[0], patternIndex) === 1; - } - - isPatternRooted(patternIndex) { - return C._ts_query_is_pattern_rooted(this[0], patternIndex) === 1; - } - - isPatternGuaranteedAtStep(patternIndex, stepIndex) { - return ( - C._ts_query_is_pattern_guaranteed_at_step( - this[0], - patternIndex, - stepIndex, - ) === 1 - ); - } -} - -function getText(tree, startIndex, endIndex) { - const length = endIndex - startIndex; - let result = tree.textCallback(startIndex, null, endIndex); - startIndex += result.length; - while (startIndex < endIndex) { - const string = tree.textCallback(startIndex, null, endIndex); - if (string && string.length > 0) { - startIndex += string.length; - result += string; - } else { - break; - } - } - if (startIndex > endIndex) { - result = result.slice(0, length); - } - return result; -} - -function unmarshalCaptures(query, tree, address, result) { - for (let i = 0, n = result.length; i < n; i++) { - const captureIndex = getValue(address, 'i32'); - address += SIZE_OF_INT; - const node = unmarshalNode(tree, address); - address += SIZE_OF_NODE; - result[i] = {name: query.captureNames[captureIndex], node}; - } - return address; -} - -function assertInternal(x) { - if (x !== INTERNAL) throw new Error('Illegal constructor'); -} - -function isPoint(point) { - return ( - point && - typeof point.row === 'number' && - typeof point.column === 'number' - ); -} - -function marshalNode(node) { - let address = TRANSFER_BUFFER; - setValue(address, node.id, 'i32'); - address += SIZE_OF_INT; - setValue(address, node.startIndex, 'i32'); - address += SIZE_OF_INT; - setValue(address, node.startPosition.row, 'i32'); - address += SIZE_OF_INT; - setValue(address, node.startPosition.column, 'i32'); - address += SIZE_OF_INT; - setValue(address, node[0], 'i32'); -} - -function unmarshalNode(tree, address = TRANSFER_BUFFER) { - const id = getValue(address, 'i32'); - address += SIZE_OF_INT; - if (id === 0) return null; - - const index = getValue(address, 'i32'); - address += SIZE_OF_INT; - const row = getValue(address, 'i32'); - address += SIZE_OF_INT; - const column = getValue(address, 'i32'); - address += SIZE_OF_INT; - const other = getValue(address, 'i32'); - - const result = new Node(INTERNAL, tree); - result.id = id; - result.startIndex = index; - result.startPosition = {row, column}; - result[0] = other; - - return result; -} - -function marshalTreeCursor(cursor, 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'); -} - -function unmarshalTreeCursor(cursor) { - 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'); -} - -function marshalPoint(address, point) { - setValue(address, point.row, 'i32'); - setValue(address + SIZE_OF_INT, point.column, 'i32'); -} - -function unmarshalPoint(address) { - const result = { - row: getValue(address, 'i32') >>> 0, - column: getValue(address + SIZE_OF_INT, 'i32') >>> 0, - }; - return result; -} - -function marshalRange(address, range) { - 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; -} - -function unmarshalRange(address) { - const result = {}; - 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; - return result; -} - -function marshalEdit(edit) { - let 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; -} diff --git a/lib/binding_web/binding.c b/lib/binding_web/lib/tree-sitter.c similarity index 92% rename from lib/binding_web/binding.c rename to lib/binding_web/lib/tree-sitter.c index b31d3a9b..45ca7ddd 100644 --- a/lib/binding_web/binding.c +++ b/lib/binding_web/lib/tree-sitter.c @@ -16,10 +16,16 @@ const void *TRANSFER_BUFFER[12] = { NULL, NULL, NULL, NULL, }; +static const int SIZE_OF_CURSOR = 4; +static const int SIZE_OF_NODE = 5; +static const int SIZE_OF_POINT = 2; +static const int SIZE_OF_RANGE = 2 + (2 * SIZE_OF_POINT); +static const int SIZE_OF_CAPTURE = 1 + SIZE_OF_NODE; + void *ts_init() { TRANSFER_BUFFER[0] = (const void *)TREE_SITTER_LANGUAGE_VERSION; TRANSFER_BUFFER[1] = (const void *)TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION; - return TRANSFER_BUFFER; + return (void*)TRANSFER_BUFFER; } static uint32_t code_unit_to_byte(uint32_t unit) { @@ -95,9 +101,9 @@ static void unmarshal_range(TSRange *range) { static TSInputEdit unmarshal_edit() { TSInputEdit edit; const void **address = TRANSFER_BUFFER; - edit.start_point = unmarshal_point(address); address += 2; - edit.old_end_point = unmarshal_point(address); address += 2; - edit.new_end_point = unmarshal_point(address); address += 2; + edit.start_point = unmarshal_point(address); address += SIZE_OF_POINT; + edit.old_end_point = unmarshal_point(address); address += SIZE_OF_POINT; + edit.new_end_point = unmarshal_point(address); address += SIZE_OF_POINT; edit.start_byte = code_unit_to_byte((uint32_t)*address); address += 1; edit.old_end_byte = code_unit_to_byte((uint32_t)*address); address += 1; edit.new_end_byte = code_unit_to_byte((uint32_t)*address); address += 1; @@ -260,7 +266,7 @@ void ts_tree_root_node_wasm(const TSTree *tree) { void ts_tree_root_node_with_offset_wasm(const TSTree *tree) { // read int and point from transfer buffer - const void **address = TRANSFER_BUFFER + 5; + const void **address = TRANSFER_BUFFER + SIZE_OF_NODE; uint32_t offset = code_unit_to_byte((uint32_t)address[0]); TSPoint extent = unmarshal_point(address + 1); TSNode node = ts_tree_root_node_with_offset(tree, offset, extent); @@ -315,14 +321,14 @@ void ts_tree_cursor_delete_wasm(const TSTree *tree) { void ts_tree_cursor_reset_wasm(const TSTree *tree) { TSNode node = unmarshal_node(tree); - TSTreeCursor cursor = unmarshal_cursor(&TRANSFER_BUFFER[5], tree); + TSTreeCursor cursor = unmarshal_cursor(&TRANSFER_BUFFER[SIZE_OF_NODE], tree); ts_tree_cursor_reset(&cursor, node); marshal_cursor(&cursor); } void ts_tree_cursor_reset_to_wasm(const TSTree *_dst, const TSTree *_src) { TSTreeCursor cursor = unmarshal_cursor(TRANSFER_BUFFER, _dst); - TSTreeCursor src = unmarshal_cursor(&TRANSFER_BUFFER[4], _src); + TSTreeCursor src = unmarshal_cursor(&TRANSFER_BUFFER[SIZE_OF_CURSOR], _src); ts_tree_cursor_reset_to(&cursor, &src); marshal_cursor(&cursor); } @@ -508,25 +514,25 @@ void ts_node_children_by_field_id_wasm(const TSTree *tree, uint32_t field_id) { if (!ts_tree_cursor_goto_next_sibling(&cursor)) { done = true; } - array_grow_by(&result, 5); - marshal_node(result.contents + result.size - 5, result_node); + array_grow_by(&result, SIZE_OF_NODE); + marshal_node(result.contents + result.size - SIZE_OF_NODE, result_node); } ts_tree_cursor_delete(&cursor); - TRANSFER_BUFFER[0] = (const void*)(result.size / 5); - TRANSFER_BUFFER[1] = result.contents; + TRANSFER_BUFFER[0] = (const void*)(result.size / SIZE_OF_NODE); + TRANSFER_BUFFER[1] = (const void*)result.contents; } void ts_node_first_child_for_byte_wasm(const TSTree *tree) { TSNode node = unmarshal_node(tree); - const void** address = TRANSFER_BUFFER + 5; + const void** address = TRANSFER_BUFFER + SIZE_OF_NODE; uint32_t byte = code_unit_to_byte((uint32_t)address[0]); marshal_node(TRANSFER_BUFFER, ts_node_first_child_for_byte(node, byte)); } void ts_node_first_named_child_for_byte_wasm(const TSTree *tree) { TSNode node = unmarshal_node(tree); - const void** address = TRANSFER_BUFFER + 5; + const void** address = TRANSFER_BUFFER + SIZE_OF_NODE; uint32_t byte = code_unit_to_byte((uint32_t)address[0]); marshal_node(TRANSFER_BUFFER, ts_node_first_named_child_for_byte(node, byte)); } @@ -593,7 +599,7 @@ void ts_node_parent_wasm(const TSTree *tree) { void ts_node_descendant_for_index_wasm(const TSTree *tree) { TSNode node = unmarshal_node(tree); - const void **address = TRANSFER_BUFFER + 5; + const void **address = TRANSFER_BUFFER + SIZE_OF_NODE; uint32_t start = code_unit_to_byte((uint32_t)address[0]); uint32_t end = code_unit_to_byte((uint32_t)address[1]); marshal_node(TRANSFER_BUFFER, ts_node_descendant_for_byte_range(node, start, end)); @@ -601,7 +607,7 @@ void ts_node_descendant_for_index_wasm(const TSTree *tree) { void ts_node_named_descendant_for_index_wasm(const TSTree *tree) { TSNode node = unmarshal_node(tree); - const void **address = TRANSFER_BUFFER + 5; + const void **address = TRANSFER_BUFFER + SIZE_OF_NODE; uint32_t start = code_unit_to_byte((uint32_t)address[0]); uint32_t end = code_unit_to_byte((uint32_t)address[1]); marshal_node(TRANSFER_BUFFER, ts_node_named_descendant_for_byte_range(node, start, end)); @@ -609,16 +615,16 @@ void ts_node_named_descendant_for_index_wasm(const TSTree *tree) { void ts_node_descendant_for_position_wasm(const TSTree *tree) { TSNode node = unmarshal_node(tree); - const void **address = TRANSFER_BUFFER + 5; - TSPoint start = unmarshal_point(address); address += 2; + const void **address = TRANSFER_BUFFER + SIZE_OF_NODE; + TSPoint start = unmarshal_point(address); address += SIZE_OF_POINT; TSPoint end = unmarshal_point(address); marshal_node(TRANSFER_BUFFER, ts_node_descendant_for_point_range(node, start, end)); } void ts_node_named_descendant_for_position_wasm(const TSTree *tree) { TSNode node = unmarshal_node(tree); - const void **address = TRANSFER_BUFFER + 5; - TSPoint start = unmarshal_point(address); address += 2; + const void **address = TRANSFER_BUFFER + SIZE_OF_NODE; + TSPoint start = unmarshal_point(address); address += SIZE_OF_POINT; TSPoint end = unmarshal_point(address); marshal_node(TRANSFER_BUFFER, ts_node_named_descendant_for_point_range(node, start, end)); } @@ -653,20 +659,20 @@ void ts_node_children_wasm(const TSTree *tree) { uint32_t count = ts_node_child_count(node); const void **result = NULL; if (count > 0) { - result = calloc(sizeof(void *), 5 * count); + result = (const void**)calloc(sizeof(void *), SIZE_OF_NODE * count); const void **address = result; ts_tree_cursor_reset(&scratch_cursor, node); ts_tree_cursor_goto_first_child(&scratch_cursor); marshal_node(address, ts_tree_cursor_current_node(&scratch_cursor)); for (uint32_t i = 1; i < count; i++) { - address += 5; + address += SIZE_OF_NODE; ts_tree_cursor_goto_next_sibling(&scratch_cursor); TSNode child = ts_tree_cursor_current_node(&scratch_cursor); marshal_node(address, child); } } TRANSFER_BUFFER[0] = (const void *)count; - TRANSFER_BUFFER[1] = result; + TRANSFER_BUFFER[1] = (const void *)result; } void ts_node_named_children_wasm(const TSTree *tree) { @@ -674,7 +680,7 @@ void ts_node_named_children_wasm(const TSTree *tree) { uint32_t count = ts_node_named_child_count(node); const void **result = NULL; if (count > 0) { - result = calloc(sizeof(void *), 5 * count); + result = (const void**)calloc(sizeof(void *), SIZE_OF_NODE * count); const void **address = result; ts_tree_cursor_reset(&scratch_cursor, node); ts_tree_cursor_goto_first_child(&scratch_cursor); @@ -683,7 +689,7 @@ void ts_node_named_children_wasm(const TSTree *tree) { TSNode child = ts_tree_cursor_current_node(&scratch_cursor); if (ts_node_is_named(child)) { marshal_node(address, child); - address += 5; + address += SIZE_OF_NODE; i++; if (i == count) { break; @@ -695,7 +701,7 @@ void ts_node_named_children_wasm(const TSTree *tree) { } } TRANSFER_BUFFER[0] = (const void *)count; - TRANSFER_BUFFER[1] = result; + TRANSFER_BUFFER[1] = (const void *)result; } bool symbols_contain(const uint32_t *set, uint32_t length, uint32_t value) { @@ -757,8 +763,8 @@ void ts_node_descendants_of_type_wasm( // Add the node to the result if its type matches one of the given // node types. if (symbols_contain(symbols, symbol_count, ts_node_symbol(descendant))) { - array_grow_by(&result, 5); - marshal_node(result.contents + result.size - 5, descendant); + array_grow_by(&result, SIZE_OF_NODE); + marshal_node(result.contents + result.size - SIZE_OF_NODE, descendant); } // Continue walking. @@ -783,8 +789,8 @@ void ts_node_descendants_of_type_wasm( } } - TRANSFER_BUFFER[0] = (const void *)(result.size / 5); - TRANSFER_BUFFER[1] = result.contents; + TRANSFER_BUFFER[0] = (const void *)(result.size / SIZE_OF_NODE); + TRANSFER_BUFFER[1] = (const void *)result.contents; } int ts_node_is_named_wasm(const TSTree *tree) { @@ -873,21 +879,21 @@ void ts_query_matches_wasm( TSQueryMatch match; while (ts_query_cursor_next_match(scratch_query_cursor, &match)) { match_count++; - array_grow_by(&result, 2 + 6 * match.capture_count); + array_grow_by(&result, 2 + (SIZE_OF_CAPTURE * match.capture_count)); result.contents[index++] = (const void *)(uint32_t)match.pattern_index; result.contents[index++] = (const void *)(uint32_t)match.capture_count; for (unsigned i = 0; i < match.capture_count; i++) { const TSQueryCapture *capture = &match.captures[i]; result.contents[index++] = (const void *)capture->index; marshal_node(result.contents + index, capture->node); - index += 5; + index += SIZE_OF_NODE; } } bool did_exceed_match_limit = ts_query_cursor_did_exceed_match_limit(scratch_query_cursor); TRANSFER_BUFFER[0] = (const void *)(match_count); - TRANSFER_BUFFER[1] = result.contents; + TRANSFER_BUFFER[1] = (const void *)result.contents; TRANSFER_BUFFER[2] = (const void *)(did_exceed_match_limit); } @@ -933,7 +939,7 @@ void ts_query_captures_wasm( )) { capture_count++; - array_grow_by(&result, 3 + 6 * match.capture_count); + array_grow_by(&result, 3 + (SIZE_OF_CAPTURE * match.capture_count)); result.contents[index++] = (const void *)(uint32_t)match.pattern_index; result.contents[index++] = (const void *)(uint32_t)match.capture_count; result.contents[index++] = (const void *)capture_index; @@ -941,13 +947,13 @@ void ts_query_captures_wasm( const TSQueryCapture *capture = &match.captures[i]; result.contents[index++] = (const void *)capture->index; marshal_node(result.contents + index, capture->node); - index += 5; + index += SIZE_OF_NODE; } } bool did_exceed_match_limit = ts_query_cursor_did_exceed_match_limit(scratch_query_cursor); TRANSFER_BUFFER[0] = (const void *)(capture_count); - TRANSFER_BUFFER[1] = result.contents; + TRANSFER_BUFFER[1] = (const void *)result.contents; TRANSFER_BUFFER[2] = (const void *)(did_exceed_match_limit); } diff --git a/lib/binding_web/package-lock.json b/lib/binding_web/package-lock.json new file mode 100644 index 00000000..b172434a --- /dev/null +++ b/lib/binding_web/package-lock.json @@ -0,0 +1,3711 @@ +{ + "name": "web-tree-sitter", + "version": "0.25.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "web-tree-sitter", + "version": "0.25.0", + "license": "MIT", + "devDependencies": { + "@types/emscripten": "^1.39.13", + "@types/node": "^22.10.7", + "@vitest/coverage-v8": "^2.1.8", + "esbuild": "^0.24.2", + "eslint": ">=9.18.0", + "source-map": "^0.7.4", + "typescript": "^5.7.3", + "vitest": "^2.1.8" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.5.tgz", + "integrity": "sha512-SRJ4jYmXRqV1/Xc+TIVG84WjHBXKlxO9sHQnA2Pf12QQEAp1LOh6kDzNHXcUnbH1QI0FDoPPVOt+vyUDucxpaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.26.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.5.tgz", + "integrity": "sha512-L6mZmwFDK6Cjh1nRCLXpa6no13ZIioJDz7mdkzHv399pThrTa/k0nUlNaenOeh2kWu/iaOQYElEpKPUswUa9Vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", + "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz", + "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz", + "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz", + "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz", + "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz", + "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz", + "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz", + "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz", + "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz", + "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz", + "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz", + "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz", + "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz", + "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz", + "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz", + "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz", + "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz", + "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz", + "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz", + "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz", + "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz", + "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz", + "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz", + "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz", + "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", + "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.1.tgz", + "integrity": "sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.5", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz", + "integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", + "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.18.0.tgz", + "integrity": "sha512-fK6L7rxcq6/z+AaQMtiFTkvbHkBLNlwyRxHpKawP0x3u9+NC6MQTnFW+AdpwC6gfHTW0051cokQgtTN2FqlxQA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.5.tgz", + "integrity": "sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz", + "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.10.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", + "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.28", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz", + "integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.30.1.tgz", + "integrity": "sha512-pSWY+EVt3rJ9fQ3IqlrEUtXh3cGqGtPDH1FQlNZehO2yYxCHEX1SPsz1M//NXwYfbTlcKr9WObLnJX9FsS9K1Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.30.1.tgz", + "integrity": "sha512-/NA2qXxE3D/BRjOJM8wQblmArQq1YoBVJjrjoTSBS09jgUisq7bqxNHJ8kjCHeV21W/9WDGwJEWSN0KQ2mtD/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.30.1.tgz", + "integrity": "sha512-r7FQIXD7gB0WJ5mokTUgUWPl0eYIH0wnxqeSAhuIwvnnpjdVB8cRRClyKLQr7lgzjctkbp5KmswWszlwYln03Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.30.1.tgz", + "integrity": "sha512-x78BavIwSH6sqfP2xeI1hd1GpHL8J4W2BXcVM/5KYKoAD3nNsfitQhvWSw+TFtQTLZ9OmlF+FEInEHyubut2OA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.30.1.tgz", + "integrity": "sha512-HYTlUAjbO1z8ywxsDFWADfTRfTIIy/oUlfIDmlHYmjUP2QRDTzBuWXc9O4CXM+bo9qfiCclmHk1x4ogBjOUpUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.30.1.tgz", + "integrity": "sha512-1MEdGqogQLccphhX5myCJqeGNYTNcmTyaic9S7CG3JhwuIByJ7J05vGbZxsizQthP1xpVx7kd3o31eOogfEirw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.30.1.tgz", + "integrity": "sha512-PaMRNBSqCx7K3Wc9QZkFx5+CX27WFpAMxJNiYGAXfmMIKC7jstlr32UhTgK6T07OtqR+wYlWm9IxzennjnvdJg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.30.1.tgz", + "integrity": "sha512-B8Rcyj9AV7ZlEFqvB5BubG5iO6ANDsRKlhIxySXcF1axXYUyqwBok+XZPgIYGBgs7LDXfWfifxhw0Ik57T0Yug==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.30.1.tgz", + "integrity": "sha512-hqVyueGxAj3cBKrAI4aFHLV+h0Lv5VgWZs9CUGqr1z0fZtlADVV1YPOij6AhcK5An33EXaxnDLmJdQikcn5NEw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.30.1.tgz", + "integrity": "sha512-i4Ab2vnvS1AE1PyOIGp2kXni69gU2DAUVt6FSXeIqUCPIR3ZlheMW3oP2JkukDfu3PsexYRbOiJrY+yVNSk9oA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.30.1.tgz", + "integrity": "sha512-fARcF5g296snX0oLGkVxPmysetwUk2zmHcca+e9ObOovBR++9ZPOhqFUM61UUZ2EYpXVPN1redgqVoBB34nTpQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.30.1.tgz", + "integrity": "sha512-GLrZraoO3wVT4uFXh67ElpwQY0DIygxdv0BNW9Hkm3X34wu+BkqrDrkcsIapAY+N2ATEbvak0XQ9gxZtCIA5Rw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.30.1.tgz", + "integrity": "sha512-0WKLaAUUHKBtll0wvOmh6yh3S0wSU9+yas923JIChfxOaaBarmb/lBKPF0w/+jTVozFnOXJeRGZ8NvOxvk/jcw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.30.1.tgz", + "integrity": "sha512-GWFs97Ruxo5Bt+cvVTQkOJ6TIx0xJDD/bMAOXWJg8TCSTEK8RnFeOeiFTxKniTc4vMIaWvCplMAFBt9miGxgkA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.30.1.tgz", + "integrity": "sha512-UtgGb7QGgXDIO+tqqJ5oZRGHsDLO8SlpE4MhqpY9Llpzi5rJMvrK6ZGhsRCST2abZdBqIBeXW6WPD5fGK5SDwg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.30.1.tgz", + "integrity": "sha512-V9U8Ey2UqmQsBT+xTOeMzPzwDzyXmnAoO4edZhL7INkwQcaW1Ckv3WJX3qrrp/VHaDkEWIBWhRwP47r8cdrOow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.30.1.tgz", + "integrity": "sha512-WabtHWiPaFF47W3PkHnjbmWawnX/aE57K47ZDT1BXTS5GgrBUEpvOzq0FI0V/UYzQJgdb8XlhVNH8/fwV8xDjw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.30.1.tgz", + "integrity": "sha512-pxHAU+Zv39hLUTdQQHUVHf4P+0C47y/ZloorHpzs2SXMRqeAWmGghzAhfOlzFHHwjvgokdFAhC4V+6kC1lRRfw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.30.1.tgz", + "integrity": "sha512-D6qjsXGcvhTjv0kI4fU8tUuBDF/Ueee4SVX79VfNDXZa64TfCW1Slkb6Z7O1p7vflqZjcmOVdZlqf8gvJxc6og==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/emscripten": { + "version": "1.39.13", + "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.39.13.tgz", + "integrity": "sha512-cFq+fO/isvhvmuP/+Sl4K4jtU6E23DoivtbO4r50e3odaxAiVdbfSYRDdJ4gCdxx+3aRjhphS5ZMwIH4hFy/Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.10.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.7.tgz", + "integrity": "sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/@vitest/coverage-v8": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.8.tgz", + "integrity": "sha512-2Y7BPlKH18mAZYAW1tYByudlCYrQyl5RGvnnDYJKW5tCiO5qg3KSAy3XAxcxKz900a0ZXxWtKrMuZLe3lKBpJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "@bcoe/v8-coverage": "^0.2.3", + "debug": "^4.3.7", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.1.7", + "magic-string": "^0.30.12", + "magicast": "^0.3.5", + "std-env": "^3.8.0", + "test-exclude": "^7.0.1", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "2.1.8", + "vitest": "2.1.8" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.8.tgz", + "integrity": "sha512-8ytZ/fFHq2g4PJVAtDX57mayemKgDR6X3Oa2Foro+EygiOJHUXhCqBAAKQYYajZpFoIfvBCF1j6R6IYRSIUFuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.8", + "@vitest/utils": "2.1.8", + "chai": "^5.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.8.tgz", + "integrity": "sha512-7guJ/47I6uqfttp33mgo6ga5Gr1VnL58rcqYKyShoRK9ebu8T5Rs6HN3s1NABiBeVTdWNrwUMcHH54uXZBN4zA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.8", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.12" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.8.tgz", + "integrity": "sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.8.tgz", + "integrity": "sha512-17ub8vQstRnRlIU5k50bG+QOMLHRhYPAna5tw8tYbj+jzjcspnwnwtPtiOlkuKC4+ixDPTuLZiqiWWQ2PSXHVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "2.1.8", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.8.tgz", + "integrity": "sha512-20T7xRFbmnkfcmgVEz+z3AU/3b0cEzZOt/zmnvZEctg64/QZbSDJEVm9fLnnlSi74KibmRsO9/Qabi+t0vCRPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.8", + "magic-string": "^0.30.12", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.8.tgz", + "integrity": "sha512-5swjf2q95gXeYPevtW0BLk6H8+bPlMb4Vw/9Em4hFxDcaOxS+e0LOX4yqNxoHzMR2akEB2xfpnWUzkZokmgWDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/ui": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-2.1.8.tgz", + "integrity": "sha512-5zPJ1fs0ixSVSs5+5V2XJjXLmNzjugHRyV11RqxYVR+oMcogZ9qTuSfKW+OcTV0JeFNznI83BNylzH6SSNJ1+w==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@vitest/utils": "2.1.8", + "fflate": "^0.8.2", + "flatted": "^3.3.1", + "pathe": "^1.1.2", + "sirv": "^3.0.0", + "tinyglobby": "^0.2.10", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "vitest": "2.1.8" + } + }, + "node_modules/@vitest/utils": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.8.tgz", + "integrity": "sha512-dwSoui6djdwbfFmIgbIjX2ZhIoG7Ex/+xpxyiEgIGzjliY8xGkcpITKTlp6B4MgtGkF2ilvm97cPM96XZaAgcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.8", + "loupe": "^3.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chai": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.2.tgz", + "integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-module-lexer": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", + "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz", + "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.24.2", + "@esbuild/android-arm": "0.24.2", + "@esbuild/android-arm64": "0.24.2", + "@esbuild/android-x64": "0.24.2", + "@esbuild/darwin-arm64": "0.24.2", + "@esbuild/darwin-x64": "0.24.2", + "@esbuild/freebsd-arm64": "0.24.2", + "@esbuild/freebsd-x64": "0.24.2", + "@esbuild/linux-arm": "0.24.2", + "@esbuild/linux-arm64": "0.24.2", + "@esbuild/linux-ia32": "0.24.2", + "@esbuild/linux-loong64": "0.24.2", + "@esbuild/linux-mips64el": "0.24.2", + "@esbuild/linux-ppc64": "0.24.2", + "@esbuild/linux-riscv64": "0.24.2", + "@esbuild/linux-s390x": "0.24.2", + "@esbuild/linux-x64": "0.24.2", + "@esbuild/netbsd-arm64": "0.24.2", + "@esbuild/netbsd-x64": "0.24.2", + "@esbuild/openbsd-arm64": "0.24.2", + "@esbuild/openbsd-x64": "0.24.2", + "@esbuild/sunos-x64": "0.24.2", + "@esbuild/win32-arm64": "0.24.2", + "@esbuild/win32-ia32": "0.24.2", + "@esbuild/win32-x64": "0.24.2" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.18.0.tgz", + "integrity": "sha512-+waTfRWQlSbpt3KWE+CjrPPYnbq9kfZIYUqapc0uBXyjTp8aYXZDsUH16m39Ryq3NjAVP4tjuF7KaukeqoCoaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.19.0", + "@eslint/core": "^0.10.0", + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "9.18.0", + "@eslint/plugin-kit": "^0.2.5", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.1", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", + "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expect-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.1.0.tgz", + "integrity": "sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.2.tgz", + "integrity": "sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", + "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", + "dev": true, + "license": "ISC" + }, + "node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/loupe": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz", + "integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/magicast": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", + "source-map-js": "^1.2.0" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mrmime": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", + "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz", + "integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/rollup": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.30.1.tgz", + "integrity": "sha512-mlJ4glW020fPuLi7DkM/lN97mYEZGWeqBnrljzN0gs7GLctqX3lNWxKQ7Gl712UAX+6fog/L3jh4gb7R6aVi3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.30.1", + "@rollup/rollup-android-arm64": "4.30.1", + "@rollup/rollup-darwin-arm64": "4.30.1", + "@rollup/rollup-darwin-x64": "4.30.1", + "@rollup/rollup-freebsd-arm64": "4.30.1", + "@rollup/rollup-freebsd-x64": "4.30.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.30.1", + "@rollup/rollup-linux-arm-musleabihf": "4.30.1", + "@rollup/rollup-linux-arm64-gnu": "4.30.1", + "@rollup/rollup-linux-arm64-musl": "4.30.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.30.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.30.1", + "@rollup/rollup-linux-riscv64-gnu": "4.30.1", + "@rollup/rollup-linux-s390x-gnu": "4.30.1", + "@rollup/rollup-linux-x64-gnu": "4.30.1", + "@rollup/rollup-linux-x64-musl": "4.30.1", + "@rollup/rollup-win32-arm64-msvc": "4.30.1", + "@rollup/rollup-win32-ia32-msvc": "4.30.1", + "@rollup/rollup-win32-x64-msvc": "4.30.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sirv": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.0.tgz", + "integrity": "sha512-BPwJGUeDaDCHihkORDchNyyTvWFhcusy1XMmhEVTQTwGeybFbp8YEmB+njbPnth1FibULBSBVwCQni25XlCUDg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.0.tgz", + "integrity": "sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^9.0.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.10.tgz", + "integrity": "sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "fdir": "^6.4.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/tinypool": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz", + "integrity": "sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "version": "5.4.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", + "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.8.tgz", + "integrity": "sha512-uPAwSr57kYjAUux+8E2j0q0Fxpn8M9VoyfGiRI8Kfktz9NcYMCenwY5RnZxnF1WTu3TGiYipirIzacLL3VVGFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.7", + "es-module-lexer": "^1.5.4", + "pathe": "^1.1.2", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/vitest": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.8.tgz", + "integrity": "sha512-1vBKTZskHw/aosXqQUlVWWlGUxSJR8YtiyZDJAFeW2kPAeX6S3Sool0mjspO+kXLuxVWlEDDowBAeqeAQefqLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "2.1.8", + "@vitest/mocker": "2.1.8", + "@vitest/pretty-format": "^2.1.8", + "@vitest/runner": "2.1.8", + "@vitest/snapshot": "2.1.8", + "@vitest/spy": "2.1.8", + "@vitest/utils": "2.1.8", + "chai": "^5.1.2", + "debug": "^4.3.7", + "expect-type": "^1.1.0", + "magic-string": "^0.30.12", + "pathe": "^1.1.2", + "std-env": "^3.8.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.1", + "tinypool": "^1.0.1", + "tinyrainbow": "^1.2.0", + "vite": "^5.0.0", + "vite-node": "2.1.8", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "2.1.8", + "@vitest/ui": "2.1.8", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/lib/binding_web/package.json b/lib/binding_web/package.json index 37a4b1ee..571aa68e 100644 --- a/lib/binding_web/package.json +++ b/lib/binding_web/package.json @@ -2,41 +2,56 @@ "name": "web-tree-sitter", "version": "0.25.0", "description": "Tree-sitter bindings for the web", - "main": "tree-sitter.js", - "types": "tree-sitter-web.d.ts", - "directories": { - "test": "test" + "repository": "https://github.com/tree-sitter/tree-sitter", + "homepage": "https://github.com/tree-sitter/tree-sitter/tree/master/lib/binding_web", + "license": "MIT", + "author": { + "name": "Max Brunsfeld", + "email": "maxbrunsfeld@gmail.com" }, + "maintainers": [ + { + "name": "Amaan Qureshi", + "email": "amaanq12@gmail.com" + } + ], + "main": "tree-sitter.js", + "type": "module", + "types": "tree-sitter-web.d.ts", + "keywords": [ + "incremental", + "parsing", + "tree-sitter", + "wasm" + ], "files": [ "README.md", "tree-sitter.js", + "tree-sitter.js.map", "tree-sitter.wasm", + "tree-sitter.wasm.map", "tree-sitter-web.d.ts" ], - "scripts": { - "test": "mocha", - "prepack": "cp ../../LICENSE .", - "prepublishOnly": "node check-artifacts-fresh.js" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/tree-sitter/tree-sitter.git" - }, - "keywords": [ - "incremental", - "parsing" - ], - "author": "Max Brunsfeld", - "license": "MIT", - "bugs": { - "url": "https://github.com/tree-sitter/tree-sitter/issues" - }, - "homepage": "https://github.com/tree-sitter/tree-sitter/tree/master/lib/binding_web", "devDependencies": { - "@types/emscripten": "^1.39.10", - "chai": "^4.3.7", - "eslint": ">=8.56.0", - "eslint-config-google": "^0.14.0", - "mocha": "^10.2.0" + "@types/emscripten": "^1.39.13", + "@types/node": "^22.10.7", + "@vitest/coverage-v8": "^2.1.8", + "esbuild": "^0.24.2", + "eslint": ">=9.18.0", + "source-map": "^0.7.4", + "typescript": "^5.7.3", + "vitest": "^2.1.8" + }, + "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:wasm": "cd ../../ && cargo xtask build-wasm", + "build:sourcemap": "node script/build-sourcemap.js", + "build": "npm run build:ts && npm run build:wasm && npm run build:sourcemap", + "build:debug": "npm run build:ts && npm run build:wasm:debug && mkdir -p debug && mv tree-sitter.* debug/", + "test": "vitest run", + "test:watch": "vitest", + "prepack": "cp ../../LICENSE .", + "prepublishOnly": "node script/check-artifacts-fresh.js", + "postinstall": "node scripts/postinstall.js" } } diff --git a/lib/binding_web/script/build-sourcemap.js b/lib/binding_web/script/build-sourcemap.js new file mode 100644 index 00000000..08cc1345 --- /dev/null +++ b/lib/binding_web/script/build-sourcemap.js @@ -0,0 +1,62 @@ +import { readFileSync, writeFileSync } from 'fs'; +import { SourceMapGenerator, SourceMapConsumer } from 'source-map'; + +async function fixSourceMap() { + const distMap = JSON.parse(readFileSync('dist/tree-sitter.js.map', 'utf8')); + const distJs = readFileSync('dist/tree-sitter.js', 'utf8').split('\n'); + const finalJs = readFileSync('tree-sitter.js', 'utf8').split('\n'); + + const lineMap = new Map(); + + for (let distLine = 0; distLine < distJs.length; distLine++) { + const line = distJs[distLine].trim(); + if (!line) continue; + + for (let finalLine = 0; finalLine < finalJs.length; finalLine++) { + if (finalJs[finalLine].trim() === line) { + lineMap.set(distLine + 1, finalLine + 1); + 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/check-artifacts-fresh.js b/lib/binding_web/script/check-artifacts-fresh.js similarity index 91% rename from lib/binding_web/check-artifacts-fresh.js rename to lib/binding_web/script/check-artifacts-fresh.js index a0c24933..53fb3661 100755 --- a/lib/binding_web/check-artifacts-fresh.js +++ b/lib/binding_web/script/check-artifacts-fresh.js @@ -1,13 +1,11 @@ -#!/usr/bin/env node - const fs = require('fs'); const path = require('path'); const inputFiles = [ - 'binding.c', + 'lib/binding.c', 'binding.js', - 'exports.txt', - 'imports.js', + 'wasm/exports.txt', + 'wasm/imports.js', 'prefix.js', ...list('../include/tree_sitter'), ...list('../src'), diff --git a/lib/binding_web/script/postinstall.js b/lib/binding_web/script/postinstall.js new file mode 100644 index 00000000..4c2478f7 --- /dev/null +++ b/lib/binding_web/script/postinstall.js @@ -0,0 +1,25 @@ +import fs from 'fs'; +import path from 'path'; + +const isDebug = process.env.npm_config_web_tree_sitter_debug === 'true'; + +if (isDebug) { + // Copy debug versions to root + fs.copyFileSync( + path.join(__dirname, '../debug/tree-sitter.js'), + path.join(__dirname, '../tree-sitter.js') + ); + fs.copyFileSync( + path.join(__dirname, '../debug/tree-sitter.wasm'), + path.join(__dirname, '../tree-sitter.wasm') + ); + // Copy sourcemaps too + fs.copyFileSync( + path.join(__dirname, '../debug/tree-sitter.js.map'), + path.join(__dirname, '../tree-sitter.js.map') + ); + fs.copyFileSync( + path.join(__dirname, '../debug/tree-sitter.wasm.map'), + path.join(__dirname, '../tree-sitter.wasm.map') + ); +} diff --git a/lib/binding_web/src/constants.ts b/lib/binding_web/src/constants.ts new file mode 100644 index 00000000..a10327c1 --- /dev/null +++ b/lib/binding_web/src/constants.ts @@ -0,0 +1,238 @@ +import { CaptureQuantifier } from "./query"; + +export interface Point { + row: number; + column: number; +} + +export interface Range { + startPosition: Point; + endPosition: Point; + startIndex: number; + endIndex: number; +} + +export interface Edit { + startPosition: Point; + oldEndPosition: Point; + newEndPosition: Point; + startIndex: number; + oldEndIndex: number; + newEndIndex: number; +} + +export interface ParserOptions { + includedRanges?: Range[]; + progressCallback?: (progress: { currentOffset: number }) => boolean; +} + +export const SIZE_OF_SHORT = 2; +export const SIZE_OF_INT = 4; +export const SIZE_OF_CURSOR = 4 * SIZE_OF_INT; +export const SIZE_OF_NODE = 5 * SIZE_OF_INT; +export const SIZE_OF_POINT = 2 * SIZE_OF_INT; +export const SIZE_OF_RANGE = 2 * SIZE_OF_INT + 2 * SIZE_OF_POINT; +export const ZERO_POINT: Point = { row: 0, column: 0 }; + +// Types for callbacks +export type ParseCallback = (index: number, position: Point) => string | null; +export type ProgressCallback = (progress: { currentOffset: number }) => boolean; +export type LogCallback = (message: string, isLex: boolean) => void; + +// Helper type for internal use +export const INTERNAL = Symbol('INTERNAL'); +export type Internal = typeof INTERNAL; + +// Helper functions for type checking +export function assertInternal(x: unknown): asserts x is Internal { + if (x !== INTERNAL) throw new Error('Illegal constructor'); +} + +export function isPoint(point: Point): point is Point { + return ( + !!point && + typeof (point as Point).row === 'number' && + typeof (point as Point).column === 'number' + ); +} + +export const C: EmscriptenModule & { + // Global + _ts_init(): number; + + // 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_language_version(address: number): number; + _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-ignore +} = Module; diff --git a/lib/binding_web/src/index.ts b/lib/binding_web/src/index.ts new file mode 100644 index 00000000..556bfd44 --- /dev/null +++ b/lib/binding_web/src/index.ts @@ -0,0 +1,9 @@ +export * from './constants'; +export * from './marshal'; +export * from './node'; +export * from './tree'; +export * from './tree_cursor'; +export * from './lookahead_iterator'; +export * from './query'; +export * from './language'; +export * from './parser'; diff --git a/lib/binding_web/src/language.ts b/lib/binding_web/src/language.ts new file mode 100644 index 00000000..43fab05f --- /dev/null +++ b/lib/binding_web/src/language.ts @@ -0,0 +1,491 @@ +import { INTERNAL, Internal, assertInternal, SIZE_OF_INT, SIZE_OF_SHORT, C } 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 UTF8ToString: (ptr: number, maxBytesToRead?: number) => string; +declare const lengthBytesUTF8: (str: string) => number; +declare const stringToUTF8: (str: string, outPtr: number, maxBytesToRead: number) => void; +declare const getValue: (ptr: number, type: string) => any; +declare const loadWebAssemblyModule: (bytes: Uint8Array, options: { loadAsync: boolean }) => Promise; + +const PREDICATE_STEP_TYPE_CAPTURE = 1; +const PREDICATE_STEP_TYPE_STRING = 2; + +const QUERY_WORD_REGEX = /[\w-]+/g; +const LANGUAGE_FUNCTION_REGEX = /^tree_sitter_\w+$/; + +export class Language { + private [0]: number; // Internal handle for WASM + public types: string[]; + public fields: (string | null)[]; + + constructor(internal: Internal, address: number) { + assertInternal(internal); + this[0] = address; + 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.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); + } else { + this.fields[i] = null; + } + } + } + + get name(): string | null { + const ptr = C._ts_language_name(this[0]); + if (ptr === 0) return null; + return UTF8ToString(ptr); + } + + get version(): number { + return C._ts_language_version(this[0]); + } + + get fieldCount(): number { + return this.fields.length - 1; + } + + get stateCount(): number { + return C._ts_language_state_count(this[0]); + } + + fieldIdForName(fieldName: string): number | null { + const result = this.fields.indexOf(fieldName); + return result !== -1 ? result : null; + } + + fieldNameForId(fieldId: number): string | null { + return this.fields[fieldId] || null; + } + + idForNodeType(type: string, named: boolean): number | null { + const typeLength = 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._free(typeAddress); + return result || null; + } + + get nodeTypeCount(): number { + return C._ts_language_symbol_count(this[0]); + } + + nodeTypeForId(typeId: number): string | null { + const name = C._ts_language_symbol_name(this[0], typeId); + return name ? UTF8ToString(name) : null; + } + + nodeTypeIsNamed(typeId: number): boolean { + return C._ts_language_type_is_named_wasm(this[0], typeId) ? true : false; + } + + nodeTypeIsVisible(typeId: number): boolean { + return C._ts_language_type_is_visible_wasm(this[0], typeId) ? true : false; + } + + 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 result = new Array(count); + + if (count > 0) { + let address = buffer; + for (let i = 0; i < count; i++) { + result[i] = getValue(address, 'i16'); + address += SIZE_OF_SHORT; + } + } + + return result; + } + + 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 result = new Array(count); + + if (count > 0) { + let address = buffer; + for (let i = 0; i < count; i++) { + result[i] = getValue(address, 'i16'); + address += SIZE_OF_SHORT; + } + } + + return result; + } + + nextState(stateId: number, typeId: number): number { + return C._ts_language_next_state(this[0], stateId, typeId); + } + + lookaheadIterator(stateId: number): LookaheadIterator | null { + const address = C._ts_lookahead_iterator_new(this[0], stateId); + if (address) return new LookaheadIterator(INTERNAL, address, this); + return null; + } + + query(source: string): Query { + const sourceLength = lengthBytesUTF8(source); + const sourceAddress = C._malloc(sourceLength + 1); + stringToUTF8(source, sourceAddress, sourceLength + 1); + const address = C._ts_query_new( + this[0], + sourceAddress, + sourceLength, + TRANSFER_BUFFER, + TRANSFER_BUFFER + SIZE_OF_INT + ); + + if (!address) { + const errorId = getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32'); + const errorByte = getValue(TRANSFER_BUFFER, 'i32'); + const errorIndex = 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; + + switch (errorId) { + case 2: + error = new RangeError(`Bad node name '${word}'`); + break; + case 3: + error = new RangeError(`Bad field name '${word}'`); + break; + case 4: + error = new RangeError(`Bad capture name @${word}`); + break; + case 5: + error = new TypeError(`Bad pattern structure at offset ${errorIndex}: '${suffix}'...`); + word = ''; + break; + default: + error = new SyntaxError(`Bad syntax at offset ${errorIndex}: '${suffix}'...`); + word = ''; + break; + } + + (error as any).index = errorIndex; + (error as any).length = word.length; + C._free(sourceAddress); + throw error; + } + + const stringCount = C._ts_query_string_count(address); + const captureCount = C._ts_query_capture_count(address); + const patternCount = C._ts_query_pattern_count(address); + const captureNames = new Array(captureCount); + const captureQuantifiers = new Array>(patternCount); + const stringValues = new Array(stringCount); + + for (let i = 0; i < captureCount; i++) { + const nameAddress = C._ts_query_capture_name_for_id( + address, + i, + TRANSFER_BUFFER + ); + const nameLength = getValue(TRANSFER_BUFFER, 'i32'); + captureNames[i] = 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; + } + captureQuantifiers[i] = captureQuantifiersArray; + } + + for (let i = 0; i < stringCount; i++) { + const valueAddress = C._ts_query_string_value_for_id( + address, + i, + TRANSFER_BUFFER + ); + const nameLength = getValue(TRANSFER_BUFFER, 'i32'); + stringValues[i] = UTF8ToString(valueAddress, nameLength); + } + + const setProperties = new Array(patternCount); + const assertedProperties = new Array(patternCount); + const refutedProperties = new Array(patternCount); + const predicates = new Array>(patternCount); + const textPredicates = new Array>(patternCount); + + for (let i = 0; i < patternCount; i++) { + const predicatesAddress = C._ts_query_predicates_for_pattern( + address, + i, + TRANSFER_BUFFER + ); + const stepCount = getValue(TRANSFER_BUFFER, 'i32'); + + predicates[i] = []; + textPredicates[i] = []; + + const steps: PredicateStep[] = []; + let stepAddress = predicatesAddress; + for (let j = 0; j < stepCount; j++) { + const stepType = getValue(stepAddress, 'i32'); + stepAddress += SIZE_OF_INT; + const stepValueId: number = getValue(stepAddress, 'i32'); + stepAddress += SIZE_OF_INT; + + if (stepType === PREDICATE_STEP_TYPE_CAPTURE) { + const name = captureNames[stepValueId]; + steps.push({ type: 'capture', name }); + } else if (stepType === PREDICATE_STEP_TYPE_STRING) { + steps.push({ type: 'string', value: stringValues[stepValueId] }); + } else if (steps.length > 0) { + if (steps[0].type !== 'string') { + throw new Error('Predicates must begin with a literal value'); + } + + const operator = steps[0].value!; + let isPositive = true; + let matchAll = true; + let captureName: string | undefined; + + switch (operator) { + case 'any-not-eq?': + case 'not-eq?': + isPositive = false; + case 'any-eq?': + case 'eq?': { + if (steps.length !== 3) { + throw new Error( + `Wrong number of arguments to \`#${operator}\` predicate. Expected 2, got ${steps.length - 1}` + ); + } + if (steps[1].type !== 'capture') { + throw new Error( + `First argument of \`#${operator}\` predicate must be a capture. Got "${steps[1].value}"` + ); + } + matchAll = !operator.startsWith('any-'); + if (steps[2].type === 'capture') { + const captureName1 = steps[1].name!; + const captureName2 = steps[2].name!; + textPredicates[i].push((captures) => { + const nodes1: Node[] = []; + const nodes2: Node[] = []; + for (const c of captures) { + if (c.name === captureName1) nodes1.push(c.node); + if (c.name === captureName2) nodes2.push(c.node); + } + const compare = (n1: { text: string }, n2: { text: string }, positive: boolean) => { + return positive ? n1.text === n2.text : n1.text !== n2.text; + }; + return matchAll + ? nodes1.every((n1) => nodes2.some((n2) => compare(n1, n2, isPositive))) + : nodes1.some((n1) => nodes2.some((n2) => compare(n1, n2, isPositive))); + }); + } else { + captureName = steps[1].name; + const stringValue = steps[2].value; + const matches = (n: Node) => n.text === stringValue; + const doesNotMatch = (n: Node) => n.text !== stringValue; + textPredicates[i].push((captures) => { + const nodes = []; + for (const c of captures) { + if (c.name === captureName) nodes.push(c.node); + } + const test = isPositive ? matches : doesNotMatch; + return matchAll ? + nodes.every(test) : + nodes.some(test); + }); + } + break; + } + + case 'any-not-match?': + case 'not-match?': + isPositive = false; + case 'any-match?': + case 'match?': { + if (steps.length !== 3) { + throw new Error( + `Wrong number of arguments to \`#${operator}\` predicate. Expected 2, got ${steps.length - 1}.`, + ); + } + if (steps[1].type !== 'capture') { + throw new Error( + `First argument of \`#${operator}\` predicate must be a capture. Got "${steps[1].value}".`, + ); + } + if (steps[2].type !== 'string') { + throw new Error( + `Second argument of \`#${operator}\` predicate must be a string. Got @${steps[2].value}.`, + ); + } + captureName = steps[1].name; + const regex = new RegExp(steps[2].value); + matchAll = !operator.startsWith('any-'); + textPredicates[i].push((captures) => { + const nodes = []; + for (const c of captures) { + if (c.name === captureName) nodes.push(c.node.text); + } + const test = (text: string, positive: boolean) => { + return positive ? + regex.test(text) : + !regex.test(text); + }; + if (nodes.length === 0) return !isPositive; + return matchAll ? + nodes.every((text) => test(text, isPositive)) : + nodes.some((text) => test(text, isPositive)); + }); + break; + } + + case 'set!': { + if (steps.length < 2 || steps.length > 3) { + throw new Error( + `Wrong number of arguments to \`#set!\` predicate. Expected 1 or 2. Got ${steps.length - 1}.`, + ); + } + if (steps.some((s) => s.type !== 'string')) { + throw new Error( + `Arguments to \`#set!\` predicate must be a strings.".`, + ); + } + if (!setProperties[i]) setProperties[i] = {}; + setProperties[i][steps[1].value!] = steps[2]?.value || null; + break; + } + + case 'is?': + case 'is-not?': { + if (steps.length < 2 || steps.length > 3) { + throw new Error( + `Wrong number of arguments to \`#${operator}\` predicate. Expected 1 or 2. Got ${steps.length - 1}.`, + ); + } + if (steps.some((s) => s.type !== 'string')) { + throw new Error( + `Arguments to \`#${operator}\` predicate must be a strings.".`, + ); + } + const properties = operator === 'is?' ? assertedProperties : refutedProperties; + if (!properties[i]) properties[i] = {}; + properties[i][steps[1].value!] = steps[2]?.value || null; + break; + } + + case 'not-any-of?': + isPositive = false; + case 'any-of?': { + if (steps.length < 2) { + throw new Error( + `Wrong number of arguments to \`#${operator}\` predicate. Expected at least 1. Got ${steps.length - 1}.`, + ); + } + if (steps[1].type !== 'capture') { + throw new Error( + `First argument of \`#${operator}\` predicate must be a capture. Got "${steps[1].value}".`, + ); + } + for (let i = 2; i < steps.length; i++) { + if (steps[i].type !== 'string') { + throw new Error( + `Arguments to \`#${operator}\` predicate must be a strings.".`, + ); + } + } + captureName = steps[1].name; + const values = steps.slice(2).map((s) => s.value); + textPredicates[i].push((captures) => { + const nodes = []; + for (const c of captures) { + if (c.name === captureName) nodes.push(c.node.text); + } + if (nodes.length === 0) return !isPositive; + return nodes.every((text) => values.includes(text)) === isPositive; + }); + break; + } + + default: + predicates[i].push({ operator, operands: steps.slice(1) }); + } + + steps.length = 0; + } + } + + Object.freeze(setProperties[i]); + Object.freeze(assertedProperties[i]); + Object.freeze(refutedProperties[i]); + } + + C._free(sourceAddress); + return new Query( + INTERNAL, + address, + captureNames, + captureQuantifiers, + textPredicates, + predicates, + setProperties, + assertedProperties, + refutedProperties, + ); + } + + static load(input: string | Uint8Array): Promise { + let bytes: Promise; + if (input instanceof Uint8Array) { + bytes = Promise.resolve(input); + } else { + // @ts-ignore + if (globalThis.process?.versions?.node) { + // @ts-ignore + const fs = require('fs/promises'); + bytes = fs.readFile(input); + } else { + bytes = fetch(input) + .then((response) => response.arrayBuffer() + .then((buffer) => { + if (response.ok) { + return new Uint8Array(buffer); + } else { + const body = new TextDecoder('utf-8').decode(buffer); + throw new Error(`Language.load failed with status ${response.status}.\n\n${body}`); + } + })); + } + } + + return bytes + .then((bytes) => loadWebAssemblyModule(bytes, { loadAsync: true })) + .then((mod) => { + const symbolNames = Object.keys(mod); + const functionName = symbolNames.find((key) => + LANGUAGE_FUNCTION_REGEX.test(key) && + !key.includes('external_scanner_'), + ); + if (!functionName) { + console.log(`Couldn't find language function in WASM file. Symbols:\n${JSON.stringify(symbolNames, null, 2)}`); + throw new Error('Language.load failed: no language function found in WASM file'); + } + const languageAddress = mod[functionName](); + return new Language(INTERNAL, languageAddress); + }); + } +} diff --git a/lib/binding_web/src/lookahead_iterator.ts b/lib/binding_web/src/lookahead_iterator.ts new file mode 100644 index 00000000..b84d2d38 --- /dev/null +++ b/lib/binding_web/src/lookahead_iterator.ts @@ -0,0 +1,50 @@ +import { C, Internal, assertInternal } from './constants'; +import { Language } from './language'; + +export class LookaheadIterator implements Iterable { + private [0]: number; // Internal handle for WASM + private language: Language; + + constructor(internal: Internal, address: number, language: Language) { + assertInternal(internal); + this[0] = address; + this.language = language; + } + + get currentTypeId(): number { + return C._ts_lookahead_iterator_current_symbol(this[0]); + } + + get currentType(): string { + return this.language.types[this.currentTypeId] || 'ERROR'; + } + + delete(): void { + C._ts_lookahead_iterator_delete(this[0]); + this[0] = 0; + } + + resetState(stateId: number): boolean { + return Boolean(C._ts_lookahead_iterator_reset_state(this[0], stateId)); + } + + reset(language: Language, stateId: number): boolean { + if (C._ts_lookahead_iterator_reset(this[0], language[0], stateId)) { + this.language = language; + return true; + } + return false; + } + + [Symbol.iterator](): Iterator { + const self = this; + return { + next(): IteratorResult { + if (C._ts_lookahead_iterator_next(self[0])) { + return { done: false, value: self.currentType }; + } + return { done: true, value: '' }; + } + }; + } +} diff --git a/lib/binding_web/src/marshal.ts b/lib/binding_web/src/marshal.ts new file mode 100644 index 00000000..38f23917 --- /dev/null +++ b/lib/binding_web/src/marshal.ts @@ -0,0 +1,106 @@ +import { Edit, INTERNAL, Point, Range, SIZE_OF_INT, SIZE_OF_NODE, SIZE_OF_POINT } from "./constants"; +import { Node } from "./node"; +import { Tree } from "./tree"; +import { Query } from "./query"; +import { TreeCursor } from "./tree_cursor"; +import { TRANSFER_BUFFER } from "./parser"; + +export function unmarshalCaptures(query: Query, tree: Tree, address: number, result: Array<{name: string, node: Node}>) { + for (let i = 0, n = result.length; i < n; i++) { + const captureIndex = getValue(address, 'i32'); + address += SIZE_OF_INT; + const node = unmarshalNode(tree, address)!; + address += SIZE_OF_NODE; + result[i] = {name: query.captureNames[captureIndex], node}; + } + return address; +} + +export function marshalNode(node: Node) { + let address = TRANSFER_BUFFER; + setValue(address, node.id, 'i32'); + address += SIZE_OF_INT; + setValue(address, node.startIndex, 'i32'); + address += SIZE_OF_INT; + setValue(address, node.startPosition.row, 'i32'); + address += SIZE_OF_INT; + setValue(address, node.startPosition.column, 'i32'); + address += SIZE_OF_INT; + setValue(address, node[0], 'i32'); +} + +export function unmarshalNode(tree: Tree, address = TRANSFER_BUFFER): Node | null { + const id = getValue(address, 'i32'); + address += SIZE_OF_INT; + if (id === 0) return null; + + const index = getValue(address, 'i32'); + address += SIZE_OF_INT; + const row = getValue(address, 'i32'); + address += SIZE_OF_INT; + const column = getValue(address, 'i32'); + address += SIZE_OF_INT; + const other = getValue(address, 'i32'); + + const result = new Node(INTERNAL, { + id, + tree, + startIndex: index, + startPosition: {row, column}, + other, + }); + + return result; +} + +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'); +} + +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'); +} + +export function marshalPoint(address: number, point: Point): void { + setValue(address, point.row, 'i32'); + 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, + }; + return result; +} + +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; +} + +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; + return result; +} + +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; +} diff --git a/lib/binding_web/src/node.ts b/lib/binding_web/src/node.ts new file mode 100644 index 00000000..9000b073 --- /dev/null +++ b/lib/binding_web/src/node.ts @@ -0,0 +1,445 @@ +import { INTERNAL, Internal, assertInternal, Point, Edit, SIZE_OF_INT, SIZE_OF_NODE, SIZE_OF_POINT, ZERO_POINT, isPoint, C } from './constants'; +import { getText, Tree } from './tree'; +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 { + // @ts-ignore + private [0]: number; // Internal handle for WASM + private _children?: (Node | null)[]; + private _namedChildren?: (Node | null)[]; + + id!: number; + startIndex!: number; + startPosition!: Point; + tree: Tree; + + constructor( + internal: Internal, + { + id, + tree, + startIndex, + startPosition, + other, + }: { + id: number; + tree: Tree; + startIndex: number; + startPosition: Point; + other: number; + } + ) { + assertInternal(internal); + this[0] = other; + this.id = id; + this.tree = tree; + this.startIndex = startIndex; + this.startPosition = startPosition; + } + + get typeId(): number { + marshalNode(this); + return C._ts_node_symbol_wasm(this.tree[0]); + } + + get grammarId(): number { + marshalNode(this); + return C._ts_node_grammar_symbol_wasm(this.tree[0]); + } + + get type(): string { + return this.tree.language.types[this.typeId] || 'ERROR'; + } + + get grammarType(): string { + return this.tree.language.types[this.grammarId] || 'ERROR'; + } + + get endPosition(): Point { + marshalNode(this); + C._ts_node_end_point_wasm(this.tree[0]); + return unmarshalPoint(TRANSFER_BUFFER); + } + + get endIndex(): number { + marshalNode(this); + return C._ts_node_end_index_wasm(this.tree[0]); + } + + get text(): string { + return getText(this.tree, this.startIndex, this.endIndex, this.startPosition); + } + + get parseState(): number { + marshalNode(this); + return C._ts_node_parse_state_wasm(this.tree[0]); + } + + get nextParseState(): number { + marshalNode(this); + return C._ts_node_next_parse_state_wasm(this.tree[0]); + } + + get isNamed(): boolean { + marshalNode(this); + return C._ts_node_is_named_wasm(this.tree[0]) === 1; + } + + get hasError(): boolean { + marshalNode(this); + return C._ts_node_has_error_wasm(this.tree[0]) === 1; + } + + get hasChanges(): boolean { + marshalNode(this); + return C._ts_node_has_changes_wasm(this.tree[0]) === 1; + } + + get isError(): boolean { + marshalNode(this); + return C._ts_node_is_error_wasm(this.tree[0]) === 1; + } + + get isMissing(): boolean { + marshalNode(this); + return C._ts_node_is_missing_wasm(this.tree[0]) === 1; + } + + get isExtra(): boolean { + marshalNode(this); + return C._ts_node_is_extra_wasm(this.tree[0]) === 1; + } + + equals(other: Node): boolean { + return this.tree === other.tree && this.id === other.id; + } + + child(index: number): Node | null { + marshalNode(this); + C._ts_node_child_wasm(this.tree[0], index); + return unmarshalNode(this.tree); + } + + namedChild(index: number): Node | null { + marshalNode(this); + C._ts_node_named_child_wasm(this.tree[0], index); + return unmarshalNode(this.tree); + } + + childForFieldId(fieldId: number): Node | null { + marshalNode(this); + C._ts_node_child_by_field_id_wasm(this.tree[0], fieldId); + return unmarshalNode(this.tree); + } + + childForFieldName(fieldName: string): Node | null { + const fieldId = this.tree.language.fields.indexOf(fieldName); + if (fieldId !== -1) return this.childForFieldId(fieldId); + return null; + } + + fieldNameForChild(index: number): string | null { + marshalNode(this); + const address = C._ts_node_field_name_for_child_wasm(this.tree[0], index); + if (!address) return null; + return 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); + } + + childrenForFieldName(fieldName: string): (Node | null)[] { + const fieldId = this.tree.language.fields.indexOf(fieldName); + if (fieldId !== -1 && fieldId !== 0) return this.childrenForFieldId(fieldId); + return []; + } + + 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 result = new Array(count); + + if (count > 0) { + let address = buffer; + for (let i = 0; i < count; i++) { + result[i] = unmarshalNode(this.tree, address); + address += SIZE_OF_NODE; + } + C._free(buffer); + } + return result; + } + + firstChildForIndex(index: number): Node | null { + marshalNode(this); + const address = TRANSFER_BUFFER + SIZE_OF_NODE; + setValue(address, index, 'i32'); + C._ts_node_first_child_for_byte_wasm(this.tree[0]); + return unmarshalNode(this.tree); + } + + firstNamedChildForIndex(index: number): Node | null { + marshalNode(this); + const address = TRANSFER_BUFFER + SIZE_OF_NODE; + setValue(address, index, 'i32'); + C._ts_node_first_named_child_for_byte_wasm(this.tree[0]); + return unmarshalNode(this.tree); + } + + get childCount(): number { + marshalNode(this); + return C._ts_node_child_count_wasm(this.tree[0]); + } + + get namedChildCount(): number { + marshalNode(this); + return C._ts_node_named_child_count_wasm(this.tree[0]); + } + + get firstChild(): Node | null { + return this.child(0); + } + + get firstNamedChild(): Node | null { + return this.namedChild(0); + } + + get lastChild(): Node | null { + return this.child(this.childCount - 1); + } + + get lastNamedChild(): Node | null { + return this.namedChild(this.namedChildCount - 1); + } + + get children(): (Node | null)[] { + 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'); + this._children = new Array(count); + if (count > 0) { + let address = buffer; + for (let i = 0; i < count; i++) { + this._children[i] = unmarshalNode(this.tree, address); + address += SIZE_OF_NODE; + } + C._free(buffer); + } + } + return this._children; + } + + get namedChildren(): (Node | null)[] { + 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'); + this._namedChildren = new Array(count); + if (count > 0) { + let address = buffer; + for (let i = 0; i < count; i++) { + this._namedChildren[i] = unmarshalNode(this.tree, address); + address += SIZE_OF_NODE; + } + C._free(buffer); + } + } + return this._namedChildren; + } + + descendantsOfType( + types: string | string[], + startPosition: Point = ZERO_POINT, + endPosition: Point = ZERO_POINT + ): (Node | null)[] { + if (!Array.isArray(types)) types = [types]; + + // Convert the type strings to numeric type symbols + const symbols: number[] = []; + const typesBySymbol = this.tree.language.types; + for (let i = 0, n = typesBySymbol.length; i < n; i++) { + if (types.includes(typesBySymbol[i])) { + symbols.push(i); + } + } + + // 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'); + } + + // Call the C API to compute the descendants + marshalNode(this); + C._ts_node_descendants_of_type_wasm( + this.tree[0], + symbolsAddress, + symbols.length, + startPosition.row, + startPosition.column, + endPosition.row, + endPosition.column + ); + + // Instantiate the nodes based on the data returned + const descendantCount = getValue(TRANSFER_BUFFER, 'i32'); + const descendantAddress = getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32'); + const result = new Array(descendantCount); + if (descendantCount > 0) { + let address = descendantAddress; + for (let i = 0; i < descendantCount; i++) { + result[i] = unmarshalNode(this.tree, address); + address += SIZE_OF_NODE; + } + } + + // Free the intermediate buffers + C._free(descendantAddress); + C._free(symbolsAddress); + return result; + } + + get nextSibling(): Node | null { + marshalNode(this); + C._ts_node_next_sibling_wasm(this.tree[0]); + return unmarshalNode(this.tree); + } + + get previousSibling(): Node | null { + marshalNode(this); + C._ts_node_prev_sibling_wasm(this.tree[0]); + return unmarshalNode(this.tree); + } + + get nextNamedSibling(): Node | null { + marshalNode(this); + C._ts_node_next_named_sibling_wasm(this.tree[0]); + return unmarshalNode(this.tree); + } + + get previousNamedSibling(): Node | null { + marshalNode(this); + C._ts_node_prev_named_sibling_wasm(this.tree[0]); + return unmarshalNode(this.tree); + } + + get descendantCount(): number { + marshalNode(this); + return C._ts_node_descendant_count_wasm(this.tree[0]); + } + + get parent(): Node | null { + marshalNode(this); + C._ts_node_parent_wasm(this.tree[0]); + return unmarshalNode(this.tree); + } + + descendantForIndex(start: number, end: number = start): Node | null { + if (typeof start !== 'number' || typeof end !== 'number') { + throw new Error('Arguments must be numbers'); + } + + marshalNode(this); + const address = TRANSFER_BUFFER + SIZE_OF_NODE; + setValue(address, start, 'i32'); + setValue(address + SIZE_OF_INT, end, 'i32'); + C._ts_node_descendant_for_index_wasm(this.tree[0]); + return unmarshalNode(this.tree); + } + + namedDescendantForIndex(start: number, end: number = start): Node | null { + if (typeof start !== 'number' || typeof end !== 'number') { + throw new Error('Arguments must be numbers'); + } + + marshalNode(this); + const address = TRANSFER_BUFFER + SIZE_OF_NODE; + setValue(address, start, 'i32'); + setValue(address + SIZE_OF_INT, end, 'i32'); + C._ts_node_named_descendant_for_index_wasm(this.tree[0]); + return unmarshalNode(this.tree); + } + + descendantForPosition(start: Point, end: Point = start) { + if (!isPoint(start) || !isPoint(end)) { + throw new Error('Arguments must be {row, column} objects'); + } + + marshalNode(this); + const address = TRANSFER_BUFFER + SIZE_OF_NODE; + marshalPoint(address, start); + marshalPoint(address + SIZE_OF_POINT, end); + C._ts_node_descendant_for_position_wasm(this.tree[0]); + return unmarshalNode(this.tree); + } + + namedDescendantForPosition(start: Point, end: Point = start) { + if (!isPoint(start) || !isPoint(end)) { + throw new Error('Arguments must be {row, column} objects'); + } + + marshalNode(this); + const address = TRANSFER_BUFFER + SIZE_OF_NODE; + marshalPoint(address, start); + marshalPoint(address + SIZE_OF_POINT, end); + C._ts_node_named_descendant_for_position_wasm(this.tree[0]); + return unmarshalNode(this.tree); + } + + walk(): TreeCursor { + marshalNode(this); + C._ts_tree_cursor_new_wasm(this.tree[0]); + return new TreeCursor(INTERNAL, this.tree); + } + + edit(edit: Edit) { + if (this.startIndex >= edit.oldEndIndex) { + this.startIndex = edit.newEndIndex + (this.startIndex - edit.oldEndIndex); + let subbedPointRow; + let subbedPointColumn; + if (this.startPosition.row > edit.oldEndPosition.row) { + subbedPointRow = this.startPosition.row - edit.oldEndPosition.row; + subbedPointColumn = this.startPosition.column; + } else { + subbedPointRow = 0; + subbedPointColumn = this.startPosition.column; + if (this.startPosition.column >= edit.oldEndPosition.column) { + subbedPointColumn = + this.startPosition.column - edit.oldEndPosition.column; + } + } + + if (subbedPointRow > 0) { + this.startPosition.row += subbedPointRow; + this.startPosition.column = subbedPointColumn; + } else { + this.startPosition.column += subbedPointColumn; + } + } else if (this.startIndex > edit.startIndex) { + this.startIndex = edit.newEndIndex; + this.startPosition.row = edit.newEndPosition.row; + this.startPosition.column = edit.newEndPosition.column; + } + } + + toString() { + marshalNode(this); + const address = C._ts_node_to_string_wasm(this.tree[0]); + const result = AsciiToString(address); + C._free(address); + return result; + } +} diff --git a/lib/binding_web/src/parser.ts b/lib/binding_web/src/parser.ts new file mode 100644 index 00000000..9a419dce --- /dev/null +++ b/lib/binding_web/src/parser.ts @@ -0,0 +1,193 @@ +import { C, INTERNAL, Point, Range, SIZE_OF_INT, SIZE_OF_RANGE } from './constants'; +import { Language } from './language'; +import { marshalRange, unmarshalRange } from './marshal'; +import { Tree } from './tree'; + +declare const getValue: (ptr: number, type: string) => any; +declare const Module: { [key: string]: any }; +declare let initPromise: Promise; + +interface ParseOptions { + includedRanges?: Range[]; + progressCallback?: (percent: number) => void; +} + +type ParseCallback = ((index: number, position: Point) => string) | string; +type LogCallback = ((message: string, type: number, row: number, column: number) => void) | null; + +// Global variable for transferring data across the FFI boundary +export let TRANSFER_BUFFER: number; + +let VERSION: number; +let MIN_COMPATIBLE_VERSION: number; + +// @ts-ignore +let currentParseCallback: ((index: number, position: Point) => string) | null = null; +// @ts-ignore +let currentLogCallback: LogCallback = null; +// @ts-ignore +let currentProgressCallback: ((percent: number) => void) | null = null; + +export class ParserImpl { + protected [0]: number = 0; + protected [1]: number = 0; + protected language: Language | null = null; + protected logCallback: LogCallback = null; + static Language: typeof Language; + + static init() { + TRANSFER_BUFFER = C._ts_init(); + VERSION = getValue(TRANSFER_BUFFER, 'i32'); + MIN_COMPATIBLE_VERSION = getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32'); + } + + initialize() { + C._ts_parser_new_wasm(); + this[0] = getValue(TRANSFER_BUFFER, 'i32'); + this[1] = getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32'); + } + + delete() { + C._ts_parser_delete(this[0]); + C._free(this[1]); + this[0] = 0; + this[1] = 0; + } + + setLanguage(language: Language | null): this { + let address: number; + if (!language) { + address = 0; + this.language = null; + } else if (language.constructor === Language) { + address = language[0]; + const version = C._ts_language_version(address); + if (version < MIN_COMPATIBLE_VERSION || VERSION < version) { + throw new Error( + `Incompatible language version ${version}. ` + + `Compatibility range ${MIN_COMPATIBLE_VERSION} through ${VERSION}.` + ); + } + this.language = language; + } else { + throw new Error('Argument must be a Language'); + } + + C._ts_parser_set_language(this[0], address); + return this; + } + + getLanguage(): Language | null { + return this.language; + } + + parse( + callback: ParseCallback, + oldTree?: Tree | null, + options: ParseOptions = {} + ): Tree { + if (typeof callback === 'string') { + currentParseCallback = (index: number) => callback.slice(index); + } else if (typeof callback === 'function') { + currentParseCallback = callback; + } else { + throw new Error('Argument must be a string or a function'); + } + + if (options?.progressCallback) { + currentProgressCallback = options.progressCallback; + } else { + currentProgressCallback = null; + } + + if (this.logCallback) { + currentLogCallback = this.logCallback; + C._ts_parser_enable_logger_wasm(this[0], 1); + } else { + currentLogCallback = null; + C._ts_parser_enable_logger_wasm(this[0], 0); + } + + let rangeCount = 0; + let rangeAddress = 0; + if (options?.includedRanges) { + rangeCount = options.includedRanges.length; + rangeAddress = C._calloc(rangeCount, SIZE_OF_RANGE); + let address = rangeAddress; + for (let i = 0; i < rangeCount; i++) { + marshalRange(address, options.includedRanges[i]); + address += SIZE_OF_RANGE; + } + } + + const treeAddress = C._ts_parser_parse_wasm( + this[0], + this[1], + oldTree ? oldTree[0] : 0, + rangeAddress, + rangeCount + ); + + if (!treeAddress) { + currentParseCallback = null; + currentLogCallback = null; + currentProgressCallback = null; + throw new Error('Parsing failed'); + } + + if (!this.language) { + 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; + return result; + } + + reset(): void { + C._ts_parser_reset(this[0]); + } + + 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 result: Range[] = new Array(count); + + if (count > 0) { + let address = buffer; + for (let i = 0; i < count; i++) { + result[i] = unmarshalRange(address); + address += SIZE_OF_RANGE; + } + C._free(buffer); + } + + return result; + } + + getTimeoutMicros(): number { + return C._ts_parser_timeout_micros(this[0]); + } + + setTimeoutMicros(timeout: number): void { + C._ts_parser_set_timeout_micros(this[0], timeout); + } + + setLogger(callback: LogCallback): this { + if (!callback) { + this.logCallback = null; + } else if (typeof callback !== 'function') { + throw new Error('Logger callback must be a function'); + } else { + this.logCallback = callback; + } + return this; + } + + getLogger(): LogCallback { + return this.logCallback; + } +} diff --git a/lib/binding_web/src/query.ts b/lib/binding_web/src/query.ts new file mode 100644 index 00000000..fdbbb2a3 --- /dev/null +++ b/lib/binding_web/src/query.ts @@ -0,0 +1,329 @@ +import { Internal, assertInternal, Point, ZERO_POINT, SIZE_OF_INT, C } from './constants'; +import { Node } from './node'; +import { marshalNode, unmarshalCaptures } from './marshal'; +import { TRANSFER_BUFFER } from './parser'; + +// @ts-ignore +let currentQueryProgressCallback: ((percent: number) => void) | null = null; + +interface QueryOptions { + startPosition?: Point; + endPosition?: Point; + startIndex?: number; + endIndex?: number; + matchLimit?: number; + maxStartDepth?: number; + timeoutMicros?: number; + progressCallback?: (percent: number) => void; +} + +export interface Properties { + [key: string]: string | null; +} + +export interface Predicate { + operator: string; + operands: PredicateStep[]; +} + +export interface Capture { + name: string; + node: Node; + setProperties?: Properties; + assertedProperties?: Properties; + refutedProperties?: Properties; +} + +export const CaptureQuantifier = { + Zero: 0, + ZeroOrOne: 1, + ZeroOrMore: 2, + One: 3, + OneOrMore: 4 +} as const; + +export type CaptureQuantifier = typeof CaptureQuantifier[keyof typeof CaptureQuantifier]; + +export interface QueryMatch { + pattern: number; + captures: Capture[]; + setProperties?: Properties; + assertedProperties?: Properties; + refutedProperties?: Properties; +} + +export type PredicateStep = + | { type: 'string'; value: string } + | { type: 'capture'; value?: string, name: string }; + +export type TextPredicate = (captures: Array) => boolean; + +export class Query { + private [0]: number; // Internal handle for WASM + private exceededMatchLimit: boolean; + private textPredicates: TextPredicate[][]; + + readonly captureNames: string[]; + readonly captureQuantifiers: number[][]; + readonly predicates: Predicate[][]; + readonly setProperties: Properties[]; + readonly assertedProperties: Properties[]; + readonly refutedProperties: Properties[]; + matchLimit?: number; + + constructor( + internal: Internal, + address: number, + captureNames: string[], + captureQuantifiers: number[][], + textPredicates: TextPredicate[][], + predicates: Predicate[][], + setProperties: Properties[], + assertedProperties: Properties[], + refutedProperties: Properties[], + ) { + assertInternal(internal); + this[0] = address; + this.captureNames = captureNames; + this.captureQuantifiers = captureQuantifiers; + this.textPredicates = textPredicates; + this.predicates = predicates; + this.setProperties = setProperties; + this.assertedProperties = assertedProperties; + this.refutedProperties = refutedProperties; + this.exceededMatchLimit = false; + } + + delete(): void { + C._ts_query_delete(this[0]); + this[0] = 0; + } + + matches( + node: Node, + options: QueryOptions = {} + ): QueryMatch[] { + const startPosition = options.startPosition || ZERO_POINT; + const endPosition = options.endPosition || ZERO_POINT; + const startIndex = options.startIndex || 0; + const endIndex = options.endIndex || 0; + const matchLimit = options.matchLimit || 0xFFFFFFFF; + const maxStartDepth = options.maxStartDepth || 0xFFFFFFFF; + const timeoutMicros = options.timeoutMicros || 0; + const progressCallback = options.progressCallback; + + if (typeof matchLimit !== 'number') { + throw new Error('Arguments must be numbers'); + } + this.matchLimit = matchLimit; + + if (endIndex !== 0 && startIndex > endIndex) { + throw new Error('`startIndex` cannot be greater than `endIndex`'); + } + + if (endPosition !== ZERO_POINT && ( + startPosition.row > endPosition.row || + (startPosition.row === endPosition.row && startPosition.column > endPosition.column) + )) { + throw new Error('`startPosition` cannot be greater than `endPosition`'); + } + + if (progressCallback) { + currentQueryProgressCallback = progressCallback; + } + + marshalNode(node); + + C._ts_query_matches_wasm( + this[0], + node.tree[0], + startPosition.row, + startPosition.column, + endPosition.row, + endPosition.column, + startIndex, + endIndex, + matchLimit, + maxStartDepth, + 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 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'); + address += SIZE_OF_INT; + const captureCount = getValue(address, 'i32'); + address += SIZE_OF_INT; + + const captures: Capture[] = new Array(captureCount); + address = unmarshalCaptures(this, node.tree, address, captures); + + if (this.textPredicates[pattern].every((p) => p(captures))) { + result[filteredCount] = { pattern, captures }; + const setProperties = this.setProperties[pattern]; + if (setProperties) result[filteredCount].setProperties = setProperties; + const assertedProperties = this.assertedProperties[pattern]; + if (assertedProperties) result[filteredCount].assertedProperties = assertedProperties; + const refutedProperties = this.refutedProperties[pattern]; + if (refutedProperties) result[filteredCount].refutedProperties = refutedProperties; + filteredCount++; + } + } + result.length = filteredCount; + + C._free(startAddress); + currentQueryProgressCallback = null; + return result; + } + + captures( + node: Node, + options: QueryOptions = {} + ): Capture[] { + const startPosition = options.startPosition || ZERO_POINT; + const endPosition = options.endPosition || ZERO_POINT; + const startIndex = options.startIndex || 0; + const endIndex = options.endIndex || 0; + const matchLimit = options.matchLimit || 0xFFFFFFFF; + const maxStartDepth = options.maxStartDepth || 0xFFFFFFFF; + const timeoutMicros = options.timeoutMicros || 0; + const progressCallback = options.progressCallback; + + if (typeof matchLimit !== 'number') { + throw new Error('Arguments must be numbers'); + } + this.matchLimit = matchLimit; + + if (endIndex !== 0 && startIndex > endIndex) { + throw new Error('`startIndex` cannot be greater than `endIndex`'); + } + + if (endPosition !== ZERO_POINT && ( + startPosition.row > endPosition.row || + (startPosition.row === endPosition.row && startPosition.column > endPosition.column) + )) { + throw new Error('`startPosition` cannot be greater than `endPosition`'); + } + + if (progressCallback) { + currentQueryProgressCallback = progressCallback; + } + + marshalNode(node); + + C._ts_query_captures_wasm( + this[0], + node.tree[0], + startPosition.row, + startPosition.column, + endPosition.row, + endPosition.column, + startIndex, + endIndex, + matchLimit, + maxStartDepth, + 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 result: Capture[] = []; + this.exceededMatchLimit = Boolean(didExceedMatchLimit); + + const captures: Capture[] = []; + let address = startAddress; + for (let i = 0; i < count; i++) { + const pattern = getValue(address, 'i32'); + address += SIZE_OF_INT; + const captureCount = getValue(address, 'i32'); + address += SIZE_OF_INT; + const captureIndex = getValue(address, 'i32'); + address += SIZE_OF_INT; + + captures.length = captureCount; + address = unmarshalCaptures(this, node.tree, address, captures); + + if (this.textPredicates[pattern].every((p) => p(captures))) { + const capture = captures[captureIndex]; + const setProperties = this.setProperties[pattern]; + if (setProperties) capture.setProperties = setProperties; + const assertedProperties = this.assertedProperties[pattern]; + if (assertedProperties) capture.assertedProperties = assertedProperties; + const refutedProperties = this.refutedProperties[pattern]; + if (refutedProperties) capture.refutedProperties = refutedProperties; + result.push(capture); + } + } + + C._free(startAddress); + currentQueryProgressCallback = null; + return result; + } + + predicatesForPattern(patternIndex: number): Predicate[] { + return this.predicates[patternIndex]; + } + + disableCapture(captureName: string): void { + const captureNameLength = lengthBytesUTF8(captureName); + const captureNameAddress = C._malloc(captureNameLength + 1); + stringToUTF8(captureName, captureNameAddress, captureNameLength + 1); + C._ts_query_disable_capture(this[0], captureNameAddress, captureNameLength); + C._free(captureNameAddress); + } + + disablePattern(patternIndex: number): void { + if (patternIndex >= this.predicates.length) { + throw new Error( + `Pattern index is ${patternIndex} but the pattern count is ${this.predicates.length}` + ); + } + C._ts_query_disable_pattern(this[0], patternIndex); + } + + didExceedMatchLimit(): boolean { + return this.exceededMatchLimit; + } + + startIndexForPattern(patternIndex: number): number { + if (patternIndex >= this.predicates.length) { + throw new Error( + `Pattern index is ${patternIndex} but the pattern count is ${this.predicates.length}` + ); + } + return C._ts_query_start_byte_for_pattern(this[0], patternIndex); + } + + endIndexForPattern(patternIndex: number): number { + if (patternIndex >= this.predicates.length) { + throw new Error( + `Pattern index is ${patternIndex} but the pattern count is ${this.predicates.length}` + ); + } + return C._ts_query_end_byte_for_pattern(this[0], patternIndex); + } + + isPatternNonLocal(patternIndex: number): boolean { + return C._ts_query_is_pattern_non_local(this[0], patternIndex) === 1; + } + + isPatternRooted(patternIndex: number): boolean { + 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; + } +} diff --git a/lib/binding_web/src/tree.ts b/lib/binding_web/src/tree.ts new file mode 100644 index 00000000..e6e97907 --- /dev/null +++ b/lib/binding_web/src/tree.ts @@ -0,0 +1,115 @@ +import { INTERNAL, Internal, assertInternal, ParseCallback, Point, Range, Edit, SIZE_OF_NODE, SIZE_OF_INT, SIZE_OF_RANGE, C } from './constants'; +import { Language } from './language'; +import { Node } from './node'; +import { TreeCursor } from './tree_cursor'; +import { marshalEdit, marshalPoint, unmarshalNode, unmarshalRange } from './marshal'; +import { TRANSFER_BUFFER } from './parser'; + +export function getText(tree: Tree, startIndex: number, endIndex: number, startPosition: Point): string { + const length = endIndex - startIndex; + let result = tree.textCallback(startIndex, startPosition); + if (result) { + startIndex += result.length; + while (startIndex < endIndex) { + const string = tree.textCallback(startIndex, startPosition); + if (string && string.length > 0) { + startIndex += string.length; + result += string; + } else { + break; + } + } + if (startIndex > endIndex) { + result = result.slice(0, length); + } + } + return result || ''; +} + +export class Tree { + private [0]: number; // Internal handle for WASM + + textCallback: ParseCallback; + language: Language; + + constructor(internal: Internal, address: number, language: Language, textCallback: ParseCallback) { + assertInternal(internal); + this[0] = address; + this.language = language; + this.textCallback = textCallback; + } + + copy(): Tree { + const address = C._ts_tree_copy(this[0]); + return new Tree(INTERNAL, address, this.language, this.textCallback); + } + + delete(): void { + C._ts_tree_delete(this[0]); + this[0] = 0; + } + + edit(edit: Edit): void { + marshalEdit(edit); + C._ts_tree_edit_wasm(this[0]); + } + + get rootNode(): Node { + C._ts_tree_root_node_wasm(this[0]); + return unmarshalNode(this)!; + } + + rootNodeWithOffset(offsetBytes: number, offsetExtent: Point): Node { + const address = TRANSFER_BUFFER + SIZE_OF_NODE; + setValue(address, offsetBytes, 'i32'); + marshalPoint(address + SIZE_OF_INT, offsetExtent); + C._ts_tree_root_node_with_offset_wasm(this[0]); + return unmarshalNode(this)!; + } + + getLanguage(): Language { + return this.language; + } + + walk(): TreeCursor { + return this.rootNode.walk(); + } + + getChangedRanges(other: Tree): Range[] { + if (!(other instanceof Tree)) { + throw new TypeError('Argument must be a 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 result = new Array(count); + + if (count > 0) { + let address = buffer; + for (let i = 0; i < count; i++) { + result[i] = unmarshalRange(address); + address += SIZE_OF_RANGE; + } + C._free(buffer); + } + return result; + } + + 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 result = new Array(count); + + if (count > 0) { + let address = buffer; + for (let i = 0; i < count; i++) { + result[i] = unmarshalRange(address); + address += SIZE_OF_RANGE; + } + C._free(buffer); + } + return result; + } +} diff --git a/lib/binding_web/src/tree_cursor.ts b/lib/binding_web/src/tree_cursor.ts new file mode 100644 index 00000000..8de0ce96 --- /dev/null +++ b/lib/binding_web/src/tree_cursor.ts @@ -0,0 +1,193 @@ +import { INTERNAL, Internal, assertInternal, Point, SIZE_OF_NODE, SIZE_OF_CURSOR, C } from './constants'; +import { marshalNode, marshalPoint, marshalTreeCursor, unmarshalNode, unmarshalPoint, unmarshalTreeCursor } from './marshal'; +import { Node } from './node'; +import { TRANSFER_BUFFER } from './parser'; +import { getText, Tree } from './tree'; + +export class TreeCursor { + // @ts-ignore + private [0]: number; // Internal handle for WASM + // @ts-ignore + private [1]: number; // Internal handle for WASM + // @ts-ignore + private [2]: number; // Internal handle for WASM + // @ts-ignore + private [3]: number; // Internal handle for WASM + + private tree: Tree; + + constructor(internal: Internal, tree: Tree) { + assertInternal(internal); + this.tree = tree; + unmarshalTreeCursor(this); + } + + copy(): TreeCursor { + const copy = new TreeCursor(INTERNAL, this.tree); + C._ts_tree_cursor_copy_wasm(this.tree[0]); + unmarshalTreeCursor(copy); + return copy; + } + + delete(): void { + marshalTreeCursor(this); + C._ts_tree_cursor_delete_wasm(this.tree[0]); + this[0] = this[1] = this[2] = 0; + } + + reset(node: Node): void { + marshalNode(node); + marshalTreeCursor(this, TRANSFER_BUFFER + SIZE_OF_NODE); + C._ts_tree_cursor_reset_wasm(this.tree[0]); + unmarshalTreeCursor(this); + } + + resetTo(cursor: TreeCursor): void { + marshalTreeCursor(this, TRANSFER_BUFFER); + marshalTreeCursor(cursor, TRANSFER_BUFFER + SIZE_OF_CURSOR); + C._ts_tree_cursor_reset_to_wasm(this.tree[0], cursor.tree[0]); + unmarshalTreeCursor(this); + } + + get nodeType(): string { + return this.tree.language.types[this.nodeTypeId] || 'ERROR'; + } + + get nodeTypeId(): number { + marshalTreeCursor(this); + return C._ts_tree_cursor_current_node_type_id_wasm(this.tree[0]); + } + + get nodeStateId(): number { + marshalTreeCursor(this); + return C._ts_tree_cursor_current_node_state_id_wasm(this.tree[0]); + } + + get nodeId(): number { + marshalTreeCursor(this); + return C._ts_tree_cursor_current_node_id_wasm(this.tree[0]); + } + + get nodeIsNamed(): boolean { + marshalTreeCursor(this); + return C._ts_tree_cursor_current_node_is_named_wasm(this.tree[0]) === 1; + } + + get nodeIsMissing(): boolean { + marshalTreeCursor(this); + return C._ts_tree_cursor_current_node_is_missing_wasm(this.tree[0]) === 1; + } + + get nodeText(): string { + marshalTreeCursor(this); + const startIndex = C._ts_tree_cursor_start_index_wasm(this.tree[0]); + const endIndex = C._ts_tree_cursor_end_index_wasm(this.tree[0]); + C._ts_tree_cursor_start_position_wasm(this.tree[0]); + const startPosition = unmarshalPoint(TRANSFER_BUFFER); + return getText(this.tree, startIndex, endIndex, startPosition); + } + + get startPosition(): Point { + marshalTreeCursor(this); + C._ts_tree_cursor_start_position_wasm(this.tree[0]); + return unmarshalPoint(TRANSFER_BUFFER); + } + + get endPosition(): Point { + marshalTreeCursor(this); + C._ts_tree_cursor_end_position_wasm(this.tree[0]); + return unmarshalPoint(TRANSFER_BUFFER); + } + + get startIndex(): number { + marshalTreeCursor(this); + return C._ts_tree_cursor_start_index_wasm(this.tree[0]); + } + + get endIndex(): number { + marshalTreeCursor(this); + return C._ts_tree_cursor_end_index_wasm(this.tree[0]); + } + + get currentNode(): Node | null { + marshalTreeCursor(this); + C._ts_tree_cursor_current_node_wasm(this.tree[0]); + return unmarshalNode(this.tree); + } + + get currentFieldId(): number { + marshalTreeCursor(this); + return C._ts_tree_cursor_current_field_id_wasm(this.tree[0]); + } + + get currentFieldName(): string | null { + return this.tree.language.fields[this.currentFieldId]; + } + + get currentDepth(): number { + marshalTreeCursor(this); + return C._ts_tree_cursor_current_depth_wasm(this.tree[0]); + } + + get currentDescendantIndex(): number { + marshalTreeCursor(this); + return C._ts_tree_cursor_current_descendant_index_wasm(this.tree[0]); + } + + gotoFirstChild(): boolean { + marshalTreeCursor(this); + const result = C._ts_tree_cursor_goto_first_child_wasm(this.tree[0]); + unmarshalTreeCursor(this); + return result === 1; + } + + gotoLastChild(): boolean { + marshalTreeCursor(this); + const result = C._ts_tree_cursor_goto_last_child_wasm(this.tree[0]); + unmarshalTreeCursor(this); + return result === 1; + } + + gotoFirstChildForIndex(goalIndex: number): boolean { + marshalTreeCursor(this); + 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; + } + + gotoFirstChildForPosition(goalPosition: Point): boolean { + marshalTreeCursor(this); + marshalPoint(TRANSFER_BUFFER + SIZE_OF_CURSOR, goalPosition); + const result = C._ts_tree_cursor_goto_first_child_for_position_wasm(this.tree[0]); + unmarshalTreeCursor(this); + return result === 1; + } + + gotoNextSibling(): boolean { + marshalTreeCursor(this); + const result = C._ts_tree_cursor_goto_next_sibling_wasm(this.tree[0]); + unmarshalTreeCursor(this); + return result === 1; + } + + gotoPreviousSibling(): boolean { + marshalTreeCursor(this); + const result = C._ts_tree_cursor_goto_previous_sibling_wasm(this.tree[0]); + unmarshalTreeCursor(this); + return result === 1; + } + + gotoDescendant(goalDescendantIndex: number): void { + marshalTreeCursor(this); + C._ts_tree_cursor_goto_descendant_wasm(this.tree[0], goalDescendantIndex); + unmarshalTreeCursor(this); + } + + gotoParent(): boolean { + marshalTreeCursor(this); + const result = C._ts_tree_cursor_goto_parent_wasm(this.tree[0]); + unmarshalTreeCursor(this); + return result === 1; + } +} diff --git a/lib/binding_web/test/helper.js b/lib/binding_web/test/helper.js deleted file mode 100644 index c70ed7f7..00000000 --- a/lib/binding_web/test/helper.js +++ /dev/null @@ -1,17 +0,0 @@ -const Parser = require('..'); - -function languageURL(name) { - return require.resolve(`../../../target/release/tree-sitter-${name}.wasm`); -} - -module.exports = Parser.init().then(async () => ({ - Parser, - 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')), -})); diff --git a/lib/binding_web/test/helper.ts b/lib/binding_web/test/helper.ts new file mode 100644 index 00000000..31287f19 --- /dev/null +++ b/lib/binding_web/test/helper.ts @@ -0,0 +1,23 @@ +import TSParser from "web-tree-sitter"; + +// @ts-ignore +const Parser: typeof TSParser = await import('..').then(m => m.default); + +// https://github.com/tree-sitter/tree-sitter/blob/master/xtask/src/fetch.rs#L15 +type LanguageName = "bash" | "c" | "cpp" | "embedded-template" | "go" | "html" | "java" | "javascript" | "jsdoc" | "json" | "php" | "python" | "ruby" | "rust" | "typescript"; + +function languageURL(name: LanguageName): string { + return new URL(`../../../target/release/tree-sitter-${name}.wasm`, import.meta.url).pathname; +} + +export default Parser.init().then(async () => ({ + Parser, + 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')), +})); diff --git a/lib/binding_web/test/language-test.js b/lib/binding_web/test/language.test.ts similarity index 56% rename from lib/binding_web/test/language-test.js rename to lib/binding_web/test/language.test.ts index e2f40e88..a7c5cc7d 100644 --- a/lib/binding_web/test/language-test.js +++ b/lib/binding_web/test/language.test.ts @@ -1,13 +1,17 @@ -const {assert} = require('chai'); -let JavaScript; +import { describe, it, expect, beforeAll, afterAll } from 'vitest'; +import helper from './helper'; +import TSParser, { type LookaheadIterable, type Language } from 'web-tree-sitter'; + +let JavaScript: Language; +let Rust: Language; describe('Language', () => { - before(async () => ({JavaScript, Rust} = await require('./helper'))); + beforeAll(async () => ({ JavaScript, Rust } = await helper)); describe('.name, .version', () => { it('returns the name and version of the language', () => { - assert.equal('javascript', JavaScript.name); - assert.equal(15, JavaScript.version); + expect(JavaScript.name).toBe('javascript'); + expect(JavaScript.version).toBe(15); }); }); @@ -16,16 +20,16 @@ describe('Language', () => { const nameId = JavaScript.fieldIdForName('name'); const bodyId = JavaScript.fieldIdForName('body'); - assert.isBelow(nameId, JavaScript.fieldCount); - assert.isBelow(bodyId, JavaScript.fieldCount); - assert.equal('name', JavaScript.fieldNameForId(nameId)); - assert.equal('body', JavaScript.fieldNameForId(bodyId)); + expect(nameId).toBeLessThan(JavaScript.fieldCount); + expect(bodyId).toBeLessThan(JavaScript.fieldCount); + expect(JavaScript.fieldNameForId(nameId!)).toBe('name'); + expect(JavaScript.fieldNameForId(bodyId!)).toBe('body'); }); it('handles invalid inputs', () => { - assert.equal(null, JavaScript.fieldIdForName('namezzz')); - assert.equal(null, JavaScript.fieldNameForId(-1)); - assert.equal(null, JavaScript.fieldNameForId(10000)); + expect(JavaScript.fieldIdForName('namezzz')).toBeNull(); + expect(JavaScript.fieldNameForId(-3)).toBeNull(); + expect(JavaScript.fieldNameForId(10000)).toBeNull(); }); }); @@ -34,18 +38,18 @@ describe('Language', () => { const exportStatementId = JavaScript.idForNodeType('export_statement', true); const starId = JavaScript.idForNodeType('*', false); - assert.isBelow(exportStatementId, JavaScript.nodeTypeCount); - assert.isBelow(starId, JavaScript.nodeTypeCount); - assert.equal(true, JavaScript.nodeTypeIsNamed(exportStatementId)); - assert.equal('export_statement', JavaScript.nodeTypeForId(exportStatementId)); - assert.equal(false, JavaScript.nodeTypeIsNamed(starId)); - assert.equal('*', JavaScript.nodeTypeForId(starId)); + expect(exportStatementId).toBeLessThan(JavaScript.nodeTypeCount); + expect(starId).toBeLessThan(JavaScript.nodeTypeCount); + expect(JavaScript.nodeTypeIsNamed(exportStatementId!)).toBe(true); + expect(JavaScript.nodeTypeForId(exportStatementId!)).toBe('export_statement'); + expect(JavaScript.nodeTypeIsNamed(starId!)).toBe(false); + expect(JavaScript.nodeTypeForId(starId!)).toBe('*'); }); it('handles invalid inputs', () => { - assert.equal(null, JavaScript.nodeTypeForId(-1)); - assert.equal(null, JavaScript.nodeTypeForId(10000)); - assert.equal(null, JavaScript.idForNodeType('export_statement', false)); + expect(JavaScript.nodeTypeForId(-3)).toBeNull(); + expect(JavaScript.nodeTypeForId(10000)).toBeNull(); + expect(JavaScript.idForNodeType('export_statement', false)).toBeNull(); }); }); @@ -53,19 +57,23 @@ describe('Language', () => { it('gets the supertypes and subtypes of a parser', () => { const supertypes = Rust.supertypes; const names = supertypes.map((id) => Rust.nodeTypeForId(id)); - assert.deepStrictEqual( - names, - ['_expression', '_literal', '_literal_pattern', '_pattern', '_type'], - ); + expect(names).toEqual([ + '_expression', + '_literal', + '_literal_pattern', + '_pattern', + '_type' + ]); for (const id of supertypes) { const name = Rust.nodeTypeForId(id); const subtypes = Rust.subtypes(id); let subtypeNames = subtypes.map((id) => Rust.nodeTypeForId(id)); subtypeNames = [...new Set(subtypeNames)].sort(); // Remove duplicates & sort + switch (name) { case '_literal': - assert.deepStrictEqual(subtypeNames, [ + expect(subtypeNames).toEqual([ 'boolean_literal', 'char_literal', 'float_literal', @@ -75,7 +83,7 @@ describe('Language', () => { ]); break; case '_pattern': - assert.deepStrictEqual(subtypeNames, [ + expect(subtypeNames).toEqual([ '_', '_literal_pattern', 'captured_pattern', @@ -96,7 +104,7 @@ describe('Language', () => { ]); break; case '_type': - assert.deepStrictEqual(subtypeNames, [ + expect(subtypeNames).toEqual([ 'abstract_type', 'array_type', 'bounded_type', @@ -116,8 +124,6 @@ describe('Language', () => { 'unit_type', ]); break; - default: - break; } } }); @@ -125,44 +131,45 @@ describe('Language', () => { }); describe('Lookahead iterator', () => { - let lookahead; - let state; - before(async () => { - let Parser; - ({JavaScript, Parser} = await require('./helper')); - const parser = new Parser().setLanguage(JavaScript); + let lookahead: LookaheadIterable; + let state: number; + + beforeAll(async () => { + let Parser: typeof TSParser; + ({ JavaScript, Parser } = await helper); + const parser = new Parser(); + parser.setLanguage(JavaScript); const tree = parser.parse('function fn() {}'); parser.delete(); const cursor = tree.walk(); - assert(cursor.gotoFirstChild()); - assert(cursor.gotoFirstChild()); + expect(cursor.gotoFirstChild()).toBe(true); + expect(cursor.gotoFirstChild()).toBe(true); state = cursor.currentNode.nextParseState; - lookahead = JavaScript.lookaheadIterator(state); - assert.exists(lookahead); + lookahead = JavaScript.lookaheadIterator(state)!; + expect(lookahead).toBeDefined(); }); - after(() => { - lookahead.delete(); - }); + afterAll(() => lookahead.delete()); const expected = ['(', 'identifier', '*', 'formal_parameters', 'html_comment', 'comment']; + it('should iterate over valid symbols in the state', () => { const symbols = Array.from(lookahead); - assert.includeMembers(symbols, expected); - assert.lengthOf(symbols, expected.length); + expect(symbols).toEqual(expect.arrayContaining(expected)); + expect(symbols).toHaveLength(expected.length); }); it('should reset to the initial state', () => { - assert(lookahead.resetState(state)); + expect(lookahead.resetState(state)).toBe(true); const symbols = Array.from(lookahead); - assert.includeMembers(symbols, expected); - assert.lengthOf(symbols, expected.length); + expect(symbols).toEqual(expect.arrayContaining(expected)); + expect(symbols).toHaveLength(expected.length); }); it('should reset', () => { - assert(lookahead.reset(JavaScript, state)); + expect(lookahead.reset(JavaScript, state)).toBe(true); const symbols = Array.from(lookahead); - assert.includeMembers(symbols, expected); - assert.lengthOf(symbols, expected.length); + expect(symbols).toEqual(expect.arrayContaining(expected)); + expect(symbols).toHaveLength(expected.length); }); }); diff --git a/lib/binding_web/test/node-test.js b/lib/binding_web/test/node-test.js deleted file mode 100644 index aa2e4bde..00000000 --- a/lib/binding_web/test/node-test.js +++ /dev/null @@ -1,693 +0,0 @@ -const {assert} = require('chai'); -let Parser; let C; let JavaScript; let JSON; let EmbeddedTemplate; let Python; - -const JSON_EXAMPLE = ` - -[ - 123, - false, - { - "x": null - } -] -`; - -function getAllNodes(tree) { - const result = []; - let visitedChildren = false; - const cursor = tree.walk(); - while (true) { - if (!visitedChildren) { - result.push(cursor.currentNode); - if (!cursor.gotoFirstChild()) { - visitedChildren = true; - } - } else if (cursor.gotoNextSibling()) { - visitedChildren = false; - } else if (!cursor.gotoParent()) { - break; - } - } - return result; -} - -describe('Node', () => { - let parser; let tree; - - before(async () => - ({Parser, C, EmbeddedTemplate, JavaScript, JSON, Python} = await require('./helper')), - ); - - beforeEach(() => { - tree = null; - parser = new Parser().setLanguage(JavaScript); - }); - - afterEach(() => { - parser.delete(); - tree.delete(); - }); - - describe('.children', () => { - it('returns an array of child nodes', () => { - tree = parser.parse('x10 + 1000'); - assert.equal(1, tree.rootNode.children.length); - const sumNode = tree.rootNode.firstChild.firstChild; - assert.deepEqual( - sumNode.children.map((child) => child.type), - ['identifier', '+', 'number'], - ); - }); - }); - - describe('.namedChildren', () => { - it('returns an array of named child nodes', () => { - tree = parser.parse('x10 + 1000'); - const sumNode = tree.rootNode.firstChild.firstChild; - assert.equal(1, tree.rootNode.namedChildren.length); - assert.deepEqual( - ['identifier', 'number'], - sumNode.namedChildren.map((child) => child.type), - ); - }); - }); - - describe('.childrenForFieldName', () => { - it('returns an array of child nodes for the given field name', () => { - parser.setLanguage(Python); - const source = ` - if one: - a() - elif two: - b() - elif three: - c() - elif four: - d()`; - - tree = parser.parse(source); - const node = tree.rootNode.firstChild; - assert.equal(node.type, 'if_statement'); - const alternatives = node.childrenForFieldName('alternative'); - const alternativeTexts = alternatives.map((n) => { - const condition = n.childForFieldName('condition'); - return source.slice(condition.startIndex, condition.endIndex); - }); - assert.deepEqual(alternativeTexts, ['two', 'three', 'four']); - }); - }); - - 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; - - assert.equal(0, quotientNode.startIndex); - assert.equal(15, quotientNode.endIndex); - assert.deepEqual( - [0, 7, 9], - quotientNode.children.map((child) => child.startIndex), - ); - assert.deepEqual( - [6, 8, 15], - quotientNode.children.map((child) => child.endIndex), - ); - }); - }); - - 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; - assert.equal('binary_expression', sumNode.type); - - assert.deepEqual({row: 0, column: 0}, sumNode.startPosition); - assert.deepEqual({row: 0, column: 10}, sumNode.endPosition); - assert.deepEqual( - [{row: 0, column: 0}, {row: 0, column: 4}, {row: 0, column: 6}], - sumNode.children.map((child) => child.startPosition), - ); - assert.deepEqual( - [{row: 0, column: 3}, {row: 0, column: 5}, {row: 0, column: 10}], - sumNode.children.map((child) => child.endPosition), - ); - }); - - it('handles characters that occupy two UTF16 code units', () => { - tree = parser.parse('a👍👎1 /\n b👎c👎'); - const sumNode = tree.rootNode.firstChild.firstChild; - assert.deepEqual( - [ - [{row: 0, column: 0}, {row: 0, column: 6}], - [{row: 0, column: 7}, {row: 0, column: 8}], - [{row: 1, column: 1}, {row: 1, column: 7}], - ], - sumNode.children.map((child) => [child.startPosition, child.endPosition]), - ); - }); - }); - - describe('.parent', () => { - it('returns the node\'s parent', () => { - tree = parser.parse('x10 + 1000'); - const sumNode = tree.rootNode.firstChild; - const variableNode = sumNode.firstChild; - assert.notEqual(sumNode.id, variableNode.id); - assert.equal(sumNode.id, variableNode.parent.id); - assert.equal(tree.rootNode.id, 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; - assert.equal(variableNode.firstChild, null); - assert.equal(variableNode.lastChild, null); - assert.equal(variableNode.firstNamedChild, null); - assert.equal(variableNode.lastNamedChild, null); - assert.equal(variableNode.child(1), null); - }); - }); - - describe('.childForFieldName()', () => { - it('returns null when the node has no children', () => { - tree = parser.parse('class A { b() {} }'); - - const classNode = tree.rootNode.firstChild; - assert.equal(classNode.type, 'class_declaration'); - - const classNameNode = classNode.childForFieldName('name'); - assert.equal(classNameNode.type, 'identifier'); - assert.equal(classNameNode.text, 'A'); - - const bodyNode = classNode.childForFieldName('body'); - assert.equal(bodyNode.type, 'class_body'); - assert.equal(bodyNode.text, '{ b() {} }'); - - const methodNode = bodyNode.firstNamedChild; - assert.equal(methodNode.type, 'method_definition'); - assert.equal(methodNode.text, 'b() {}'); - - const methodNameNode = methodNode.childForFieldName('name'); - assert.equal(methodNameNode.type, 'property_identifier'); - assert.equal(methodNameNode.text, 'b'); - - const paramsNode = methodNode.childForFieldName('parameters'); - assert.equal(paramsNode.type, 'formal_parameters'); - assert.equal(paramsNode.text, '()'); - }); - }); - - describe('.nextSibling and .previousSibling', () => { - it('returns the node\'s next and previous sibling', () => { - tree = parser.parse('x10 + 1000'); - const sumNode = tree.rootNode.firstChild.firstChild; - assert.equal(sumNode.children[1].id, sumNode.children[0].nextSibling.id); - assert.equal(sumNode.children[2].id, sumNode.children[1].nextSibling.id); - assert.equal( - sumNode.children[0].id, - sumNode.children[1].previousSibling.id, - ); - assert.equal( - sumNode.children[1].id, - 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; - assert.equal( - sumNode.namedChildren[1].id, - sumNode.namedChildren[0].nextNamedSibling.id, - ); - assert.equal( - sumNode.namedChildren[0].id, - 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; - assert.equal('identifier', sumNode.descendantForIndex(1, 2).type); - assert.equal('+', sumNode.descendantForIndex(4, 4).type); - - assert.throws(() => { - sumNode.descendantForIndex(1, {}); - }, 'Arguments must be numbers'); - - assert.throws(() => { - sumNode.descendantForIndex(); - }, 'Arguments must be numbers'); - }); - }); - - describe('.namedDescendantForIndex', () => { - it('returns the smallest node that spans the given range', () => { - tree = parser.parse('x10 + 1000'); - const sumNode = tree.rootNode.firstChild; - assert.equal('identifier', sumNode.descendantForIndex(1, 2).type); - assert.equal('+', sumNode.descendantForIndex(4, 4).type); - }); - }); - - describe('.descendantForPosition(min, max)', () => { - it('returns the smallest node that spans the given range', () => { - tree = parser.parse('x10 + 1000'); - const sumNode = tree.rootNode.firstChild; - - assert.equal( - 'identifier', - sumNode.descendantForPosition( - {row: 0, column: 1}, - {row: 0, column: 2}, - ).type, - ); - - assert.equal( - '+', - sumNode.descendantForPosition({row: 0, column: 4}).type, - ); - - assert.throws(() => { - sumNode.descendantForPosition(1, {}); - }, 'Arguments must be {row, column} objects'); - - assert.throws(() => { - sumNode.descendantForPosition(); - }, 'Arguments must be {row, column} objects'); - }); - }); - - describe('.namedDescendantForPosition(min, max)', () => { - it('returns the smallest named node that spans the given range', () => { - tree = parser.parse('x10 + 1000'); - const sumNode = tree.rootNode.firstChild; - - assert.equal( - sumNode.namedDescendantForPosition( - {row: 0, column: 1}, - {row: 0, column: 2}, - ).type, - 'identifier', - ); - - assert.equal( - sumNode.namedDescendantForPosition({row: 0, column: 4}).type, - 'binary_expression', - ); - }); - }); - - describe('.hasError', () => { - it('returns true if the node contains an error', () => { - tree = parser.parse('1 + 2 * * 3'); - const node = tree.rootNode; - assert.equal( - node.toString(), - '(program (expression_statement (binary_expression left: (number) right: (binary_expression left: (number) (ERROR) right: (number)))))', - ); - - const sum = node.firstChild.firstChild; - assert(sum.hasError); - assert(!sum.children[0].hasError); - assert(!sum.children[1].hasError); - assert(sum.children[2].hasError); - }); - }); - - describe('.isError', () => { - it('returns true if the node is an error', () => { - tree = parser.parse('2 * * 3'); - const node = tree.rootNode; - assert.equal( - node.toString(), - '(program (expression_statement (binary_expression left: (number) (ERROR) right: (number))))', - ); - - const multi = node.firstChild.firstChild; - assert(multi.hasError); - assert(!multi.children[0].isError); - assert(!multi.children[1].isError); - assert(multi.children[2].isError); - assert(!multi.children[3].isError); - }); - }); - - describe('.isMissing', () => { - it('returns true if the node is missing from the source and was inserted via error recovery', () => { - tree = parser.parse('(2 ||)'); - const node = tree.rootNode; - assert.equal( - node.toString(), - '(program (expression_statement (parenthesized_expression (binary_expression left: (number) right: (MISSING identifier)))))', - ); - - const sum = node.firstChild.firstChild.firstNamedChild; - assert.equal(sum.type, 'binary_expression'); - assert(sum.hasError); - assert(!sum.children[0].isMissing); - assert(!sum.children[1].isMissing); - assert(sum.children[2].isMissing); - }); - }); - - describe('.isExtra', () => { - 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); - - assert.equal(node.type, 'program'); - assert.equal(commentNode.type, 'comment'); - assert(!node.isExtra); - assert(commentNode.isExtra); - }); - }); - - describe('.text', () => { - const text = 'α0 / b👎c👎'; - - Object.entries({ - '.parse(String)': text, - '.parse(Function)': (offset) => text.slice(offset, 4), - }).forEach(([method, _parse]) => - it(`returns the text of a node generated by ${method}`, async () => { - const [numeratorSrc, denominatorSrc] = text.split(/\s*\/\s+/); - tree = await parser.parse(text); - const quotientNode = tree.rootNode.firstChild.firstChild; - const [numerator, slash, denominator] = quotientNode.children; - - assert.equal(text, tree.rootNode.text, 'root node text'); - assert.equal(denominatorSrc, denominator.text, 'denominator text'); - assert.equal(text, quotientNode.text, 'quotient text'); - assert.equal(numeratorSrc, numerator.text, 'numerator text'); - assert.equal('/', slash.text, '"/" text'); - }), - ); - }); - - describe('.descendantCount', () => { - it('returns the number of descendants', () => { - parser.setLanguage(JSON); - tree = parser.parse(JSON_EXAMPLE); - const valueNode = tree.rootNode; - const allNodes = getAllNodes(tree); - - assert.equal(valueNode.descendantCount, allNodes.length); - - const cursor = tree.walk(); - for (let i = 0; i < allNodes.length; i++) { - const node = allNodes[i]; - cursor.gotoDescendant(i); - assert.equal(cursor.currentNode.id, node.id, `index ${i}`); - } - - for (let i = allNodes.length - 1; i >= 0; i--) { - const node = allNodes[i]; - cursor.gotoDescendant(i); - assert.equal(cursor.currentNode.id, node.id, `rev index ${i}`); - } - }); - - it('tests a single node tree', () => { - parser.setLanguage(EmbeddedTemplate); - tree = parser.parse('hello'); - - const nodes = getAllNodes(tree); - assert.equal(nodes.length, 2); - assert.equal(tree.rootNode.descendantCount, 2); - - const cursor = tree.walk(); - - cursor.gotoDescendant(0); - assert.equal(cursor.currentDepth, 0); - assert.equal(cursor.currentNode.id, nodes[0].id); - - cursor.gotoDescendant(1); - assert.equal(cursor.currentDepth, 1); - assert.equal(cursor.currentNode.id, nodes[1].id); - }); - }); - - describe('.rootNodeWithOffset', () => { - it('returns the root node of the tree, offset by the given byte offset', () => { - tree = parser.parse(' if (a) b'); - const node = tree.rootNodeWithOffset(6, {row: 2, column: 2}); - assert.equal(node.startIndex, 8); - assert.equal(node.endIndex, 16); - assert.deepEqual(node.startPosition, {row: 2, column: 4}); - assert.deepEqual(node.endPosition, {row: 2, column: 12}); - - let child = node.firstChild.child(2); - assert.equal(child.type, 'expression_statement'); - assert.equal(child.startIndex, 15); - assert.equal(child.endIndex, 16); - assert.deepEqual(child.startPosition, {row: 2, column: 11}); - assert.deepEqual(child.endPosition, {row: 2, column: 12}); - - const cursor = node.walk(); - cursor.gotoFirstChild(); - cursor.gotoFirstChild(); - cursor.gotoNextSibling(); - child = cursor.currentNode; - assert.equal(child.type, 'parenthesized_expression'); - assert.equal(child.startIndex, 11); - assert.equal(child.endIndex, 14); - assert.deepEqual(child.startPosition, {row: 2, column: 7}); - assert.deepEqual(child.endPosition, {row: 2, column: 10}); - }); - }); - - describe('.parseState, .nextParseState', () => { - const text = '10 / 5'; - - it('returns node parse state ids', async () => { - tree = await parser.parse(text); - const quotientNode = tree.rootNode.firstChild.firstChild; - const [numerator, slash, denominator] = quotientNode.children; - - assert.equal(tree.rootNode.parseState, 0); - // parse states will change on any change to the grammar so test that it - // returns something instead - assert.isAbove(numerator.parseState, 0); - assert.isAbove(slash.parseState, 0); - assert.isAbove(denominator.parseState, 0); - }); - - it('returns next parse state equal to the language', async () => { - tree = await parser.parse(text); - const quotientNode = tree.rootNode.firstChild.firstChild; - quotientNode.children.forEach((node) => { - assert.equal( - node.nextParseState, - JavaScript.nextState(node.parseState, node.grammarId), - ); - }); - }); - }); - - describe('.descendantsOfType("ERROR", null, null)', () => { - it('finds all of the descendants of an ERROR node', () => { - tree = parser.parse( - `if ({a: 'b'} {c: 'd'}) { - // ^ ERROR - x = function(a) { b; } function(c) { d; } - }` - ); - const errorNode = tree.rootNode; - let descendants = errorNode.descendantsOfType('ERROR', null, null); - assert.deepEqual( - descendants.map((node) => node.startIndex), - [4], - ); - }); - }); - - describe('.descendantsOfType(type, min, max)', () => { - it('finds all of the descendants of the given type in the given range', () => { - tree = parser.parse('a + 1 * b * 2 + c + 3'); - const outerSum = tree.rootNode.firstChild.firstChild; - let descendants = outerSum.descendantsOfType('number', {row: 0, column: 2}, {row: 0, column: 15}); - assert.deepEqual( - descendants.map((node) => node.startIndex), - [4, 12], - ); - assert.deepEqual( - descendants.map((node) => node.endPosition), - [{row: 0, column: 5}, {row: 0, column: 13}], - ); - - descendants = outerSum.descendantsOfType('identifier', {row: 0, column: 2}, {row: 0, column: 15}); - assert.deepEqual( - descendants.map((node) => node.startIndex), - [8], - ); - - descendants = outerSum.descendantsOfType('identifier', {row: 0, column: 0}, {row: 0, column: 30}); - assert.deepEqual( - descendants.map((node) => node.startIndex), - [0, 8, 16], - ); - - descendants = outerSum.descendantsOfType('number', {row: 0, column: 0}, {row: 0, column: 30}); - assert.deepEqual( - descendants.map((node) => node.startIndex), - [4, 12, 20], - ); - - descendants = outerSum.descendantsOfType( - ['identifier', 'number'], - {row: 0, column: 0}, - {row: 0, column: 30}, - ); - assert.deepEqual( - descendants.map((node) => node.startIndex), - [0, 4, 8, 12, 16, 20], - ); - - descendants = outerSum.descendantsOfType('number'); - assert.deepEqual( - descendants.map((node) => node.startIndex), - [4, 12, 20], - ); - - descendants = outerSum.firstChild.descendantsOfType('number', {row: 0, column: 0}, {row: 0, column: 30}); - assert.deepEqual( - descendants.map((node) => node.startIndex), - [4, 12], - ); - }); - }); - - describe.skip('.closest(type)', () => { - it('returns the closest ancestor of the given type', () => { - tree = parser.parse('a(b + -d.e)'); - const property = tree.rootNode.descendantForIndex('a(b + -d.'.length); - assert.equal(property.type, 'property_identifier'); - - const unary = property.closest('unary_expression'); - assert.equal(unary.type, 'unary_expression'); - assert.equal(unary.startIndex, 'a(b + '.length); - assert.equal(unary.endIndex, 'a(b + -d.e'.length); - - const sum = property.closest(['binary_expression', 'call_expression']); - assert.equal(sum.type, 'binary_expression'); - assert.equal(sum.startIndex, 2); - assert.equal(sum.endIndex, 'a(b + -d.e'.length); - }); - - it('throws an exception when an invalid argument is given', () => { - tree = parser.parse('a + 1 * b * 2 + c + 3'); - const number = tree.rootNode.descendantForIndex(4); - - assert.throws(() => number.closest({a: 1}), /Argument must be a string or array of strings/); - }); - }); - - 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; - - assert.equal('identifier', sumNode.firstChildForIndex(0).type); - assert.equal('identifier', sumNode.firstChildForIndex(1).type); - assert.equal('+', sumNode.firstChildForIndex(3).type); - assert.equal('number', sumNode.firstChildForIndex(5).type); - }); - }); - - 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; - - assert.equal('identifier', sumNode.firstNamedChildForIndex(0).type); - assert.equal('identifier', sumNode.firstNamedChildForIndex(1).type); - assert.equal('number', sumNode.firstNamedChildForIndex(3).type); - }); - }); - - describe('.equals(other)', () => { - 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; - assert(node1.equals(node2)); - }); - - 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; - assert(!node1.equals(node2)); - }); - }); - - describe('.fieldNameForChild(index)', () => { - it('returns the field of a child or null', () => { - parser.setLanguage(C); - tree = parser.parse('int w = x + /* y is special! */ y;'); - - const translationUnitNode = tree.rootNode; - const declarationNode = translationUnitNode.firstChild; - const binaryExpressionNode = declarationNode - .childForFieldName('declarator') - .childForFieldName('value'); - - // ------------------- - // left: (identifier) 0 - // operator: "+" _ <--- (not a named child) - // (comment) 1 <--- (is an extra) - // right: (identifier) 2 - // ------------------- - - assert.equal(binaryExpressionNode.fieldNameForChild(0), 'left'); - assert.equal(binaryExpressionNode.fieldNameForChild(1), 'operator'); - // The comment should not have a field name, as it's just an extra - assert.equal(binaryExpressionNode.fieldNameForChild(2), null); - assert.equal(binaryExpressionNode.fieldNameForChild(3), 'right'); - // Negative test - Not a valid child index - assert.equal(binaryExpressionNode.fieldNameForChild(4), null); - }); - }); - - describe('.fieldNameForNamedChild(index)', () => { - it('returns the field of a named child or null', () => { - parser.setLanguage(C); - tree = parser.parse('int w = x + /* y is special! */ y;'); - - const translationUnitNode = tree.rootNode; - const declarationNode = translationUnitNode.firstNamedChild; - const binaryExpressionNode = declarationNode - .childForFieldName('declarator') - .childForFieldName('value'); - - // ------------------- - // left: (identifier) 0 - // operator: "+" _ <--- (not a named child) - // (comment) 1 <--- (is an extra) - // right: (identifier) 2 - // ------------------- - - assert.equal(binaryExpressionNode.fieldNameForNamedChild(0), 'left'); - // The comment should not have a field name, as it's just an extra - assert.equal(binaryExpressionNode.fieldNameForNamedChild(1), null); - // The operator is not a named child, so the named child at index 2 is the right child - assert.equal(binaryExpressionNode.fieldNameForNamedChild(2), 'right'); - // Negative test - Not a valid child index - assert.equal(binaryExpressionNode.fieldNameForNamedChild(3), null); - }); - }); -}); diff --git a/lib/binding_web/test/node.test.ts b/lib/binding_web/test/node.test.ts new file mode 100644 index 00000000..0ace6534 --- /dev/null +++ b/lib/binding_web/test/node.test.ts @@ -0,0 +1,587 @@ +import { describe, it, expect, beforeAll, beforeEach, afterEach } from 'vitest'; +import TSParser, { type Language, type Tree, type SyntaxNode, type Point } from 'web-tree-sitter'; +import helper from './helper'; + +let Parser: typeof TSParser; +let C: Language; +let JavaScript: Language; +let JSON: Language; +let EmbeddedTemplate: Language; +let Python: Language; + +const JSON_EXAMPLE = ` +[ + 123, + false, + { + "x": null + } +] +`; + +function getAllNodes(tree: Tree): SyntaxNode[] { + const result: SyntaxNode[] = []; + let visitedChildren = false; + const cursor = tree.walk(); + + while (true) { + if (!visitedChildren) { + result.push(cursor.currentNode); + if (!cursor.gotoFirstChild()) { + visitedChildren = true; + } + } else if (cursor.gotoNextSibling()) { + visitedChildren = false; + } else if (!cursor.gotoParent()) { + break; + } + } + return result; +} + +describe('Node', () => { + let parser: TSParser; + let tree: Tree | null; + + beforeAll(async () => { + ({ Parser, C, EmbeddedTemplate, JavaScript, JSON, Python } = await helper); + }); + + beforeEach(() => { + tree = null; + parser = new Parser(); + parser.setLanguage(JavaScript); + }); + + afterEach(() => { + parser.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([ + 'identifier', + '+', + 'number' + ]); + }); + }); + + describe('.namedChildren', () => { + it('returns an array of named child nodes', () => { + tree = parser.parse('x10 + 1000'); + const sumNode = tree.rootNode.firstChild!.firstChild; + expect(tree.rootNode.namedChildren).toHaveLength(1); + expect(sumNode!.namedChildren.map(child => child.type)).toEqual([ + 'identifier', + 'number' + ]); + }); + }); + + describe('.childrenForFieldName', () => { + it('returns an array of child nodes for the given field name', () => { + parser.setLanguage(Python); + const source = ` + if one: + a() + elif two: + b() + elif three: + c() + elif four: + d()`; + + 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(alternativeTexts).toEqual(['two', 'three', 'four']); + }); + }); + + 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; + + 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'); + + 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([ + { row: 0, column: 3 }, + { row: 0, column: 5 }, + { row: 0, column: 10 }, + ]); + }); + + 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([ + [{ row: 0, column: 0 }, { row: 0, column: 6 }], + [{ row: 0, column: 7 }, { row: 0, column: 8 }], + [{ row: 1, column: 1 }, { row: 1, column: 7 }] + ]); + }); + }); + + describe('.parent', () => { + 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); + }); + }); + + 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(); + }); + }); + + describe('.childForFieldName()', () => { + it('returns node for the given field name', () => { + tree = parser.parse('class A { b() {} }'); + + const classNode = tree.rootNode.firstChild; + expect(classNode!.type).toBe('class_declaration'); + + 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 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); + }); + }); + + 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); + }); + }); + + 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('+'); + + expect(() => { + sumNode!.descendantForIndex(1, {} as any); + }).toThrow('Arguments must be numbers'); + + expect(() => { + sumNode!.descendantForIndex(undefined as any); + }).toThrow('Arguments must be numbers'); + }); + }); + + 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('+'); + }); + }); + + describe('.descendantForPosition', () => { + it('returns the smallest node that spans the given range', () => { + tree = parser.parse('x10 + 1000'); + const sumNode = tree.rootNode.firstChild; + + expect( + sumNode!.descendantForPosition( + { row: 0, column: 1 }, + { row: 0, column: 2 } + )!.type + ).toBe('identifier'); + + expect( + sumNode!.descendantForPosition({ row: 0, column: 4 })!.type + ).toBe('+'); + + expect(() => { + sumNode!.descendantForPosition(1 as any, {} as any); + }).toThrow('Arguments must be {row, column} objects'); + + expect(() => { + sumNode!.descendantForPosition(undefined as any); + }).toThrow('Arguments must be {row, column} objects'); + }); + }); + + describe('.namedDescendantForPosition(min, max)', () => { + it('returns the smallest named node that spans the given range', () => { + tree = parser.parse('x10 + 1000'); + const sumNode = tree.rootNode.firstChild!; + + expect( + sumNode.namedDescendantForPosition( + { row: 0, column: 1 }, + { row: 0, column: 2 }, + ).type + ).toBe('identifier') + + expect( + sumNode.namedDescendantForPosition({ row: 0, column: 4 }).type + ).toBe('binary_expression'); + }); + }); + + describe('.hasError', () => { + it('returns true if the node contains an error', () => { + tree = parser.parse('1 + 2 * * 3'); + const node = tree.rootNode; + expect(node.toString()).toBe( + '(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); + }); + }); + + describe('.isError', () => { + it('returns true if the node is an error', () => { + tree = parser.parse('2 * * 3'); + const node = tree.rootNode; + expect(node.toString()).toBe( + '(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); + }); + }); + + describe('.isMissing', () => { + it('returns true if the node was inserted via error recovery', () => { + tree = parser.parse('(2 ||)'); + const node = tree.rootNode; + expect(node.toString()).toBe( + '(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); + }); + }); + + describe('.isExtra', () => { + 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); + + expect(node.type).toBe('program'); + expect(commentNode!.type).toBe('comment'); + expect(node.isExtra).toBe(false); + expect(commentNode!.isExtra).toBe(true); + }); + }); + + describe('.text', () => { + const text = 'α0 / b👎c👎'; + + Object.entries({ + '.parse(String)': text, + '.parse(Function)': (offset: number) => text.slice(offset, offset + 4), + }).forEach(([method, _parse]) => + it(`returns the text of a node generated by ${method}`, async () => { + const [numeratorSrc, denominatorSrc] = text.split(/\s*\/\s+/); + tree = parser.parse(_parse); + 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('/'); + }), + ); + }); + + describe('.descendantCount', () => { + it('returns the number of descendants', () => { + parser.setLanguage(JSON); + tree = parser.parse(JSON_EXAMPLE); + const valueNode = tree.rootNode; + const allNodes = getAllNodes(tree); + + expect(valueNode.descendantCount).toBe(allNodes.length); + + const cursor = tree.walk(); + for (let i = 0; i < allNodes.length; i++) { + const node = allNodes[i]; + cursor.gotoDescendant(i); + expect(cursor.currentNode.id).toBe(node.id); + } + + for (let i = allNodes.length - 1; i >= 0; i--) { + const node = allNodes[i]; + cursor.gotoDescendant(i); + expect(cursor.currentNode.id).toBe(node.id); + } + }); + + it('tests a single node tree', () => { + parser.setLanguage(EmbeddedTemplate); + tree = parser.parse('hello'); + + const nodes = getAllNodes(tree); + expect(nodes).toHaveLength(2); + expect(tree.rootNode.descendantCount).toBe(2); + + const cursor = tree.walk(); + + cursor.gotoDescendant(0); + expect(cursor.currentDepth).toBe(0); + expect(cursor.currentNode.id).toBe(nodes[0].id); + + cursor.gotoDescendant(1); + expect(cursor.currentDepth).toBe(1); + expect(cursor.currentNode.id).toBe(nodes[1].id); + }); + }); + + describe('.rootNodeWithOffset', () => { + it('returns the root node of the tree, offset by the given byte offset', () => { + tree = parser.parse(' if (a) b'); + const node = tree.rootNodeWithOffset(6, { row: 2, column: 2 }); + expect(node.startIndex).toBe(8); + expect(node.endIndex).toBe(16); + 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 }); + + const cursor = node.walk(); + cursor.gotoFirstChild(); + cursor.gotoFirstChild(); + cursor.gotoNextSibling(); + child = cursor.currentNode; + expect(child.type).toBe('parenthesized_expression'); + expect(child.startIndex).toBe(11); + expect(child.endIndex).toBe(14); + expect(child.startPosition).toEqual({ row: 2, column: 7 }); + expect(child.endPosition).toEqual({ row: 2, column: 10 }); + }); + }); + + describe('.parseState, .nextParseState', () => { + const text = '10 / 5'; + + it('returns node parse state ids', () => { + tree = parser.parse(text); + 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); + }); + + 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) + ); + }); + }); + }); + + 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; + + let 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 }, + ]); + }); + }); + + + + 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; + + 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; + + expect(sumNode!.firstNamedChildForIndex(0)!.type).toBe('identifier'); + expect(sumNode!.firstNamedChildForIndex(1)!.type).toBe('identifier'); + expect(sumNode!.firstNamedChildForIndex(3)!.type).toBe('number'); + }); + }); + + describe('.equals(other)', () => { + 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); + }); + + 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); + }); + }); + + describe('.fieldNameForChild(index)', () => { + it('returns the field of a child or null', () => { + parser.setLanguage(C); + tree = parser.parse('int w = x + /* y is special! */ y;'); + + const translationUnitNode = tree.rootNode; + const declarationNode = translationUnitNode.firstChild; + const binaryExpressionNode = declarationNode! + .childForFieldName('declarator')! + .childForFieldName('value'); + + // ------------------- + // left: (identifier) 0 + // operator: "+" 1 <--- (not a named child) + // (comment) 2 <--- (is an extra) + // right: (identifier) 3 + // ------------------- + + 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'); + // Negative test - Not a valid child index + expect(binaryExpressionNode!.fieldNameForChild(4)).toBeNull(); + }); + }); + + describe('.fieldNameForNamedChild(index)', () => { + it('returns the field of a named child or null', () => { + parser.setLanguage(C); + tree = parser.parse('int w = x + /* y is special! */ y;'); + + const translationUnitNode = tree.rootNode; + const declarationNode = translationUnitNode.firstNamedChild; + const binaryExpressionNode = declarationNode! + .childForFieldName('declarator')! + .childForFieldName('value'); + + // ------------------- + // left: (identifier) 0 + // operator: "+" _ <--- (not a named child) + // (comment) 1 <--- (is an extra) + // right: (identifier) 2 + // ------------------- + + 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(); + // The operator is not a named child, so the named child at index 2 is the right child + expect(binaryExpressionNode!.fieldNameForNamedChild(2)).toBe('right'); + // Negative test - Not a valid child index + expect(binaryExpressionNode!.fieldNameForNamedChild(3)).toBeNull(); + }); + }); +}); diff --git a/lib/binding_web/test/parser-test.js b/lib/binding_web/test/parser-test.js deleted file mode 100644 index 37ddb939..00000000 --- a/lib/binding_web/test/parser-test.js +++ /dev/null @@ -1,413 +0,0 @@ -const {assert} = require('chai'); -let Parser; let JavaScript; let HTML; let languageURL; let JSON; - -describe('Parser', () => { - let parser; - - before(async () => - ({Parser, JavaScript, HTML, JSON, languageURL} = await require('./helper')), - ); - - beforeEach(() => { - parser = new Parser(); - }); - - afterEach(() => { - parser.delete(); - }); - - describe('.setLanguage', () => { - it('allows setting the language to null', () => { - assert.equal(parser.getLanguage(), null); - parser.setLanguage(JavaScript); - assert.equal(parser.getLanguage(), JavaScript); - parser.setLanguage(null); - assert.equal(parser.getLanguage(), null); - }); - - it('throws an exception when the given object is not a tree-sitter language', () => { - assert.throws(() => parser.setLanguage({}), /Argument must be a Language/); - assert.throws(() => parser.setLanguage(1), /Argument must be a Language/); - }); - }); - - describe('.setLogger', () => { - beforeEach(() => { - parser.setLanguage(JavaScript); - }); - - it('calls the given callback for each parse event', () => { - const debugMessages = []; - parser.setLogger((message) => debugMessages.push(message)); - parser.parse('a + b + c'); - assert.includeMembers(debugMessages, [ - 'skip character:\' \'', - 'consume character:\'b\'', - 'reduce sym:program, child_count:1', - 'accept', - ]); - }); - - it('allows the callback to be retrieved later', () => { - const callback = () => {}; - parser.setLogger(callback); - assert.equal(parser.getLogger(), callback); - parser.setLogger(false); - assert.equal(parser.getLogger(), null); - }); - - it('disables debugging when given a falsy value', () => { - const debugMessages = []; - parser.setLogger((message) => debugMessages.push(message)); - parser.setLogger(false); - parser.parse('a + b * c'); - assert.equal(debugMessages.length, 0); - }); - - it('throws an error when given a truthy value that isn\'t a function ', () => { - assert.throws( - () => parser.setLogger('5'), - 'Logger callback must be a function', - ); - }); - - it('rethrows errors thrown by the logging callback', () => { - const error = new Error('The error message'); - parser.setLogger((_msg, _params) => { - throw error; - }); - assert.throws( - () => parser.parse('ok;'), - 'The error message', - ); - }); - }); - - describe('one included range', () => { - it('parses the text within a range', () => { - parser.setLanguage(HTML); - const sourceCode = 'hi'; - const htmlTree = parser.parse(sourceCode); - const scriptContentNode = htmlTree.rootNode.child(1).child(1); - assert.equal(scriptContentNode.type, 'raw_text'); - - parser.setLanguage(JavaScript); - assert.deepEqual(parser.getIncludedRanges(), [{ - startIndex: 0, - endIndex: 2147483647, - startPosition: {row: 0, column: 0}, - endPosition: {row: 4294967295, column: 2147483647}, - }]); - const ranges = [{ - startIndex: scriptContentNode.startIndex, - endIndex: scriptContentNode.endIndex, - startPosition: scriptContentNode.startPosition, - endPosition: scriptContentNode.endPosition, - }]; - const jsTree = parser.parse( - sourceCode, - null, - {includedRanges: ranges}, - ); - assert.deepEqual(parser.getIncludedRanges(), ranges); - - assert.equal( - jsTree.rootNode.toString(), - '(program (expression_statement (call_expression ' + - 'function: (member_expression object: (identifier) property: (property_identifier)) ' + - 'arguments: (arguments (string (string_fragment))))))', - ); - assert.deepEqual(jsTree.rootNode.startPosition, {row: 0, column: sourceCode.indexOf('console')}); - }); - }); - - describe('multiple included ranges', () => { - it('parses the text within multiple ranges', () => { - parser.setLanguage(JavaScript); - const sourceCode = 'html `
Hello, ${name.toUpperCase()}, it\'s ${now()}.
`'; - const jsTree = parser.parse(sourceCode); - const templateStringNode = jsTree.rootNode.descendantForIndex(sourceCode.indexOf('`<'), sourceCode.indexOf('>`')); - assert.equal(templateStringNode.type, 'template_string'); - - const openQuoteNode = templateStringNode.child(0); - const interpolationNode1 = templateStringNode.child(2); - const interpolationNode2 = templateStringNode.child(4); - const closeQuoteNode = templateStringNode.child(6); - - parser.setLanguage(HTML); - const htmlRanges = [ - { - startIndex: openQuoteNode.endIndex, - startPosition: openQuoteNode.endPosition, - endIndex: interpolationNode1.startIndex, - endPosition: interpolationNode1.startPosition, - }, - { - startIndex: interpolationNode1.endIndex, - startPosition: interpolationNode1.endPosition, - endIndex: interpolationNode2.startIndex, - endPosition: interpolationNode2.startPosition, - }, - { - startIndex: interpolationNode2.endIndex, - startPosition: interpolationNode2.endPosition, - endIndex: closeQuoteNode.startIndex, - endPosition: closeQuoteNode.startPosition, - }, - ]; - const htmlTree = parser.parse(sourceCode, null, {includedRanges: htmlRanges}); - - assert.equal( - htmlTree.rootNode.toString(), - '(document (element' + - ' (start_tag (tag_name))' + - ' (text)' + - ' (element (start_tag (tag_name)) (end_tag (tag_name)))' + - ' (text)' + - ' (end_tag (tag_name))))', - ); - assert.deepEqual(htmlTree.getIncludedRanges(), htmlRanges); - - const divElementNode = htmlTree.rootNode.child(0); - const helloTextNode = divElementNode.child(1); - const bElementNode = divElementNode.child(2); - const bStartTagNode = bElementNode.child(0); - const bEndTagNode = bElementNode.child(1); - - assert.equal(helloTextNode.type, 'text'); - assert.equal(helloTextNode.startIndex, sourceCode.indexOf('Hello')); - assert.equal(helloTextNode.endIndex, sourceCode.indexOf(' ')); - - assert.equal(bStartTagNode.type, 'start_tag'); - assert.equal(bStartTagNode.startIndex, sourceCode.indexOf('')); - assert.equal(bStartTagNode.endIndex, sourceCode.indexOf('${now()}')); - - assert.equal(bEndTagNode.type, 'end_tag'); - assert.equal(bEndTagNode.startIndex, sourceCode.indexOf('')); - assert.equal(bEndTagNode.endIndex, sourceCode.indexOf('.')); - }); - }); - - describe('an included range containing mismatched positions', () => { - it('parses the text within the range', () => { - const sourceCode = '
test
{_ignore_this_part_}'; - - parser.setLanguage(HTML); - - const endIndex = sourceCode.indexOf('{_ignore_this_part_'); - - const rangeToParse = { - startIndex: 0, - startPosition: {row: 10, column: 12}, - endIndex, - endPosition: {row: 10, column: 12 + endIndex}, - }; - - const htmlTree = parser.parse(sourceCode, null, {includedRanges: [rangeToParse]}); - - assert.deepEqual(htmlTree.getIncludedRanges()[0], rangeToParse); - - assert.equal( - htmlTree.rootNode.toString(), - '(document (element (start_tag (tag_name)) (text) (end_tag (tag_name))))', - ); - }); - }); - - describe('.parse', () => { - let tree; - - beforeEach(() => { - tree = null; - parser.setLanguage(JavaScript); - }); - - afterEach(() => { - if (tree) tree.delete(); - }); - - it('reads from the given input', () => { - const parts = ['first', '_', 'second', '_', 'third']; - tree = parser.parse(() => parts.shift()); - assert.equal(tree.rootNode.toString(), '(program (expression_statement (identifier)))'); - }); - - it('stops reading when the input callback return something that\'s not a string', () => { - const parts = ['abc', 'def', 'ghi', {}, {}, {}, 'second-word', ' ']; - tree = parser.parse(() => parts.shift()); - assert.equal( - tree.rootNode.toString(), - '(program (expression_statement (identifier)))', - ); - assert.equal(tree.rootNode.endIndex, 9); - assert.equal(parts.length, 2); - }); - - it('throws an exception when the given input is not a function', () => { - assert.throws(() => parser.parse(null), 'Argument must be a string or a function'); - assert.throws(() => parser.parse(5), 'Argument must be a string or a function'); - assert.throws(() => parser.parse({}), 'Argument must be a string or a function'); - }); - - it('handles long input strings', () => { - const repeatCount = 10000; - const inputString = `[${Array(repeatCount).fill('0').join(',')}]`; - - tree = parser.parse(inputString); - assert.equal(tree.rootNode.type, 'program'); - assert.equal(tree.rootNode.firstChild.firstChild.namedChildCount, repeatCount); - }).timeout(5000); - - it('can use the bash parser', async () => { - parser.setLanguage(await Parser.Language.load(languageURL('bash'))); - tree = parser.parse('FOO=bar echo < err.txt > hello.txt \nhello${FOO}\nEOF'); - assert.equal( - tree.rootNode.toString(), - '(program ' + - '(redirected_statement ' + - 'body: (command ' + - '(variable_assignment name: (variable_name) value: (word)) ' + - 'name: (command_name (word))) ' + - 'redirect: (heredoc_redirect (heredoc_start) ' + - 'redirect: (file_redirect descriptor: (file_descriptor) destination: (word)) ' + - 'redirect: (file_redirect destination: (word)) ' + - '(heredoc_body ' + - '(expansion (variable_name)) (heredoc_content)) (heredoc_end))))', - ); - }).timeout(5000); - - it('can use the c++ parser', async () => { - parser.setLanguage(await Parser.Language.load(languageURL('cpp'))); - tree = parser.parse('const char *s = R"EOF(HELLO WORLD)EOF";'); - assert.equal( - tree.rootNode.toString(), - '(translation_unit (declaration ' + - '(type_qualifier) ' + - 'type: (primitive_type) ' + - 'declarator: (init_declarator ' + - 'declarator: (pointer_declarator declarator: (identifier)) ' + - 'value: (raw_string_literal delimiter: (raw_string_delimiter) (raw_string_content) (raw_string_delimiter)))))', - ); - }).timeout(5000); - - it('can use the HTML parser', async () => { - parser.setLanguage(await Parser.Language.load(languageURL('html'))); - tree = parser.parse('
'); - assert.equal( - tree.rootNode.toString(), - '(document (element (start_tag (tag_name)) (element (start_tag (tag_name)) (element (start_tag (tag_name)) (end_tag (tag_name))) (end_tag (tag_name))) (end_tag (tag_name))))', - ); - }).timeout(5000); - - it('can use the python parser', async () => { - parser.setLanguage(await Parser.Language.load(languageURL('python'))); - tree = parser.parse('class A:\n def b():\n c()'); - assert.equal( - tree.rootNode.toString(), - '(module (class_definition ' + - 'name: (identifier) ' + - 'body: (block ' + - '(function_definition ' + - 'name: (identifier) ' + - 'parameters: (parameters) ' + - 'body: (block (expression_statement (call ' + - 'function: (identifier) ' + - 'arguments: (argument_list))))))))', - ); - }).timeout(5000); - - it('can use the rust parser', async () => { - parser.setLanguage(await Parser.Language.load(languageURL('rust'))); - tree = parser.parse('const x: &\'static str = r###"hello"###;'); - assert.equal( - tree.rootNode.toString(), - '(source_file (const_item ' + - 'name: (identifier) ' + - 'type: (reference_type (lifetime (identifier)) type: (primitive_type)) ' + - 'value: (raw_string_literal (string_content))))', - ); - }).timeout(5000); - - it('can use the typescript parser', async () => { - parser.setLanguage(await Parser.Language.load(languageURL('typescript'))); - tree = parser.parse('a()\nb()\n[c]'); - assert.equal( - tree.rootNode.toString(), - '(program ' + - '(expression_statement (call_expression function: (identifier) arguments: (arguments))) ' + - '(expression_statement (subscript_expression ' + - 'object: (call_expression ' + - 'function: (identifier) ' + - 'arguments: (arguments)) ' + - 'index: (identifier))))', - ); - }).timeout(5000); - - it('can use the tsx parser', async () => { - parser.setLanguage(await Parser.Language.load(languageURL('tsx'))); - tree = parser.parse('a()\nb()\n[c]'); - assert.equal( - tree.rootNode.toString(), - '(program ' + - '(expression_statement (call_expression function: (identifier) arguments: (arguments))) ' + - '(expression_statement (subscript_expression ' + - 'object: (call_expression ' + - 'function: (identifier) ' + - 'arguments: (arguments)) ' + - 'index: (identifier))))', - ); - }).timeout(5000); - - it('parses only the text within the `includedRanges` if they are specified', () => { - const sourceCode = '<% foo() %> <% bar %>'; - - const start1 = sourceCode.indexOf('foo'); - const end1 = start1 + 5; - const start2 = sourceCode.indexOf('bar'); - const end2 = start2 + 3; - - const tree = parser.parse(sourceCode, null, { - includedRanges: [ - { - startIndex: start1, - endIndex: end1, - startPosition: {row: 0, column: start1}, - endPosition: {row: 0, column: end1}, - }, - { - startIndex: start2, - endIndex: end2, - startPosition: {row: 0, column: start2}, - endPosition: {row: 0, column: end2}, - }, - ], - }); - - assert.equal( - tree.rootNode.toString(), - '(program (expression_statement (call_expression function: (identifier) arguments: (arguments))) (expression_statement (identifier)))', - ); - }); - - it('parses with a timeout', () => { - parser.setLanguage(JSON); - - const startTime = performance.now(); - assert.throws(() => { - parser.parse( - (offset, _) => offset === 0 ? '[' : ',0', - null, - { - progressCallback: (_) => { - if (performance.now() - startTime > 1) { - return true; - } - return false; - }, - }, - ); - }, - ); - }).timeout(5000); - }); -}); diff --git a/lib/binding_web/test/parser.test.ts b/lib/binding_web/test/parser.test.ts new file mode 100644 index 00000000..d7adc31a --- /dev/null +++ b/lib/binding_web/test/parser.test.ts @@ -0,0 +1,412 @@ +import { describe, it, expect, beforeAll, beforeEach, afterEach } from 'vitest'; +import helper from './helper'; +import TSParser, { type Language } from 'web-tree-sitter'; + +let Parser: typeof TSParser; +let JavaScript: Language; +let HTML: Language; +let JSON: Language; +let languageURL: (name: string) => string; + +describe('Parser', () => { + let parser: TSParser; + + beforeAll(async () => { + ({ Parser, JavaScript, HTML, JSON, languageURL } = await helper); + }); + + beforeEach(() => { + parser = new Parser(); + }); + + afterEach(() => { + parser.delete(); + }); + + describe('.setLanguage', () => { + it('allows setting the language to null', () => { + expect(parser.getLanguage()).toBeUndefined(); + parser.setLanguage(JavaScript); + expect(parser.getLanguage()).toBe(JavaScript); + parser.setLanguage(null); + expect(parser.getLanguage()).toBeNull(); + }); + + it('throws an exception when the given object is not a tree-sitter language', () => { + expect(() => parser.setLanguage({} as any)).toThrow(/Argument must be a Language/); + expect(() => parser.setLanguage(1 as any)).toThrow(/Argument must be a Language/); + }); + }); + + describe('.setLogger', () => { + beforeEach(() => { + parser.setLanguage(JavaScript); + }); + + it('calls the given callback for each parse event', () => { + const debugMessages: string[] = []; + parser.setLogger((message) => debugMessages.push(message)); + parser.parse('a + b + c'); + expect(debugMessages).toEqual(expect.arrayContaining([ + 'skip character:\' \'', + 'consume character:\'b\'', + 'reduce sym:program, child_count:1', + 'accept' + ])); + }); + + it('allows the callback to be retrieved later', () => { + const callback = () => { }; + parser.setLogger(callback); + expect(parser.getLogger()).toBe(callback); + parser.setLogger(false); + expect(parser.getLogger()).toBeNull(); + }); + + it('disables debugging when given a falsy value', () => { + const debugMessages: string[] = []; + parser.setLogger((message) => debugMessages.push(message)); + parser.setLogger(false); + parser.parse('a + b * c'); + expect(debugMessages).toHaveLength(0); + }); + + it('throws an error when given a truthy value that isn\'t a function', () => { + expect(() => parser.setLogger('5' as any)).toThrow('Logger callback must be a function'); + }); + + it('rethrows errors thrown by the logging callback', () => { + const error = new Error('The error message'); + parser.setLogger((_msg) => { + throw error; + }); + expect(() => parser.parse('ok;')).toThrow('The error message'); + }); + }); + + describe('one included range', () => { + it('parses the text within a range', () => { + parser.setLanguage(HTML); + const sourceCode = 'hi'; + const htmlTree = parser.parse(sourceCode); + const scriptContentNode = htmlTree.rootNode.child(1)!.child(1)!; + expect(scriptContentNode.type).toBe('raw_text'); + + parser.setLanguage(JavaScript); + expect(parser.getIncludedRanges()).toEqual([{ + startIndex: 0, + endIndex: 2147483647, + startPosition: { row: 0, column: 0 }, + endPosition: { row: 4294967295, column: 2147483647 } + }]); + + const ranges = [{ + startIndex: scriptContentNode.startIndex, + endIndex: scriptContentNode.endIndex, + startPosition: scriptContentNode.startPosition, + endPosition: scriptContentNode.endPosition, + }]; + + const jsTree = parser.parse( + sourceCode, + null, + { includedRanges: ranges } + ); + expect(parser.getIncludedRanges()).toEqual(ranges); + + expect(jsTree.rootNode.toString()).toBe( + '(program (expression_statement (call_expression ' + + 'function: (member_expression object: (identifier) property: (property_identifier)) ' + + 'arguments: (arguments (string (string_fragment))))))' + ); + expect(jsTree.rootNode.startPosition).toEqual({ row: 0, column: sourceCode.indexOf('console') }); + }); + }); + + describe('multiple included ranges', () => { + it('parses the text within multiple ranges', () => { + parser.setLanguage(JavaScript); + const sourceCode = 'html `
Hello, ${name.toUpperCase()}, it\'s ${now()}.
`'; + const jsTree = parser.parse(sourceCode); + const templateStringNode = jsTree.rootNode.descendantForIndex( + sourceCode.indexOf('`<'), + sourceCode.indexOf('>`') + ); + expect(templateStringNode.type).toBe('template_string'); + + const openQuoteNode = templateStringNode.child(0)!; + const interpolationNode1 = templateStringNode.child(2)!; + const interpolationNode2 = templateStringNode.child(4)!; + const closeQuoteNode = templateStringNode.child(6)!; + + parser.setLanguage(HTML); + const htmlRanges = [ + { + startIndex: openQuoteNode.endIndex, + startPosition: openQuoteNode.endPosition, + endIndex: interpolationNode1.startIndex, + endPosition: interpolationNode1.startPosition, + }, + { + startIndex: interpolationNode1.endIndex, + startPosition: interpolationNode1.endPosition, + endIndex: interpolationNode2.startIndex, + endPosition: interpolationNode2.startPosition, + }, + { + startIndex: interpolationNode2.endIndex, + startPosition: interpolationNode2.endPosition, + endIndex: closeQuoteNode.startIndex, + endPosition: closeQuoteNode.startPosition, + }, + ]; + + const htmlTree = parser.parse(sourceCode, null, { includedRanges: htmlRanges }); + + expect(htmlTree.rootNode.toString()).toBe( + '(document (element' + + ' (start_tag (tag_name))' + + ' (text)' + + ' (element (start_tag (tag_name)) (end_tag (tag_name)))' + + ' (text)' + + ' (end_tag (tag_name))))' + ); + expect(htmlTree.getIncludedRanges()).toEqual(htmlRanges); + + const divElementNode = htmlTree.rootNode.child(0)!; + const helloTextNode = divElementNode.child(1)!; + const bElementNode = divElementNode.child(2)!; + const bStartTagNode = bElementNode.child(0)!; + const bEndTagNode = bElementNode.child(1)!; + + expect(helloTextNode.type).toBe('text'); + expect(helloTextNode.startIndex).toBe(sourceCode.indexOf('Hello')); + expect(helloTextNode.endIndex).toBe(sourceCode.indexOf(' ')); + + expect(bStartTagNode.type).toBe('start_tag'); + expect(bStartTagNode.startIndex).toBe(sourceCode.indexOf('')); + expect(bStartTagNode.endIndex).toBe(sourceCode.indexOf('${now()}')); + + expect(bEndTagNode.type).toBe('end_tag'); + expect(bEndTagNode.startIndex).toBe(sourceCode.indexOf('')); + expect(bEndTagNode.endIndex).toBe(sourceCode.indexOf('.')); + }); + }); + + describe('an included range containing mismatched positions', () => { + it('parses the text within the range', () => { + const sourceCode = '
test
{_ignore_this_part_}'; + + parser.setLanguage(HTML); + + const endIndex = sourceCode.indexOf('{_ignore_this_part_'); + + const rangeToParse = { + startIndex: 0, + startPosition: { row: 10, column: 12 }, + endIndex, + endPosition: { row: 10, column: 12 + endIndex }, + }; + + const htmlTree = parser.parse(sourceCode, null, { includedRanges: [rangeToParse] }); + + expect(htmlTree.getIncludedRanges()[0]).toEqual(rangeToParse); + + expect(htmlTree.rootNode.toString()).toBe( + '(document (element (start_tag (tag_name)) (text) (end_tag (tag_name))))' + ); + }); + }); + + describe('.parse', () => { + let tree: TSParser.Tree | null; + + beforeEach(() => { + tree = null; + parser.setLanguage(JavaScript); + }); + + afterEach(() => { + if (tree) tree.delete(); + }); + + it('reads from the given input', () => { + const parts = ['first', '_', 'second', '_', 'third']; + tree = parser.parse(() => parts.shift()); + expect(tree.rootNode.toString()).toBe('(program (expression_statement (identifier)))'); + }); + + it('stops reading when the input callback returns something that\'s not a string', () => { + const parts = ['abc', 'def', 'ghi', {}, {}, {}, 'second-word', ' ']; + tree = parser.parse(() => parts.shift() as string); + expect(tree.rootNode.toString()).toBe('(program (expression_statement (identifier)))'); + expect(tree.rootNode.endIndex).toBe(9); + expect(parts).toHaveLength(2); + }); + + it('throws an exception when the given input is not a function', () => { + expect(() => parser.parse(null as any)).toThrow('Argument must be a string or a function'); + expect(() => parser.parse(5 as any)).toThrow('Argument must be a string or a function'); + expect(() => parser.parse({} as any)).toThrow('Argument must be a string or a function'); + }); + + it('handles long input strings', { timeout: 5000 }, () => { + const repeatCount = 10000; + const inputString = `[${Array(repeatCount).fill('0').join(',')}]`; + + tree = parser.parse(inputString); + expect(tree.rootNode.type).toBe('program'); + expect(tree.rootNode.firstChild!.firstChild!.namedChildCount).toBe(repeatCount); + }); + + it('can use the bash parser', async () => { + parser.setLanguage(await Parser.Language.load(languageURL('bash'))); + tree = parser.parse('FOO=bar echo < err.txt > hello.txt \nhello${FOO}\nEOF'); + expect(tree.rootNode.toString()).toBe( + '(program ' + + '(redirected_statement ' + + 'body: (command ' + + '(variable_assignment name: (variable_name) value: (word)) ' + + 'name: (command_name (word))) ' + + 'redirect: (heredoc_redirect (heredoc_start) ' + + 'redirect: (file_redirect descriptor: (file_descriptor) destination: (word)) ' + + 'redirect: (file_redirect destination: (word)) ' + + '(heredoc_body ' + + '(expansion (variable_name)) (heredoc_content)) (heredoc_end))))' + ); + }, { timeout: 5000 }); + + it('can use the c++ parser', { timeout: 5000 }, async () => { + parser.setLanguage(await Parser.Language.load(languageURL('cpp'))); + tree = parser.parse('const char *s = R"EOF(HELLO WORLD)EOF";'); + expect(tree.rootNode.toString()).toBe( + '(translation_unit (declaration ' + + '(type_qualifier) ' + + 'type: (primitive_type) ' + + 'declarator: (init_declarator ' + + 'declarator: (pointer_declarator declarator: (identifier)) ' + + 'value: (raw_string_literal delimiter: (raw_string_delimiter) (raw_string_content) (raw_string_delimiter)))))' + ); + }); + + it('can use the HTML parser', { timeout: 5000 }, async () => { + parser.setLanguage(await Parser.Language.load(languageURL('html'))); + tree = parser.parse('
'); + expect(tree.rootNode.toString()).toBe( + '(document (element (start_tag (tag_name)) (element (start_tag (tag_name)) ' + + '(element (start_tag (tag_name)) (end_tag (tag_name))) (end_tag (tag_name))) (end_tag (tag_name))))' + ); + }); + + it('can use the python parser', { timeout: 5000 }, async () => { + parser.setLanguage(await Parser.Language.load(languageURL('python'))); + tree = parser.parse('class A:\n def b():\n c()'); + expect(tree.rootNode.toString()).toBe( + '(module (class_definition ' + + 'name: (identifier) ' + + 'body: (block ' + + '(function_definition ' + + 'name: (identifier) ' + + 'parameters: (parameters) ' + + 'body: (block (expression_statement (call ' + + 'function: (identifier) ' + + 'arguments: (argument_list))))))))' + ); + }); + + it('can use the rust parser', { timeout: 5000 }, async () => { + parser.setLanguage(await Parser.Language.load(languageURL('rust'))); + tree = parser.parse('const x: &\'static str = r###"hello"###;'); + expect(tree.rootNode.toString()).toBe( + '(source_file (const_item ' + + 'name: (identifier) ' + + 'type: (reference_type (lifetime (identifier)) type: (primitive_type)) ' + + 'value: (raw_string_literal (string_content))))' + ); + }); + + it('can use the typescript parser', { timeout: 5000 }, async () => { + parser.setLanguage(await Parser.Language.load(languageURL('typescript'))); + tree = parser.parse('a()\nb()\n[c]'); + expect(tree.rootNode.toString()).toBe( + '(program ' + + '(expression_statement (call_expression function: (identifier) arguments: (arguments))) ' + + '(expression_statement (subscript_expression ' + + 'object: (call_expression ' + + 'function: (identifier) ' + + 'arguments: (arguments)) ' + + 'index: (identifier))))' + ); + }); + + it('can use the tsx parser', { timeout: 5000 }, async () => { + parser.setLanguage(await Parser.Language.load(languageURL('tsx'))); + tree = parser.parse('a()\nb()\n[c]'); + expect(tree.rootNode.toString()).toBe( + '(program ' + + '(expression_statement (call_expression function: (identifier) arguments: (arguments))) ' + + '(expression_statement (subscript_expression ' + + 'object: (call_expression ' + + 'function: (identifier) ' + + 'arguments: (arguments)) ' + + 'index: (identifier))))', + + ); + }); + + it('parses only the text within the `includedRanges` if they are specified', () => { + const sourceCode = '<% foo() %> <% bar %>'; + + const start1 = sourceCode.indexOf('foo'); + const end1 = start1 + 5; + const start2 = sourceCode.indexOf('bar'); + const end2 = start2 + 3; + + const tree = parser.parse(sourceCode, null, { + includedRanges: [ + { + startIndex: start1, + endIndex: end1, + startPosition: { row: 0, column: start1 }, + endPosition: { row: 0, column: end1 }, + }, + { + startIndex: start2, + endIndex: end2, + startPosition: { row: 0, column: start2 }, + endPosition: { row: 0, column: end2 }, + }, + ], + }); + + expect(tree.rootNode.toString()).toBe( + '(program ' + + '(expression_statement (call_expression function: (identifier) arguments: (arguments))) ' + + '(expression_statement (identifier)))' + ); + }); + + it('parses with a timeout', { timeout: 5000 }, () => { + parser.setLanguage(JSON); + + const startTime = performance.now(); + let currentByteOffset = 0; + const progressCallback = (state: TSParser.State) => { + expect(state.currentOffset).toBeGreaterThanOrEqual(currentByteOffset); + currentByteOffset = state.currentOffset; + + if (performance.now() - startTime > 1) { + return true; + } + return false; + }; + + expect(() => parser.parse( + (offset, _) => offset === 0 ? '[' : ',0', + null, + { progressCallback }, + ) + ).toThrowError(); + }); + }); +}); diff --git a/lib/binding_web/test/query-test.js b/lib/binding_web/test/query.test.ts similarity index 61% rename from lib/binding_web/test/query-test.js rename to lib/binding_web/test/query.test.ts index 1a3e3a44..50195ff5 100644 --- a/lib/binding_web/test/query-test.js +++ b/lib/binding_web/test/query.test.ts @@ -1,13 +1,22 @@ -const {assert} = require('chai'); -let Parser; let JavaScript; +import { describe, it, expect, beforeAll, beforeEach, afterEach } from 'vitest'; +import TSParser, { type Language, type Tree, type Query, type QueryCapture, type QueryMatch } from 'web-tree-sitter'; +import helper from './helper'; + +let Parser: typeof TSParser; +let JavaScript: Language; describe('Query', () => { - let parser; let tree; let query; + let parser: TSParser; + let tree: Tree | null; + let query: Query | null; - before(async () => ({Parser, JavaScript} = await require('./helper'))); + beforeAll(async () => { + ({ Parser, JavaScript } = await helper); + }); beforeEach(() => { - parser = new Parser().setLanguage(JavaScript); + parser = new Parser(); + parser.setLanguage(JavaScript); }); afterEach(() => { @@ -18,36 +27,39 @@ describe('Query', () => { describe('construction', () => { it('throws an error on invalid patterns', () => { - assert.throws(() => { + expect(() => { JavaScript.query('(function_declaration wat)'); - }, 'Bad syntax at offset 22: \'wat)\'...'); - assert.throws(() => { + }).toThrow('Bad syntax at offset 22: \'wat)\'...'); + + expect(() => { JavaScript.query('(non_existent)'); - }, 'Bad node name \'non_existent\''); - assert.throws(() => { + }).toThrow('Bad node name \'non_existent\''); + + expect(() => { JavaScript.query('(a)'); - }, 'Bad node name \'a\''); - assert.throws(() => { + }).toThrow('Bad node name \'a\''); + + expect(() => { JavaScript.query('(function_declaration non_existent:(identifier))'); - }, 'Bad field name \'non_existent\''); - assert.throws(() => { + }).toThrow('Bad field name \'non_existent\''); + + expect(() => { JavaScript.query('(function_declaration name:(statement_block))'); - }, 'Bad pattern structure at offset 22: \'name:(statement_block))\''); + }).toThrow('Bad pattern structure at offset 22: \'name:(statement_block))\''); }); it('throws an error on invalid predicates', () => { - assert.throws(() => { + expect(() => { JavaScript.query('((identifier) @abc (#eq? @ab hi))'); - }, 'Bad capture name @ab'); - assert.throws(() => { - JavaScript.query('((identifier) @abc (#eq? @ab hi))'); - }, 'Bad capture name @ab'); - assert.throws(() => { + }).toThrow('Bad capture name @ab'); + + expect(() => { JavaScript.query('((identifier) @abc (#eq?))'); - }, 'Wrong number of arguments to `#eq?` predicate. Expected 2, got 0'); - assert.throws(() => { + }).toThrow('Wrong number of arguments to `#eq?` predicate. Expected 2, got 0'); + + expect(() => { JavaScript.query('((identifier) @a (#eq? @a @a @a))'); - }, 'Wrong number of arguments to `#eq?` predicate. Expected 2, got 3'); + }).toThrow('Wrong number of arguments to `#eq?` predicate. Expected 2, got 3'); }); }); @@ -59,28 +71,28 @@ describe('Query', () => { (call_expression function: (identifier) @fn-ref) `); const matches = query.matches(tree.rootNode); - assert.deepEqual(formatMatches(matches), [ - {pattern: 0, captures: [{name: 'fn-def', text: 'one'}]}, - {pattern: 1, captures: [{name: 'fn-ref', text: 'two'}]}, - {pattern: 0, captures: [{name: 'fn-def', text: 'three'}]}, + expect(formatMatches(matches)).toEqual([ + { pattern: 0, captures: [{ name: 'fn-def', text: 'one' }] }, + { pattern: 1, captures: [{ name: 'fn-ref', text: 'two' }] }, + { pattern: 0, captures: [{ name: 'fn-def', text: 'three' }] }, ]); }); - it('can search in a specified ranges', () => { + it('can search in specified ranges', () => { tree = parser.parse('[a, b,\nc, d,\ne, f,\ng, h]'); query = JavaScript.query('(identifier) @element'); const matches = query.matches( tree.rootNode, { - startPosition: {row: 1, column: 1}, - endPosition: {row: 3, column: 1}, - }, + startPosition: { row: 1, column: 1 }, + endPosition: { row: 3, column: 1 }, + } ); - assert.deepEqual(formatMatches(matches), [ - {pattern: 0, captures: [{name: 'element', text: 'd'}]}, - {pattern: 0, captures: [{name: 'element', text: 'e'}]}, - {pattern: 0, captures: [{name: 'element', text: 'f'}]}, - {pattern: 0, captures: [{name: 'element', text: 'g'}]}, + expect(formatMatches(matches)).toEqual([ + { pattern: 0, captures: [{ name: 'element', text: 'd' }] }, + { pattern: 0, captures: [{ name: 'element', text: 'e' }] }, + { pattern: 0, captures: [{ name: 'element', text: 'f' }] }, + { pattern: 0, captures: [{ name: 'element', text: 'g' }] }, ]); }); @@ -104,9 +116,9 @@ describe('Query', () => { `); const matches = query.matches(tree.rootNode); - assert.deepEqual(formatMatches(matches), [ - {pattern: 0, captures: [{name: 'name', text: 'giraffe'}]}, - {pattern: 0, captures: [{name: 'name', text: 'gross'}]}, + expect(formatMatches(matches)).toEqual([ + { pattern: 0, captures: [{ name: 'name', text: 'giraffe' }] }, + { pattern: 0, captures: [{ name: 'name', text: 'gross' }] }, ]); }); @@ -122,8 +134,8 @@ describe('Query', () => { `); const matches = query.matches(tree.rootNode); - assert.deepEqual(formatMatches(matches), [ - {pattern: 0, captures: [{name: 'variable.builtin', text: 'window'}]}, + expect(formatMatches(matches)).toEqual([ + { pattern: 0, captures: [{ name: 'variable.builtin', text: 'window' }] }, ]); }); }); @@ -156,19 +168,19 @@ describe('Query', () => { `); const captures = query.captures(tree.rootNode); - assert.deepEqual(formatCaptures(captures), [ - {name: 'method.def', text: 'bc'}, - {name: 'delimiter', text: ':'}, - {name: 'method.alias', text: 'de'}, - {name: 'function.def', text: 'fg'}, - {name: 'operator', text: '='}, - {name: 'function.alias', text: 'hi'}, - {name: 'method.def', text: 'jk'}, - {name: 'delimiter', text: ':'}, - {name: 'method.alias', text: 'lm'}, - {name: 'function.def', text: 'no'}, - {name: 'operator', text: '='}, - {name: 'function.alias', text: 'pq'}, + expect(formatCaptures(captures)).toEqual([ + { name: 'method.def', text: 'bc' }, + { name: 'delimiter', text: ':' }, + { name: 'method.alias', text: 'de' }, + { name: 'function.def', text: 'fg' }, + { name: 'operator', text: '=' }, + { name: 'function.alias', text: 'hi' }, + { name: 'method.def', text: 'jk' }, + { name: 'delimiter', text: ':' }, + { name: 'method.alias', text: 'lm' }, + { name: 'function.def', text: 'no' }, + { name: 'operator', text: '=' }, + { name: 'function.alias', text: 'pq' }, ]); }); @@ -197,21 +209,21 @@ describe('Query', () => { `); const captures = query.captures(tree.rootNode); - assert.deepEqual(formatCaptures(captures), [ - {name: 'variable', text: 'panda'}, - {name: 'variable', text: 'toad'}, - {name: 'variable', text: 'ab'}, - {name: 'variable', text: 'require'}, - {name: 'function.builtin', text: 'require'}, - {name: 'variable', text: 'Cd'}, - {name: 'constructor', text: 'Cd'}, - {name: 'variable', text: 'EF'}, - {name: 'constructor', text: 'EF'}, - {name: 'constant', text: 'EF'}, + expect(formatCaptures(captures)).toEqual([ + { name: 'variable', text: 'panda' }, + { name: 'variable', text: 'toad' }, + { name: 'variable', text: 'ab' }, + { name: 'variable', text: 'require' }, + { name: 'function.builtin', text: 'require' }, + { name: 'variable', text: 'Cd' }, + { name: 'constructor', text: 'Cd' }, + { name: 'variable', text: 'EF' }, + { name: 'constructor', text: 'EF' }, + { name: 'constant', text: 'EF' }, ]); }); - it('handles conditions that compare the text of capture to each other', () => { + it('handles conditions that compare the text of captures to each other', () => { tree = parser.parse(` ab = abc + 1; def = de + 1; @@ -229,9 +241,9 @@ describe('Query', () => { `); const captures = query.captures(tree.rootNode); - assert.deepEqual(formatCaptures(captures), [ - {name: 'id1', text: 'ghi'}, - {name: 'id2', text: 'ghi'}, + expect(formatCaptures(captures)).toEqual([ + { name: 'id1', text: 'ghi' }, + { name: 'id2', text: 'ghi' }, ]); }); @@ -248,16 +260,20 @@ describe('Query', () => { `); const captures = query.captures(tree.rootNode); - assert.deepEqual(formatCaptures(captures), [ - {name: 'func', text: 'a', setProperties: {foo: null, bar: 'baz'}}, + expect(formatCaptures(captures)).toEqual([ + { + name: 'func', + text: 'a', + setProperties: { foo: null, bar: 'baz' } + }, { name: 'prop', text: 'c', - assertedProperties: {foo: null}, - refutedProperties: {bar: 'baz'}, + assertedProperties: { foo: null }, + refutedProperties: { bar: 'baz' }, }, ]); - assert.ok(!query.didExceedMatchLimit()); + expect(query.didExceedMatchLimit()).toBe(false); }); it('detects queries with too many permutations to track', () => { @@ -275,90 +291,81 @@ describe('Query', () => { (array (identifier) @pre (identifier) @post) `); - query.captures(tree.rootNode, {matchLimit: 32}); - assert.ok(query.didExceedMatchLimit()); + query.captures(tree.rootNode, { matchLimit: 32 }); + expect(query.didExceedMatchLimit()).toBe(true); }); it('handles quantified captures properly', () => { - let captures; - tree = parser.parse(` /// foo /// bar /// baz `); - query = JavaScript.query(` - ( - (comment)+ @foo - (#any-eq? @foo "/// foo") - ) - `); - - const expectCount = (tree, queryText, expectedCount) => { + const expectCount = (tree: Tree, queryText: string, expectedCount: number) => { query = JavaScript.query(queryText); - captures = query.captures(tree.rootNode); - assert.equal(captures.length, expectedCount); + const captures = query.captures(tree.rootNode); + expect(captures).toHaveLength(expectedCount); }; expectCount( tree, `((comment)+ @foo (#any-eq? @foo "/// foo"))`, - 3, + 3 ); expectCount( tree, `((comment)+ @foo (#eq? @foo "/// foo"))`, - 0, + 0 ); expectCount( tree, `((comment)+ @foo (#any-not-eq? @foo "/// foo"))`, - 3, + 3 ); expectCount( tree, `((comment)+ @foo (#not-eq? @foo "/// foo"))`, - 0, + 0 ); expectCount( tree, `((comment)+ @foo (#match? @foo "^/// foo"))`, - 0, + 0 ); expectCount( tree, `((comment)+ @foo (#any-match? @foo "^/// foo"))`, - 3, + 3 ); expectCount( tree, `((comment)+ @foo (#not-match? @foo "^/// foo"))`, - 0, + 0 ); expectCount( tree, `((comment)+ @foo (#not-match? @foo "fsdfsdafdfs"))`, - 3, + 3 ); expectCount( tree, `((comment)+ @foo (#any-not-match? @foo "^///"))`, - 0, + 0 ); expectCount( tree, `((comment)+ @foo (#any-not-match? @foo "^/// foo"))`, - 3, + 3 ); }); }); @@ -381,37 +388,39 @@ describe('Query', () => { "if" @d `); - assert.deepEqual(query.predicatesForPattern(0), [ + expect(query.predicatesForPattern(0)).toStrictEqual([ { operator: 'something?', operands: [ - {type: 'capture', name: 'a'}, - {type: 'capture', name: 'b'}, + { type: 'capture', name: 'a' }, + { type: 'capture', name: 'b' }, ], }, { operator: 'something-else?', operands: [ - {type: 'capture', name: 'a'}, - {type: 'string', value: 'A'}, - {type: 'capture', name: 'b'}, - {type: 'string', value: 'B'}, + { type: 'capture', name: 'a' }, + { type: 'string', value: 'A' }, + { type: 'capture', name: 'b' }, + { type: 'string', value: 'B' }, ], }, ]); - assert.deepEqual(query.predicatesForPattern(1), [ + + expect(query.predicatesForPattern(1)).toStrictEqual([ { operator: 'hello!', - operands: [{type: 'capture', name: 'c'}], + operands: [{ type: 'capture', name: 'c' }], }, ]); - assert.deepEqual(query.predicatesForPattern(2), []); + + expect(query.predicatesForPattern(2)).toEqual([]); }); }); describe('.disableCapture', () => { it('disables a capture', () => { - const query = JavaScript.query(` + query = JavaScript.query(` (function_declaration (identifier) @name1 @name2 @name3 (statement_block) @body1 @body2) @@ -421,15 +430,15 @@ describe('Query', () => { const tree = parser.parse(source); let matches = query.matches(tree.rootNode); - assert.deepEqual(formatMatches(matches), [ + expect(formatMatches(matches)).toEqual([ { pattern: 0, captures: [ - {name: 'name1', text: 'foo'}, - {name: 'name2', text: 'foo'}, - {name: 'name3', text: 'foo'}, - {name: 'body1', text: '{ return 1; }'}, - {name: 'body2', text: '{ return 1; }'}, + { name: 'name1', text: 'foo' }, + { name: 'name2', text: 'foo' }, + { name: 'name3', text: 'foo' }, + { name: 'body1', text: '{ return 1; }' }, + { name: 'body2', text: '{ return 1; }' }, ], }, ]); @@ -438,31 +447,32 @@ describe('Query', () => { // single node. query.disableCapture('name2'); matches = query.matches(tree.rootNode); - assert.deepEqual(formatMatches(matches), [ + expect(formatMatches(matches)).toEqual([ { pattern: 0, captures: [ - {name: 'name1', text: 'foo'}, - {name: 'name3', text: 'foo'}, - {name: 'body1', text: '{ return 1; }'}, - {name: 'body2', text: '{ return 1; }'}, + { name: 'name1', text: 'foo' }, + { name: 'name3', text: 'foo' }, + { name: 'body1', text: '{ return 1; }' }, + { name: 'body2', text: '{ return 1; }' }, ], }, ]); }); }); - describe('Set a timeout', () => + describe('Set a timeout', () => { it('returns less than the expected matches', () => { tree = parser.parse('function foo() while (true) { } }\n'.repeat(1000)); query = JavaScript.query( - '(function_declaration name: (identifier) @function)', + '(function_declaration name: (identifier) @function)' ); - const matches = query.matches(tree.rootNode, {timeoutMicros: 1000}); - assert.isBelow(matches.length, 1000); - const matches2 = query.matches(tree.rootNode, {timeoutMicros: 0}); - assert.equal(matches2.length, 1000); - })); + const matches = query.matches(tree.rootNode, { timeoutMicros: 1000 }); + expect(matches.length).toBeLessThan(1000); + const matches2 = query.matches(tree.rootNode, { timeoutMicros: 0 }); + expect(matches2).toHaveLength(1000); + }); + }); describe('Start and end indices for patterns', () => { it('Returns the start and end indices for a pattern', () => { @@ -489,22 +499,17 @@ describe('Query', () => { const query = JavaScript.query(source); - assert.equal(query.startIndexForPattern(0), 0); - assert.equal(query.endIndexForPattern(0), '"+" @operator\n'.length); - assert.equal(query.startIndexForPattern(5), patterns1.length); - assert.equal( - query.endIndexForPattern(5), - patterns1.length + '(identifier) @a\n'.length, + expect(query.startIndexForPattern(0)).toBe(0); + expect(query.endIndexForPattern(0)).toBe('"+" @operator\n'.length); + expect(query.startIndexForPattern(5)).toBe(patterns1.length); + expect(query.endIndexForPattern(5)).toBe( + patterns1.length + '(identifier) @a\n'.length ); - assert.equal( - query.startIndexForPattern(7), - patterns1.length + patterns2.length, - ); - assert.equal( - query.endIndexForPattern(7), + expect(query.startIndexForPattern(7)).toBe(patterns1.length + patterns2.length); + expect(query.endIndexForPattern(7)).toBe( patterns1.length + - patterns2.length + - '((identifier) @b (#match? @b i))\n'.length, + patterns2.length + + '((identifier) @b (#match? @b i))\n'.length ); }); }); @@ -525,12 +530,12 @@ describe('Query', () => { const source = 'class A { constructor() {} } function b() { return 1; }'; tree = parser.parse(source); const matches = query.matches(tree.rootNode); - assert.deepEqual(formatMatches(matches), [ + expect(formatMatches(matches)).toEqual([ { pattern: 3, - captures: [{name: 'body', text: '{ constructor() {} }'}], + captures: [{ name: 'body', text: '{ constructor() {} }' }], }, - {pattern: 1, captures: [{name: 'body', text: '{ return 1; }'}]}, + { pattern: 1, captures: [{ name: 'body', text: '{ return 1; }' }] }, ]); }); }); @@ -539,7 +544,7 @@ describe('Query', () => { it('Returns less than the expected matches', () => { tree = parser.parse('function foo() while (true) { } }\n'.repeat(1000)); query = JavaScript.query( - '(function_declaration) @function', + '(function_declaration) @function' ); const startTime = performance.now(); @@ -553,24 +558,25 @@ describe('Query', () => { } return false; }, - }, + } ); - assert.isBelow(matches.length, 1000); + expect(matches.length).toBeLessThan(1000); const matches2 = query.matches(tree.rootNode); - assert.equal(matches2.length, 1000); + expect(matches2).toHaveLength(1000); }); }); }); -function formatMatches(matches) { - return matches.map(({pattern, captures}) => ({ +// Helper functions +function formatMatches(matches: any[]): QueryMatch[] { + return matches.map(({ pattern, captures }) => ({ pattern, captures: formatCaptures(captures), })); } -function formatCaptures(captures) { +function formatCaptures(captures: any[]): QueryCapture[] { return captures.map((c) => { const node = c.node; delete c.node; diff --git a/lib/binding_web/test/tree-test.js b/lib/binding_web/test/tree-test.js deleted file mode 100644 index a79da588..00000000 --- a/lib/binding_web/test/tree-test.js +++ /dev/null @@ -1,426 +0,0 @@ -const {assert} = require('chai'); -let Parser; let JavaScript; - -describe('Tree', () => { - let parser; let tree; - - before(async () => - ({Parser, JavaScript} = await require('./helper')), - ); - - beforeEach(() => { - parser = new Parser().setLanguage(JavaScript); - }); - - afterEach(() => { - parser.delete(); - tree.delete(); - }); - - describe('.edit', () => { - let input; let edit; - - it('updates the positions of nodes', () => { - input = 'abc + cde'; - tree = parser.parse(input); - assert.equal( - tree.rootNode.toString(), - '(program (expression_statement (binary_expression left: (identifier) right: (identifier))))', - ); - - let sumNode = tree.rootNode.firstChild.firstChild; - let variableNode1 = sumNode.firstChild; - let variableNode2 = sumNode.lastChild; - assert.equal(variableNode1.startIndex, 0); - assert.equal(variableNode1.endIndex, 3); - assert.equal(variableNode2.startIndex, 6); - assert.equal(variableNode2.endIndex, 9); - - ([input, edit] = spliceInput(input, input.indexOf('bc'), 0, ' * ')); - assert.equal(input, 'a * bc + cde'); - tree.edit(edit); - - sumNode = tree.rootNode.firstChild.firstChild; - variableNode1 = sumNode.firstChild; - variableNode2 = sumNode.lastChild; - assert.equal(variableNode1.startIndex, 0); - assert.equal(variableNode1.endIndex, 6); - assert.equal(variableNode2.startIndex, 9); - assert.equal(variableNode2.endIndex, 12); - - tree = parser.parse(input, tree); - assert.equal( - tree.rootNode.toString(), - '(program (expression_statement (binary_expression left: (binary_expression left: (identifier) right: (identifier)) right: (identifier))))', - ); - }); - - it('handles non-ascii characters', () => { - input = 'αβδ + cde'; - - tree = parser.parse(input); - assert.equal( - tree.rootNode.toString(), - '(program (expression_statement (binary_expression left: (identifier) right: (identifier))))', - ); - - let variableNode = tree.rootNode.firstChild.firstChild.lastChild; - - ([input, edit] = spliceInput(input, input.indexOf('δ'), 0, '👍 * ')); - assert.equal(input, 'αβ👍 * δ + cde'); - tree.edit(edit); - - variableNode = tree.rootNode.firstChild.firstChild.lastChild; - assert.equal(variableNode.startIndex, input.indexOf('cde')); - - tree = parser.parse(input, tree); - assert.equal( - tree.rootNode.toString(), - '(program (expression_statement (binary_expression left: (binary_expression left: (identifier) right: (identifier)) right: (identifier))))', - ); - }); - }); - - describe('.getChangedRanges(previous)', () => { - it('reports the ranges of text whose syntactic meaning has changed', () => { - let sourceCode = 'abcdefg + hij'; - tree = parser.parse(sourceCode); - - assert.equal( - tree.rootNode.toString(), - '(program (expression_statement (binary_expression left: (identifier) right: (identifier))))', - ); - - sourceCode = 'abc + defg + hij'; - tree.edit({ - startIndex: 2, - oldEndIndex: 2, - newEndIndex: 5, - startPosition: {row: 0, column: 2}, - oldEndPosition: {row: 0, column: 2}, - newEndPosition: {row: 0, column: 5}, - }); - - const tree2 = parser.parse(sourceCode, tree); - assert.equal( - tree2.rootNode.toString(), - '(program (expression_statement (binary_expression left: (binary_expression left: (identifier) right: (identifier)) right: (identifier))))', - ); - - const ranges = tree.getChangedRanges(tree2); - assert.deepEqual(ranges, [ - { - startIndex: 0, - endIndex: 'abc + defg'.length, - startPosition: {row: 0, column: 0}, - endPosition: {row: 0, column: 'abc + defg'.length}, - }, - ]); - - tree2.delete(); - }); - - it('throws an exception if the argument is not a tree', () => { - tree = parser.parse('abcdefg + hij'); - - assert.throws(() => { - tree.getChangedRanges({}); - }, /Argument must be a Tree/); - }); - }); - - describe('.walk()', () => { - let cursor; - - afterEach(() => { - cursor.delete(); - }); - - it('returns a cursor that can be used to walk the tree', () => { - tree = parser.parse('a * b + c / d'); - cursor = tree.walk(); - - assertCursorState(cursor, { - nodeType: 'program', - nodeIsNamed: true, - startPosition: {row: 0, column: 0}, - endPosition: {row: 0, column: 13}, - startIndex: 0, - endIndex: 13, - }); - - assert(cursor.gotoFirstChild()); - assertCursorState(cursor, { - nodeType: 'expression_statement', - nodeIsNamed: true, - startPosition: {row: 0, column: 0}, - endPosition: {row: 0, column: 13}, - startIndex: 0, - endIndex: 13, - }); - - assert(cursor.gotoFirstChild()); - assertCursorState(cursor, { - nodeType: 'binary_expression', - nodeIsNamed: true, - startPosition: {row: 0, column: 0}, - endPosition: {row: 0, column: 13}, - startIndex: 0, - endIndex: 13, - }); - - assert(cursor.gotoFirstChild()); - assertCursorState(cursor, { - nodeType: 'binary_expression', - nodeIsNamed: true, - startPosition: {row: 0, column: 0}, - endPosition: {row: 0, column: 5}, - startIndex: 0, - endIndex: 5, - }); - - assert(cursor.gotoFirstChild()); - assert.equal(cursor.nodeText, 'a'); - assertCursorState(cursor, { - nodeType: 'identifier', - nodeIsNamed: true, - startPosition: {row: 0, column: 0}, - endPosition: {row: 0, column: 1}, - startIndex: 0, - endIndex: 1, - }); - - assert(!cursor.gotoFirstChild()); - assert(cursor.gotoNextSibling()); - assert.equal(cursor.nodeText, '*'); - assertCursorState(cursor, { - nodeType: '*', - nodeIsNamed: false, - startPosition: {row: 0, column: 2}, - endPosition: {row: 0, column: 3}, - startIndex: 2, - endIndex: 3, - }); - - assert(cursor.gotoNextSibling()); - assert.equal(cursor.nodeText, 'b'); - assertCursorState(cursor, { - nodeType: 'identifier', - nodeIsNamed: true, - startPosition: {row: 0, column: 4}, - endPosition: {row: 0, column: 5}, - startIndex: 4, - endIndex: 5, - }); - - assert(!cursor.gotoNextSibling()); - assert(cursor.gotoParent()); - assertCursorState(cursor, { - nodeType: 'binary_expression', - nodeIsNamed: true, - startPosition: {row: 0, column: 0}, - endPosition: {row: 0, column: 5}, - startIndex: 0, - endIndex: 5, - }); - - assert(cursor.gotoNextSibling()); - assertCursorState(cursor, { - nodeType: '+', - nodeIsNamed: false, - startPosition: {row: 0, column: 6}, - endPosition: {row: 0, column: 7}, - startIndex: 6, - endIndex: 7, - }); - - assert(cursor.gotoNextSibling()); - assertCursorState(cursor, { - nodeType: 'binary_expression', - nodeIsNamed: true, - startPosition: {row: 0, column: 8}, - endPosition: {row: 0, column: 13}, - startIndex: 8, - endIndex: 13, - }); - - const copy = tree.walk(); - copy.resetTo(cursor); - - assert(copy.gotoPreviousSibling()); - assertCursorState(copy, { - nodeType: '+', - nodeIsNamed: false, - startPosition: {row: 0, column: 6}, - endPosition: {row: 0, column: 7}, - startIndex: 6, - endIndex: 7, - }); - - assert(copy.gotoPreviousSibling()); - assertCursorState(copy, { - nodeType: 'binary_expression', - nodeIsNamed: true, - startPosition: {row: 0, column: 0}, - endPosition: {row: 0, column: 5}, - startIndex: 0, - endIndex: 5, - }); - - assert(copy.gotoLastChild()); - assertCursorState(copy, { - nodeType: 'identifier', - nodeIsNamed: true, - startPosition: {row: 0, column: 4}, - endPosition: {row: 0, column: 5}, - startIndex: 4, - endIndex: 5, - }); - - assert(copy.gotoParent()); - assert(copy.gotoParent()); - assert.equal(copy.nodeType, 'binary_expression'); - assert(copy.gotoParent()); - assert.equal(copy.nodeType, 'expression_statement'); - assert(copy.gotoParent()); - assert.equal(copy.nodeType, 'program'); - assert(!copy.gotoParent()); - - assert(cursor.gotoParent()); - assert.equal(cursor.nodeType, 'binary_expression'); - assert(cursor.gotoParent()); - assert.equal(cursor.nodeType, 'expression_statement'); - assert(cursor.gotoParent()); - assert.equal(cursor.nodeType, 'program'); - assert(!cursor.gotoParent()); - }); - - it('keeps track of the field name associated with each node', () => { - tree = parser.parse('a.b();'); - cursor = tree.walk(); - cursor.gotoFirstChild(); - cursor.gotoFirstChild(); - - assert.equal(cursor.currentNode.type, 'call_expression'); - assert.equal(cursor.currentFieldName, null); - - cursor.gotoFirstChild(); - assert.equal(cursor.currentNode.type, 'member_expression'); - assert.equal(cursor.currentFieldName, 'function'); - - cursor.gotoFirstChild(); - assert.equal(cursor.currentNode.type, 'identifier'); - assert.equal(cursor.currentFieldName, 'object'); - - cursor.gotoNextSibling(); - cursor.gotoNextSibling(); - assert.equal(cursor.currentNode.type, 'property_identifier'); - assert.equal(cursor.currentFieldName, 'property'); - - cursor.gotoParent(); - cursor.gotoNextSibling(); - assert.equal(cursor.currentNode.type, 'arguments'); - assert.equal(cursor.currentFieldName, 'arguments'); - }); - - it('returns a cursor that can be reset anywhere in the tree', () => { - tree = parser.parse('a * b + c / d'); - cursor = tree.walk(); - const root = tree.rootNode.firstChild; - - cursor.reset(root.firstChild.firstChild); - assertCursorState(cursor, { - nodeType: 'binary_expression', - nodeIsNamed: true, - startPosition: {row: 0, column: 0}, - endPosition: {row: 0, column: 5}, - startIndex: 0, - endIndex: 5, - }); - - cursor.gotoFirstChild(); - assertCursorState(cursor, { - nodeType: 'identifier', - nodeIsNamed: true, - startPosition: {row: 0, column: 0}, - endPosition: {row: 0, column: 1}, - startIndex: 0, - endIndex: 1, - }); - - assert(cursor.gotoParent()); - assert(!cursor.gotoParent()); - }); - }); - - describe('.copy', () => { - it('creates another tree that remains stable if the original tree is edited', () => { - input = 'abc + cde'; - tree = parser.parse(input); - assert.equal( - tree.rootNode.toString(), - '(program (expression_statement (binary_expression left: (identifier) right: (identifier))))', - ); - - const tree2 = tree.copy(); - [input, edit] = spliceInput(input, 3, 0, '123'); - assert.equal(input, 'abc123 + cde'); - tree.edit(edit); - - const leftNode = tree.rootNode.firstChild.firstChild.firstChild; - const leftNode2 = tree2.rootNode.firstChild.firstChild.firstChild; - const rightNode = tree.rootNode.firstChild.firstChild.lastChild; - const rightNode2 = tree2.rootNode.firstChild.firstChild.lastChild; - assert.equal(leftNode.endIndex, 6); - assert.equal(leftNode2.endIndex, 3); - assert.equal(rightNode.startIndex, 9); - assert.equal(rightNode2.startIndex, 6); - }); - }); -}); - -function spliceInput(input, startIndex, lengthRemoved, newText) { - const oldEndIndex = startIndex + lengthRemoved; - const newEndIndex = startIndex + newText.length; - const startPosition = getExtent(input.slice(0, startIndex)); - const oldEndPosition = getExtent(input.slice(0, oldEndIndex)); - input = input.slice(0, startIndex) + newText + input.slice(oldEndIndex); - const newEndPosition = getExtent(input.slice(0, newEndIndex)); - return [ - input, - { - startIndex, startPosition, - oldEndIndex, oldEndPosition, - newEndIndex, newEndPosition, - }, - ]; -} - -// Gets the extent of the text in terms of zero-based row and column numbers. -function getExtent(text) { - let row = 0; - let index = -1; - let lastIndex = 0; - while ((index = text.indexOf('\n', index + 1)) !== -1) { - row++; - lastIndex = index + 1; - } - return {row, column: text.length - lastIndex}; -} - -function assertCursorState(cursor, params) { - assert.equal(cursor.nodeType, params.nodeType); - assert.equal(cursor.nodeIsNamed, params.nodeIsNamed); - assert.deepEqual(cursor.startPosition, params.startPosition); - assert.deepEqual(cursor.endPosition, params.endPosition); - assert.deepEqual(cursor.startIndex, params.startIndex); - assert.deepEqual(cursor.endIndex, params.endIndex); - - const node = cursor.currentNode; - assert.equal(node.type, params.nodeType); - assert.equal(node.isNamed, params.nodeIsNamed); - assert.deepEqual(node.startPosition, params.startPosition); - assert.deepEqual(node.endPosition, params.endPosition); - assert.deepEqual(node.startIndex, params.startIndex); - assert.deepEqual(node.endIndex, params.endIndex); -} diff --git a/lib/binding_web/test/tree.test.ts b/lib/binding_web/test/tree.test.ts new file mode 100644 index 00000000..2f3568c8 --- /dev/null +++ b/lib/binding_web/test/tree.test.ts @@ -0,0 +1,443 @@ +import { describe, it, expect, beforeAll, beforeEach, afterEach } from 'vitest'; +import TSParser, { type Language, type Tree, type TreeCursor, type Edit, Point } from 'web-tree-sitter'; +import helper from './helper'; + +let Parser: typeof TSParser; +let JavaScript: Language; + +interface CursorState { + nodeType: string; + nodeIsNamed: boolean; + startPosition: Point; + endPosition: Point; + startIndex: number; + endIndex: number; +} + +describe('Tree', () => { + let parser: TSParser; + let tree: Tree; + + beforeAll(async () => { + ({ Parser, JavaScript } = await helper); + }); + + beforeEach(() => { + parser = new Parser(); + parser.setLanguage(JavaScript); + }); + + afterEach(() => { + parser.delete(); + tree.delete(); + }); + + describe('.edit', () => { + let input: string; + let edit: Edit; + + it('updates the positions of nodes', () => { + input = 'abc + cde'; + tree = parser.parse(input); + expect(tree.rootNode.toString()).toBe( + '(program (expression_statement (binary_expression left: (identifier) right: (identifier))))' + ); + + let sumNode = tree.rootNode.firstChild!.firstChild; + let variableNode1 = sumNode!.firstChild; + let variableNode2 = sumNode!.lastChild; + expect(variableNode1!.startIndex).toBe(0); + expect(variableNode1!.endIndex).toBe(3); + expect(variableNode2!.startIndex).toBe(6); + expect(variableNode2!.endIndex).toBe(9); + + [input, edit] = spliceInput(input, input.indexOf('bc'), 0, ' * '); + expect(input).toBe('a * bc + cde'); + tree.edit(edit); + + sumNode = tree.rootNode.firstChild!.firstChild; + variableNode1 = sumNode!.firstChild; + variableNode2 = sumNode!.lastChild; + expect(variableNode1!.startIndex).toBe(0); + expect(variableNode1!.endIndex).toBe(6); + expect(variableNode2!.startIndex).toBe(9); + expect(variableNode2!.endIndex).toBe(12); + + tree = parser.parse(input, tree); + expect(tree.rootNode.toString()).toBe( + '(program (expression_statement (binary_expression left: (binary_expression left: (identifier) right: (identifier)) right: (identifier))))' + ); + }); + + it('handles non-ascii characters', () => { + input = 'αβδ + cde'; + + tree = parser.parse(input); + expect(tree.rootNode.toString()).toBe( + '(program (expression_statement (binary_expression left: (identifier) right: (identifier))))' + ); + + let variableNode = tree.rootNode.firstChild!.firstChild!.lastChild; + + [input, edit] = spliceInput(input, input.indexOf('δ'), 0, '👍 * '); + expect(input).toBe('αβ👍 * δ + cde'); + tree.edit(edit); + + variableNode = tree.rootNode.firstChild!.firstChild!.lastChild; + expect(variableNode!.startIndex).toBe(input.indexOf('cde')); + + tree = parser.parse(input, tree); + expect(tree.rootNode.toString()).toBe( + '(program (expression_statement (binary_expression left: (binary_expression left: (identifier) right: (identifier)) right: (identifier))))' + ); + }); + }); + + describe('.getChangedRanges(previous)', () => { + it('reports the ranges of text whose syntactic meaning has changed', () => { + let sourceCode = 'abcdefg + hij'; + tree = parser.parse(sourceCode); + + expect(tree.rootNode.toString()).toBe( + '(program (expression_statement (binary_expression left: (identifier) right: (identifier))))' + ); + + sourceCode = 'abc + defg + hij'; + tree.edit({ + startIndex: 2, + oldEndIndex: 2, + newEndIndex: 5, + startPosition: { row: 0, column: 2 }, + oldEndPosition: { row: 0, column: 2 }, + newEndPosition: { row: 0, column: 5 }, + }); + + const tree2 = parser.parse(sourceCode, tree); + expect(tree2.rootNode.toString()).toBe( + '(program (expression_statement (binary_expression left: (binary_expression left: (identifier) right: (identifier)) right: (identifier))))' + ); + + const ranges = tree.getChangedRanges(tree2); + expect(ranges).toEqual([ + { + startIndex: 0, + endIndex: 'abc + defg'.length, + startPosition: { row: 0, column: 0 }, + endPosition: { row: 0, column: 'abc + defg'.length }, + }, + ]); + + tree2.delete(); + }); + + it('throws an exception if the argument is not a tree', () => { + tree = parser.parse('abcdefg + hij'); + + expect(() => { + tree.getChangedRanges({} as Tree); + }).toThrow(/Argument must be a Tree/); + }); + }); + + describe('.walk()', () => { + let cursor: TreeCursor; + + afterEach(() => { + cursor.delete(); + }); + + it('returns a cursor that can be used to walk the tree', () => { + tree = parser.parse('a * b + c / d'); + cursor = tree.walk(); + + assertCursorState(cursor, { + nodeType: 'program', + nodeIsNamed: true, + startPosition: { row: 0, column: 0 }, + endPosition: { row: 0, column: 13 }, + startIndex: 0, + endIndex: 13, + }); + + expect(cursor.gotoFirstChild()).toBe(true); + assertCursorState(cursor, { + nodeType: 'expression_statement', + nodeIsNamed: true, + startPosition: { row: 0, column: 0 }, + endPosition: { row: 0, column: 13 }, + startIndex: 0, + endIndex: 13, + }); + + expect(cursor.gotoFirstChild()).toBe(true); + assertCursorState(cursor, { + nodeType: 'binary_expression', + nodeIsNamed: true, + startPosition: { row: 0, column: 0 }, + endPosition: { row: 0, column: 13 }, + startIndex: 0, + endIndex: 13, + }); + + expect(cursor.gotoFirstChild()).toBe(true); + assertCursorState(cursor, { + nodeType: 'binary_expression', + nodeIsNamed: true, + startPosition: { row: 0, column: 0 }, + endPosition: { row: 0, column: 5 }, + startIndex: 0, + endIndex: 5, + }); + + expect(cursor.gotoFirstChild()).toBe(true); + expect(cursor.nodeText).toBe('a'); + assertCursorState(cursor, { + nodeType: 'identifier', + nodeIsNamed: true, + startPosition: { row: 0, column: 0 }, + endPosition: { row: 0, column: 1 }, + startIndex: 0, + endIndex: 1, + }); + + expect(cursor.gotoFirstChild()).toBe(false); + expect(cursor.gotoNextSibling()).toBe(true); + expect(cursor.nodeText).toBe('*'); + assertCursorState(cursor, { + nodeType: '*', + nodeIsNamed: false, + startPosition: { row: 0, column: 2 }, + endPosition: { row: 0, column: 3 }, + startIndex: 2, + endIndex: 3, + }); + + expect(cursor.gotoNextSibling()).toBe(true); + expect(cursor.nodeText).toBe('b'); + assertCursorState(cursor, { + nodeType: 'identifier', + nodeIsNamed: true, + startPosition: { row: 0, column: 4 }, + endPosition: { row: 0, column: 5 }, + startIndex: 4, + endIndex: 5, + }); + + expect(cursor.gotoNextSibling()).toBe(false); + expect(cursor.gotoParent()).toBe(true); + assertCursorState(cursor, { + nodeType: 'binary_expression', + nodeIsNamed: true, + startPosition: { row: 0, column: 0 }, + endPosition: { row: 0, column: 5 }, + startIndex: 0, + endIndex: 5, + }); + + expect(cursor.gotoNextSibling()).toBe(true); + assertCursorState(cursor, { + nodeType: '+', + nodeIsNamed: false, + startPosition: { row: 0, column: 6 }, + endPosition: { row: 0, column: 7 }, + startIndex: 6, + endIndex: 7, + }); + + expect(cursor.gotoNextSibling()).toBe(true); + assertCursorState(cursor, { + nodeType: 'binary_expression', + nodeIsNamed: true, + startPosition: { row: 0, column: 8 }, + endPosition: { row: 0, column: 13 }, + startIndex: 8, + endIndex: 13, + }); + + const copy = tree.walk(); + copy.resetTo(cursor); + + expect(copy.gotoPreviousSibling()).toBe(true); + assertCursorState(copy, { + nodeType: '+', + nodeIsNamed: false, + startPosition: { row: 0, column: 6 }, + endPosition: { row: 0, column: 7 }, + startIndex: 6, + endIndex: 7, + }); + + expect(copy.gotoPreviousSibling()).toBe(true); + assertCursorState(copy, { + nodeType: 'binary_expression', + nodeIsNamed: true, + startPosition: { row: 0, column: 0 }, + endPosition: { row: 0, column: 5 }, + startIndex: 0, + endIndex: 5, + }); + + expect(copy.gotoLastChild()).toBe(true); + assertCursorState(copy, { + nodeType: 'identifier', + nodeIsNamed: true, + startPosition: { row: 0, column: 4 }, + endPosition: { row: 0, column: 5 }, + startIndex: 4, + endIndex: 5, + }); + + expect(copy.gotoParent()).toBe(true); + expect(copy.gotoParent()).toBe(true); + expect(copy.nodeType).toBe('binary_expression'); + expect(copy.gotoParent()).toBe(true); + expect(copy.nodeType).toBe('expression_statement'); + expect(copy.gotoParent()).toBe(true); + expect(copy.nodeType).toBe('program'); + expect(copy.gotoParent()).toBe(false); + copy.delete(); + + expect(cursor.gotoParent()).toBe(true); + expect(cursor.nodeType).toBe('binary_expression'); + expect(cursor.gotoParent()).toBe(true); + expect(cursor.nodeType).toBe('expression_statement'); + expect(cursor.gotoParent()).toBe(true); + expect(cursor.nodeType).toBe('program'); + }); + + it('keeps track of the field name associated with each node', () => { + tree = parser.parse('a.b();'); + cursor = tree.walk(); + cursor.gotoFirstChild(); + cursor.gotoFirstChild(); + + expect(cursor.currentNode.type).toBe('call_expression'); + expect(cursor.currentFieldName).toBeNull(); + + cursor.gotoFirstChild(); + expect(cursor.currentNode.type).toBe('member_expression'); + expect(cursor.currentFieldName).toBe('function'); + + cursor.gotoFirstChild(); + expect(cursor.currentNode.type).toBe('identifier'); + expect(cursor.currentFieldName).toBe('object'); + + cursor.gotoNextSibling(); + cursor.gotoNextSibling(); + expect(cursor.currentNode.type).toBe('property_identifier'); + expect(cursor.currentFieldName).toBe('property'); + + cursor.gotoParent(); + cursor.gotoNextSibling(); + expect(cursor.currentNode.type).toBe('arguments'); + expect(cursor.currentFieldName).toBe('arguments'); + }); + + it('returns a cursor that can be reset anywhere in the tree', () => { + tree = parser.parse('a * b + c / d'); + cursor = tree.walk(); + const root = tree.rootNode.firstChild; + + cursor.reset(root!.firstChild!.firstChild!); + assertCursorState(cursor, { + nodeType: 'binary_expression', + nodeIsNamed: true, + startPosition: { row: 0, column: 0 }, + endPosition: { row: 0, column: 5 }, + startIndex: 0, + endIndex: 5, + }); + + cursor.gotoFirstChild(); + assertCursorState(cursor, { + nodeType: 'identifier', + nodeIsNamed: true, + startPosition: { row: 0, column: 0 }, + endPosition: { row: 0, column: 1 }, + startIndex: 0, + endIndex: 1, + }); + + expect(cursor.gotoParent()).toBe(true); + expect(cursor.gotoParent()).toBe(false); + }); + }); + + describe('.copy', () => { + let input: string; + let edit: Edit; + + it('creates another tree that remains stable if the original tree is edited', () => { + input = 'abc + cde'; + tree = parser.parse(input); + expect(tree.rootNode.toString()).toBe( + '(program (expression_statement (binary_expression left: (identifier) right: (identifier))))' + ); + + const tree2 = tree.copy(); + [input, edit] = spliceInput(input, 3, 0, '123'); + expect(input).toBe('abc123 + cde'); + tree.edit(edit); + + const leftNode = tree.rootNode.firstChild!.firstChild!.firstChild; + const leftNode2 = tree2.rootNode.firstChild!.firstChild!.firstChild; + const rightNode = tree.rootNode.firstChild!.firstChild!.lastChild; + const rightNode2 = tree2.rootNode.firstChild!.firstChild!.lastChild; + expect(leftNode!.endIndex).toBe(6); + expect(leftNode2!.endIndex).toBe(3); + expect(rightNode!.startIndex).toBe(9); + expect(rightNode2!.startIndex).toBe(6); + + tree2.delete(); + }); + }); +}); + +function spliceInput(input: string, startIndex: number, lengthRemoved: number, newText: string): [string, Edit] { + const oldEndIndex = startIndex + lengthRemoved; + const newEndIndex = startIndex + newText.length; + const startPosition = getExtent(input.slice(0, startIndex)); + const oldEndPosition = getExtent(input.slice(0, oldEndIndex)); + input = input.slice(0, startIndex) + newText + input.slice(oldEndIndex); + const newEndPosition = getExtent(input.slice(0, newEndIndex)); + return [ + input, + { + startIndex, + startPosition, + oldEndIndex, + oldEndPosition, + newEndIndex, + newEndPosition, + }, + ]; +} + +// Gets the extent of the text in terms of zero-based row and column numbers. +function getExtent(text: string): Point { + let row = 0; + let index = -1; + let lastIndex = 0; + while ((index = text.indexOf('\n', index + 1)) !== -1) { + row++; + lastIndex = index + 1; + } + return { row, column: text.length - lastIndex }; +} + +function assertCursorState(cursor: TreeCursor, params: CursorState): void { + expect(cursor.nodeType).toBe(params.nodeType); + expect(cursor.nodeIsNamed).toBe(params.nodeIsNamed); + expect(cursor.startPosition).toEqual(params.startPosition); + expect(cursor.endPosition).toEqual(params.endPosition); + expect(cursor.startIndex).toEqual(params.startIndex); + expect(cursor.endIndex).toEqual(params.endIndex); + + const node = cursor.currentNode; + expect(node.type).toBe(params.nodeType); + expect(node.isNamed).toBe(params.nodeIsNamed); + expect(node.startPosition).toEqual(params.startPosition); + expect(node.endPosition).toEqual(params.endPosition); + expect(node.startIndex).toEqual(params.startIndex); + expect(node.endIndex).toEqual(params.endIndex); +} diff --git a/lib/binding_web/tree-sitter-web.d.ts b/lib/binding_web/tree-sitter-web.d.ts index 18e17bbf..74db2ba3 100644 --- a/lib/binding_web/tree-sitter-web.d.ts +++ b/lib/binding_web/tree-sitter-web.d.ts @@ -8,7 +8,7 @@ declare module "web-tree-sitter" { delete(): void; parse( input: string | Parser.Input, - oldTree?: Parser.Tree, + oldTree?: Parser.Tree | null, options?: Parser.Options, ): Parser.Tree; getIncludedRanges(): Parser.Range[]; @@ -59,7 +59,7 @@ declare module "web-tree-sitter" { ) => void; export interface Input { - (index: number, position?: Point): string | null; + (index: number, position?: Point): string | null | undefined; } export interface SyntaxNode { @@ -104,6 +104,7 @@ declare module "web-tree-sitter" { childForFieldName(fieldName: string): SyntaxNode | null; childForFieldId(fieldId: number): SyntaxNode | null; fieldNameForChild(childIndex: number): string | null; + fieldNameForNamedChild(childIndex: number): string | null; childrenForFieldName(fieldName: string): Array; childrenForFieldId(fieldId: number): Array; firstChildForIndex(index: number): SyntaxNode | null; @@ -203,9 +204,19 @@ declare module "web-tree-sitter" { matchLimit?: number; maxStartDepth?: number; timeoutMicros?: number; - progressCallback: (state: QueryState) => boolean; + progressCallback?: (state: QueryState) => boolean; }; + export interface Predicate { + operator: string; + operands: PredicateStep[]; + } + + type PredicateStep = + | { type: 'string'; value: string } + | { type: 'capture'; name: string }; + + export interface PredicateResult { operator: string; operands: { name: string; type: string }[]; @@ -220,13 +231,13 @@ declare module "web-tree-sitter" { } export class Query { - captureNames: string[]; - captureQuantifiers: CaptureQuantifier[]; + readonly captureNames: string[]; + readonly captureQuantifiers: CaptureQuantifier[]; readonly predicates: { [name: string]: Function }[]; readonly setProperties: any[]; readonly assertedProperties: any[]; readonly refutedProperties: any[]; - readonly matchLimit: number; + readonly matchLimit?: number; delete(): void; captures(node: SyntaxNode, options?: QueryOptions): QueryCapture[]; @@ -245,17 +256,21 @@ declare module "web-tree-sitter" { class Language { static load(input: string | Uint8Array): Promise; + readonly name: string | null; readonly version: number; readonly fieldCount: number; readonly stateCount: number; readonly nodeTypeCount: number; + fieldNameForId(fieldId: number): string | null; fieldIdForName(fieldName: string): number | null; idForNodeType(type: string, named: boolean): number; nodeTypeForId(typeId: number): string | null; nodeTypeIsNamed(typeId: number): boolean; nodeTypeIsVisible(typeId: number): boolean; + get supertypes(): number[]; + subtypes(supertype: number): number[]; nextState(stateId: number, typeId: number): number; query(source: string): Query; lookaheadIterator(stateId: number): LookaheadIterable | null; diff --git a/lib/binding_web/tsconfig.json b/lib/binding_web/tsconfig.json new file mode 100644 index 00000000..5d74457b --- /dev/null +++ b/lib/binding_web/tsconfig.json @@ -0,0 +1,37 @@ +{ + "compilerOptions": { + "target": "es2022", + "module": "es2022", + "lib": [ + "es2022", + "dom" + ], + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "rootDir": "./", + "outDir": "./dist", + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "strictPropertyInitialization": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "moduleResolution": "node", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true, + }, + "include": [ + "src/**/*", + "test/**/*" + ], + "exclude": [ + "node_modules", + "dist", + ] +} diff --git a/lib/binding_web/vitest.config.ts b/lib/binding_web/vitest.config.ts new file mode 100644 index 00000000..11067a36 --- /dev/null +++ b/lib/binding_web/vitest.config.ts @@ -0,0 +1,19 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + coverage: { + include: [ + 'tree-sitter.js', + ], + exclude: [ + 'test/**', + 'dist/**', + 'lib/**', + 'wasm/**' + ], + }, + } +}) diff --git a/lib/binding_web/exports.txt b/lib/binding_web/wasm/exports.txt similarity index 100% rename from lib/binding_web/exports.txt rename to lib/binding_web/wasm/exports.txt diff --git a/lib/binding_web/imports.js b/lib/binding_web/wasm/imports.js similarity index 100% rename from lib/binding_web/imports.js rename to lib/binding_web/wasm/imports.js diff --git a/lib/binding_web/prefix.js b/lib/binding_web/wasm/prefix.js similarity index 100% rename from lib/binding_web/prefix.js rename to lib/binding_web/wasm/prefix.js diff --git a/lib/binding_web/suffix.js b/lib/binding_web/wasm/suffix.js similarity index 100% rename from lib/binding_web/suffix.js rename to lib/binding_web/wasm/suffix.js diff --git a/xtask/src/build_wasm.rs b/xtask/src/build_wasm.rs index 77a198b9..37c18e9f 100644 --- a/xtask/src/build_wasm.rs +++ b/xtask/src/build_wasm.rs @@ -106,7 +106,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/exports.txt")? + fs::read_to_string("lib/binding_web/wasm/exports.txt")? ) .replace('"', "") .lines() @@ -120,53 +120,37 @@ pub fn run_wasm(args: &BuildWasm) -> Result<()> { let exported_functions = format!("EXPORTED_FUNCTIONS={exported_functions}"); let exported_runtime_methods = "EXPORTED_RUNTIME_METHODS=stringToUTF16,AsciiToString"; + std::env::set_var("EMCC_DEBUG_SAVE", "1"); + + #[rustfmt::skip] emscripten_flags.extend([ - "-s", - "WASM=1", - "-s", - "INITIAL_MEMORY=33554432", - "-s", - "ALLOW_MEMORY_GROWTH=1", - "-s", - "SUPPORT_BIG_ENDIAN=1", - "-s", - "MAIN_MODULE=2", - "-s", - "FILESYSTEM=0", - "-s", - "NODEJS_CATCH_EXIT=0", - "-s", - "NODEJS_CATCH_REJECTION=0", - "-s", - &exported_functions, - "-s", - exported_runtime_methods, + "-gsource-map", + "--source-map-base", ".", + "-s", "WASM=1", + "-s", "INITIAL_MEMORY=33554432", + "-s", "ALLOW_MEMORY_GROWTH=1", + "-s", "SUPPORT_BIG_ENDIAN=1", + "-s", "MAIN_MODULE=2", + "-s", "FILESYSTEM=0", + "-s", "NODEJS_CATCH_EXIT=0", + "-s", "NODEJS_CATCH_REJECTION=0", + "-s", &exported_functions, + "-s", exported_runtime_methods, "-fno-exceptions", "-std=c11", - "-D", - "fprintf(...)=", - "-D", - "NDEBUG=", - "-D", - "_POSIX_C_SOURCE=200112L", - "-D", - "_DEFAULT_SOURCE=", - "-I", - "lib/src", - "-I", - "lib/include", - "--js-library", - "lib/binding_web/imports.js", - "--pre-js", - "lib/binding_web/prefix.js", - "--post-js", - "lib/binding_web/binding.js", - "--post-js", - "lib/binding_web/suffix.js", + "-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", "lib/src/lib.c", - "lib/binding_web/binding.c", - "-o", - "target/scratch/tree-sitter.js", + "lib/binding_web/lib/tree-sitter.c", ]); let command = command.args(&emscripten_flags); @@ -195,6 +179,11 @@ fn build_wasm(cmd: &mut Command) -> Result<()> { "lib/binding_web/tree-sitter.wasm", )?; + fs::rename( + "target/scratch/tree-sitter.wasm.map", + "lib/binding_web/tree-sitter.wasm.map", + )?; + Ok(()) }