UNPKG

web-tree-sitter

Version:
974 lines (844 loc) 31.9 kB
import { Point, ZERO_POINT, SIZE_OF_INT, C } from './constants'; import { Node } from './node'; import { marshalNode, unmarshalCaptures } from './marshal'; import { TRANSFER_BUFFER } from './parser'; import { Language } from './language'; const PREDICATE_STEP_TYPE_CAPTURE = 1; const PREDICATE_STEP_TYPE_STRING = 2; const QUERY_WORD_REGEX = /[\w-]+/g; /** * Options for query execution */ export interface QueryOptions { /** The start position of the range to query */ startPosition?: Point; /** The end position of the range to query */ endPosition?: Point; /** The start index of the range to query */ startIndex?: number; /** The end index of the range to query */ endIndex?: number; /** * The maximum number of in-progress matches for this query. * The limit must be > 0 and <= 65536. */ matchLimit?: number; /** * The maximum start depth for a query cursor. * * This prevents cursors from exploring children nodes at a certain depth. * Note if a pattern includes many children, then they will still be * checked. * * The zero max start depth value can be used as a special behavior and * it helps to destructure a subtree by staying on a node and using * captures for interested parts. Note that the zero max start depth * only limit a search depth for a pattern's root node but other nodes * that are parts of the pattern may be searched at any depth what * defined by the pattern structure. * * Set to `null` to remove the maximum start depth. */ maxStartDepth?: number; /** * The maximum duration in microseconds that query execution should be allowed to * take before halting. * * If query execution takes longer than this, it will halt early, returning an empty array. */ timeoutMicros?: number; /** * A function that will be called periodically during the execution of the query to check * if query execution should be cancelled. You can also use this to instrument query execution * and check where the query is at in the document. The progress callback takes a single argument, * which is a {@link QueryState} representing the current state of the query. */ progressCallback?: (state: QueryState) => void; } /** * A stateful object that is passed into the progress callback {@link QueryOptions#progressCallback} * to provide the current state of the query. */ export interface QueryState { /** The byte offset in the document that the query is at. */ currentOffset: number; } /** A record of key-value pairs associated with a particular pattern in a {@link Query}. */ export type QueryProperties = Record<string, string | null>; /** * A predicate that contains an operator and list of operands. */ export interface QueryPredicate { /** The operator of the predicate, like `match?`, `eq?`, `set!`, etc. */ operator: string; /** The operands of the predicate, which are either captures or strings. */ operands: PredicateStep[]; } /** * A particular {@link Node} that has been captured with a particular name within a * {@link Query}. */ export interface QueryCapture { /** The index of the pattern that matched. */ patternIndex: number; /** The name of the capture */ name: string; /** The captured node */ node: Node; /** The properties for predicates declared with the operator `set!`. */ setProperties?: QueryProperties; /** The properties for predicates declared with the operator `is?`. */ assertedProperties?: QueryProperties; /** The properties for predicates declared with the operator `is-not?`. */ refutedProperties?: QueryProperties; } /** A match of a {@link Query} to a particular set of {@link Node}s. */ export interface QueryMatch { /** @deprecated since version 0.25.0, use `patternIndex` instead. */ pattern: number; /** The index of the pattern that matched. */ patternIndex: number; /** The captures associated with the match. */ captures: QueryCapture[]; /** The properties for predicates declared with the operator `set!`. */ setProperties?: QueryProperties; /** The properties for predicates declared with the operator `is?`. */ assertedProperties?: QueryProperties; /** The properties for predicates declared with the operator `is-not?`. */ refutedProperties?: QueryProperties; } /** A quantifier for captures */ export const CaptureQuantifier = { Zero: 0, ZeroOrOne: 1, ZeroOrMore: 2, One: 3, OneOrMore: 4 } as const; /** A quantifier for captures */ export type CaptureQuantifier = typeof CaptureQuantifier[keyof typeof CaptureQuantifier]; /** * Predicates are represented as a single array of steps. There are two * types of steps, which correspond to the two legal values for * the `type` field: * * - `CapturePredicateStep` - Steps with this type represent names * of captures. * * - `StringPredicateStep` - Steps with this type represent literal * strings. */ export type PredicateStep = CapturePredicateStep | StringPredicateStep; /** * A step in a predicate that refers to a capture. * * The `name` field is the name of the capture. */ export interface CapturePredicateStep { type: 'capture', name: string } /** * A step in a predicate that refers to a string. * * The `value` field is the string value. */ export interface StringPredicateStep { type: 'string', value: string } const isCaptureStep = (step: PredicateStep): step is Extract<PredicateStep, { type: 'capture' }> => step.type === 'capture'; const isStringStep = (step: PredicateStep): step is Extract<PredicateStep, { type: 'string' }> => step.type === 'string'; /** * @internal * * A function that checks if a given set of captures matches a particular * condition. This is used in the built-in `eq?`, `match?`, and `any-of?` * predicates. */ export type TextPredicate = (captures: QueryCapture[]) => boolean; /** Error codes returned from tree-sitter query parsing */ export const QueryErrorKind = { Syntax: 1, NodeName: 2, FieldName: 3, CaptureName: 4, PatternStructure: 5, } as const; /** An error that occurred while parsing a query string. */ export type QueryErrorKind = typeof QueryErrorKind[keyof typeof QueryErrorKind]; /** Information about a {@link QueryError}. */ export interface QueryErrorInfo { [QueryErrorKind.NodeName]: { word: string }; [QueryErrorKind.FieldName]: { word: string }; [QueryErrorKind.CaptureName]: { word: string }; [QueryErrorKind.PatternStructure]: { suffix: string }; [QueryErrorKind.Syntax]: { suffix: string }; } /** Error thrown when parsing a tree-sitter query fails */ export class QueryError extends Error { constructor( public kind: QueryErrorKind, public info: QueryErrorInfo[typeof kind], public index: number, public length: number ) { super(QueryError.formatMessage(kind, info)); this.name = 'QueryError'; } /** Formats an error message based on the error kind and info */ private static formatMessage(kind: QueryErrorKind, info: QueryErrorInfo[QueryErrorKind]): string { switch (kind) { case QueryErrorKind.NodeName: return `Bad node name '${(info as QueryErrorInfo[2]).word}'`; case QueryErrorKind.FieldName: return `Bad field name '${(info as QueryErrorInfo[3]).word}'`; case QueryErrorKind.CaptureName: return `Bad capture name @${(info as QueryErrorInfo[4]).word}`; case QueryErrorKind.PatternStructure: return `Bad pattern structure at offset ${(info as QueryErrorInfo[5]).suffix}`; case QueryErrorKind.Syntax: return `Bad syntax at offset ${(info as QueryErrorInfo[1]).suffix}`; } } } /** * Parses the `eq?` and `not-eq?` predicates in a query, and updates the text predicates. */ function parseAnyPredicate( steps: PredicateStep[], index: number, operator: string, textPredicates: TextPredicate[][], ) { if (steps.length !== 3) { throw new Error( `Wrong number of arguments to \`#${operator}\` predicate. Expected 2, got ${steps.length - 1}` ); } if (!isCaptureStep(steps[1])) { throw new Error( `First argument of \`#${operator}\` predicate must be a capture. Got "${steps[1].value}"` ); } const isPositive = operator === 'eq?' || operator === 'any-eq?'; const matchAll = !operator.startsWith('any-'); if (isCaptureStep(steps[2])) { const captureName1 = steps[1].name; const captureName2 = steps[2].name; textPredicates[index].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 { const captureName = steps[1].name; const stringValue = steps[2].value; const matches = (n: Node) => n.text === stringValue; const doesNotMatch = (n: Node) => n.text !== stringValue; textPredicates[index].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); }); } } /** * Parses the `match?` and `not-match?` predicates in a query, and updates the text predicates. */ function parseMatchPredicate( steps: PredicateStep[], index: number, operator: string, textPredicates: TextPredicate[][], ) { 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].name}.`, ); } const isPositive = operator === 'match?' || operator === 'any-match?'; const matchAll = !operator.startsWith('any-'); const captureName = steps[1].name; const regex = new RegExp(steps[2].value); textPredicates[index].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)); }); } /** * Parses the `any-of?` and `not-any-of?` predicates in a query, and updates the text predicates. */ function parseAnyOfPredicate( steps: PredicateStep[], index: number, operator: string, textPredicates: TextPredicate[][], ) { 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}".`, ); } const isPositive = operator === 'any-of?'; const captureName = steps[1].name; const stringSteps = steps.slice(2); if (!stringSteps.every(isStringStep)) { throw new Error( `Arguments to \`#${operator}\` predicate must be strings.".`, ); } const values = stringSteps.map((s) => s.value); textPredicates[index].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; }); } /** * Parses the `is?` and `is-not?` predicates in a query, and updates the asserted or refuted properties, * depending on if the operator is positive or negative. */ function parseIsPredicate( steps: PredicateStep[], index: number, operator: string, assertedProperties: QueryProperties[], refutedProperties: QueryProperties[], ) { 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.every(isStringStep)) { throw new Error( `Arguments to \`#${operator}\` predicate must be strings.".`, ); } const properties = operator === 'is?' ? assertedProperties : refutedProperties; if (!properties[index]) properties[index] = {}; properties[index][steps[1].value] = steps[2]?.value ?? null; } /** * Parses the `set!` directive in a query, and updates the set properties. */ function parseSetDirective( steps: PredicateStep[], index: number, setProperties: QueryProperties[], ) { 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.every(isStringStep)) { throw new Error(`Arguments to \`#set!\` predicate must be strings.".`); } if (!setProperties[index]) setProperties[index] = {}; setProperties[index][steps[1].value] = steps[2]?.value ?? null; } /** * Parses the predicate at a given step in a pattern, and updates the appropriate * predicates or properties. */ function parsePattern( index: number, stepType: number, stepValueId: number, captureNames: string[], stringValues: string[], steps: PredicateStep[], textPredicates: TextPredicate[][], predicates: QueryPredicate[][], setProperties: QueryProperties[], assertedProperties: QueryProperties[], refutedProperties: QueryProperties[], ) { 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; switch (operator) { case 'any-not-eq?': case 'not-eq?': case 'any-eq?': case 'eq?': parseAnyPredicate(steps, index, operator, textPredicates); break; case 'any-not-match?': case 'not-match?': case 'any-match?': case 'match?': parseMatchPredicate(steps, index, operator, textPredicates); break; case 'not-any-of?': case 'any-of?': parseAnyOfPredicate(steps, index, operator, textPredicates); break; case 'is?': case 'is-not?': parseIsPredicate(steps, index, operator, assertedProperties, refutedProperties); break; case 'set!': parseSetDirective(steps, index, setProperties); break; default: predicates[index].push({ operator, operands: steps.slice(1) }); } steps.length = 0; } } export class Query { /** @internal */ private [0] = 0; // Internal handle for WASM /** @internal */ private exceededMatchLimit: boolean; /** @internal */ private textPredicates: TextPredicate[][]; /** The names of the captures used in the query. */ readonly captureNames: string[]; /** The quantifiers of the captures used in the query. */ readonly captureQuantifiers: CaptureQuantifier[][]; /** * The other user-defined predicates associated with the given index. * * This includes predicates with operators other than: * - `match?` * - `eq?` and `not-eq?` * - `any-of?` and `not-any-of?` * - `is?` and `is-not?` * - `set!` */ readonly predicates: QueryPredicate[][]; /** The properties for predicates with the operator `set!`. */ readonly setProperties: QueryProperties[]; /** The properties for predicates with the operator `is?`. */ readonly assertedProperties: QueryProperties[]; /** The properties for predicates with the operator `is-not?`. */ readonly refutedProperties: QueryProperties[]; /** The maximum number of in-progress matches for this cursor. */ matchLimit?: number; /** * Create a new query from a string containing one or more S-expression * patterns. * * The query is associated with a particular language, and can only be run * on syntax nodes parsed with that language. References to Queries can be * shared between multiple threads. * * @link {@see https://tree-sitter.github.io/tree-sitter/using-parsers/queries} */ constructor(language: Language, source: string) { const sourceLength = C.lengthBytesUTF8(source); const sourceAddress = C._malloc(sourceLength + 1); C.stringToUTF8(source, sourceAddress, sourceLength + 1); const address = C._ts_query_new( language[0], sourceAddress, sourceLength, TRANSFER_BUFFER, TRANSFER_BUFFER + SIZE_OF_INT ); if (!address) { const errorId = C.getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32') as QueryErrorKind; const errorByte = C.getValue(TRANSFER_BUFFER, 'i32'); const errorIndex = C.UTF8ToString(sourceAddress, errorByte).length; const suffix = source.slice(errorIndex, errorIndex + 100).split('\n')[0]; const word = suffix.match(QUERY_WORD_REGEX)?.[0] ?? ''; C._free(sourceAddress); switch (errorId) { case QueryErrorKind.Syntax: throw new QueryError(QueryErrorKind.Syntax, { suffix: `${errorIndex}: '${suffix}'...` }, errorIndex, 0); case QueryErrorKind.NodeName: throw new QueryError(errorId, { word }, errorIndex, word.length); case QueryErrorKind.FieldName: throw new QueryError(errorId, { word }, errorIndex, word.length); case QueryErrorKind.CaptureName: throw new QueryError(errorId, { word }, errorIndex, word.length); case QueryErrorKind.PatternStructure: throw new QueryError(errorId, { suffix: `${errorIndex}: '${suffix}'...` }, errorIndex, 0); } } 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<string>(captureCount); const captureQuantifiers = new Array<CaptureQuantifier[]>(patternCount); const stringValues = new Array<string>(stringCount); // Fill in the capture names for (let i = 0; i < captureCount; i++) { const nameAddress = C._ts_query_capture_name_for_id( address, i, TRANSFER_BUFFER ); const nameLength = C.getValue(TRANSFER_BUFFER, 'i32'); captureNames[i] = C.UTF8ToString(nameAddress, nameLength); } // Fill in the capture quantifiers for (let i = 0; i < patternCount; i++) { const captureQuantifiersArray = new Array<CaptureQuantifier>(captureCount); for (let j = 0; j < captureCount; j++) { const quantifier = C._ts_query_capture_quantifier_for_id(address, i, j); captureQuantifiersArray[j] = quantifier as CaptureQuantifier; } captureQuantifiers[i] = captureQuantifiersArray; } // Fill in the string values for (let i = 0; i < stringCount; i++) { const valueAddress = C._ts_query_string_value_for_id( address, i, TRANSFER_BUFFER ); const nameLength = C.getValue(TRANSFER_BUFFER, 'i32'); stringValues[i] = C.UTF8ToString(valueAddress, nameLength); } const setProperties = new Array<QueryProperties>(patternCount); const assertedProperties = new Array<QueryProperties>(patternCount); const refutedProperties = new Array<QueryProperties>(patternCount); const predicates = new Array<QueryPredicate[]>(patternCount); const textPredicates = new Array<TextPredicate[]>(patternCount); // Parse the predicates, and add the appropriate predicates or properties for (let i = 0; i < patternCount; i++) { const predicatesAddress = C._ts_query_predicates_for_pattern(address, i, TRANSFER_BUFFER); const stepCount = C.getValue(TRANSFER_BUFFER, 'i32'); predicates[i] = []; textPredicates[i] = []; const steps = new Array<PredicateStep>(); let stepAddress = predicatesAddress; for (let j = 0; j < stepCount; j++) { const stepType = C.getValue(stepAddress, 'i32'); stepAddress += SIZE_OF_INT; const stepValueId = C.getValue(stepAddress, 'i32'); stepAddress += SIZE_OF_INT; parsePattern( i, stepType, stepValueId, captureNames, stringValues, steps, textPredicates, predicates, setProperties, assertedProperties, refutedProperties, ); } Object.freeze(textPredicates[i]); Object.freeze(predicates[i]); Object.freeze(setProperties[i]); Object.freeze(assertedProperties[i]); Object.freeze(refutedProperties[i]); } C._free(sourceAddress); 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 the query, freeing its resources. */ delete(): void { C._ts_query_delete(this[0]); this[0] = 0; } /** * Iterate over all of the matches in the order that they were found. * * Each match contains the index of the pattern that matched, and a list of * captures. Because multiple patterns can match the same set of nodes, * one match may contain captures that appear *before* some of the * captures from a previous match. * * @param {Node} node - The node to execute the query on. * * @param {QueryOptions} options - Options for query execution. */ 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) { C.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 = C.getValue(TRANSFER_BUFFER, 'i32'); const startAddress = C.getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32'); const didExceedMatchLimit = C.getValue(TRANSFER_BUFFER + 2 * SIZE_OF_INT, 'i32'); const result = new Array<QueryMatch>(rawCount); this.exceededMatchLimit = Boolean(didExceedMatchLimit); let filteredCount = 0; let address = startAddress; for (let i = 0; i < rawCount; i++) { const patternIndex = C.getValue(address, 'i32'); address += SIZE_OF_INT; const captureCount = C.getValue(address, 'i32'); address += SIZE_OF_INT; const captures = new Array<QueryCapture>(captureCount); address = unmarshalCaptures(this, node.tree, address, patternIndex, captures); if (this.textPredicates[patternIndex].every((p) => p(captures))) { result[filteredCount] = { pattern: patternIndex, patternIndex, captures }; const setProperties = this.setProperties[patternIndex]; result[filteredCount].setProperties = setProperties; const assertedProperties = this.assertedProperties[patternIndex]; result[filteredCount].assertedProperties = assertedProperties; const refutedProperties = this.refutedProperties[patternIndex]; result[filteredCount].refutedProperties = refutedProperties; filteredCount++; } } result.length = filteredCount; C._free(startAddress); C.currentQueryProgressCallback = null; return result; } /** * Iterate over all of the individual captures in the order that they * appear. * * This is useful if you don't care about which pattern matched, and just * want a single, ordered sequence of captures. * * @param {Node} node - The node to execute the query on. * * @param {QueryOptions} options - Options for query execution. */ captures( node: Node, options: QueryOptions = {} ): QueryCapture[] { 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) { C.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 = C.getValue(TRANSFER_BUFFER, 'i32'); const startAddress = C.getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32'); const didExceedMatchLimit = C.getValue(TRANSFER_BUFFER + 2 * SIZE_OF_INT, 'i32'); const result = new Array<QueryCapture>(); this.exceededMatchLimit = Boolean(didExceedMatchLimit); const captures = new Array<QueryCapture>(); let address = startAddress; for (let i = 0; i < count; i++) { const patternIndex = C.getValue(address, 'i32'); address += SIZE_OF_INT; const captureCount = C.getValue(address, 'i32'); address += SIZE_OF_INT; const captureIndex = C.getValue(address, 'i32'); address += SIZE_OF_INT; captures.length = captureCount; address = unmarshalCaptures(this, node.tree, address, patternIndex, captures); if (this.textPredicates[patternIndex].every(p => p(captures))) { const capture = captures[captureIndex]; const setProperties = this.setProperties[patternIndex]; capture.setProperties = setProperties; const assertedProperties = this.assertedProperties[patternIndex]; capture.assertedProperties = assertedProperties; const refutedProperties = this.refutedProperties[patternIndex]; capture.refutedProperties = refutedProperties; result.push(capture); } } C._free(startAddress); C.currentQueryProgressCallback = null; return result; } /** Get the predicates for a given pattern. */ predicatesForPattern(patternIndex: number): QueryPredicate[] { return this.predicates[patternIndex]; } /** * Disable a certain capture within a query. * * This prevents the capture from being returned in matches, and also * avoids any resource usage associated with recording the capture. */ disableCapture(captureName: string): void { const captureNameLength = C.lengthBytesUTF8(captureName); const captureNameAddress = C._malloc(captureNameLength + 1); C.stringToUTF8(captureName, captureNameAddress, captureNameLength + 1); C._ts_query_disable_capture(this[0], captureNameAddress, captureNameLength); C._free(captureNameAddress); } /** * Disable a certain pattern within a query. * * This prevents the pattern from matching, and also avoids any resource * usage associated with the pattern. This throws an error if the pattern * index is out of bounds. */ 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); } /** * Check if, on its last execution, this cursor exceeded its maximum number * of in-progress matches. */ didExceedMatchLimit(): boolean { return this.exceededMatchLimit; } /** Get the byte offset where the given pattern starts in the query's source. */ 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); } /** Get the byte offset where the given pattern ends in the query's source. */ 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); } /** Get the number of patterns in the query. */ patternCount(): number { return C._ts_query_pattern_count(this[0]); } /** Get the index for a given capture name. */ captureIndexForName(captureName: string): number { return this.captureNames.indexOf(captureName); } /** Check if a given pattern within a query has a single root node. */ isPatternRooted(patternIndex: number): boolean { return C._ts_query_is_pattern_rooted(this[0], patternIndex) === 1; } /** Check if a given pattern within a query has a single root node. */ isPatternNonLocal(patternIndex: number): boolean { return C._ts_query_is_pattern_non_local(this[0], patternIndex) === 1; } /** * Check if a given step in a query is 'definite'. * * A query step is 'definite' if its parent pattern will be guaranteed to * match successfully once it reaches the step. */ isPatternGuaranteedAtStep(byteIndex: number): boolean { return C._ts_query_is_pattern_guaranteed_at_step(this[0], byteIndex) === 1; } }