2025-01-13 01:48:42 -05:00
|
|
|
import { describe, it, expect, beforeAll, beforeEach, afterEach } from 'vitest';
|
2025-01-20 03:12:52 -05:00
|
|
|
import type { Language, Tree, QueryMatch, QueryCapture } from '../src';
|
|
|
|
|
import { Parser, Query } from '../src';
|
2025-01-13 01:48:42 -05:00
|
|
|
import helper from './helper';
|
|
|
|
|
|
|
|
|
|
let JavaScript: Language;
|
2019-09-10 20:54:21 -07:00
|
|
|
|
2024-02-07 11:32:27 -05:00
|
|
|
describe('Query', () => {
|
2025-01-20 03:12:52 -05:00
|
|
|
let parser: Parser;
|
2025-01-13 01:48:42 -05:00
|
|
|
let tree: Tree | null;
|
|
|
|
|
let query: Query | null;
|
2019-09-10 20:54:21 -07:00
|
|
|
|
2025-01-13 01:48:42 -05:00
|
|
|
beforeAll(async () => {
|
2025-01-20 03:12:52 -05:00
|
|
|
({ JavaScript } = await helper);
|
2025-01-13 01:48:42 -05:00
|
|
|
});
|
2019-09-10 20:54:21 -07:00
|
|
|
|
|
|
|
|
beforeEach(() => {
|
2025-01-13 01:48:42 -05:00
|
|
|
parser = new Parser();
|
|
|
|
|
parser.setLanguage(JavaScript);
|
2019-09-10 20:54:21 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
afterEach(() => {
|
|
|
|
|
parser.delete();
|
|
|
|
|
if (tree) tree.delete();
|
|
|
|
|
if (query) query.delete();
|
|
|
|
|
});
|
|
|
|
|
|
2024-02-07 11:32:27 -05:00
|
|
|
describe('construction', () => {
|
|
|
|
|
it('throws an error on invalid patterns', () => {
|
2025-01-13 01:48:42 -05:00
|
|
|
expect(() => {
|
2025-01-22 23:11:45 -05:00
|
|
|
new Query(JavaScript, '(function_declaration wat)');
|
2025-01-13 01:48:42 -05:00
|
|
|
}).toThrow('Bad syntax at offset 22: \'wat)\'...');
|
|
|
|
|
|
|
|
|
|
expect(() => {
|
2025-01-22 23:11:45 -05:00
|
|
|
new Query(JavaScript, '(non_existent)');
|
2025-01-13 01:48:42 -05:00
|
|
|
}).toThrow('Bad node name \'non_existent\'');
|
|
|
|
|
|
|
|
|
|
expect(() => {
|
2025-01-22 23:11:45 -05:00
|
|
|
new Query(JavaScript, '(a)');
|
2025-01-13 01:48:42 -05:00
|
|
|
}).toThrow('Bad node name \'a\'');
|
|
|
|
|
|
|
|
|
|
expect(() => {
|
2025-01-22 23:11:45 -05:00
|
|
|
new Query(JavaScript, '(function_declaration non_existent:(identifier))');
|
2025-01-13 01:48:42 -05:00
|
|
|
}).toThrow('Bad field name \'non_existent\'');
|
|
|
|
|
|
|
|
|
|
expect(() => {
|
2025-01-22 23:11:45 -05:00
|
|
|
new Query(JavaScript, '(function_declaration name:(statement_block))');
|
2025-01-13 01:48:42 -05:00
|
|
|
}).toThrow('Bad pattern structure at offset 22: \'name:(statement_block))\'');
|
2019-09-11 14:44:49 -07:00
|
|
|
});
|
2019-09-16 10:25:44 -07:00
|
|
|
|
2024-02-07 11:32:27 -05:00
|
|
|
it('throws an error on invalid predicates', () => {
|
2025-01-13 01:48:42 -05:00
|
|
|
expect(() => {
|
2025-01-22 23:11:45 -05:00
|
|
|
new Query(JavaScript, '((identifier) @abc (#eq? @ab hi))');
|
2025-01-13 01:48:42 -05:00
|
|
|
}).toThrow('Bad capture name @ab');
|
|
|
|
|
|
|
|
|
|
expect(() => {
|
2025-01-22 23:11:45 -05:00
|
|
|
new Query(JavaScript, '((identifier) @abc (#eq?))');
|
2025-01-13 01:48:42 -05:00
|
|
|
}).toThrow('Wrong number of arguments to `#eq?` predicate. Expected 2, got 0');
|
|
|
|
|
|
|
|
|
|
expect(() => {
|
2025-01-22 23:11:45 -05:00
|
|
|
new Query(JavaScript, '((identifier) @a (#eq? @a @a @a))');
|
2025-01-13 01:48:42 -05:00
|
|
|
}).toThrow('Wrong number of arguments to `#eq?` predicate. Expected 2, got 3');
|
2019-09-16 10:25:44 -07:00
|
|
|
});
|
2019-09-10 20:54:21 -07:00
|
|
|
});
|
|
|
|
|
|
2024-02-07 11:32:27 -05:00
|
|
|
describe('.matches', () => {
|
2025-04-18 22:28:51 -04:00
|
|
|
it('returns all of the matches for the given query', { timeout: 10000 }, () => {
|
2025-01-20 03:12:52 -05:00
|
|
|
tree = parser.parse('function one() { two(); function three() {} }')!;
|
2025-01-22 23:11:45 -05:00
|
|
|
query = new Query(JavaScript, `
|
2020-05-11 15:22:05 -07:00
|
|
|
(function_declaration name: (identifier) @fn-def)
|
|
|
|
|
(call_expression function: (identifier) @fn-ref)
|
2019-09-11 14:44:49 -07:00
|
|
|
`);
|
|
|
|
|
const matches = query.matches(tree.rootNode);
|
2025-01-13 01:48:42 -05:00
|
|
|
expect(formatMatches(matches)).toEqual([
|
2025-01-22 23:11:45 -05:00
|
|
|
{ patternIndex: 0, captures: [{ patternIndex: 0, name: 'fn-def', text: 'one' }] },
|
|
|
|
|
{ patternIndex: 1, captures: [{ patternIndex: 1, name: 'fn-ref', text: 'two' }] },
|
|
|
|
|
{ patternIndex: 0, captures: [{ patternIndex: 0, name: 'fn-def', text: 'three' }] },
|
2020-05-11 15:22:05 -07:00
|
|
|
]);
|
2019-09-11 14:44:49 -07:00
|
|
|
});
|
|
|
|
|
|
2025-01-13 01:48:42 -05:00
|
|
|
it('can search in specified ranges', () => {
|
2025-01-20 03:12:52 -05:00
|
|
|
tree = parser.parse('[a, b,\nc, d,\ne, f,\ng, h]')!;
|
2025-01-22 23:11:45 -05:00
|
|
|
query = new Query(JavaScript, '(identifier) @element');
|
2019-09-11 14:44:49 -07:00
|
|
|
const matches = query.matches(
|
|
|
|
|
tree.rootNode,
|
2024-03-09 01:28:35 -05:00
|
|
|
{
|
2025-01-13 01:48:42 -05:00
|
|
|
startPosition: { row: 1, column: 1 },
|
|
|
|
|
endPosition: { row: 3, column: 1 },
|
|
|
|
|
}
|
2019-09-11 14:44:49 -07:00
|
|
|
);
|
2025-01-13 01:48:42 -05:00
|
|
|
expect(formatMatches(matches)).toEqual([
|
2025-01-22 23:11:45 -05:00
|
|
|
{ patternIndex: 0, captures: [{ patternIndex: 0, name: 'element', text: 'd' }] },
|
|
|
|
|
{ patternIndex: 0, captures: [{ patternIndex: 0, name: 'element', text: 'e' }] },
|
|
|
|
|
{ patternIndex: 0, captures: [{ patternIndex: 0, name: 'element', text: 'f' }] },
|
|
|
|
|
{ patternIndex: 0, captures: [{ patternIndex: 0, name: 'element', text: 'g' }] },
|
2020-05-11 15:22:05 -07:00
|
|
|
]);
|
2019-09-11 14:44:49 -07:00
|
|
|
});
|
2021-02-26 15:16:07 -08:00
|
|
|
|
2025-10-02 19:02:05 +02:00
|
|
|
it('can search in contained within point ranges', () => {
|
|
|
|
|
tree = parser.parse(`[
|
|
|
|
|
{"key1": "value1"},
|
|
|
|
|
{"key2": "value2"},
|
|
|
|
|
{"key3": "value3"},
|
|
|
|
|
{"key4": "value4"},
|
|
|
|
|
{"key5": "value5"},
|
|
|
|
|
{"key6": "value6"},
|
|
|
|
|
{"key7": "value7"},
|
|
|
|
|
{"key8": "value8"},
|
|
|
|
|
{"key9": "value9"},
|
|
|
|
|
{"key10": "value10"},
|
|
|
|
|
{"key11": "value11"},
|
|
|
|
|
{"key12": "value12"},
|
|
|
|
|
]`)!;
|
|
|
|
|
query = new Query(JavaScript, '("[" @l_bracket "]" @r_bracket) ("{" @l_brace "}" @r_brace)');
|
|
|
|
|
const matches = query.matches(
|
|
|
|
|
tree.rootNode,
|
|
|
|
|
{
|
|
|
|
|
startContainingPosition: { row: 5, column: 0 },
|
|
|
|
|
endContainingPosition: { row: 7, column: 0 },
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
expect(formatMatches(matches)).toEqual([
|
|
|
|
|
{ patternIndex: 1, captures: [{ patternIndex: 1, name: 'l_brace', text: '{' }, { patternIndex: 1, name: 'r_brace', text: '}' },] },
|
|
|
|
|
{ patternIndex: 1, captures: [{ patternIndex: 1, name: 'l_brace', text: '{' }, { patternIndex: 1, name: 'r_brace', text: '}' },] },
|
|
|
|
|
]);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('can search in contained within byte ranges', () => {
|
|
|
|
|
tree = parser.parse(`[
|
|
|
|
|
{"key1": "value1"},
|
|
|
|
|
{"key2": "value2"},
|
|
|
|
|
{"key3": "value3"},
|
|
|
|
|
{"key4": "value4"},
|
|
|
|
|
{"key5": "value5"},
|
|
|
|
|
{"key6": "value6"},
|
|
|
|
|
{"key7": "value7"},
|
|
|
|
|
{"key8": "value8"},
|
|
|
|
|
{"key9": "value9"},
|
|
|
|
|
{"key10": "value10"},
|
|
|
|
|
{"key11": "value11"},
|
|
|
|
|
{"key12": "value12"},
|
|
|
|
|
]`)!;
|
|
|
|
|
query = new Query(JavaScript, '("[" @l_bracket "]" @r_bracket) ("{" @l_brace "}" @r_brace)');
|
|
|
|
|
const matches = query.matches(
|
|
|
|
|
tree.rootNode,
|
|
|
|
|
{
|
|
|
|
|
startContainingIndex: 290,
|
|
|
|
|
endContainingIndex: 432,
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
expect(formatMatches(matches)).toEqual([
|
|
|
|
|
{ patternIndex: 1, captures: [{ patternIndex: 1, name: 'l_brace', text: '{' }, { patternIndex: 1, name: 'r_brace', text: '}' },] },
|
|
|
|
|
{ patternIndex: 1, captures: [{ patternIndex: 1, name: 'l_brace', text: '{' }, { patternIndex: 1, name: 'r_brace', text: '}' },] },
|
|
|
|
|
]);
|
|
|
|
|
});
|
|
|
|
|
|
2024-02-07 11:32:27 -05:00
|
|
|
it('handles predicates that compare the text of capture to literal strings', () => {
|
2021-02-26 15:16:07 -08:00
|
|
|
tree = parser.parse(`
|
|
|
|
|
giraffe(1, 2, []);
|
|
|
|
|
helment([false]);
|
|
|
|
|
goat(false);
|
|
|
|
|
gross(3, []);
|
|
|
|
|
hiccup([]);
|
|
|
|
|
gaff(5);
|
2025-01-20 03:12:52 -05:00
|
|
|
`)!;
|
2021-02-26 15:16:07 -08:00
|
|
|
|
|
|
|
|
// Find all calls to functions beginning with 'g', where one argument
|
|
|
|
|
// is an array literal.
|
2025-01-22 23:11:45 -05:00
|
|
|
query = new Query(JavaScript, `
|
2021-02-26 15:16:07 -08:00
|
|
|
(call_expression
|
|
|
|
|
function: (identifier) @name
|
|
|
|
|
arguments: (arguments (array))
|
|
|
|
|
(#match? @name "^g"))
|
|
|
|
|
`);
|
|
|
|
|
|
|
|
|
|
const matches = query.matches(tree.rootNode);
|
2025-01-13 01:48:42 -05:00
|
|
|
expect(formatMatches(matches)).toEqual([
|
2025-01-22 23:11:45 -05:00
|
|
|
{ patternIndex: 0, captures: [{ patternIndex: 0, name: 'name', text: 'giraffe' }] },
|
|
|
|
|
{ patternIndex: 0, captures: [{ patternIndex: 0, name: 'name', text: 'gross' }] },
|
2021-02-26 15:16:07 -08:00
|
|
|
]);
|
|
|
|
|
});
|
2024-02-13 15:37:35 -05:00
|
|
|
|
|
|
|
|
it('handles multiple matches where the first one is filtered', () => {
|
|
|
|
|
tree = parser.parse(`
|
|
|
|
|
const a = window.b;
|
2025-01-20 03:12:52 -05:00
|
|
|
`)!;
|
2024-02-13 15:37:35 -05:00
|
|
|
|
2025-01-22 23:11:45 -05:00
|
|
|
query = new Query(JavaScript, `
|
2024-02-13 15:37:35 -05:00
|
|
|
((identifier) @variable.builtin
|
|
|
|
|
(#match? @variable.builtin "^(arguments|module|console|window|document)$")
|
|
|
|
|
(#is-not? local))
|
|
|
|
|
`);
|
|
|
|
|
|
|
|
|
|
const matches = query.matches(tree.rootNode);
|
2025-01-13 01:48:42 -05:00
|
|
|
expect(formatMatches(matches)).toEqual([
|
2025-01-22 23:11:45 -05:00
|
|
|
{ patternIndex: 0, captures: [{ patternIndex: 0, name: 'variable.builtin', text: 'window' }] },
|
2024-02-13 15:37:35 -05:00
|
|
|
]);
|
|
|
|
|
});
|
2019-09-10 20:54:21 -07:00
|
|
|
});
|
2019-09-11 14:44:49 -07:00
|
|
|
|
2024-02-07 11:32:27 -05:00
|
|
|
describe('.captures', () => {
|
|
|
|
|
it('returns all of the captures for the given query, in order', () => {
|
2019-09-11 14:44:49 -07:00
|
|
|
tree = parser.parse(`
|
|
|
|
|
a({
|
|
|
|
|
bc: function de() {
|
|
|
|
|
const fg = function hi() {}
|
|
|
|
|
},
|
|
|
|
|
jk: function lm() {
|
|
|
|
|
const no = function pq() {}
|
|
|
|
|
},
|
|
|
|
|
});
|
2025-01-20 03:12:52 -05:00
|
|
|
`)!;
|
2025-01-22 23:11:45 -05:00
|
|
|
query = new Query(JavaScript, `
|
2019-09-11 14:44:49 -07:00
|
|
|
(pair
|
2020-05-11 15:22:05 -07:00
|
|
|
key: _ @method.def
|
2024-02-02 11:30:19 -05:00
|
|
|
(function_expression
|
2019-09-11 14:44:49 -07:00
|
|
|
name: (identifier) @method.alias))
|
|
|
|
|
|
|
|
|
|
(variable_declarator
|
2020-05-11 15:22:05 -07:00
|
|
|
name: _ @function.def
|
2024-02-02 11:30:19 -05:00
|
|
|
value: (function_expression
|
2019-09-11 14:44:49 -07:00
|
|
|
name: (identifier) @function.alias))
|
|
|
|
|
|
|
|
|
|
":" @delimiter
|
|
|
|
|
"=" @operator
|
|
|
|
|
`);
|
|
|
|
|
|
|
|
|
|
const captures = query.captures(tree.rootNode);
|
2025-01-13 01:48:42 -05:00
|
|
|
expect(formatCaptures(captures)).toEqual([
|
2025-01-22 23:11:45 -05:00
|
|
|
{ patternIndex: 0, name: 'method.def', text: 'bc' },
|
|
|
|
|
{ patternIndex: 2, name: 'delimiter', text: ':' },
|
|
|
|
|
{ patternIndex: 0, name: 'method.alias', text: 'de' },
|
|
|
|
|
{ patternIndex: 1, name: 'function.def', text: 'fg' },
|
|
|
|
|
{ patternIndex: 3, name: 'operator', text: '=' },
|
|
|
|
|
{ patternIndex: 1, name: 'function.alias', text: 'hi' },
|
|
|
|
|
{ patternIndex: 0, name: 'method.def', text: 'jk' },
|
|
|
|
|
{ patternIndex: 2, name: 'delimiter', text: ':' },
|
|
|
|
|
{ patternIndex: 0, name: 'method.alias', text: 'lm' },
|
|
|
|
|
{ patternIndex: 1, name: 'function.def', text: 'no' },
|
|
|
|
|
{ patternIndex: 3, name: 'operator', text: '=' },
|
|
|
|
|
{ patternIndex: 1, name: 'function.alias', text: 'pq' },
|
2020-05-11 15:22:05 -07:00
|
|
|
]);
|
2019-09-11 14:44:49 -07:00
|
|
|
});
|
2019-09-16 10:25:44 -07:00
|
|
|
|
2024-02-07 11:32:27 -05:00
|
|
|
it('handles conditions that compare the text of capture to literal strings', () => {
|
2019-09-16 10:25:44 -07:00
|
|
|
tree = parser.parse(`
|
2020-07-24 10:49:20 -07:00
|
|
|
lambda
|
|
|
|
|
panda
|
|
|
|
|
load
|
|
|
|
|
toad
|
2019-09-16 10:25:44 -07:00
|
|
|
const ab = require('./ab');
|
|
|
|
|
new Cd(EF);
|
2025-01-20 03:12:52 -05:00
|
|
|
`)!;
|
2019-09-16 10:25:44 -07:00
|
|
|
|
2025-01-22 23:11:45 -05:00
|
|
|
query = new Query(JavaScript, `
|
2020-07-24 10:49:20 -07:00
|
|
|
((identifier) @variable
|
|
|
|
|
(#not-match? @variable "^(lambda|load)$"))
|
2019-09-16 10:25:44 -07:00
|
|
|
|
|
|
|
|
((identifier) @function.builtin
|
2020-05-11 15:22:05 -07:00
|
|
|
(#eq? @function.builtin "require"))
|
2019-09-16 10:25:44 -07:00
|
|
|
|
|
|
|
|
((identifier) @constructor
|
2020-05-11 15:22:05 -07:00
|
|
|
(#match? @constructor "^[A-Z]"))
|
2019-09-16 10:25:44 -07:00
|
|
|
|
|
|
|
|
((identifier) @constant
|
2020-05-11 15:22:05 -07:00
|
|
|
(#match? @constant "^[A-Z]{2,}$"))
|
2019-09-16 10:25:44 -07:00
|
|
|
`);
|
|
|
|
|
|
|
|
|
|
const captures = query.captures(tree.rootNode);
|
2025-01-13 01:48:42 -05:00
|
|
|
expect(formatCaptures(captures)).toEqual([
|
2025-01-22 23:11:45 -05:00
|
|
|
{ patternIndex: 0, name: 'variable', text: 'panda' },
|
|
|
|
|
{ patternIndex: 0, name: 'variable', text: 'toad' },
|
|
|
|
|
{ patternIndex: 0, name: 'variable', text: 'ab' },
|
|
|
|
|
{ patternIndex: 0, name: 'variable', text: 'require' },
|
|
|
|
|
{ patternIndex: 1, name: 'function.builtin', text: 'require' },
|
|
|
|
|
{ patternIndex: 0, name: 'variable', text: 'Cd' },
|
|
|
|
|
{ patternIndex: 2, name: 'constructor', text: 'Cd' },
|
|
|
|
|
{ patternIndex: 0, name: 'variable', text: 'EF' },
|
|
|
|
|
{ patternIndex: 2, name: 'constructor', text: 'EF' },
|
|
|
|
|
{ patternIndex: 3, name: 'constant', text: 'EF' },
|
2020-05-11 15:22:05 -07:00
|
|
|
]);
|
2019-09-16 10:25:44 -07:00
|
|
|
});
|
|
|
|
|
|
2025-01-13 01:48:42 -05:00
|
|
|
it('handles conditions that compare the text of captures to each other', () => {
|
2019-09-16 10:25:44 -07:00
|
|
|
tree = parser.parse(`
|
2019-09-16 12:48:01 -07:00
|
|
|
ab = abc + 1;
|
|
|
|
|
def = de + 1;
|
|
|
|
|
ghi = ghi + 1;
|
2025-01-20 03:12:52 -05:00
|
|
|
`)!;
|
2019-09-16 10:25:44 -07:00
|
|
|
|
2025-01-22 23:11:45 -05:00
|
|
|
query = new Query(JavaScript, `
|
2020-05-11 15:22:05 -07:00
|
|
|
(
|
|
|
|
|
(assignment_expression
|
2019-09-16 12:48:01 -07:00
|
|
|
left: (identifier) @id1
|
|
|
|
|
right: (binary_expression
|
2019-09-16 10:25:44 -07:00
|
|
|
left: (identifier) @id2))
|
2020-05-11 15:22:05 -07:00
|
|
|
(#eq? @id1 @id2)
|
|
|
|
|
)
|
2019-09-16 10:25:44 -07:00
|
|
|
`);
|
|
|
|
|
|
|
|
|
|
const captures = query.captures(tree.rootNode);
|
2025-01-13 01:48:42 -05:00
|
|
|
expect(formatCaptures(captures)).toEqual([
|
2025-01-22 23:11:45 -05:00
|
|
|
{ patternIndex: 0, name: 'id1', text: 'ghi' },
|
|
|
|
|
{ patternIndex: 0, name: 'id2', text: 'ghi' },
|
2020-05-11 15:22:05 -07:00
|
|
|
]);
|
2019-09-16 10:25:44 -07:00
|
|
|
});
|
2019-09-18 17:35:47 -07:00
|
|
|
|
2024-02-07 11:32:27 -05:00
|
|
|
it('handles patterns with properties', () => {
|
2025-01-20 03:12:52 -05:00
|
|
|
tree = parser.parse(`a(b.c);`)!;
|
2025-01-22 23:11:45 -05:00
|
|
|
query = new Query(JavaScript, `
|
2019-09-18 17:35:47 -07:00
|
|
|
((call_expression (identifier) @func)
|
2020-05-11 15:22:05 -07:00
|
|
|
(#set! foo)
|
|
|
|
|
(#set! bar baz))
|
2019-09-18 17:35:47 -07:00
|
|
|
|
2019-09-18 17:40:13 -07:00
|
|
|
((property_identifier) @prop
|
2020-05-11 15:22:05 -07:00
|
|
|
(#is? foo)
|
|
|
|
|
(#is-not? bar baz))
|
2019-09-18 17:35:47 -07:00
|
|
|
`);
|
|
|
|
|
|
|
|
|
|
const captures = query.captures(tree.rootNode);
|
2025-01-13 01:48:42 -05:00
|
|
|
expect(formatCaptures(captures)).toEqual([
|
|
|
|
|
{
|
2025-01-22 23:11:45 -05:00
|
|
|
patternIndex: 0,
|
2025-01-13 01:48:42 -05:00
|
|
|
name: 'func',
|
|
|
|
|
text: 'a',
|
|
|
|
|
setProperties: { foo: null, bar: 'baz' }
|
|
|
|
|
},
|
2020-05-11 15:22:05 -07:00
|
|
|
{
|
2025-01-22 23:11:45 -05:00
|
|
|
patternIndex: 1,
|
2024-02-07 11:32:27 -05:00
|
|
|
name: 'prop',
|
|
|
|
|
text: 'c',
|
2025-01-13 01:48:42 -05:00
|
|
|
assertedProperties: { foo: null },
|
|
|
|
|
refutedProperties: { bar: 'baz' },
|
2020-05-11 15:22:05 -07:00
|
|
|
},
|
2019-09-18 17:35:47 -07:00
|
|
|
]);
|
2025-01-13 01:48:42 -05:00
|
|
|
expect(query.didExceedMatchLimit()).toBe(false);
|
2021-04-27 09:21:38 -04:00
|
|
|
});
|
|
|
|
|
|
2024-02-07 11:32:27 -05:00
|
|
|
it('detects queries with too many permutations to track', () => {
|
2021-04-27 09:21:38 -04:00
|
|
|
tree = parser.parse(`
|
|
|
|
|
[
|
|
|
|
|
hello, hello, hello, hello, hello, hello, hello, hello, hello, hello,
|
|
|
|
|
hello, hello, hello, hello, hello, hello, hello, hello, hello, hello,
|
|
|
|
|
hello, hello, hello, hello, hello, hello, hello, hello, hello, hello,
|
|
|
|
|
hello, hello, hello, hello, hello, hello, hello, hello, hello, hello,
|
|
|
|
|
hello, hello, hello, hello, hello, hello, hello, hello, hello, hello,
|
|
|
|
|
];
|
2025-01-20 03:12:52 -05:00
|
|
|
`)!;
|
2021-04-27 09:21:38 -04:00
|
|
|
|
2025-01-22 23:11:45 -05:00
|
|
|
query = new Query(JavaScript, `(array (identifier) @pre (identifier) @post)`);
|
2021-04-27 09:21:38 -04:00
|
|
|
|
2025-01-13 01:48:42 -05:00
|
|
|
query.captures(tree.rootNode, { matchLimit: 32 });
|
|
|
|
|
expect(query.didExceedMatchLimit()).toBe(true);
|
2019-09-18 17:35:47 -07:00
|
|
|
});
|
2023-11-28 13:07:32 -08:00
|
|
|
|
2024-02-07 11:32:27 -05:00
|
|
|
it('handles quantified captures properly', () => {
|
2023-11-28 13:07:32 -08:00
|
|
|
tree = parser.parse(`
|
|
|
|
|
/// foo
|
|
|
|
|
/// bar
|
|
|
|
|
/// baz
|
2025-01-20 03:12:52 -05:00
|
|
|
`)!;
|
2023-11-28 13:07:32 -08:00
|
|
|
|
2025-01-13 01:48:42 -05:00
|
|
|
const expectCount = (tree: Tree, queryText: string, expectedCount: number) => {
|
2025-01-22 23:11:45 -05:00
|
|
|
query = new Query(JavaScript, queryText);
|
2025-01-13 01:48:42 -05:00
|
|
|
const captures = query.captures(tree.rootNode);
|
|
|
|
|
expect(captures).toHaveLength(expectedCount);
|
2023-11-28 13:07:32 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
expectCount(
|
|
|
|
|
tree,
|
2024-02-07 11:32:27 -05:00
|
|
|
`((comment)+ @foo (#any-eq? @foo "/// foo"))`,
|
2025-01-13 01:48:42 -05:00
|
|
|
3
|
2023-11-28 13:07:32 -08:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expectCount(
|
|
|
|
|
tree,
|
2024-02-07 11:32:27 -05:00
|
|
|
`((comment)+ @foo (#eq? @foo "/// foo"))`,
|
2025-01-13 01:48:42 -05:00
|
|
|
0
|
2023-11-28 13:07:32 -08:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expectCount(
|
|
|
|
|
tree,
|
2024-02-07 11:32:27 -05:00
|
|
|
`((comment)+ @foo (#any-not-eq? @foo "/// foo"))`,
|
2025-01-13 01:48:42 -05:00
|
|
|
3
|
2023-11-28 13:07:32 -08:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expectCount(
|
|
|
|
|
tree,
|
2024-02-07 11:32:27 -05:00
|
|
|
`((comment)+ @foo (#not-eq? @foo "/// foo"))`,
|
2025-01-13 01:48:42 -05:00
|
|
|
0
|
2023-11-28 13:07:32 -08:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expectCount(
|
|
|
|
|
tree,
|
2024-02-07 11:32:27 -05:00
|
|
|
`((comment)+ @foo (#match? @foo "^/// foo"))`,
|
2025-01-13 01:48:42 -05:00
|
|
|
0
|
2023-11-28 13:07:32 -08:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expectCount(
|
|
|
|
|
tree,
|
2024-02-07 11:32:27 -05:00
|
|
|
`((comment)+ @foo (#any-match? @foo "^/// foo"))`,
|
2025-01-13 01:48:42 -05:00
|
|
|
3
|
2023-11-28 13:07:32 -08:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expectCount(
|
|
|
|
|
tree,
|
2024-02-07 11:32:27 -05:00
|
|
|
`((comment)+ @foo (#not-match? @foo "^/// foo"))`,
|
2025-01-13 01:48:42 -05:00
|
|
|
0
|
2023-11-28 13:07:32 -08:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expectCount(
|
|
|
|
|
tree,
|
2024-02-07 11:32:27 -05:00
|
|
|
`((comment)+ @foo (#not-match? @foo "fsdfsdafdfs"))`,
|
2025-01-13 01:48:42 -05:00
|
|
|
3
|
2023-11-28 13:07:32 -08:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expectCount(
|
|
|
|
|
tree,
|
2024-02-07 11:32:27 -05:00
|
|
|
`((comment)+ @foo (#any-not-match? @foo "^///"))`,
|
2025-01-13 01:48:42 -05:00
|
|
|
0
|
2023-11-28 13:07:32 -08:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expectCount(
|
|
|
|
|
tree,
|
2024-02-07 11:32:27 -05:00
|
|
|
`((comment)+ @foo (#any-not-match? @foo "^/// foo"))`,
|
2025-01-13 01:48:42 -05:00
|
|
|
3
|
2023-11-28 13:07:32 -08:00
|
|
|
);
|
2024-02-07 11:32:27 -05:00
|
|
|
});
|
2019-09-11 14:44:49 -07:00
|
|
|
});
|
2020-06-01 13:28:52 -07:00
|
|
|
|
2024-02-07 11:32:27 -05:00
|
|
|
describe('.predicatesForPattern(index)', () => {
|
|
|
|
|
it('returns all of the predicates as objects', () => {
|
2025-01-22 23:11:45 -05:00
|
|
|
query = new Query(JavaScript, `
|
2020-06-01 13:28:52 -07:00
|
|
|
(
|
|
|
|
|
(binary_expression
|
|
|
|
|
left: (identifier) @a
|
|
|
|
|
right: (identifier) @b)
|
|
|
|
|
(#something? @a @b)
|
|
|
|
|
(#match? @a "c")
|
|
|
|
|
(#something-else? @a "A" @b "B")
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
((identifier) @c
|
|
|
|
|
(#hello! @c))
|
|
|
|
|
|
|
|
|
|
"if" @d
|
|
|
|
|
`);
|
|
|
|
|
|
2025-01-13 01:48:42 -05:00
|
|
|
expect(query.predicatesForPattern(0)).toStrictEqual([
|
2020-06-01 13:28:52 -07:00
|
|
|
{
|
2024-02-07 11:32:27 -05:00
|
|
|
operator: 'something?',
|
2020-06-01 13:28:52 -07:00
|
|
|
operands: [
|
2025-01-13 01:48:42 -05:00
|
|
|
{ type: 'capture', name: 'a' },
|
|
|
|
|
{ type: 'capture', name: 'b' },
|
2020-06-01 13:28:52 -07:00
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
2024-02-07 11:32:27 -05:00
|
|
|
operator: 'something-else?',
|
2020-06-01 13:28:52 -07:00
|
|
|
operands: [
|
2025-01-13 01:48:42 -05:00
|
|
|
{ type: 'capture', name: 'a' },
|
|
|
|
|
{ type: 'string', value: 'A' },
|
|
|
|
|
{ type: 'capture', name: 'b' },
|
|
|
|
|
{ type: 'string', value: 'B' },
|
2020-06-01 13:28:52 -07:00
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
]);
|
2025-01-13 01:48:42 -05:00
|
|
|
|
|
|
|
|
expect(query.predicatesForPattern(1)).toStrictEqual([
|
2020-06-01 13:28:52 -07:00
|
|
|
{
|
2024-02-07 11:32:27 -05:00
|
|
|
operator: 'hello!',
|
2025-01-13 01:48:42 -05:00
|
|
|
operands: [{ type: 'capture', name: 'c' }],
|
2020-06-01 13:28:52 -07:00
|
|
|
},
|
|
|
|
|
]);
|
2025-01-13 01:48:42 -05:00
|
|
|
|
|
|
|
|
expect(query.predicatesForPattern(2)).toEqual([]);
|
2020-06-01 13:28:52 -07:00
|
|
|
});
|
|
|
|
|
});
|
2024-03-09 01:28:35 -05:00
|
|
|
|
|
|
|
|
describe('.disableCapture', () => {
|
|
|
|
|
it('disables a capture', () => {
|
2025-01-22 23:11:45 -05:00
|
|
|
query = new Query(JavaScript, `
|
2024-03-09 01:28:35 -05:00
|
|
|
(function_declaration
|
|
|
|
|
(identifier) @name1 @name2 @name3
|
|
|
|
|
(statement_block) @body1 @body2)
|
|
|
|
|
`);
|
|
|
|
|
|
|
|
|
|
const source = 'function foo() { return 1; }';
|
2025-01-20 03:12:52 -05:00
|
|
|
const tree = parser.parse(source)!;
|
2024-03-09 01:28:35 -05:00
|
|
|
|
|
|
|
|
let matches = query.matches(tree.rootNode);
|
2025-01-13 01:48:42 -05:00
|
|
|
expect(formatMatches(matches)).toEqual([
|
2024-03-09 01:28:35 -05:00
|
|
|
{
|
2025-01-22 23:11:45 -05:00
|
|
|
patternIndex: 0,
|
2024-03-09 01:28:35 -05:00
|
|
|
captures: [
|
2025-01-22 23:11:45 -05:00
|
|
|
{ patternIndex: 0, name: 'name1', text: 'foo' },
|
|
|
|
|
{ patternIndex: 0, name: 'name2', text: 'foo' },
|
|
|
|
|
{ patternIndex: 0, name: 'name3', text: 'foo' },
|
|
|
|
|
{ patternIndex: 0, name: 'body1', text: '{ return 1; }' },
|
|
|
|
|
{ patternIndex: 0, name: 'body2', text: '{ return 1; }' },
|
2024-03-09 01:28:35 -05:00
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// disabling captures still works when there are multiple captures on a
|
|
|
|
|
// single node.
|
|
|
|
|
query.disableCapture('name2');
|
|
|
|
|
matches = query.matches(tree.rootNode);
|
2025-01-13 01:48:42 -05:00
|
|
|
expect(formatMatches(matches)).toEqual([
|
2024-03-09 01:28:35 -05:00
|
|
|
{
|
2025-01-22 23:11:45 -05:00
|
|
|
patternIndex: 0,
|
2024-03-09 01:28:35 -05:00
|
|
|
captures: [
|
2025-01-22 23:11:45 -05:00
|
|
|
{ patternIndex: 0, name: 'name1', text: 'foo' },
|
|
|
|
|
{ patternIndex: 0, name: 'name3', text: 'foo' },
|
|
|
|
|
{ patternIndex: 0, name: 'body1', text: '{ return 1; }' },
|
|
|
|
|
{ patternIndex: 0, name: 'body2', text: '{ return 1; }' },
|
2024-03-09 01:28:35 -05:00
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
]);
|
|
|
|
|
});
|
|
|
|
|
});
|
2024-08-29 17:21:52 -04:00
|
|
|
|
2025-01-05 22:06:33 -05:00
|
|
|
describe('Start and end indices for patterns', () => {
|
|
|
|
|
it('Returns the start and end indices for a pattern', () => {
|
|
|
|
|
const patterns1 = `
|
|
|
|
|
"+" @operator
|
|
|
|
|
"-" @operator
|
|
|
|
|
"*" @operator
|
|
|
|
|
"=" @operator
|
|
|
|
|
"=>" @operator
|
|
|
|
|
`.trim();
|
|
|
|
|
|
|
|
|
|
const patterns2 = `
|
|
|
|
|
(identifier) @a
|
|
|
|
|
(string) @b
|
|
|
|
|
`.trim();
|
|
|
|
|
|
|
|
|
|
const patterns3 = `
|
|
|
|
|
((identifier) @b (#match? @b i))
|
|
|
|
|
(function_declaration name: (identifier) @c)
|
|
|
|
|
(method_definition name: (property_identifier) @d)
|
|
|
|
|
`.trim();
|
|
|
|
|
|
|
|
|
|
const source = patterns1 + patterns2 + patterns3;
|
|
|
|
|
|
2025-01-22 23:11:45 -05:00
|
|
|
const query = new Query(JavaScript, source);
|
2025-01-05 22:06:33 -05:00
|
|
|
|
2025-01-13 01:48:42 -05:00
|
|
|
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
|
2025-01-05 22:06:33 -05:00
|
|
|
);
|
2025-01-13 01:48:42 -05:00
|
|
|
expect(query.startIndexForPattern(7)).toBe(patterns1.length + patterns2.length);
|
|
|
|
|
expect(query.endIndexForPattern(7)).toBe(
|
2025-01-05 22:06:33 -05:00
|
|
|
patterns1.length +
|
2025-01-13 01:48:42 -05:00
|
|
|
patterns2.length +
|
|
|
|
|
'((identifier) @b (#match? @b i))\n'.length
|
2025-01-05 22:06:33 -05:00
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('Disable pattern', () => {
|
|
|
|
|
it('Disables patterns in the query', () => {
|
2025-01-22 23:11:45 -05:00
|
|
|
const query = new Query(JavaScript, `
|
2025-01-05 22:06:33 -05:00
|
|
|
(function_declaration name: (identifier) @name)
|
|
|
|
|
(function_declaration body: (statement_block) @body)
|
|
|
|
|
(class_declaration name: (identifier) @name)
|
|
|
|
|
(class_declaration body: (class_body) @body)
|
|
|
|
|
`);
|
|
|
|
|
|
|
|
|
|
// disable the patterns that match names
|
|
|
|
|
query.disablePattern(0);
|
|
|
|
|
query.disablePattern(2);
|
|
|
|
|
|
|
|
|
|
const source = 'class A { constructor() {} } function b() { return 1; }';
|
2025-01-20 03:12:52 -05:00
|
|
|
tree = parser.parse(source)!;
|
2025-01-05 22:06:33 -05:00
|
|
|
const matches = query.matches(tree.rootNode);
|
2025-01-13 01:48:42 -05:00
|
|
|
expect(formatMatches(matches)).toEqual([
|
2025-01-05 22:06:33 -05:00
|
|
|
{
|
2025-01-22 23:11:45 -05:00
|
|
|
patternIndex: 3,
|
|
|
|
|
captures: [{ patternIndex: 3, name: 'body', text: '{ constructor() {} }' }],
|
2025-01-05 22:06:33 -05:00
|
|
|
},
|
2025-01-22 23:11:45 -05:00
|
|
|
{ patternIndex: 1, captures: [{ patternIndex: 1, name: 'body', text: '{ return 1; }' }] },
|
2025-01-05 22:06:33 -05:00
|
|
|
]);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2025-04-18 22:28:51 -04:00
|
|
|
describe('Executes with a timeout', { timeout: 10000 }, () => {
|
2025-01-05 22:06:33 -05:00
|
|
|
it('Returns less than the expected matches', () => {
|
2025-01-20 03:12:52 -05:00
|
|
|
tree = parser.parse('function foo() while (true) { } }\n'.repeat(1000))!;
|
2025-01-22 23:11:45 -05:00
|
|
|
query = new Query(JavaScript, '(function_declaration) @function');
|
2025-01-05 22:06:33 -05:00
|
|
|
|
|
|
|
|
const startTime = performance.now();
|
|
|
|
|
|
|
|
|
|
const matches = query.matches(
|
|
|
|
|
tree.rootNode,
|
|
|
|
|
{
|
2025-01-16 01:10:54 -05:00
|
|
|
progressCallback: () => {
|
2025-01-05 22:06:33 -05:00
|
|
|
if (performance.now() - startTime > 1) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
},
|
2025-01-13 01:48:42 -05:00
|
|
|
}
|
2025-01-05 22:06:33 -05:00
|
|
|
);
|
2025-01-13 01:48:42 -05:00
|
|
|
expect(matches.length).toBeLessThan(1000);
|
2025-01-05 22:06:33 -05:00
|
|
|
|
|
|
|
|
const matches2 = query.matches(tree.rootNode);
|
2025-01-13 01:48:42 -05:00
|
|
|
expect(matches2).toHaveLength(1000);
|
2025-01-05 22:06:33 -05:00
|
|
|
});
|
|
|
|
|
});
|
2019-09-10 20:54:21 -07:00
|
|
|
});
|
2019-09-11 14:44:49 -07:00
|
|
|
|
2025-01-13 01:48:42 -05:00
|
|
|
// Helper functions
|
2025-01-22 23:11:45 -05:00
|
|
|
function formatMatches(matches: QueryMatch[]): Omit<QueryMatch, 'pattern'>[] {
|
|
|
|
|
return matches.map(({ patternIndex, captures }) => ({
|
|
|
|
|
patternIndex,
|
2020-05-11 15:22:05 -07:00
|
|
|
captures: formatCaptures(captures),
|
|
|
|
|
}));
|
2019-09-11 14:44:49 -07:00
|
|
|
}
|
2019-09-11 14:44:49 -07:00
|
|
|
|
2025-01-19 15:15:01 -05:00
|
|
|
function formatCaptures(captures: QueryCapture[]): (QueryCapture & { text: string })[] {
|
2020-05-11 15:22:05 -07:00
|
|
|
return captures.map((c) => {
|
2019-09-18 17:35:47 -07:00
|
|
|
const node = c.node;
|
2025-01-16 01:10:54 -05:00
|
|
|
// @ts-expect-error We're not interested in the node object for these tests
|
2019-09-18 17:35:47 -07:00
|
|
|
delete c.node;
|
2025-01-19 15:15:01 -05:00
|
|
|
return { ...c, text: node.text };
|
2020-05-11 15:22:05 -07:00
|
|
|
});
|
2019-09-11 14:44:49 -07:00
|
|
|
}
|