neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

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)