mirror of
				https://github.com/neovim/neovim.git
				synced 2025-10-26 12:27:24 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1099 lines
		
	
	
		
			33 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			1099 lines
		
	
	
		
			33 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
| local helpers = require('test.functional.helpers')(after_each)
 | |
| 
 | |
| local clear = helpers.clear
 | |
| local dedent = helpers.dedent
 | |
| local eq = helpers.eq
 | |
| local insert = helpers.insert
 | |
| local exec_lua = helpers.exec_lua
 | |
| local pcall_err = helpers.pcall_err
 | |
| local feed = helpers.feed
 | |
| local is_os = helpers.is_os
 | |
| 
 | |
| describe('treesitter parser API', function()
 | |
|   before_each(function()
 | |
|     clear()
 | |
|     exec_lua[[
 | |
|       vim.g.__ts_debug = 1
 | |
|     ]]
 | |
|   end)
 | |
| 
 | |
|   it('parses buffer', function()
 | |
|     insert([[
 | |
|       int main() {
 | |
|         int x = 3;
 | |
|       }]])
 | |
| 
 | |
|     exec_lua([[
 | |
|       parser = vim.treesitter.get_parser(0, "c")
 | |
|       tree = parser:parse()[1]
 | |
|       root = tree:root()
 | |
|       lang = vim.treesitter.language.inspect('c')
 | |
|     ]])
 | |
| 
 | |
|     eq("<tree>", exec_lua("return tostring(tree)"))
 | |
|     eq("<node translation_unit>", exec_lua("return tostring(root)"))
 | |
|     eq({0,0,3,0}, exec_lua("return {root:range()}"))
 | |
| 
 | |
|     eq(1, exec_lua("return root:child_count()"))
 | |
|     exec_lua("child = root:child(0)")
 | |
|     eq("<node function_definition>", exec_lua("return tostring(child)"))
 | |
|     eq({0,0,2,1}, exec_lua("return {child:range()}"))
 | |
| 
 | |
|     eq("function_definition", exec_lua("return child:type()"))
 | |
|     eq(true, exec_lua("return child:named()"))
 | |
|     eq("number", type(exec_lua("return child:symbol()")))
 | |
|     eq({'function_definition', true}, exec_lua("return lang.symbols[child:symbol()]"))
 | |
| 
 | |
|     exec_lua("anon = root:descendant_for_range(0,8,0,9)")
 | |
|     eq("(", exec_lua("return anon:type()"))
 | |
|     eq(false, exec_lua("return anon:named()"))
 | |
|     eq("number", type(exec_lua("return anon:symbol()")))
 | |
|     eq({'(', false}, exec_lua("return lang.symbols[anon:symbol()]"))
 | |
| 
 | |
|     exec_lua("descendant = root:descendant_for_range(1,2,1,12)")
 | |
|     eq("<node declaration>", exec_lua("return tostring(descendant)"))
 | |
|     eq({1,2,1,12}, exec_lua("return {descendant:range()}"))
 | |
|     eq("(declaration type: (primitive_type) declarator: (init_declarator declarator: (identifier) value: (number_literal)))", exec_lua("return descendant:sexpr()"))
 | |
| 
 | |
|     feed("2G7|ay")
 | |
|     exec_lua([[
 | |
|       tree2 = parser:parse()[1]
 | |
|       root2 = tree2:root()
 | |
|       descendant2 = root2:descendant_for_range(1,2,1,13)
 | |
|     ]])
 | |
|     eq(false, exec_lua("return tree2 == tree1"))
 | |
|     eq(false, exec_lua("return root2 == root"))
 | |
|     eq("<node declaration>", exec_lua("return tostring(descendant2)"))
 | |
|     eq({1,2,1,13}, exec_lua("return {descendant2:range()}"))
 | |
| 
 | |
|     eq(true, exec_lua("return child == child"))
 | |
|     -- separate lua object, but represents same node
 | |
|     eq(true, exec_lua("return child == root:child(0)"))
 | |
|     eq(false, exec_lua("return child == descendant2"))
 | |
|     eq(false, exec_lua("return child == nil"))
 | |
|     eq(false, exec_lua("return child == tree"))
 | |
| 
 | |
|     eq("string", exec_lua("return type(child:id())"))
 | |
|     eq(true, exec_lua("return child:id() == child:id()"))
 | |
|     -- separate lua object, but represents same node
 | |
|     eq(true, exec_lua("return child:id() == root:child(0):id()"))
 | |
|     eq(false, exec_lua("return child:id() == descendant2:id()"))
 | |
|     eq(false, exec_lua("return child:id() == nil"))
 | |
|     eq(false, exec_lua("return child:id() == tree"))
 | |
| 
 | |
|     -- unchanged buffer: return the same tree
 | |
|     eq(true, exec_lua("return parser:parse()[1] == tree2"))
 | |
|   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);
 | |
|     }
 | |
|   }
 | |
| }]]
 | |
| 
 | |
|   it('allows to iterate over nodes children', function()
 | |
|     insert(test_text);
 | |
| 
 | |
|     local res = exec_lua([[
 | |
|       parser = vim.treesitter.get_parser(0, "c")
 | |
| 
 | |
|       func_node = parser:parse()[1]:root():child(0)
 | |
| 
 | |
|       res = {}
 | |
|       for node, field in func_node:iter_children() do
 | |
|         table.insert(res, {node:type(), field})
 | |
|       end
 | |
|       return res
 | |
|     ]])
 | |
| 
 | |
|     eq({
 | |
|       {"primitive_type", "type"},
 | |
|       {"function_declarator", "declarator"},
 | |
|       {"compound_statement", "body"}
 | |
|     }, res)
 | |
|   end)
 | |
| 
 | |
|   it('does not get parser for empty filetype', function()
 | |
|     insert(test_text);
 | |
| 
 | |
|     eq('.../treesitter.lua:0: There is no parser available for buffer 1 and one'
 | |
|          .. ' could not be created because lang could not be determined. Either'
 | |
|          .. ' pass lang or set the buffer filetype',
 | |
|       pcall_err(exec_lua, 'vim.treesitter.get_parser(0)'))
 | |
| 
 | |
|     -- Must provide language for buffers with an empty filetype
 | |
|     exec_lua("vim.treesitter.get_parser(0, 'c')")
 | |
|   end)
 | |
| 
 | |
|   it('allows to get a child by field', function()
 | |
|     insert(test_text);
 | |
| 
 | |
|     local res = exec_lua([[
 | |
|       parser = vim.treesitter.get_parser(0, "c")
 | |
| 
 | |
|       func_node = parser:parse()[1]:root():child(0)
 | |
| 
 | |
|       local res = {}
 | |
|       for _, node in ipairs(func_node:field("type")) do
 | |
|         table.insert(res, {node:type(), node:range()})
 | |
|       end
 | |
|       return res
 | |
|     ]])
 | |
| 
 | |
|     eq({{ "primitive_type", 0, 0, 0, 4 }}, res)
 | |
| 
 | |
|     local res_fail = exec_lua([[
 | |
|       parser = vim.treesitter.get_parser(0, "c")
 | |
| 
 | |
|       return #func_node:field("foo") == 0
 | |
|     ]])
 | |
| 
 | |
|     assert(res_fail)
 | |
|   end)
 | |
| 
 | |
|   local 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()
 | |
|     local ret = exec_lua [[
 | |
|       return vim.treesitter.query.get("c", "highlights").captures[1]
 | |
|     ]]
 | |
| 
 | |
|     eq('variable', ret)
 | |
|   end)
 | |
| 
 | |
|   it("supports caching queries", function()
 | |
|     local long_query = query:rep(100)
 | |
|     local function q(n)
 | |
|       return exec_lua ([[
 | |
|         local query, n = ...
 | |
|         local before = vim.uv.hrtime()
 | |
|         for i=1,n,1 do
 | |
|           cquery = vim.treesitter.query.parse("c", ...)
 | |
|         end
 | |
|         local after = vim.uv.hrtime()
 | |
|         return after - before
 | |
|       ]], long_query, n)
 | |
|     end
 | |
| 
 | |
|     local firstrun = q(1)
 | |
|     local manyruns = q(100)
 | |
| 
 | |
|     -- First run should be at least 200x slower than an 100 subsequent runs.
 | |
|     local factor = is_os('win') and 100 or 200
 | |
|     assert(factor * manyruns < firstrun, ('firstrun: %f ms, manyruns: %f ms'):format(firstrun / 1e6, manyruns / 1e6))
 | |
|   end)
 | |
| 
 | |
|   it('support query and iter by capture', function()
 | |
|     insert(test_text)
 | |
| 
 | |
|     local res = exec_lua([[
 | |
|       cquery = vim.treesitter.query.parse("c", ...)
 | |
|       parser = vim.treesitter.get_parser(0, "c")
 | |
|       tree = parser:parse()[1]
 | |
|       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
 | |
|     ]], query)
 | |
| 
 | |
|     eq({
 | |
|       { "type", "primitive_type", 8, 2, 8, 6 },
 | |
|       { "keyword", "for", 9, 2, 9, 5 },
 | |
|       { "type", "primitive_type", 9, 7, 9, 13 },
 | |
|       { "minfunc", "identifier", 11, 12, 11, 15 },
 | |
|       { "fieldarg", "identifier", 11, 16, 11, 18 },
 | |
|       { "min_id", "identifier", 11, 27, 11, 32 },
 | |
|       { "minfunc", "identifier", 12, 13, 12, 16 },
 | |
|       { "fieldarg", "identifier", 12, 17, 12, 19 },
 | |
|       { "min_id", "identifier", 12, 29, 12, 35 },
 | |
|       { "fieldarg", "identifier", 13, 14, 13, 16 }
 | |
|     }, res)
 | |
|   end)
 | |
| 
 | |
|   it('support query and iter by match', function()
 | |
|     insert(test_text)
 | |
| 
 | |
|     local res = exec_lua([[
 | |
|       cquery = vim.treesitter.query.parse("c", ...)
 | |
|       parser = vim.treesitter.get_parser(0, "c")
 | |
|       tree = parser:parse()[1]
 | |
|       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,node in pairs(match) do
 | |
|           table.insert(mrepr, {cquery.captures[cid], node:type(), node:range()})
 | |
|         end
 | |
|         table.insert(res, {pattern, mrepr})
 | |
|       end
 | |
|       return res
 | |
|     ]], query)
 | |
| 
 | |
|     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 getting text of multiline node', function()
 | |
|     insert(test_text)
 | |
|     local res = exec_lua([[
 | |
|       local parser = vim.treesitter.get_parser(0, "c")
 | |
|       local tree = parser:parse()[1]
 | |
|       return vim.treesitter.get_node_text(tree:root(), 0)
 | |
|     ]])
 | |
|     eq(test_text, res)
 | |
| 
 | |
|     local res2 = exec_lua([[
 | |
|       local parser = vim.treesitter.get_parser(0, "c")
 | |
|       local root = parser:parse()[1]:root()
 | |
|       return vim.treesitter.get_node_text(root:child(0):child(0), 0)
 | |
|     ]])
 | |
|     eq('void', res2)
 | |
|   end)
 | |
| 
 | |
|   it('support getting text where start of node is one past EOF', function()
 | |
|     local text = [[
 | |
| def run
 | |
|   a = <<~E
 | |
| end]]
 | |
|     insert(text)
 | |
|     eq('', exec_lua[[
 | |
|       local fake_node = {}
 | |
|       function fake_node:start()
 | |
|         return 3, 0, 23
 | |
|       end
 | |
|       function fake_node:end_()
 | |
|         return 3, 0, 23
 | |
|       end
 | |
|       function fake_node:range(bytes)
 | |
|         if bytes then
 | |
|           return 3, 0, 23, 3, 0, 23
 | |
|         end
 | |
|         return 3, 0, 3, 0
 | |
|       end
 | |
|       return vim.treesitter.get_node_text(fake_node, 0)
 | |
|     ]])
 | |
|   end)
 | |
| 
 | |
|   it('support getting empty text if node range is zero width', function()
 | |
|     local text = [[
 | |
| ```lua
 | |
| {}
 | |
| ```]]
 | |
|     insert(text)
 | |
|     local result = exec_lua([[
 | |
|       local fake_node = {}
 | |
|       function fake_node:start()
 | |
|         return 1, 0, 7
 | |
|       end
 | |
|       function fake_node:end_()
 | |
|         return 1, 0, 7
 | |
|       end
 | |
|       function fake_node:range()
 | |
|         return 1, 0, 1, 0
 | |
|       end
 | |
|       return vim.treesitter.get_node_text(fake_node, 0) == ''
 | |
|     ]])
 | |
|     eq(true, result)
 | |
|   end)
 | |
| 
 | |
|   it('can match special regex characters like \\ * + ( with `vim-match?`', function()
 | |
|     insert('char* astring = "\\n"; (1 + 1) * 2 != 2;')
 | |
| 
 | |
|     local res = exec_lua([[
 | |
|       cquery = vim.treesitter.query.parse("c", '([_] @plus (#vim-match? @plus "^\\\\+$"))'..
 | |
|                                                '([_] @times (#vim-match? @times "^\\\\*$"))'..
 | |
|                                                '([_] @paren (#vim-match? @paren "^\\\\($"))'..
 | |
|                                                '([_] @escape (#vim-match? @escape "^\\\\\\\\n$"))'..
 | |
|                                                '([_] @string (#vim-match? @string "^\\"\\\\\\\\n\\"$"))')
 | |
|       parser = vim.treesitter.get_parser(0, "c")
 | |
|       tree = parser:parse()[1]
 | |
|       res = {}
 | |
|       for pattern, match in cquery:iter_matches(tree:root(), 0) do
 | |
|         -- can't transmit node over RPC. just check the name and range
 | |
|         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
 | |
|     ]])
 | |
| 
 | |
|     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;
 | |
|       }
 | |
|     ]])
 | |
|     exec_lua([[
 | |
|       function get_query_result(query_text)
 | |
|         cquery = vim.treesitter.query.parse("c", query_text)
 | |
|         parser = vim.treesitter.get_parser(0, "c")
 | |
|         tree = parser:parse()[1]
 | |
|         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
 | |
|     ]])
 | |
| 
 | |
|     local res0 = exec_lua([[return 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([[return 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('allow loading query with escaped quotes and capture them with `lua-match?` and `vim-match?`', function()
 | |
|     insert('char* astring = "Hello World!";')
 | |
| 
 | |
|     local res = exec_lua([[
 | |
|       cquery = vim.treesitter.query.parse("c", '([_] @quote (#vim-match? @quote "^\\"$")) ([_] @quote (#lua-match? @quote "^\\"$"))')
 | |
|       parser = vim.treesitter.get_parser(0, "c")
 | |
|       tree = parser:parse()[1]
 | |
|       res = {}
 | |
|       for pattern, match in cquery:iter_matches(tree:root(), 0) do
 | |
|         -- can't transmit node over RPC. just check the name and range
 | |
|         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
 | |
|     ]])
 | |
| 
 | |
|     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))"
 | |
| 
 | |
|     local res = exec_lua([[
 | |
|     local query = vim.treesitter.query
 | |
| 
 | |
|     local function is_main(match, pattern, bufnr, predicate)
 | |
|       local node = match[ predicate[2] ]
 | |
| 
 | |
|       return query.get_node_text(node, bufnr)
 | |
|     end
 | |
| 
 | |
|     local parser = vim.treesitter.get_parser(0, "c")
 | |
| 
 | |
|     query.add_predicate("is-main?", is_main)
 | |
| 
 | |
|     local query = query.parse("c", ...)
 | |
| 
 | |
|     local nodes = {}
 | |
|     for _, node in query:iter_captures(parser:parse()[1]:root(), 0) do
 | |
|       table.insert(nodes, {node:range()})
 | |
|     end
 | |
| 
 | |
|     return nodes
 | |
|     ]], custom_query)
 | |
| 
 | |
|     eq({{0, 4, 0, 8}}, res)
 | |
| 
 | |
|     local res_list = exec_lua[[
 | |
|     local query = vim.treesitter.query
 | |
| 
 | |
|     local list = query.list_predicates()
 | |
| 
 | |
|     table.sort(list)
 | |
| 
 | |
|     return list
 | |
|     ]]
 | |
| 
 | |
|     eq({ 'any-of?', 'contains?', 'eq?', 'has-ancestor?', 'has-parent?', 'is-main?', 'lua-match?', 'match?', 'vim-match?' }, res_list)
 | |
|   end)
 | |
| 
 | |
|   it('allows to set simple ranges', function()
 | |
|     insert(test_text)
 | |
| 
 | |
|     local res = exec_lua [[
 | |
|     parser = vim.treesitter.get_parser(0, "c")
 | |
|     return { parser:parse()[1]:root():range() }
 | |
|     ]]
 | |
| 
 | |
|     eq({0, 0, 19, 0}, res)
 | |
| 
 | |
|     -- The following sets the included ranges for the current parser
 | |
|     -- As stated here, this only includes the function (thus the whole buffer, without the last line)
 | |
|     local res2 = exec_lua [[
 | |
|     local root = parser:parse()[1]:root()
 | |
|     parser:set_included_regions({{root:child(0)}})
 | |
|     parser:invalidate()
 | |
|     return { parser:parse(true)[1]:root():range() }
 | |
|     ]]
 | |
| 
 | |
|     eq({0, 0, 18, 1}, res2)
 | |
| 
 | |
|     eq({ { { 0, 0, 0, 18, 1, 512 } } }, exec_lua [[ return parser:included_regions() ]])
 | |
| 
 | |
|     local range_tbl = exec_lua [[
 | |
|       parser:set_included_regions { { { 0, 0, 17, 1 } } }
 | |
|       parser:parse()
 | |
|       return parser:included_regions()
 | |
|     ]]
 | |
| 
 | |
|     eq(range_tbl, { { { 0, 0, 0, 17, 1, 508 } } })
 | |
|   end)
 | |
| 
 | |
|   it("allows to set complex ranges", function()
 | |
|     insert(test_text)
 | |
| 
 | |
|     local res = exec_lua [[
 | |
|     parser = vim.treesitter.get_parser(0, "c")
 | |
|     query = vim.treesitter.query.parse("c", "(declaration) @decl")
 | |
| 
 | |
|     local nodes = {}
 | |
|     for _, node in query:iter_captures(parser:parse()[1]:root(), 0) do
 | |
|       table.insert(nodes, node)
 | |
|     end
 | |
| 
 | |
|     parser:set_included_regions({nodes})
 | |
| 
 | |
|     local root = parser:parse(true)[1]:root()
 | |
| 
 | |
|     local res = {}
 | |
|     for i=0,(root:named_child_count() - 1) do
 | |
|       table.insert(res, { root:named_child(i):range() })
 | |
|     end
 | |
|     return res
 | |
|     ]]
 | |
| 
 | |
|     eq({
 | |
|       { 2, 2, 2, 40 },
 | |
|       { 3, 2, 3, 32 },
 | |
|       { 4, 7, 4, 25 },
 | |
|       { 8, 2, 8, 33 },
 | |
|       { 9, 7, 9, 20 },
 | |
|       { 10, 4, 10, 20 },
 | |
|       { 14, 9, 14, 27 } }, res)
 | |
|   end)
 | |
| 
 | |
|   it("allows to create string parsers", function()
 | |
|     local ret = exec_lua [[
 | |
|       local parser = vim.treesitter.get_string_parser("int foo = 42;", "c")
 | |
|       return { parser:parse()[1]:root():range() }
 | |
|     ]]
 | |
| 
 | |
|     eq({ 0, 0, 0, 13 }, ret)
 | |
|   end)
 | |
| 
 | |
|   it("allows to run queries with string parsers", function()
 | |
|     local txt = [[
 | |
|       int foo = 42;
 | |
|       int bar = 13;
 | |
|     ]]
 | |
| 
 | |
|     local ret = exec_lua([[
 | |
|     local str = ...
 | |
|     local parser = vim.treesitter.get_string_parser(str, "c")
 | |
| 
 | |
|     local nodes = {}
 | |
|     local query = vim.treesitter.query.parse("c", '((identifier) @id (eq? @id "foo"))')
 | |
| 
 | |
|     for _, node in query:iter_captures(parser:parse()[1]:root(), str) do
 | |
|       table.insert(nodes, { node:range() })
 | |
|     end
 | |
| 
 | |
|     return nodes]], txt)
 | |
| 
 | |
|     eq({ {0, 10, 0, 13} }, ret)
 | |
|   end)
 | |
| 
 | |
|   it("should use node range when omitted", function()
 | |
|     local txt = [[
 | |
|       int foo = 42;
 | |
|       int bar = 13;
 | |
|     ]]
 | |
| 
 | |
|     local ret = exec_lua([[
 | |
|     local str = ...
 | |
|     local parser = vim.treesitter.get_string_parser(str, "c")
 | |
| 
 | |
|     local nodes = {}
 | |
|     local query = vim.treesitter.query.parse("c", '((identifier) @foo)')
 | |
|     local first_child = parser:parse()[1]:root():child(1)
 | |
| 
 | |
|     for _, node in query:iter_captures(first_child, str) do
 | |
|       table.insert(nodes, { node:range() })
 | |
|     end
 | |
| 
 | |
|     return nodes]], txt)
 | |
| 
 | |
|     eq({ {1, 10, 1, 13} }, ret)
 | |
|   end)
 | |
| 
 | |
|   describe("when creating a language tree", function()
 | |
|     local function get_ranges()
 | |
|       return exec_lua([[
 | |
|       local result = {}
 | |
|       parser:for_each_tree(function(tree) table.insert(result, {tree:root():range()}) end)
 | |
|       return result
 | |
|       ]])
 | |
|     end
 | |
| 
 | |
|     before_each(function()
 | |
|       insert([[
 | |
| int x = INT_MAX;
 | |
| #define READ_STRING(x, y) (char *)read_string((x), (size_t)(y))
 | |
| #define READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y))
 | |
| #define VALUE 123
 | |
| #define VALUE1 123
 | |
| #define VALUE2 123
 | |
|       ]])
 | |
|     end)
 | |
| 
 | |
|     describe("when parsing regions independently", function()
 | |
|       it("should inject a language", function()
 | |
|         exec_lua([[
 | |
|         parser = vim.treesitter.get_parser(0, "c", {
 | |
|           injections = {
 | |
|             c = '(preproc_def (preproc_arg) @injection.content (#set! injection.language "c")) (preproc_function_def value: (preproc_arg) @injection.content (#set! injection.language "c"))'}})
 | |
|         parser:parse(true)
 | |
|         ]])
 | |
| 
 | |
|         eq("table", exec_lua("return type(parser:children().c)"))
 | |
|         eq(5, exec_lua("return #parser:children().c:trees()"))
 | |
|         eq({
 | |
|           {0, 0, 7, 0},   -- root tree
 | |
|           {3, 14, 3, 17}, -- VALUE 123
 | |
|           {4, 15, 4, 18}, -- VALUE1 123
 | |
|           {5, 15, 5, 18}, -- VALUE2 123
 | |
|           {1, 26, 1, 63}, -- READ_STRING(x, y) (char *)read_string((x), (size_t)(y))
 | |
|           {2, 29, 2, 66}  -- READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y))
 | |
|         }, get_ranges())
 | |
| 
 | |
|         helpers.feed('ggo<esc>')
 | |
|         eq(5, exec_lua("return #parser:children().c:trees()"))
 | |
|         eq({
 | |
|           {0, 0, 8, 0},   -- root tree
 | |
|           {4, 14, 4, 17}, -- VALUE 123
 | |
|           {5, 15, 5, 18}, -- VALUE1 123
 | |
|           {6, 15, 6, 18}, -- VALUE2 123
 | |
|           {2, 26, 2, 63}, -- READ_STRING(x, y) (char *)read_string((x), (size_t)(y))
 | |
|           {3, 29, 3, 66}  -- READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y))
 | |
|         }, get_ranges())
 | |
|       end)
 | |
|     end)
 | |
| 
 | |
|     describe("when parsing regions combined", function()
 | |
|       it("should inject a language", function()
 | |
|         exec_lua([[
 | |
|         parser = vim.treesitter.get_parser(0, "c", {
 | |
|           injections = {
 | |
|             c = '(preproc_def (preproc_arg) @injection.content (#set! injection.language "c") (#set! injection.combined)) (preproc_function_def value: (preproc_arg) @injection.content (#set! injection.language "c") (#set! injection.combined))'}})
 | |
|         parser:parse(true)
 | |
|         ]])
 | |
| 
 | |
|         eq("table", exec_lua("return type(parser:children().c)"))
 | |
|         eq(2, exec_lua("return #parser:children().c:trees()"))
 | |
|         eq({
 | |
|           {0, 0, 7, 0},   -- root tree
 | |
|           {3, 14, 5, 18}, -- VALUE 123
 | |
|                           -- VALUE1 123
 | |
|                           -- VALUE2 123
 | |
|           {1, 26, 2, 66}  -- READ_STRING(x, y) (char *)read_string((x), (size_t)(y))
 | |
|                           -- READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y))
 | |
|         }, get_ranges())
 | |
| 
 | |
|         helpers.feed('ggo<esc>')
 | |
|         eq("table", exec_lua("return type(parser:children().c)"))
 | |
|         eq(2, exec_lua("return #parser:children().c:trees()"))
 | |
|         eq({
 | |
|           {0, 0, 8, 0},   -- root tree
 | |
|           {4, 14, 6, 18}, -- VALUE 123
 | |
|                           -- VALUE1 123
 | |
|                           -- VALUE2 123
 | |
|           {2, 26, 3, 66}  -- READ_STRING(x, y) (char *)read_string((x), (size_t)(y))
 | |
|                           -- READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y))
 | |
|         }, get_ranges())
 | |
|       end)
 | |
|     end)
 | |
| 
 | |
|     describe("when using injection.self", function()
 | |
|       it("should inject the source language", function()
 | |
|         exec_lua([[
 | |
|         parser = vim.treesitter.get_parser(0, "c", {
 | |
|           injections = {
 | |
|             c = '(preproc_def (preproc_arg) @injection.content (#set! injection.self)) (preproc_function_def value: (preproc_arg) @injection.content (#set! injection.self))'}})
 | |
|         parser:parse(true)
 | |
|         ]])
 | |
| 
 | |
|         eq("table", exec_lua("return type(parser:children().c)"))
 | |
|         eq(5, exec_lua("return #parser:children().c:trees()"))
 | |
|         eq({
 | |
|           {0, 0, 7, 0},   -- root tree
 | |
|           {3, 14, 3, 17}, -- VALUE 123
 | |
|           {4, 15, 4, 18}, -- VALUE1 123
 | |
|           {5, 15, 5, 18}, -- VALUE2 123
 | |
|           {1, 26, 1, 63}, -- READ_STRING(x, y) (char *)read_string((x), (size_t)(y))
 | |
|           {2, 29, 2, 66}  -- READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y))
 | |
|         }, get_ranges())
 | |
| 
 | |
|         helpers.feed('ggo<esc>')
 | |
|         eq(5, exec_lua("return #parser:children().c:trees()"))
 | |
|         eq({
 | |
|           {0, 0, 8, 0},   -- root tree
 | |
|           {4, 14, 4, 17}, -- VALUE 123
 | |
|           {5, 15, 5, 18}, -- VALUE1 123
 | |
|           {6, 15, 6, 18}, -- VALUE2 123
 | |
|           {2, 26, 2, 63}, -- READ_STRING(x, y) (char *)read_string((x), (size_t)(y))
 | |
|           {3, 29, 3, 66}  -- READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y))
 | |
|         }, get_ranges())
 | |
|       end)
 | |
|     end)
 | |
| 
 | |
|     describe("when using the offset directive", function()
 | |
|       it("should shift the range by the directive amount", function()
 | |
|         exec_lua([[
 | |
|         parser = vim.treesitter.get_parser(0, "c", {
 | |
|           injections = {
 | |
|             c = '(preproc_def ((preproc_arg) @injection.content (#set! injection.language "c") (#offset! @injection.content 0 2 0 -1))) (preproc_function_def value: (preproc_arg) @injection.content (#set! injection.language "c"))'}})
 | |
|         parser:parse(true)
 | |
|         ]])
 | |
| 
 | |
|         eq("table", exec_lua("return type(parser:children().c)"))
 | |
|         eq({
 | |
|           {0, 0, 7, 0},   -- root tree
 | |
|           {3, 16, 3, 16}, -- VALUE 123
 | |
|           {4, 17, 4, 17}, -- VALUE1 123
 | |
|           {5, 17, 5, 17}, -- VALUE2 123
 | |
|           {1, 26, 1, 63}, -- READ_STRING(x, y) (char *)read_string((x), (size_t)(y))
 | |
|           {2, 29, 2, 66}  -- READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y))
 | |
|         }, get_ranges())
 | |
|       end)
 | |
|       it("should list all directives", function()
 | |
|         local res_list = exec_lua[[
 | |
|         local query = vim.treesitter.query
 | |
| 
 | |
|         local list = query.list_directives()
 | |
| 
 | |
|         table.sort(list)
 | |
| 
 | |
|         return list
 | |
|         ]]
 | |
| 
 | |
|         eq({ 'gsub!', 'offset!', 'set!', 'trim!' }, res_list)
 | |
|       end)
 | |
|     end)
 | |
|   end)
 | |
| 
 | |
|   describe("when getting the language for a range", function()
 | |
|     before_each(function()
 | |
|       insert([[
 | |
| int x = INT_MAX;
 | |
| #define VALUE 123456789
 | |
|       ]])
 | |
|     end)
 | |
| 
 | |
|     it("should return the correct language tree", function()
 | |
|       local result = exec_lua([[
 | |
|       parser = vim.treesitter.get_parser(0, "c", {
 | |
|         injections = { c = '(preproc_def (preproc_arg) @injection.content (#set! injection.language "c"))'}})
 | |
|       parser:parse(true)
 | |
| 
 | |
|       local sub_tree = parser:language_for_range({1, 18, 1, 19})
 | |
| 
 | |
|       return sub_tree == parser:children().c
 | |
|       ]])
 | |
| 
 | |
|       eq(result, true)
 | |
|     end)
 | |
|   end)
 | |
| 
 | |
|   describe("when getting/setting match data", function()
 | |
|     describe("when setting for the whole match", function()
 | |
|       it("should set/get the data correctly", function()
 | |
|         insert([[
 | |
|           int x = 3;
 | |
|         ]])
 | |
| 
 | |
|         local result = exec_lua([[
 | |
|         local result
 | |
| 
 | |
|         query = vim.treesitter.query.parse("c", '((number_literal) @number (#set! "key" "value"))')
 | |
|         parser = vim.treesitter.get_parser(0, "c")
 | |
| 
 | |
|         for pattern, match, metadata in query:iter_matches(parser:parse()[1]:root(), 0) do
 | |
|           result = metadata.key
 | |
|         end
 | |
| 
 | |
|         return result
 | |
|         ]])
 | |
| 
 | |
|         eq(result, "value")
 | |
|       end)
 | |
| 
 | |
|       describe("when setting a key on a capture", function()
 | |
|         it("it should create the nested table", function()
 | |
|           insert([[
 | |
|             int x = 3;
 | |
|           ]])
 | |
| 
 | |
|           local result = exec_lua([[
 | |
|           local query = vim.treesitter.query
 | |
|           local value
 | |
| 
 | |
|           query = vim.treesitter.query.parse("c", '((number_literal) @number (#set! @number "key" "value"))')
 | |
|           parser = vim.treesitter.get_parser(0, "c")
 | |
| 
 | |
|           for pattern, match, metadata in query:iter_matches(parser:parse()[1]:root(), 0) do
 | |
|             for _, nested_tbl in pairs(metadata) do
 | |
|               return nested_tbl.key
 | |
|             end
 | |
|           end
 | |
|           ]])
 | |
| 
 | |
|           eq(result, "value")
 | |
|         end)
 | |
| 
 | |
|         it("it should not overwrite the nested table", function()
 | |
|           insert([[
 | |
|             int x = 3;
 | |
|           ]])
 | |
| 
 | |
|           local result = exec_lua([[
 | |
|           local query = vim.treesitter.query
 | |
|           local result
 | |
| 
 | |
|           query = vim.treesitter.query.parse("c", '((number_literal) @number (#set! @number "key" "value") (#set! @number "key2" "value2"))')
 | |
|           parser = vim.treesitter.get_parser(0, "c")
 | |
| 
 | |
|           for pattern, match, metadata in query:iter_matches(parser:parse()[1]:root(), 0) do
 | |
|             for _, nested_tbl in pairs(metadata) do
 | |
|               return nested_tbl
 | |
|             end
 | |
|           end
 | |
|           ]])
 | |
|           local expected = {
 | |
|             ["key"] = "value",
 | |
|             ["key2"] = "value2",
 | |
|           }
 | |
| 
 | |
|           eq(expected, result)
 | |
|         end)
 | |
|       end)
 | |
|     end)
 | |
|   end)
 | |
| 
 | |
|   it('tracks the root range properly (#22911)', function()
 | |
|     insert([[
 | |
|       int main() {
 | |
|         int x = 3;
 | |
|       }]])
 | |
| 
 | |
|     local query0 = [[
 | |
|       (declaration) @declaration
 | |
|       (function_definition) @function
 | |
|     ]]
 | |
| 
 | |
|     exec_lua([[
 | |
|       vim.treesitter.start(0, 'c')
 | |
|     ]])
 | |
| 
 | |
|     local function run_query()
 | |
|       return exec_lua([[
 | |
|       local query = vim.treesitter.query.parse("c", ...)
 | |
|       parser = vim.treesitter.get_parser()
 | |
|       tree = parser:parse()[1]
 | |
|       res = {}
 | |
|       for id, node in query:iter_captures(tree:root()) do
 | |
|         table.insert(res, {query.captures[id], node:range()})
 | |
|       end
 | |
|       return res
 | |
|       ]], query0)
 | |
|     end
 | |
| 
 | |
|     eq({
 | |
|       { 'function', 0, 0, 2, 1 },
 | |
|       { 'declaration', 1, 2, 1, 12 }
 | |
|     }, run_query())
 | |
| 
 | |
|     helpers.command'normal ggO'
 | |
|     insert('int a;')
 | |
| 
 | |
|     eq({
 | |
|       { 'declaration', 0, 0, 0, 6 },
 | |
|       { 'function', 1, 0, 3, 1 },
 | |
|       { 'declaration', 2, 2, 2, 12 }
 | |
|     }, run_query())
 | |
| 
 | |
|   end)
 | |
| 
 | |
|   it('handles ranges when source is a multiline string (#20419)', function()
 | |
|     local source = [==[
 | |
|       vim.cmd[[
 | |
|         set number
 | |
|         set cmdheight=2
 | |
|         set lastsatus=2
 | |
|       ]]
 | |
| 
 | |
|       set query = [[;; query
 | |
|         ((function_call
 | |
|           name: [
 | |
|             (identifier) @_cdef_identifier
 | |
|             (_ _ (identifier) @_cdef_identifier)
 | |
|           ]
 | |
|           arguments: (arguments (string content: _ @injection.content)))
 | |
|           (#set! injection.language "c")
 | |
|           (#eq? @_cdef_identifier "cdef"))
 | |
|       ]]
 | |
|     ]==]
 | |
| 
 | |
|     local r = exec_lua([[
 | |
|       local parser = vim.treesitter.get_string_parser(..., 'lua')
 | |
|       parser:parse(true)
 | |
|       local ranges = {}
 | |
|       parser:for_each_tree(function(tstree, tree)
 | |
|         ranges[tree:lang()] = { tstree:root():range(true) }
 | |
|       end)
 | |
|       return ranges
 | |
|     ]], source)
 | |
| 
 | |
|     eq({
 | |
|       lua = { 0, 6, 6, 16, 4, 438 },
 | |
|       query = { 6, 20, 113, 15, 6, 431 },
 | |
|       vim = { 1, 0, 16, 4, 6, 89 }
 | |
|     }, r)
 | |
| 
 | |
|     -- The above ranges are provided directly from treesitter, however query directives may mutate
 | |
|     -- the ranges but only provide a Range4. Strip the byte entries from the ranges and make sure
 | |
|     -- add_bytes() produces the same result.
 | |
| 
 | |
|     local rb = exec_lua([[
 | |
|       local r, source = ...
 | |
|       local add_bytes = require('vim.treesitter._range').add_bytes
 | |
|       for lang, range in pairs(r) do
 | |
|         r[lang] = {range[1], range[2], range[4], range[5]}
 | |
|         r[lang] = add_bytes(source, r[lang])
 | |
|       end
 | |
|       return r
 | |
|     ]], r, source)
 | |
| 
 | |
|     eq(rb, r)
 | |
| 
 | |
|   end)
 | |
| 
 | |
|   it("does not produce empty injection ranges (#23409)", function()
 | |
|     insert [[
 | |
|       Examples: >lua
 | |
|         local a = {}
 | |
| <
 | |
|     ]]
 | |
| 
 | |
|     -- This is not a valid injection since (code) has children and include-children is not set
 | |
|     exec_lua [[
 | |
|       parser1 = require('vim.treesitter.languagetree').new(0, "vimdoc", {
 | |
|         injections = {
 | |
|           vimdoc = "((codeblock (language) @injection.language (code) @injection.content))"
 | |
|         }
 | |
|       })
 | |
|       parser1:parse(true)
 | |
|     ]]
 | |
| 
 | |
|     eq(0, exec_lua("return #vim.tbl_keys(parser1:children())"))
 | |
| 
 | |
|     exec_lua [[
 | |
|       parser2 = require('vim.treesitter.languagetree').new(0, "vimdoc", {
 | |
|         injections = {
 | |
|           vimdoc = "((codeblock (language) @injection.language (code) @injection.content) (#set! injection.include-children))"
 | |
|         }
 | |
|       })
 | |
|       parser2:parse(true)
 | |
|     ]]
 | |
| 
 | |
|     eq(1, exec_lua("return #vim.tbl_keys(parser2:children())"))
 | |
|     eq( { { { 1, 0, 21, 2, 0, 42 } } }, exec_lua("return parser2:children().lua:included_regions()"))
 | |
| 
 | |
|   end)
 | |
| 
 | |
|   it("parsers injections incrementally", function()
 | |
|     insert(dedent[[
 | |
|       >lua
 | |
|         local a = {}
 | |
|       <
 | |
| 
 | |
|       >lua
 | |
|         local b = {}
 | |
|       <
 | |
| 
 | |
|       >lua
 | |
|         local c = {}
 | |
|       <
 | |
| 
 | |
|       >lua
 | |
|         local d = {}
 | |
|       <
 | |
| 
 | |
|       >lua
 | |
|         local e = {}
 | |
|       <
 | |
| 
 | |
|       >lua
 | |
|         local f = {}
 | |
|       <
 | |
| 
 | |
|       >lua
 | |
|         local g = {}
 | |
|       <
 | |
|     ]])
 | |
| 
 | |
|     exec_lua [[
 | |
|       parser = require('vim.treesitter.languagetree').new(0, "vimdoc", {
 | |
|         injections = {
 | |
|           vimdoc = "((codeblock (language) @injection.language (code) @injection.content) (#set! injection.include-children))"
 | |
|         }
 | |
|       })
 | |
|     ]]
 | |
| 
 | |
|     --- Do not parse injections by default
 | |
|     eq(0, exec_lua [[
 | |
|       parser:parse()
 | |
|       return #vim.tbl_keys(parser:children())
 | |
|     ]])
 | |
| 
 | |
|     --- Only parse injections between lines 0, 2
 | |
|     eq(1, exec_lua [[
 | |
|       parser:parse({0, 2})
 | |
|       return #parser:children().lua:trees()
 | |
|     ]])
 | |
| 
 | |
|     eq(2, exec_lua [[
 | |
|       parser:parse({2, 6})
 | |
|       return #parser:children().lua:trees()
 | |
|     ]])
 | |
| 
 | |
|     eq(7, exec_lua [[
 | |
|       parser:parse(true)
 | |
|       return #parser:children().lua:trees()
 | |
|     ]])
 | |
|   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 type
 | |
|     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)
 | |
| end)
 | 
