mirror of
				https://github.com/neovim/neovim.git
				synced 2025-10-26 12:27:24 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			857 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			857 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
| local t = require('test.testutil')
 | |
| local n = require('test.functional.testnvim')()
 | |
| 
 | |
| local clear = n.clear
 | |
| local dedent = t.dedent
 | |
| local eq = t.eq
 | |
| local insert = n.insert
 | |
| local exec_lua = n.exec_lua
 | |
| local pcall_err = t.pcall_err
 | |
| local api = n.api
 | |
| local fn = n.fn
 | |
| 
 | |
| local function get_query_result(query_text)
 | |
|   local cquery = vim.treesitter.query.parse('c', query_text)
 | |
|   local parser = vim.treesitter.get_parser(0, 'c')
 | |
|   local tree = parser:parse()[1]
 | |
|   local res = {}
 | |
|   for cid, node in cquery:iter_captures(tree:root(), 0) do
 | |
|     -- can't transmit node over RPC. just check the name, range, and text
 | |
|     local text = vim.treesitter.get_node_text(node, 0)
 | |
|     local range = { node:range() }
 | |
|     table.insert(res, { cquery.captures[cid], node:type(), range, text })
 | |
|   end
 | |
|   return res
 | |
| end
 | |
| 
 | |
| describe('treesitter query API', function()
 | |
|   before_each(function()
 | |
|     clear()
 | |
|     exec_lua(function()
 | |
|       vim.g.__ts_debug = 1
 | |
|     end)
 | |
|   end)
 | |
| 
 | |
|   local test_text = [[
 | |
| void ui_refresh(void)
 | |
| {
 | |
|   int width = INT_MAX, height = INT_MAX;
 | |
|   bool ext_widgets[kUIExtCount];
 | |
|   for (UIExtension i = 0; (int)i < kUIExtCount; i++) {
 | |
|     ext_widgets[i] = true;
 | |
|   }
 | |
| 
 | |
|   bool inclusive = ui_override();
 | |
|   for (size_t i = 0; i < ui_count; i++) {
 | |
|     UI *ui = uis[i];
 | |
|     width = MIN(ui->width, width);
 | |
|     height = MIN(ui->height, height);
 | |
|     foo = BAR(ui->bazaar, bazaar);
 | |
|     for (UIExtension j = 0; (int)j < kUIExtCount; j++) {
 | |
|       ext_widgets[j] &= (ui->ui_ext[j] || inclusive);
 | |
|     }
 | |
|   }
 | |
| }]]
 | |
| 
 | |
|   local test_query = [[
 | |
|     ((call_expression
 | |
|       function: (identifier) @minfunc
 | |
|       (argument_list (identifier) @min_id))
 | |
|       (#eq? @minfunc "MIN")
 | |
|     )
 | |
| 
 | |
|     "for" @keyword
 | |
| 
 | |
|     (primitive_type) @type
 | |
| 
 | |
|     (field_expression argument: (identifier) @fieldarg)
 | |
|   ]]
 | |
| 
 | |
|   it('supports runtime queries', function()
 | |
|     ---@type string[]
 | |
|     local ret = exec_lua(function()
 | |
|       return vim.treesitter.query.get('c', 'highlights').captures
 | |
|     end)
 | |
| 
 | |
|     -- see $VIMRUNTIME/queries/c/highlights.scm
 | |
|     eq('variable', ret[1])
 | |
|     eq('keyword', ret[2])
 | |
|   end)
 | |
| 
 | |
|   it('supports caching queries', function()
 | |
|     local long_query = test_query:rep(100)
 | |
|     ---@return number
 | |
|     local function q(_n)
 | |
|       return exec_lua(function()
 | |
|         local before = vim.api.nvim__stats().ts_query_parse_count
 | |
|         collectgarbage('stop')
 | |
|         for _ = 1, _n, 1 do
 | |
|           vim.treesitter.query.parse('c', long_query, _n)
 | |
|         end
 | |
|         collectgarbage('restart')
 | |
|         collectgarbage('collect')
 | |
|         local after = vim.api.nvim__stats().ts_query_parse_count
 | |
|         return after - before
 | |
|       end)
 | |
|     end
 | |
| 
 | |
|     eq(1, q(1))
 | |
|     -- cache is cleared by garbage collection even if valid "cquery" reference is kept around
 | |
|     eq(1, q(100))
 | |
|   end)
 | |
| 
 | |
|   it('supports query and iter by capture (iter_captures)', function()
 | |
|     insert(test_text)
 | |
| 
 | |
|     local res = exec_lua(function()
 | |
|       local cquery = vim.treesitter.query.parse('c', test_query)
 | |
|       local parser = vim.treesitter.get_parser(0, 'c')
 | |
|       local tree = parser:parse()[1]
 | |
|       local res = {}
 | |
|       for cid, node in cquery:iter_captures(tree:root(), 0, 7, 14) do
 | |
|         -- can't transmit node over RPC. just check the name and range
 | |
|         table.insert(res, { '@' .. cquery.captures[cid], node:type(), node:range() })
 | |
|       end
 | |
|       return res
 | |
|     end)
 | |
| 
 | |
|     eq({
 | |
|       { '@type', 'primitive_type', 8, 2, 8, 6 }, -- bool
 | |
|       { '@keyword', 'for', 9, 2, 9, 5 }, -- for
 | |
|       { '@type', 'primitive_type', 9, 7, 9, 13 }, -- size_t
 | |
|       { '@minfunc', 'identifier', 11, 12, 11, 15 }, -- "MIN"(ui->width, width);
 | |
|       { '@fieldarg', 'identifier', 11, 16, 11, 18 }, --      ui
 | |
|       { '@min_id', 'identifier', 11, 27, 11, 32 }, -- width
 | |
|       { '@minfunc', 'identifier', 12, 13, 12, 16 }, -- "MIN"(ui->height, height);
 | |
|       { '@fieldarg', 'identifier', 12, 17, 12, 19 }, --      ui
 | |
|       { '@min_id', 'identifier', 12, 29, 12, 35 }, -- height
 | |
|       { '@fieldarg', 'identifier', 13, 14, 13, 16 }, -- ui   ; in BAR(..)
 | |
|     }, res)
 | |
|   end)
 | |
| 
 | |
|   it('supports query and iter by match (iter_matches)', function()
 | |
|     insert(test_text)
 | |
| 
 | |
|     ---@type table
 | |
|     local res = exec_lua(function()
 | |
|       local cquery = vim.treesitter.query.parse('c', test_query)
 | |
|       local parser = vim.treesitter.get_parser(0, 'c')
 | |
|       local tree = parser:parse()[1]
 | |
|       local res = {}
 | |
|       for pattern, match in cquery:iter_matches(tree:root(), 0, 7, 14) do
 | |
|         -- can't transmit node over RPC. just check the name and range
 | |
|         local mrepr = {}
 | |
|         for cid, nodes in pairs(match) do
 | |
|           for _, node in ipairs(nodes) do
 | |
|             table.insert(mrepr, { '@' .. cquery.captures[cid], node:type(), node:range() })
 | |
|           end
 | |
|         end
 | |
|         table.insert(res, { pattern, mrepr })
 | |
|       end
 | |
|       return res
 | |
|     end)
 | |
| 
 | |
|     eq({
 | |
|       { 3, { { '@type', 'primitive_type', 8, 2, 8, 6 } } },
 | |
|       { 2, { { '@keyword', 'for', 9, 2, 9, 5 } } },
 | |
|       { 3, { { '@type', 'primitive_type', 9, 7, 9, 13 } } },
 | |
|       { 4, { { '@fieldarg', 'identifier', 11, 16, 11, 18 } } },
 | |
|       {
 | |
|         1,
 | |
|         {
 | |
|           { '@minfunc', 'identifier', 11, 12, 11, 15 },
 | |
|           { '@min_id', 'identifier', 11, 27, 11, 32 },
 | |
|         },
 | |
|       },
 | |
|       { 4, { { '@fieldarg', 'identifier', 12, 17, 12, 19 } } },
 | |
|       {
 | |
|         1,
 | |
|         {
 | |
|           { '@minfunc', 'identifier', 12, 13, 12, 16 },
 | |
|           { '@min_id', 'identifier', 12, 29, 12, 35 },
 | |
|         },
 | |
|       },
 | |
|       { 4, { { '@fieldarg', 'identifier', 13, 14, 13, 16 } } },
 | |
|     }, res)
 | |
|   end)
 | |
| 
 | |
|   it('supports query and iter by capture for quantifiers', function()
 | |
|     insert(test_text)
 | |
| 
 | |
|     local res = exec_lua(function()
 | |
|       local cquery = vim.treesitter.query.parse(
 | |
|         'c',
 | |
|         '(expression_statement (assignment_expression (call_expression)))+ @funccall'
 | |
|       )
 | |
|       local parser = vim.treesitter.get_parser(0, 'c')
 | |
|       local tree = parser:parse()[1]
 | |
|       local res = {}
 | |
|       for cid, node in cquery:iter_captures(tree:root(), 0, 7, 14) do
 | |
|         -- can't transmit node over RPC. just check the name and range
 | |
|         table.insert(res, { '@' .. cquery.captures[cid], node:type(), node:range() })
 | |
|       end
 | |
|       return res
 | |
|     end)
 | |
| 
 | |
|     eq({
 | |
|       { '@funccall', 'expression_statement', 11, 4, 11, 34 },
 | |
|       { '@funccall', 'expression_statement', 12, 4, 12, 37 },
 | |
|       { '@funccall', 'expression_statement', 13, 4, 13, 34 },
 | |
|     }, res)
 | |
|   end)
 | |
| 
 | |
|   it('supports query and iter by match for quantifiers', function()
 | |
|     insert(test_text)
 | |
| 
 | |
|     local res = exec_lua(function()
 | |
|       local cquery = vim.treesitter.query.parse(
 | |
|         'c',
 | |
|         '(expression_statement (assignment_expression (call_expression)))+ @funccall'
 | |
|       )
 | |
|       local parser = vim.treesitter.get_parser(0, 'c')
 | |
|       local tree = parser:parse()[1]
 | |
|       local res = {}
 | |
|       for pattern, match in cquery:iter_matches(tree:root(), 0, 7, 14) do
 | |
|         -- can't transmit node over RPC. just check the name and range
 | |
|         local mrepr = {}
 | |
|         for cid, nodes in pairs(match) do
 | |
|           for _, node in ipairs(nodes) do
 | |
|             table.insert(mrepr, { '@' .. cquery.captures[cid], node:type(), node:range() })
 | |
|           end
 | |
|         end
 | |
|         table.insert(res, { pattern, mrepr })
 | |
|       end
 | |
|       return res
 | |
|     end, '(expression_statement (assignment_expression (call_expression)))+ @funccall')
 | |
| 
 | |
|     eq({
 | |
|       {
 | |
|         1,
 | |
|         {
 | |
|           { '@funccall', 'expression_statement', 11, 4, 11, 34 },
 | |
|           { '@funccall', 'expression_statement', 12, 4, 12, 37 },
 | |
|           { '@funccall', 'expression_statement', 13, 4, 13, 34 },
 | |
|         },
 | |
|       },
 | |
|     }, res)
 | |
|   end)
 | |
| 
 | |
|   it('returns quantified matches in order of range #29344', function()
 | |
|     insert([[
 | |
|     int main() {
 | |
|       int a, b, c, d, e, f, g, h, i;
 | |
|       a = MIN(0, 1);
 | |
|       b = MIN(0, 1);
 | |
|       c = MIN(0, 1);
 | |
|       d = MIN(0, 1);
 | |
|       e = MIN(0, 1);
 | |
|       f = MIN(0, 1);
 | |
|       g = MIN(0, 1);
 | |
|       h = MIN(0, 1);
 | |
|       i = MIN(0, 1);
 | |
|     }
 | |
|     ]])
 | |
| 
 | |
|     local res = exec_lua(function()
 | |
|       local cquery = vim.treesitter.query.parse(
 | |
|         'c',
 | |
|         '(expression_statement (assignment_expression (call_expression)))+ @funccall'
 | |
|       )
 | |
|       local parser = vim.treesitter.get_parser(0, 'c')
 | |
|       local tree = parser:parse()[1]
 | |
|       local res = {}
 | |
|       for pattern, match in cquery:iter_matches(tree:root(), 0, 7, 14) do
 | |
|         -- can't transmit node over RPC. just check the name and range
 | |
|         local mrepr = {}
 | |
|         for cid, nodes in pairs(match) do
 | |
|           for _, node in ipairs(nodes) do
 | |
|             table.insert(mrepr, { '@' .. cquery.captures[cid], node:type(), node:range() })
 | |
|           end
 | |
|         end
 | |
|         table.insert(res, { pattern, mrepr })
 | |
|       end
 | |
|       return res
 | |
|     end)
 | |
| 
 | |
|     eq({
 | |
|       {
 | |
|         1,
 | |
|         {
 | |
|           { '@funccall', 'expression_statement', 2, 2, 2, 16 },
 | |
|           { '@funccall', 'expression_statement', 3, 2, 3, 16 },
 | |
|           { '@funccall', 'expression_statement', 4, 2, 4, 16 },
 | |
|           { '@funccall', 'expression_statement', 5, 2, 5, 16 },
 | |
|           { '@funccall', 'expression_statement', 6, 2, 6, 16 },
 | |
|           { '@funccall', 'expression_statement', 7, 2, 7, 16 },
 | |
|           { '@funccall', 'expression_statement', 8, 2, 8, 16 },
 | |
|           { '@funccall', 'expression_statement', 9, 2, 9, 16 },
 | |
|           { '@funccall', 'expression_statement', 10, 2, 10, 16 },
 | |
|         },
 | |
|       },
 | |
|     }, res)
 | |
|   end)
 | |
| 
 | |
|   it('can match special regex characters like \\ * + ( with `vim-match?`', function()
 | |
|     insert('char* astring = "\\n"; (1 + 1) * 2 != 2;')
 | |
| 
 | |
|     ---@type table
 | |
|     local res = exec_lua(function()
 | |
|       local query = (
 | |
|         '([_] @plus (#vim-match? @plus "^\\\\+$"))'
 | |
|         .. '([_] @times (#vim-match? @times "^\\\\*$"))'
 | |
|         .. '([_] @paren (#vim-match? @paren "^\\\\($"))'
 | |
|         .. '([_] @escape (#vim-match? @escape "^\\\\\\\\n$"))'
 | |
|         .. '([_] @string (#vim-match? @string "^\\"\\\\\\\\n\\"$"))'
 | |
|       )
 | |
|       local cquery = vim.treesitter.query.parse('c', query)
 | |
|       local parser = vim.treesitter.get_parser(0, 'c')
 | |
|       local tree = parser:parse()[1]
 | |
|       local res = {}
 | |
|       for pattern, match in cquery:iter_matches(tree:root(), 0, 0, -1) do
 | |
|         -- can't transmit node over RPC. just check the name and range
 | |
|         local mrepr = {}
 | |
|         for cid, nodes in pairs(match) do
 | |
|           for _, node in ipairs(nodes) do
 | |
|             table.insert(mrepr, { '@' .. cquery.captures[cid], node:type(), node:range() })
 | |
|           end
 | |
|         end
 | |
|         table.insert(res, { pattern, mrepr })
 | |
|       end
 | |
|       return res
 | |
|     end)
 | |
| 
 | |
|     eq({
 | |
|       { 2, { { '@times', '*', 0, 4, 0, 5 } } },
 | |
|       { 5, { { '@string', 'string_literal', 0, 16, 0, 20 } } },
 | |
|       { 4, { { '@escape', 'escape_sequence', 0, 17, 0, 19 } } },
 | |
|       { 3, { { '@paren', '(', 0, 22, 0, 23 } } },
 | |
|       { 1, { { '@plus', '+', 0, 25, 0, 26 } } },
 | |
|       { 2, { { '@times', '*', 0, 30, 0, 31 } } },
 | |
|     }, res)
 | |
|   end)
 | |
| 
 | |
|   it('supports builtin query predicate any-of?', function()
 | |
|     insert([[
 | |
|       #include <stdio.h>
 | |
| 
 | |
|       int main(void) {
 | |
|         int i;
 | |
|         for(i=1; i<=100; i++) {
 | |
|           if(((i%3)||(i%5))== 0)
 | |
|             printf("number= %d FizzBuzz\n", i);
 | |
|           else if((i%3)==0)
 | |
|             printf("number= %d Fizz\n", i);
 | |
|           else if((i%5)==0)
 | |
|             printf("number= %d Buzz\n", i);
 | |
|           else
 | |
|             printf("number= %d\n",i);
 | |
|         }
 | |
|         return 0;
 | |
|       }
 | |
|     ]])
 | |
| 
 | |
|     local res0 = exec_lua(
 | |
|       get_query_result,
 | |
|       [[((primitive_type) @c-keyword (#any-of? @c-keyword "int" "float"))]]
 | |
|     )
 | |
|     eq({
 | |
|       { 'c-keyword', 'primitive_type', { 2, 2, 2, 5 }, 'int' },
 | |
|       { 'c-keyword', 'primitive_type', { 3, 4, 3, 7 }, 'int' },
 | |
|     }, res0)
 | |
| 
 | |
|     local res1 = exec_lua(
 | |
|       get_query_result,
 | |
|       [[
 | |
|         ((string_literal) @fizzbuzz-strings (#any-of? @fizzbuzz-strings
 | |
|           "\"number= %d FizzBuzz\\n\""
 | |
|           "\"number= %d Fizz\\n\""
 | |
|           "\"number= %d Buzz\\n\""
 | |
|         ))
 | |
|       ]]
 | |
|     )
 | |
|     eq({
 | |
|       { 'fizzbuzz-strings', 'string_literal', { 6, 15, 6, 38 }, '"number= %d FizzBuzz\\n"' },
 | |
|       { 'fizzbuzz-strings', 'string_literal', { 8, 15, 8, 34 }, '"number= %d Fizz\\n"' },
 | |
|       { 'fizzbuzz-strings', 'string_literal', { 10, 15, 10, 34 }, '"number= %d Buzz\\n"' },
 | |
|     }, res1)
 | |
|   end)
 | |
| 
 | |
|   it('supports builtin predicate has-ancestor?', function()
 | |
|     insert([[
 | |
|       int x = 123;
 | |
|       enum C { y = 124 };
 | |
|       int main() { int z = 125; }]])
 | |
| 
 | |
|     local result = exec_lua(
 | |
|       get_query_result,
 | |
|       [[((number_literal) @literal (#has-ancestor? @literal "function_definition"))]]
 | |
|     )
 | |
|     eq({ { 'literal', 'number_literal', { 2, 21, 2, 24 }, '125' } }, result)
 | |
| 
 | |
|     result = exec_lua(
 | |
|       get_query_result,
 | |
|       [[((number_literal) @literal (#has-ancestor? @literal "function_definition" "enum_specifier"))]]
 | |
|     )
 | |
|     eq({
 | |
|       { 'literal', 'number_literal', { 1, 13, 1, 16 }, '124' },
 | |
|       { 'literal', 'number_literal', { 2, 21, 2, 24 }, '125' },
 | |
|     }, result)
 | |
| 
 | |
|     result = exec_lua(
 | |
|       get_query_result,
 | |
|       [[((number_literal) @literal (#not-has-ancestor? @literal "enum_specifier"))]]
 | |
|     )
 | |
|     eq({
 | |
|       { 'literal', 'number_literal', { 0, 8, 0, 11 }, '123' },
 | |
|       { 'literal', 'number_literal', { 2, 21, 2, 24 }, '125' },
 | |
|     }, result)
 | |
| 
 | |
|     result = exec_lua(
 | |
|       get_query_result,
 | |
|       [[((number_literal) @literal (#has-ancestor? @literal "enumerator"))]]
 | |
|     )
 | |
|     eq({
 | |
|       { 'literal', 'number_literal', { 1, 13, 1, 16 }, '124' },
 | |
|     }, result)
 | |
| 
 | |
|     result = exec_lua(
 | |
|       get_query_result,
 | |
|       [[((number_literal) @literal (#has-ancestor? @literal "number_literal"))]]
 | |
|     )
 | |
|     eq({}, result)
 | |
|   end)
 | |
| 
 | |
|   it('allows loading query with escaped quotes and capture them `#{lua,vim}-match`?', function()
 | |
|     insert('char* astring = "Hello World!";')
 | |
| 
 | |
|     local res = exec_lua(function()
 | |
|       local cquery = vim.treesitter.query.parse(
 | |
|         'c',
 | |
|         '([_] @quote (#vim-match? @quote "^\\"$")) ([_] @quote (#lua-match? @quote "^\\"$"))'
 | |
|       )
 | |
|       local parser = vim.treesitter.get_parser(0, 'c')
 | |
|       local tree = parser:parse()[1]
 | |
|       local res = {}
 | |
|       for pattern, match in cquery:iter_matches(tree:root(), 0, 0, -1) do
 | |
|         -- can't transmit node over RPC. just check the name and range
 | |
|         local mrepr = {}
 | |
|         for cid, nodes in pairs(match) do
 | |
|           for _, node in ipairs(nodes) do
 | |
|             table.insert(mrepr, { '@' .. cquery.captures[cid], node:type(), node:range() })
 | |
|           end
 | |
|         end
 | |
|         table.insert(res, { pattern, mrepr })
 | |
|       end
 | |
|       return res
 | |
|     end)
 | |
| 
 | |
|     eq({
 | |
|       { 1, { { '@quote', '"', 0, 16, 0, 17 } } },
 | |
|       { 2, { { '@quote', '"', 0, 16, 0, 17 } } },
 | |
|       { 1, { { '@quote', '"', 0, 29, 0, 30 } } },
 | |
|       { 2, { { '@quote', '"', 0, 29, 0, 30 } } },
 | |
|     }, res)
 | |
|   end)
 | |
| 
 | |
|   it('allows to add predicates', function()
 | |
|     insert([[
 | |
|     int main(void) {
 | |
|       return 0;
 | |
|     }
 | |
|     ]])
 | |
| 
 | |
|     local custom_query = '((identifier) @main (#is-main? @main))'
 | |
| 
 | |
|     do
 | |
|       local res = exec_lua(function()
 | |
|         local query = vim.treesitter.query
 | |
| 
 | |
|         local function is_main(match, _pattern, bufnr, predicate)
 | |
|           local nodes = match[predicate[2]]
 | |
|           for _, node in ipairs(nodes) do
 | |
|             if vim.treesitter.get_node_text(node, bufnr) == 'main' then
 | |
|               return true
 | |
|             end
 | |
|           end
 | |
|           return false
 | |
|         end
 | |
| 
 | |
|         local parser = vim.treesitter.get_parser(0, 'c')
 | |
| 
 | |
|         query.add_predicate('is-main?', is_main)
 | |
| 
 | |
|         local query0 = query.parse('c', custom_query)
 | |
| 
 | |
|         local nodes = {}
 | |
|         for _, node in query0:iter_captures(parser:parse()[1]:root(), 0) do
 | |
|           table.insert(nodes, { node:range() })
 | |
|         end
 | |
| 
 | |
|         return nodes
 | |
|       end)
 | |
| 
 | |
|       eq({ { 0, 4, 0, 8 } }, res)
 | |
|     end
 | |
| 
 | |
|     -- Once with the old API. Remove this whole 'do' block in 0.12
 | |
|     do
 | |
|       local res = exec_lua(function()
 | |
|         local query = vim.treesitter.query
 | |
| 
 | |
|         local function is_main(match, _pattern, bufnr, predicate)
 | |
|           local node = match[predicate[2]]
 | |
| 
 | |
|           return vim.treesitter.get_node_text(node, bufnr) == 'main'
 | |
|         end
 | |
| 
 | |
|         local parser = vim.treesitter.get_parser(0, 'c')
 | |
| 
 | |
|         query.add_predicate('is-main?', is_main, { all = false, force = true })
 | |
| 
 | |
|         local query0 = query.parse('c', custom_query)
 | |
| 
 | |
|         local nodes = {}
 | |
|         for _, node in query0:iter_captures(parser:parse()[1]:root(), 0) do
 | |
|           table.insert(nodes, { node:range() })
 | |
|         end
 | |
| 
 | |
|         return nodes
 | |
|       end)
 | |
| 
 | |
|       -- Remove this 'do' block in 0.12
 | |
|       eq(0, fn.has('nvim-0.12'))
 | |
|       eq({ { 0, 4, 0, 8 } }, res)
 | |
|     end
 | |
| 
 | |
|     do
 | |
|       local res = exec_lua(function()
 | |
|         local query = vim.treesitter.query
 | |
| 
 | |
|         local r = {}
 | |
|         for _, v in ipairs(query.list_predicates()) do
 | |
|           r[v] = true
 | |
|         end
 | |
| 
 | |
|         return r
 | |
|       end)
 | |
| 
 | |
|       eq(true, res['is-main?'])
 | |
|     end
 | |
|   end)
 | |
| 
 | |
|   it('supports "all" and "any" semantics for predicates on quantified captures #24738', function()
 | |
|     local query_all = [[
 | |
|       (((comment (comment_content))+) @bar
 | |
|         (#lua-match? @bar "Yes"))
 | |
|     ]]
 | |
| 
 | |
|     local query_any = [[
 | |
|       (((comment (comment_content))+) @bar
 | |
|         (#any-lua-match? @bar "Yes"))
 | |
|     ]]
 | |
| 
 | |
|     local function test(input, query)
 | |
|       api.nvim_buf_set_lines(0, 0, -1, true, vim.split(dedent(input), '\n'))
 | |
|       return exec_lua(function()
 | |
|         local parser = vim.treesitter.get_parser(0, 'lua')
 | |
|         local query0 = vim.treesitter.query.parse('lua', query)
 | |
|         local nodes = {}
 | |
|         for _, node in query0:iter_captures(parser:parse()[1]:root(), 0) do
 | |
|           nodes[#nodes + 1] = { node:range() }
 | |
|         end
 | |
|         return nodes
 | |
|       end)
 | |
|     end
 | |
| 
 | |
|     eq(
 | |
|       {},
 | |
|       test(
 | |
|         [[
 | |
|       -- Yes
 | |
|       -- No
 | |
|       -- Yes
 | |
|     ]],
 | |
|         query_all
 | |
|       )
 | |
|     )
 | |
| 
 | |
|     eq(
 | |
|       {
 | |
|         { 0, 2, 0, 8 },
 | |
|         { 1, 2, 1, 8 },
 | |
|         { 2, 2, 2, 8 },
 | |
|       },
 | |
|       test(
 | |
|         [[
 | |
|       -- Yes
 | |
|       -- Yes
 | |
|       -- Yes
 | |
|     ]],
 | |
|         query_all
 | |
|       )
 | |
|     )
 | |
| 
 | |
|     eq(
 | |
|       {},
 | |
|       test(
 | |
|         [[
 | |
|       -- No
 | |
|       -- No
 | |
|       -- No
 | |
|     ]],
 | |
|         query_any
 | |
|       )
 | |
|     )
 | |
| 
 | |
|     eq(
 | |
|       {
 | |
|         { 0, 2, 0, 7 },
 | |
|         { 1, 2, 1, 8 },
 | |
|         { 2, 2, 2, 7 },
 | |
|       },
 | |
|       test(
 | |
|         [[
 | |
|       -- No
 | |
|       -- Yes
 | |
|       -- No
 | |
|     ]],
 | |
|         query_any
 | |
|       )
 | |
|     )
 | |
|   end)
 | |
| 
 | |
|   it('supports any- prefix to match any capture when using quantifiers #24738', function()
 | |
|     insert([[
 | |
|       -- Comment
 | |
|       -- Comment
 | |
|       -- Comment
 | |
|     ]])
 | |
| 
 | |
|     local result = exec_lua(function()
 | |
|       local parser = vim.treesitter.get_parser(0, 'lua')
 | |
|       local query = vim.treesitter.query.parse(
 | |
|         'lua',
 | |
|         [[
 | |
|       (((comment (comment_content))+) @bar
 | |
|         (#lua-match? @bar "Comment"))
 | |
|     ]]
 | |
|       )
 | |
|       local nodes = {}
 | |
|       for _, node in query:iter_captures(parser:parse()[1]:root(), 0) do
 | |
|         nodes[#nodes + 1] = { node:range() }
 | |
|       end
 | |
|       return nodes
 | |
|     end)
 | |
| 
 | |
|     eq({
 | |
|       { 0, 2, 0, 12 },
 | |
|       { 1, 2, 1, 12 },
 | |
|       { 2, 2, 2, 12 },
 | |
|     }, result)
 | |
|   end)
 | |
| 
 | |
|   it('supports the old broken version of iter_matches #24738', function()
 | |
|     -- Delete this test in 0.12 when iter_matches is removed
 | |
|     eq(0, fn.has('nvim-0.12'))
 | |
| 
 | |
|     insert(test_text)
 | |
|     local res = exec_lua(function()
 | |
|       local cquery = vim.treesitter.query.parse('c', test_query)
 | |
|       local parser = vim.treesitter.get_parser(0, 'c')
 | |
|       local tree = parser:parse()[1]
 | |
|       local res = {}
 | |
|       for pattern, match in cquery:iter_matches(tree:root(), 0, 7, 14, { all = false }) do
 | |
|         local mrepr = {}
 | |
|         for cid, node in pairs(match) do
 | |
|           table.insert(mrepr, { '@' .. cquery.captures[cid], node:type(), node:range() })
 | |
|         end
 | |
|         table.insert(res, { pattern, mrepr })
 | |
|       end
 | |
|       return res
 | |
|     end)
 | |
| 
 | |
|     eq({
 | |
|       { 3, { { '@type', 'primitive_type', 8, 2, 8, 6 } } },
 | |
|       { 2, { { '@keyword', 'for', 9, 2, 9, 5 } } },
 | |
|       { 3, { { '@type', 'primitive_type', 9, 7, 9, 13 } } },
 | |
|       { 4, { { '@fieldarg', 'identifier', 11, 16, 11, 18 } } },
 | |
|       {
 | |
|         1,
 | |
|         {
 | |
|           { '@minfunc', 'identifier', 11, 12, 11, 15 },
 | |
|           { '@min_id', 'identifier', 11, 27, 11, 32 },
 | |
|         },
 | |
|       },
 | |
|       { 4, { { '@fieldarg', 'identifier', 12, 17, 12, 19 } } },
 | |
|       {
 | |
|         1,
 | |
|         {
 | |
|           { '@minfunc', 'identifier', 12, 13, 12, 16 },
 | |
|           { '@min_id', 'identifier', 12, 29, 12, 35 },
 | |
|         },
 | |
|       },
 | |
|       { 4, { { '@fieldarg', 'identifier', 13, 14, 13, 16 } } },
 | |
|     }, res)
 | |
|   end)
 | |
| 
 | |
|   it('should use node range when omitted', function()
 | |
|     local txt = [[
 | |
|       int foo = 42;
 | |
|       int bar = 13;
 | |
|     ]]
 | |
| 
 | |
|     local ret = exec_lua(function()
 | |
|       local parser = vim.treesitter.get_string_parser(txt, 'c')
 | |
| 
 | |
|       local nodes = {}
 | |
|       local query = vim.treesitter.query.parse('c', '((identifier) @foo)')
 | |
|       local first_child = assert(parser:parse()[1]:root():child(1))
 | |
| 
 | |
|       for _, node in query:iter_captures(first_child, txt) do
 | |
|         table.insert(nodes, { node:range() })
 | |
|       end
 | |
| 
 | |
|       return nodes
 | |
|     end)
 | |
| 
 | |
|     eq({ { 1, 10, 1, 13 } }, ret)
 | |
|   end)
 | |
| 
 | |
|   it('fails to load queries', function()
 | |
|     local function test(exp, cquery)
 | |
|       eq(exp, pcall_err(exec_lua, "vim.treesitter.query.parse('c', ...)", cquery))
 | |
|     end
 | |
| 
 | |
|     -- Invalid node types
 | |
|     test(
 | |
|       '.../query.lua:0: Query error at 1:2. Invalid node type ">\\">>":\n'
 | |
|         .. '">\\">>" @operator\n'
 | |
|         .. ' ^',
 | |
|       '">\\">>" @operator'
 | |
|     )
 | |
|     test(
 | |
|       '.../query.lua:0: Query error at 1:2. Invalid node type "\\\\":\n'
 | |
|         .. '"\\\\" @operator\n'
 | |
|         .. ' ^',
 | |
|       '"\\\\" @operator'
 | |
|     )
 | |
|     test(
 | |
|       '.../query.lua:0: Query error at 1:2. Invalid node type ">>>":\n'
 | |
|         .. '">>>" @operator\n'
 | |
|         .. ' ^',
 | |
|       '">>>" @operator'
 | |
|     )
 | |
|     test(
 | |
|       '.../query.lua:0: Query error at 1:2. Invalid node type "dentifier":\n'
 | |
|         .. '(dentifier) @variable\n'
 | |
|         .. ' ^',
 | |
|       '(dentifier) @variable'
 | |
|     )
 | |
| 
 | |
|     -- Impossible pattern
 | |
|     test(
 | |
|       '.../query.lua:0: Query error at 1:13. Impossible pattern:\n'
 | |
|         .. '(identifier (identifier) @variable)\n'
 | |
|         .. '            ^',
 | |
|       '(identifier (identifier) @variable)'
 | |
|     )
 | |
| 
 | |
|     -- Invalid syntax
 | |
|     test(
 | |
|       '.../query.lua:0: Query error at 1:13. Invalid syntax:\n'
 | |
|         .. '(identifier @variable\n'
 | |
|         .. '            ^',
 | |
|       '(identifier @variable'
 | |
|     )
 | |
| 
 | |
|     -- Invalid field name
 | |
|     test(
 | |
|       '.../query.lua:0: Query error at 1:15. Invalid field name "invalid_field":\n'
 | |
|         .. '((identifier) invalid_field: (identifier))\n'
 | |
|         .. '              ^',
 | |
|       '((identifier) invalid_field: (identifier))'
 | |
|     )
 | |
| 
 | |
|     -- Invalid capture name
 | |
|     test(
 | |
|       '.../query.lua:0: Query error at 3:2. Invalid capture name "ok.capture":\n'
 | |
|         .. '@ok.capture\n'
 | |
|         .. ' ^',
 | |
|       '((identifier) @id \n(#eq? @id\n@ok.capture\n))'
 | |
|     )
 | |
|   end)
 | |
| 
 | |
|   describe('Query:iter_captures', function()
 | |
|     it('includes metadata for all captured nodes #23664', function()
 | |
|       insert([[
 | |
|         const char *sql = "SELECT * FROM Students WHERE name = 'Robert'); DROP TABLE Students;--";
 | |
|       ]])
 | |
| 
 | |
|       local result = exec_lua(function()
 | |
|         local query = vim.treesitter.query.parse(
 | |
|           'c',
 | |
|           [[
 | |
|         (declaration
 | |
|           type: (_)
 | |
|           declarator: (init_declarator
 | |
|             declarator: (pointer_declarator
 | |
|               declarator: (identifier)) @_id
 | |
|             value: (string_literal
 | |
|               (string_content) @injection.content))
 | |
|           (#set! injection.language "sql")
 | |
|           (#contains? @_id "sql"))
 | |
|       ]]
 | |
|         )
 | |
|         local parser = vim.treesitter.get_parser(0, 'c')
 | |
|         local root = parser:parse()[1]:root()
 | |
|         local res = {}
 | |
|         for id, _, metadata in query:iter_captures(root, 0) do
 | |
|           res[query.captures[id]] = metadata
 | |
|         end
 | |
|         return res
 | |
|       end)
 | |
| 
 | |
|       eq({
 | |
|         ['_id'] = { ['injection.language'] = 'sql' },
 | |
|         ['injection.content'] = { ['injection.language'] = 'sql' },
 | |
|       }, result)
 | |
|     end)
 | |
| 
 | |
|     it('only evaluates predicates once per match', function()
 | |
|       insert([[
 | |
|         void foo(int x, int y);
 | |
|       ]])
 | |
|       local query = [[
 | |
|         (declaration
 | |
|           type: (_)
 | |
|           declarator: (function_declarator
 | |
|             declarator: (identifier) @function.name
 | |
|             parameters: (parameter_list
 | |
|               (parameter_declaration
 | |
|                 type: (_)
 | |
|                 declarator: (identifier) @argument)))
 | |
|           (#eq? @function.name "foo"))
 | |
|       ]]
 | |
| 
 | |
|       local result = exec_lua(function()
 | |
|         local query0 = vim.treesitter.query.parse('c', query)
 | |
|         local match_preds = query0._match_predicates
 | |
|         local called = 0
 | |
|         function query0:_match_predicates(...)
 | |
|           called = called + 1
 | |
|           return match_preds(self, ...)
 | |
|         end
 | |
|         local parser = vim.treesitter.get_parser(0, 'c')
 | |
|         local root = parser:parse()[1]:root()
 | |
|         local captures = {}
 | |
|         for id in query0:iter_captures(root, 0) do
 | |
|           captures[#captures + 1] = id
 | |
|         end
 | |
|         return { called, captures }
 | |
|       end)
 | |
| 
 | |
|       eq({ 2, { 1, 1, 2, 2 } }, result)
 | |
|     end)
 | |
|   end)
 | |
| end)
 | 
