query_spec.lua (29257B)
1 local t = require('test.testutil') 2 local n = require('test.functional.testnvim')() 3 4 local clear = n.clear 5 local dedent = t.dedent 6 local eq = t.eq 7 local insert = n.insert 8 local exec_lua = n.exec_lua 9 local pcall_err = t.pcall_err 10 local api = n.api 11 12 local function get_query_result(query_text) 13 local cquery = vim.treesitter.query.parse('c', query_text) 14 local parser = vim.treesitter.get_parser(0, 'c') 15 local tree = parser:parse()[1] 16 local res = {} 17 for cid, node in cquery:iter_captures(tree:root(), 0) do 18 -- can't transmit node over RPC. just check the name, range, and text 19 local text = vim.treesitter.get_node_text(node, 0) 20 local range = { node:range() } 21 table.insert(res, { cquery.captures[cid], node:type(), range, text }) 22 end 23 return res 24 end 25 26 describe('treesitter query API', function() 27 before_each(function() 28 clear() 29 exec_lua(function() 30 vim.g.__ts_debug = 1 31 end) 32 end) 33 34 local test_text = [[ 35 void ui_refresh(void) 36 { 37 int width = INT_MAX, height = INT_MAX; 38 bool ext_widgets[kUIExtCount]; 39 for (UIExtension i = 0; (int)i < kUIExtCount; i++) { 40 ext_widgets[i] = true; 41 } 42 43 bool inclusive = ui_override(); 44 for (size_t i = 0; i < ui_count; i++) { 45 UI *ui = uis[i]; 46 width = MIN(ui->width, width); 47 height = MIN(ui->height, height); 48 foo = BAR(ui->bazaar, bazaar); 49 for (UIExtension j = 0; (int)j < kUIExtCount; j++) { 50 ext_widgets[j] &= (ui->ui_ext[j] || inclusive); 51 } 52 } 53 }]] 54 55 local test_query = [[ 56 ((call_expression 57 function: (identifier) @minfunc 58 (argument_list (identifier) @min_id)) 59 (#eq? @minfunc "MIN") 60 ) 61 62 "for" @keyword 63 64 (primitive_type) @type 65 66 (field_expression argument: (identifier) @fieldarg) 67 ]] 68 69 it('supports runtime queries', function() 70 ---@type string[] 71 local ret = exec_lua(function() 72 return vim.treesitter.query.get('c', 'highlights').captures 73 end) 74 75 -- see $VIMRUNTIME/queries/c/highlights.scm 76 eq('variable', ret[1]) 77 eq('keyword', ret[2]) 78 end) 79 80 it('supports caching queries', function() 81 local long_query = test_query:rep(100) 82 ---@return number 83 local function q(_n) 84 return exec_lua(function() 85 local before = vim.api.nvim__stats().ts_query_parse_count 86 collectgarbage('stop') 87 for _ = 1, _n, 1 do 88 vim.treesitter.query.parse('c', long_query) 89 end 90 collectgarbage('restart') 91 collectgarbage('collect') 92 local after = vim.api.nvim__stats().ts_query_parse_count 93 return after - before 94 end) 95 end 96 97 eq(1, q(1)) 98 -- cache is retained even after garbage collection 99 eq(0, q(100)) 100 end) 101 102 it('cache is cleared upon runtimepath changes, or setting query manually', function() 103 ---@return number 104 exec_lua(function() 105 _G.query_parse_count = _G.query_parse_count or 0 106 local parse = vim.treesitter.query.parse 107 vim.treesitter.query.parse = function(...) 108 _G.query_parse_count = _G.query_parse_count + 1 109 return parse(...) 110 end 111 end) 112 113 local function q(_n) 114 return exec_lua(function() 115 for _ = 1, _n, 1 do 116 vim.treesitter.query.get('c', 'highlights') 117 end 118 return _G.query_parse_count 119 end) 120 end 121 122 eq(1, q(10)) 123 exec_lua(function() 124 vim.opt.rtp:prepend('/another/dir') 125 end) 126 eq(2, q(100)) 127 exec_lua(function() 128 vim.treesitter.query.set('c', 'highlights', [[; test]]) 129 end) 130 eq(3, q(100)) 131 end) 132 133 it('supports query and iter by capture (iter_captures)', function() 134 insert(test_text) 135 136 local res = exec_lua(function() 137 local cquery = vim.treesitter.query.parse('c', test_query) 138 local parser = vim.treesitter.get_parser(0, 'c') 139 local tree = parser:parse()[1] 140 local res = {} 141 for cid, node in cquery:iter_captures(tree:root(), 0, 7, 14) do 142 -- can't transmit node over RPC. just check the name and range 143 table.insert(res, { '@' .. cquery.captures[cid], node:type(), node:range() }) 144 end 145 return res 146 end) 147 148 eq({ 149 { '@type', 'primitive_type', 8, 2, 8, 6 }, -- bool 150 { '@keyword', 'for', 9, 2, 9, 5 }, -- for 151 { '@type', 'primitive_type', 9, 7, 9, 13 }, -- size_t 152 { '@minfunc', 'identifier', 11, 12, 11, 15 }, -- "MIN"(ui->width, width); 153 { '@fieldarg', 'identifier', 11, 16, 11, 18 }, -- ui 154 { '@min_id', 'identifier', 11, 27, 11, 32 }, -- width 155 { '@minfunc', 'identifier', 12, 13, 12, 16 }, -- "MIN"(ui->height, height); 156 { '@fieldarg', 'identifier', 12, 17, 12, 19 }, -- ui 157 { '@min_id', 'identifier', 12, 29, 12, 35 }, -- height 158 { '@fieldarg', 'identifier', 13, 14, 13, 16 }, -- ui ; in BAR(..) 159 }, res) 160 end) 161 162 it('supports query and iter by match (iter_matches)', function() 163 insert(test_text) 164 165 ---@type table 166 local res = exec_lua(function() 167 local cquery = vim.treesitter.query.parse('c', test_query) 168 local parser = vim.treesitter.get_parser(0, 'c') 169 local tree = parser:parse()[1] 170 local res = {} 171 for pattern, match in cquery:iter_matches(tree:root(), 0, 7, 14) do 172 -- can't transmit node over RPC. just check the name and range 173 local mrepr = {} 174 for cid, nodes in pairs(match) do 175 for _, node in ipairs(nodes) do 176 table.insert(mrepr, { '@' .. cquery.captures[cid], node:type(), node:range() }) 177 end 178 end 179 table.insert(res, { pattern, mrepr }) 180 end 181 return res 182 end) 183 184 eq({ 185 { 3, { { '@type', 'primitive_type', 8, 2, 8, 6 } } }, 186 { 2, { { '@keyword', 'for', 9, 2, 9, 5 } } }, 187 { 3, { { '@type', 'primitive_type', 9, 7, 9, 13 } } }, 188 { 4, { { '@fieldarg', 'identifier', 11, 16, 11, 18 } } }, 189 { 190 1, 191 { 192 { '@minfunc', 'identifier', 11, 12, 11, 15 }, 193 { '@min_id', 'identifier', 11, 27, 11, 32 }, 194 }, 195 }, 196 { 4, { { '@fieldarg', 'identifier', 12, 17, 12, 19 } } }, 197 { 198 1, 199 { 200 { '@minfunc', 'identifier', 12, 13, 12, 16 }, 201 { '@min_id', 'identifier', 12, 29, 12, 35 }, 202 }, 203 }, 204 { 4, { { '@fieldarg', 'identifier', 13, 14, 13, 16 } } }, 205 }, res) 206 end) 207 208 it('supports query and iter by capture for quantifiers', function() 209 insert(test_text) 210 211 local res = exec_lua(function() 212 local cquery = vim.treesitter.query.parse( 213 'c', 214 '(expression_statement (assignment_expression (call_expression)))+ @funccall' 215 ) 216 local parser = vim.treesitter.get_parser(0, 'c') 217 local tree = parser:parse()[1] 218 local res = {} 219 for cid, node in cquery:iter_captures(tree:root(), 0, 7, 14) do 220 -- can't transmit node over RPC. just check the name and range 221 table.insert(res, { '@' .. cquery.captures[cid], node:type(), node:range() }) 222 end 223 return res 224 end) 225 226 eq({ 227 { '@funccall', 'expression_statement', 11, 4, 11, 34 }, 228 { '@funccall', 'expression_statement', 12, 4, 12, 37 }, 229 { '@funccall', 'expression_statement', 13, 4, 13, 34 }, 230 }, res) 231 end) 232 233 it('supports query and iter by match for quantifiers', function() 234 insert(test_text) 235 236 local res = exec_lua(function() 237 local cquery = vim.treesitter.query.parse( 238 'c', 239 '(expression_statement (assignment_expression (call_expression)))+ @funccall' 240 ) 241 local parser = vim.treesitter.get_parser(0, 'c') 242 local tree = parser:parse()[1] 243 local res = {} 244 for pattern, match in cquery:iter_matches(tree:root(), 0, 7, 14) do 245 -- can't transmit node over RPC. just check the name and range 246 local mrepr = {} 247 for cid, nodes in pairs(match) do 248 for _, node in ipairs(nodes) do 249 table.insert(mrepr, { '@' .. cquery.captures[cid], node:type(), node:range() }) 250 end 251 end 252 table.insert(res, { pattern, mrepr }) 253 end 254 return res 255 end, '(expression_statement (assignment_expression (call_expression)))+ @funccall') 256 257 eq({ 258 { 259 1, 260 { 261 { '@funccall', 'expression_statement', 11, 4, 11, 34 }, 262 { '@funccall', 'expression_statement', 12, 4, 12, 37 }, 263 { '@funccall', 'expression_statement', 13, 4, 13, 34 }, 264 }, 265 }, 266 }, res) 267 end) 268 269 it('returns quantified matches in order of range #29344', function() 270 insert([[ 271 int main() { 272 int a, b, c, d, e, f, g, h, i; 273 a = MIN(0, 1); 274 b = MIN(0, 1); 275 c = MIN(0, 1); 276 d = MIN(0, 1); 277 e = MIN(0, 1); 278 f = MIN(0, 1); 279 g = MIN(0, 1); 280 h = MIN(0, 1); 281 i = MIN(0, 1); 282 } 283 ]]) 284 285 local res = exec_lua(function() 286 local cquery = vim.treesitter.query.parse( 287 'c', 288 '(expression_statement (assignment_expression (call_expression)))+ @funccall' 289 ) 290 local parser = vim.treesitter.get_parser(0, 'c') 291 local tree = parser:parse()[1] 292 local res = {} 293 for pattern, match in cquery:iter_matches(tree:root(), 0, 7, 14) do 294 -- can't transmit node over RPC. just check the name and range 295 local mrepr = {} 296 for cid, nodes in pairs(match) do 297 for _, node in ipairs(nodes) do 298 table.insert(mrepr, { '@' .. cquery.captures[cid], node:type(), node:range() }) 299 end 300 end 301 table.insert(res, { pattern, mrepr }) 302 end 303 return res 304 end) 305 306 eq({ 307 { 308 1, 309 { 310 { '@funccall', 'expression_statement', 2, 2, 2, 16 }, 311 { '@funccall', 'expression_statement', 3, 2, 3, 16 }, 312 { '@funccall', 'expression_statement', 4, 2, 4, 16 }, 313 { '@funccall', 'expression_statement', 5, 2, 5, 16 }, 314 { '@funccall', 'expression_statement', 6, 2, 6, 16 }, 315 { '@funccall', 'expression_statement', 7, 2, 7, 16 }, 316 { '@funccall', 'expression_statement', 8, 2, 8, 16 }, 317 { '@funccall', 'expression_statement', 9, 2, 9, 16 }, 318 { '@funccall', 'expression_statement', 10, 2, 10, 16 }, 319 }, 320 }, 321 }, res) 322 end) 323 324 it('can match special regex characters like \\ * + ( with `vim-match?`', function() 325 insert('char* astring = "\\n"; (1 + 1) * 2 != 2;') 326 327 ---@type table 328 local res = exec_lua(function() 329 local query = ( 330 '([_] @plus (#vim-match? @plus "^\\\\+$"))' 331 .. '([_] @times (#vim-match? @times "^\\\\*$"))' 332 .. '([_] @paren (#vim-match? @paren "^\\\\($"))' 333 .. '([_] @escape (#vim-match? @escape "^\\\\\\\\n$"))' 334 .. '([_] @string (#vim-match? @string "^\\"\\\\\\\\n\\"$"))' 335 ) 336 local cquery = vim.treesitter.query.parse('c', query) 337 local parser = vim.treesitter.get_parser(0, 'c') 338 local tree = parser:parse()[1] 339 local res = {} 340 for pattern, match in cquery:iter_matches(tree:root(), 0, 0, -1) do 341 -- can't transmit node over RPC. just check the name and range 342 local mrepr = {} 343 for cid, nodes in pairs(match) do 344 for _, node in ipairs(nodes) do 345 table.insert(mrepr, { '@' .. cquery.captures[cid], node:type(), node:range() }) 346 end 347 end 348 table.insert(res, { pattern, mrepr }) 349 end 350 return res 351 end) 352 353 eq({ 354 { 2, { { '@times', '*', 0, 4, 0, 5 } } }, 355 { 5, { { '@string', 'string_literal', 0, 16, 0, 20 } } }, 356 { 4, { { '@escape', 'escape_sequence', 0, 17, 0, 19 } } }, 357 { 3, { { '@paren', '(', 0, 22, 0, 23 } } }, 358 { 1, { { '@plus', '+', 0, 25, 0, 26 } } }, 359 { 2, { { '@times', '*', 0, 30, 0, 31 } } }, 360 }, res) 361 end) 362 363 it('supports builtin query predicate any-of?', function() 364 insert([[ 365 #include <stdio.h> 366 367 int main(void) { 368 int i; 369 for(i=1; i<=100; i++) { 370 if(((i%3)||(i%5))== 0) 371 printf("number= %d FizzBuzz\n", i); 372 else if((i%3)==0) 373 printf("number= %d Fizz\n", i); 374 else if((i%5)==0) 375 printf("number= %d Buzz\n", i); 376 else 377 printf("number= %d\n",i); 378 } 379 return 0; 380 } 381 ]]) 382 383 local res0 = exec_lua( 384 get_query_result, 385 [[((primitive_type) @c-keyword (#any-of? @c-keyword "int" "float"))]] 386 ) 387 eq({ 388 { 'c-keyword', 'primitive_type', { 2, 0, 2, 3 }, 'int' }, 389 { 'c-keyword', 'primitive_type', { 3, 2, 3, 5 }, 'int' }, 390 }, res0) 391 392 local res1 = exec_lua( 393 get_query_result, 394 [[ 395 ((string_literal) @fizzbuzz-strings (#any-of? @fizzbuzz-strings 396 "\"number= %d FizzBuzz\\n\"" 397 "\"number= %d Fizz\\n\"" 398 "\"number= %d Buzz\\n\"" 399 )) 400 ]] 401 ) 402 eq({ 403 { 'fizzbuzz-strings', 'string_literal', { 6, 13, 6, 36 }, '"number= %d FizzBuzz\\n"' }, 404 { 'fizzbuzz-strings', 'string_literal', { 8, 13, 8, 32 }, '"number= %d Fizz\\n"' }, 405 { 'fizzbuzz-strings', 'string_literal', { 10, 13, 10, 32 }, '"number= %d Buzz\\n"' }, 406 }, res1) 407 end) 408 409 it('supports builtin predicate has-ancestor?', function() 410 insert([[ 411 int x = 123; 412 enum C { y = 124 }; 413 int main() { int z = 125; }]]) 414 415 local result = exec_lua( 416 get_query_result, 417 [[((number_literal) @literal (#has-ancestor? @literal "function_definition"))]] 418 ) 419 eq({ { 'literal', 'number_literal', { 2, 21, 2, 24 }, '125' } }, result) 420 421 result = exec_lua( 422 get_query_result, 423 [[((number_literal) @literal (#has-ancestor? @literal "function_definition" "enum_specifier"))]] 424 ) 425 eq({ 426 { 'literal', 'number_literal', { 1, 13, 1, 16 }, '124' }, 427 { 'literal', 'number_literal', { 2, 21, 2, 24 }, '125' }, 428 }, result) 429 430 result = exec_lua( 431 get_query_result, 432 [[((number_literal) @literal (#not-has-ancestor? @literal "enum_specifier"))]] 433 ) 434 eq({ 435 { 'literal', 'number_literal', { 0, 8, 0, 11 }, '123' }, 436 { 'literal', 'number_literal', { 2, 21, 2, 24 }, '125' }, 437 }, result) 438 439 result = exec_lua( 440 get_query_result, 441 [[((number_literal) @literal (#has-ancestor? @literal "enumerator"))]] 442 ) 443 eq({ 444 { 'literal', 'number_literal', { 1, 13, 1, 16 }, '124' }, 445 }, result) 446 447 result = exec_lua( 448 get_query_result, 449 [[((number_literal) @literal (#has-ancestor? @literal "number_literal"))]] 450 ) 451 eq({}, result) 452 end) 453 454 it('allows loading query with escaped quotes and capture them `#{lua,vim}-match`?', function() 455 insert('char* astring = "Hello World!";') 456 457 local res = exec_lua(function() 458 local cquery = vim.treesitter.query.parse( 459 'c', 460 '([_] @quote (#vim-match? @quote "^\\"$")) ([_] @quote (#lua-match? @quote "^\\"$"))' 461 ) 462 local parser = vim.treesitter.get_parser(0, 'c') 463 local tree = parser:parse()[1] 464 local res = {} 465 for pattern, match in cquery:iter_matches(tree:root(), 0, 0, -1) do 466 -- can't transmit node over RPC. just check the name and range 467 local mrepr = {} 468 for cid, nodes in pairs(match) do 469 for _, node in ipairs(nodes) do 470 table.insert(mrepr, { '@' .. cquery.captures[cid], node:type(), node:range() }) 471 end 472 end 473 table.insert(res, { pattern, mrepr }) 474 end 475 return res 476 end) 477 478 eq({ 479 { 1, { { '@quote', '"', 0, 16, 0, 17 } } }, 480 { 2, { { '@quote', '"', 0, 16, 0, 17 } } }, 481 { 1, { { '@quote', '"', 0, 29, 0, 30 } } }, 482 { 2, { { '@quote', '"', 0, 29, 0, 30 } } }, 483 }, res) 484 end) 485 486 it('allows to add predicates', function() 487 insert([[ 488 int main(void) { 489 return 0; 490 } 491 ]]) 492 493 local custom_query = '((identifier) @main (#is-main? @main))' 494 495 do 496 local res = exec_lua(function() 497 local query = vim.treesitter.query 498 499 local function is_main(match, _pattern, bufnr, predicate) 500 local nodes = match[predicate[2]] 501 for _, node in ipairs(nodes) do 502 if vim.treesitter.get_node_text(node, bufnr) == 'main' then 503 return true 504 end 505 end 506 return false 507 end 508 509 local parser = vim.treesitter.get_parser(0, 'c') 510 511 query.add_predicate('is-main?', is_main) 512 513 local query0 = query.parse('c', custom_query) 514 515 local nodes = {} 516 for _, node in query0:iter_captures(parser:parse()[1]:root(), 0) do 517 table.insert(nodes, { node:range() }) 518 end 519 520 return nodes 521 end) 522 523 eq({ { 0, 4, 0, 8 } }, res) 524 end 525 526 -- Once with the old API. Remove this whole 'do' block in 0.12 527 do 528 local res = exec_lua(function() 529 local query = vim.treesitter.query 530 531 local function is_main(match, _pattern, bufnr, predicate) 532 local node = match[predicate[2]] 533 534 return vim.treesitter.get_node_text(node, bufnr) == 'main' 535 end 536 537 local parser = vim.treesitter.get_parser(0, 'c') 538 539 query.add_predicate('is-main?', is_main, { all = false, force = true }) 540 541 local query0 = query.parse('c', custom_query) 542 543 local nodes = {} 544 for _, node in query0:iter_captures(parser:parse()[1]:root(), 0) do 545 table.insert(nodes, { node:range() }) 546 end 547 548 return nodes 549 end) 550 551 -- Remove this 'do' block in 0.12 552 -- eq(0, n.fn.has('nvim-0.12')) 553 eq({ { 0, 4, 0, 8 } }, res) 554 end 555 556 do 557 local res = exec_lua(function() 558 local query = vim.treesitter.query 559 560 local r = {} 561 for _, v in ipairs(query.list_predicates()) do 562 r[v] = true 563 end 564 565 return r 566 end) 567 568 eq(true, res['is-main?']) 569 end 570 end) 571 572 it('supports "all" and "any" semantics for predicates on quantified captures #24738', function() 573 local query_all = [[ 574 (((comment (comment_content))+) @bar 575 (#lua-match? @bar "Yes")) 576 ]] 577 578 local query_any = [[ 579 (((comment (comment_content))+) @bar 580 (#any-lua-match? @bar "Yes")) 581 ]] 582 583 local function test(input, query) 584 api.nvim_buf_set_lines(0, 0, -1, true, vim.split(dedent(input), '\n')) 585 return exec_lua(function() 586 local parser = vim.treesitter.get_parser(0, 'lua') 587 local query0 = vim.treesitter.query.parse('lua', query) 588 local nodes = {} 589 for _, node in query0:iter_captures(parser:parse()[1]:root(), 0) do 590 nodes[#nodes + 1] = { node:range() } 591 end 592 return nodes 593 end) 594 end 595 596 eq( 597 {}, 598 test( 599 [[ 600 -- Yes 601 -- No 602 -- Yes 603 ]], 604 query_all 605 ) 606 ) 607 608 eq( 609 { 610 { 0, 0, 0, 6 }, 611 { 1, 0, 1, 6 }, 612 { 2, 0, 2, 6 }, 613 }, 614 test( 615 [[ 616 -- Yes 617 -- Yes 618 -- Yes 619 ]], 620 query_all 621 ) 622 ) 623 624 eq( 625 {}, 626 test( 627 [[ 628 -- No 629 -- No 630 -- No 631 ]], 632 query_any 633 ) 634 ) 635 636 eq( 637 { 638 { 0, 0, 0, 5 }, 639 { 1, 0, 1, 6 }, 640 { 2, 0, 2, 5 }, 641 }, 642 test( 643 [[ 644 -- No 645 -- Yes 646 -- No 647 ]], 648 query_any 649 ) 650 ) 651 end) 652 653 it('supports any- prefix to match any capture when using quantifiers #24738', function() 654 insert([[ 655 -- Comment 656 -- Comment 657 -- Comment 658 ]]) 659 660 local result = exec_lua(function() 661 local parser = vim.treesitter.get_parser(0, 'lua') 662 local query = vim.treesitter.query.parse( 663 'lua', 664 [[ 665 (((comment (comment_content))+) @bar 666 (#lua-match? @bar "Comment")) 667 ]] 668 ) 669 local nodes = {} 670 for _, node in query:iter_captures(parser:parse()[1]:root(), 0) do 671 nodes[#nodes + 1] = { node:range() } 672 end 673 return nodes 674 end) 675 676 eq({ 677 { 0, 0, 0, 10 }, 678 { 1, 0, 1, 10 }, 679 { 2, 0, 2, 10 }, 680 }, result) 681 end) 682 683 it('supports the old broken version of iter_matches #24738', function() 684 -- Delete this test in 0.12 when iter_matches is removed 685 -- eq(0, n.fn.has('nvim-0.12')) 686 687 insert(test_text) 688 local res = exec_lua(function() 689 local cquery = vim.treesitter.query.parse('c', test_query) 690 local parser = vim.treesitter.get_parser(0, 'c') 691 local tree = parser:parse()[1] 692 local res = {} 693 for pattern, match in cquery:iter_matches(tree:root(), 0, 7, 14, { all = false }) do 694 local mrepr = {} 695 for cid, node in pairs(match) do 696 table.insert(mrepr, { '@' .. cquery.captures[cid], node:type(), node:range() }) 697 end 698 table.insert(res, { pattern, mrepr }) 699 end 700 return res 701 end) 702 703 eq({ 704 { 3, { { '@type', 'primitive_type', 8, 2, 8, 6 } } }, 705 { 2, { { '@keyword', 'for', 9, 2, 9, 5 } } }, 706 { 3, { { '@type', 'primitive_type', 9, 7, 9, 13 } } }, 707 { 4, { { '@fieldarg', 'identifier', 11, 16, 11, 18 } } }, 708 { 709 1, 710 { 711 { '@minfunc', 'identifier', 11, 12, 11, 15 }, 712 { '@min_id', 'identifier', 11, 27, 11, 32 }, 713 }, 714 }, 715 { 4, { { '@fieldarg', 'identifier', 12, 17, 12, 19 } } }, 716 { 717 1, 718 { 719 { '@minfunc', 'identifier', 12, 13, 12, 16 }, 720 { '@min_id', 'identifier', 12, 29, 12, 35 }, 721 }, 722 }, 723 { 4, { { '@fieldarg', 'identifier', 13, 14, 13, 16 } } }, 724 }, res) 725 end) 726 727 it('should use node range when omitted', function() 728 local txt = [[ 729 int foo = 42; 730 int bar = 13; 731 ]] 732 733 local ret = exec_lua(function() 734 local parser = vim.treesitter.get_string_parser(txt, 'c') 735 736 local nodes = {} 737 local query = vim.treesitter.query.parse('c', '((identifier) @foo)') 738 local first_child = assert(parser:parse()[1]:root():child(1)) 739 740 for _, node in query:iter_captures(first_child, txt) do 741 table.insert(nodes, { node:range() }) 742 end 743 744 return nodes 745 end) 746 747 eq({ { 1, 10, 1, 13 } }, ret) 748 end) 749 750 it('iter_captures supports columns', function() 751 local txt = table.concat({ 752 'int aaa = 1, bbb = 2;', 753 'int foo = 1, bar = 2;', 754 'int baz = 3, qux = 4;', 755 'int ccc = 1, ddd = 2;', 756 }, '\n') 757 758 local function test(opts) 759 local parser = vim.treesitter.get_string_parser(txt, 'c') 760 761 local nodes = {} 762 local query = vim.treesitter.query.parse('c', '((identifier) @foo)') 763 local root = assert(parser:parse()[1]:root()) 764 local iter = query:iter_captures(root, txt, 1, 2, opts) 765 766 while true do 767 local capture, node = iter() 768 if not capture then 769 break 770 end 771 table.insert(nodes, { node:range() }) 772 end 773 774 return nodes 775 end 776 777 local ret 778 ret = exec_lua(test, { start_col = 7, end_col = 13 }) 779 eq({ { 1, 13, 1, 16 }, { 2, 4, 2, 7 } }, ret) 780 781 ret = exec_lua(test, { start_col = 7 }) 782 eq({ { 1, 13, 1, 16 } }, ret) 783 784 ret = exec_lua(test, { end_col = 13 }) 785 eq({ { 1, 4, 1, 7 }, { 1, 13, 1, 16 }, { 2, 4, 2, 7 } }, ret) 786 787 ret = exec_lua(test, {}) 788 eq({ { 1, 4, 1, 7 }, { 1, 13, 1, 16 } }, ret) 789 end) 790 791 it('fails to load queries', function() 792 local function test(exp, cquery) 793 eq(exp, pcall_err(exec_lua, "vim.treesitter.query.parse('c', ...)", cquery)) 794 end 795 796 -- Invalid node types 797 test( 798 '.../query.lua:0: Query error at 1:2. Invalid node type ">\\">>":\n' 799 .. '">\\">>" @operator\n' 800 .. ' ^', 801 '">\\">>" @operator' 802 ) 803 test( 804 '.../query.lua:0: Query error at 1:2. Invalid node type "\\\\":\n' 805 .. '"\\\\" @operator\n' 806 .. ' ^', 807 '"\\\\" @operator' 808 ) 809 test( 810 '.../query.lua:0: Query error at 1:2. Invalid node type ">>>":\n' 811 .. '">>>" @operator\n' 812 .. ' ^', 813 '">>>" @operator' 814 ) 815 test( 816 '.../query.lua:0: Query error at 1:2. Invalid node type "dentifier":\n' 817 .. '(dentifier) @variable\n' 818 .. ' ^', 819 '(dentifier) @variable' 820 ) 821 822 -- Impossible pattern 823 test( 824 '.../query.lua:0: Query error at 1:13. Impossible pattern:\n' 825 .. '(identifier (identifier) @variable)\n' 826 .. ' ^', 827 '(identifier (identifier) @variable)' 828 ) 829 830 -- Invalid syntax 831 test( 832 '.../query.lua:0: Query error at 1:13. Invalid syntax:\n' 833 .. '(identifier @variable\n' 834 .. ' ^', 835 '(identifier @variable' 836 ) 837 838 -- Invalid field name 839 test( 840 '.../query.lua:0: Query error at 1:15. Invalid field name "invalid_field":\n' 841 .. '((identifier) invalid_field: (identifier))\n' 842 .. ' ^', 843 '((identifier) invalid_field: (identifier))' 844 ) 845 846 -- Invalid capture name 847 test( 848 '.../query.lua:0: Query error at 3:2. Invalid capture name "ok.capture":\n' 849 .. '@ok.capture\n' 850 .. ' ^', 851 '((identifier) @id \n(#eq? @id\n@ok.capture\n))' 852 ) 853 end) 854 855 it('supports "; extends" modeline in custom queries', function() 856 insert('int zeero = 0;') 857 local result = exec_lua(function() 858 vim.treesitter.query.set( 859 'c', 860 'highlights', 861 [[; extends 862 (identifier) @spell]] 863 ) 864 local query = vim.treesitter.query.get('c', 'highlights') 865 local parser = vim.treesitter.get_parser(0, 'c') 866 local root = parser:parse()[1]:root() 867 local res = {} 868 for id, node in query:iter_captures(root, 0) do 869 table.insert(res, { query.captures[id], vim.treesitter.get_node_text(node, 0) }) 870 end 871 return res 872 end) 873 eq({ 874 { 'type.builtin', 'int' }, 875 { 'variable', 'zeero' }, 876 { 'spell', 'zeero' }, 877 { 'operator', '=' }, 878 { 'number', '0' }, 879 { 'punctuation.delimiter', ';' }, 880 }, result) 881 end) 882 883 describe('Query:iter_captures', function() 884 it('includes metadata for all captured nodes #23664', function() 885 insert([[ 886 const char *sql = "SELECT * FROM Students WHERE name = 'Robert'); DROP TABLE Students;--"; 887 ]]) 888 889 local result = exec_lua(function() 890 local query = vim.treesitter.query.parse( 891 'c', 892 [[ 893 (declaration 894 type: (_) 895 declarator: (init_declarator 896 declarator: (pointer_declarator 897 declarator: (identifier)) @_id 898 value: (string_literal 899 (string_content) @injection.content)) 900 (#set! injection.language "sql") 901 (#contains? @_id "sql")) 902 ]] 903 ) 904 local parser = vim.treesitter.get_parser(0, 'c') 905 local root = parser:parse()[1]:root() 906 local res = {} 907 for id, _, metadata in query:iter_captures(root, 0) do 908 res[query.captures[id]] = metadata 909 end 910 return res 911 end) 912 913 eq({ 914 ['_id'] = { ['injection.language'] = 'sql' }, 915 ['injection.content'] = { ['injection.language'] = 'sql' }, 916 }, result) 917 end) 918 919 it('only evaluates predicates once per match', function() 920 insert([[ 921 void foo(int x, int y); 922 ]]) 923 local query = [[ 924 (declaration 925 type: (_) 926 declarator: (function_declarator 927 declarator: (identifier) @function.name 928 parameters: (parameter_list 929 (parameter_declaration 930 type: (_) 931 declarator: (identifier) @argument))) 932 (#eq? @function.name "foo")) 933 ]] 934 935 local result = exec_lua(function() 936 local query0 = vim.treesitter.query.parse('c', query) 937 local match_preds = query0._match_predicates 938 local called = 0 939 function query0:_match_predicates(...) 940 called = called + 1 941 return match_preds(self, ...) 942 end 943 local parser = vim.treesitter.get_parser(0, 'c') 944 local root = parser:parse()[1]:root() 945 local captures = {} 946 for id in query0:iter_captures(root, 0) do 947 captures[#captures + 1] = id 948 end 949 return { called, captures } 950 end) 951 952 eq({ 2, { 1, 1, 2, 2 } }, result) 953 end) 954 end) 955 956 describe('TSQuery', function() 957 local source = [[ 958 void foo(int x, int y); 959 ]] 960 961 local query_text = [[ 962 ((identifier) @func 963 (#eq? @func "foo")) 964 ((identifier) @param 965 (#eq? @param "x")) 966 ((identifier) @param 967 (#eq? @param "y")) 968 ]] 969 970 ---@param query string 971 ---@param disabled { capture: string?, pattern: integer? } 972 local function get_patterns(query, disabled) 973 local q = vim.treesitter.query.parse('c', query) 974 if disabled.capture then 975 q.query:disable_capture(disabled.capture) 976 end 977 if disabled.pattern then 978 q.query:disable_pattern(disabled.pattern) 979 end 980 981 local parser = vim.treesitter.get_parser(0, 'c') 982 local root = parser:parse()[1]:root() 983 local captures = {} ---@type {id: number, pattern: number}[] 984 for id, _, _, match in q:iter_captures(root, 0) do 985 local _, pattern = match:info() 986 captures[#captures + 1] = { id = id, pattern = pattern } 987 end 988 return captures 989 end 990 991 it('supports disabling patterns', function() 992 insert(source) 993 local result = exec_lua(get_patterns, query_text, { pattern = 2 }) 994 eq({ { id = 1, pattern = 1 }, { id = 2, pattern = 3 } }, result) 995 end) 996 997 it('supports disabling captures', function() 998 insert(source) 999 local result = exec_lua(get_patterns, query_text, { capture = 'param' }) 1000 eq({ { id = 1, pattern = 1 } }, result) 1001 end) 1002 end) 1003 end)