aboutsummaryrefslogtreecommitdiff
path: root/test/functional/treesitter
diff options
context:
space:
mode:
Diffstat (limited to 'test/functional/treesitter')
-rw-r--r--test/functional/treesitter/fold_spec.lua40
-rw-r--r--test/functional/treesitter/highlight_spec.lua183
-rw-r--r--test/functional/treesitter/inspect_tree_spec.lua18
-rw-r--r--test/functional/treesitter/language_spec.lua31
-rw-r--r--test/functional/treesitter/node_spec.lua39
-rw-r--r--test/functional/treesitter/parser_spec.lua886
-rw-r--r--test/functional/treesitter/query_spec.lua808
-rw-r--r--test/functional/treesitter/utils_spec.lua11
8 files changed, 1231 insertions, 785 deletions
diff --git a/test/functional/treesitter/fold_spec.lua b/test/functional/treesitter/fold_spec.lua
index 9428432f66..a7f278aa01 100644
--- a/test/functional/treesitter/fold_spec.lua
+++ b/test/functional/treesitter/fold_spec.lua
@@ -1,13 +1,15 @@
-local helpers = require('test.functional.helpers')(after_each)
-local clear = helpers.clear
-local eq = helpers.eq
-local insert = helpers.insert
-local exec_lua = helpers.exec_lua
-local command = helpers.command
-local feed = helpers.feed
-local poke_eventloop = helpers.poke_eventloop
+local t = require('test.testutil')
+local n = require('test.functional.testnvim')()
local Screen = require('test.functional.ui.screen')
+local clear = n.clear
+local eq = t.eq
+local insert = n.insert
+local exec_lua = n.exec_lua
+local command = n.command
+local feed = n.feed
+local poke_eventloop = n.poke_eventloop
+
before_each(clear)
describe('treesitter foldexpr', function()
@@ -404,6 +406,28 @@ t3]])
}, get_fold_levels())
end)
+ it('handles quantified patterns', function()
+ insert([[
+import hello
+import hello
+import hello
+import hello
+import hello
+import hello]])
+
+ exec_lua([[vim.treesitter.query.set('python', 'folds', '(import_statement)+ @fold')]])
+ parse('python')
+
+ eq({
+ [1] = '>1',
+ [2] = '1',
+ [3] = '1',
+ [4] = '1',
+ [5] = '1',
+ [6] = '1',
+ }, get_fold_levels())
+ end)
+
it('updates folds in all windows', function()
local screen = Screen.new(60, 48)
screen:attach()
diff --git a/test/functional/treesitter/highlight_spec.lua b/test/functional/treesitter/highlight_spec.lua
index 2bf230fe69..69984b3233 100644
--- a/test/functional/treesitter/highlight_spec.lua
+++ b/test/functional/treesitter/highlight_spec.lua
@@ -1,13 +1,14 @@
-local helpers = require('test.functional.helpers')(after_each)
+local t = require('test.testutil')
+local n = require('test.functional.testnvim')()
local Screen = require('test.functional.ui.screen')
-local clear = helpers.clear
-local insert = helpers.insert
-local exec_lua = helpers.exec_lua
-local feed = helpers.feed
-local command = helpers.command
-local api = helpers.api
-local eq = helpers.eq
+local clear = n.clear
+local insert = n.insert
+local exec_lua = n.exec_lua
+local feed = n.feed
+local command = n.command
+local api = n.api
+local eq = t.eq
before_each(clear)
@@ -483,7 +484,7 @@ describe('treesitter highlighting (C)', function()
exec_lua [[
vim.treesitter.language.register("c", "foo")
local parser = vim.treesitter.get_parser(0, "c", {
- injections = {c = '(preproc_def (preproc_arg) @injection.content (#set! injection.language "fOO")) (preproc_function_def value: (preproc_arg) @injection.content (#set! injection.language "fOO"))'}
+ injections = {c = '(preproc_def (preproc_arg) @injection.content (#set! injection.language "foo")) (preproc_function_def value: (preproc_arg) @injection.content (#set! injection.language "foo"))'}
})
local highlighter = vim.treesitter.highlighter
test_hl = highlighter.new(parser, {queries = {c = hl_query}})
@@ -681,6 +682,12 @@ describe('treesitter highlighting (C)', function()
((identifier) @Identifier
(#set! conceal "")
(#eq? @Identifier "lstate"))
+
+ ((call_expression
+ function: (identifier) @function
+ arguments: (argument_list) @arguments)
+ (#eq? @function "multiqueue_put")
+ (#set! @function conceal "V"))
]]}})
]=]
@@ -697,7 +704,7 @@ describe('treesitter highlighting (C)', function()
|
LuaRef cb = nlua_ref(, 1); |
|
- multiqueue_put(main_loop.events, nlua_schedule_event, |
+ {11:V}(main_loop.events, nlua_schedule_event, |
1, (void *)(ptrdiff_t)cb); |
return 0; |
^} |
@@ -756,6 +763,75 @@ describe('treesitter highlighting (C)', function()
]],
}
end)
+
+ it('gives higher priority to more specific captures #27895', function()
+ insert([[
+ void foo(int *bar);
+ ]])
+
+ local query = [[
+ "*" @operator
+
+ (parameter_declaration
+ declarator: (pointer_declarator) @variable.parameter)
+ ]]
+
+ exec_lua(
+ [[
+ local query = ...
+ vim.treesitter.query.set('c', 'highlights', query)
+ vim.treesitter.highlighter.new(vim.treesitter.get_parser(0, 'c'))
+ ]],
+ query
+ )
+
+ screen:expect {
+ grid = [[
+ void foo(int {4:*}{11:bar}); |
+ ^ |
+ {1:~ }|*15
+ |
+ ]],
+ }
+ end)
+end)
+
+describe('treesitter highlighting (lua)', function()
+ local screen
+
+ before_each(function()
+ screen = Screen.new(65, 18)
+ screen:attach()
+ screen:set_default_attr_ids {
+ [1] = { bold = true, foreground = Screen.colors.Blue },
+ [2] = { foreground = Screen.colors.DarkCyan },
+ [3] = { foreground = Screen.colors.Magenta },
+ [4] = { foreground = Screen.colors.SlateBlue },
+ [5] = { bold = true, foreground = Screen.colors.Brown },
+ }
+ end)
+
+ it('supports language injections', function()
+ insert [[
+ local ffi = require('ffi')
+ ffi.cdef("int (*fun)(int, char *);")
+ ]]
+
+ exec_lua [[
+ vim.bo.filetype = 'lua'
+ vim.treesitter.start()
+ ]]
+
+ screen:expect {
+ grid = [[
+ {5:local} {2:ffi} {5:=} {4:require(}{3:'ffi'}{4:)} |
+ {2:ffi}{4:.}{2:cdef}{4:(}{3:"}{4:int}{3: }{4:(}{5:*}{3:fun}{4:)(int,}{3: }{4:char}{3: }{5:*}{4:);}{3:"}{4:)} |
+ ^ |
+ {1:~ }|*14
+ |
+ ]],
+ }
+ end)
end)
describe('treesitter highlighting (help)', function()
@@ -788,7 +864,7 @@ describe('treesitter highlighting (help)', function()
screen:expect {
grid = [[
- {1:>ruby} |
+ {1:>}{3:ruby} |
{1: -- comment} |
{1: local this_is = 'actually_lua'} |
{1:<} |
@@ -797,11 +873,11 @@ describe('treesitter highlighting (help)', function()
]],
}
- helpers.api.nvim_buf_set_text(0, 0, 1, 0, 5, { 'lua' })
+ n.api.nvim_buf_set_text(0, 0, 1, 0, 5, { 'lua' })
screen:expect {
grid = [[
- {1:>lua} |
+ {1:>}{3:lua} |
{1: -- comment} |
{1: }{3:local}{1: }{4:this_is}{1: }{3:=}{1: }{5:'actually_lua'} |
{1:<} |
@@ -810,11 +886,11 @@ describe('treesitter highlighting (help)', function()
]],
}
- helpers.api.nvim_buf_set_text(0, 0, 1, 0, 4, { 'ruby' })
+ n.api.nvim_buf_set_text(0, 0, 1, 0, 4, { 'ruby' })
screen:expect {
grid = [[
- {1:>ruby} |
+ {1:>}{3:ruby} |
{1: -- comment} |
{1: local this_is = 'actually_lua'} |
{1:<} |
@@ -823,6 +899,40 @@ describe('treesitter highlighting (help)', function()
]],
}
end)
+
+ it('correctly redraws injections subpriorities', function()
+ -- The top level string node will be highlighted first
+ -- with an extmark spanning multiple lines.
+ -- When the next line is drawn, which includes an injection,
+ -- make sure the highlight appears above the base tree highlight
+
+ insert([=[
+ local s = [[
+ local also = lua
+ ]]
+ ]=])
+
+ exec_lua [[
+ parser = vim.treesitter.get_parser(0, "lua", {
+ injections = {
+ lua = '(string content: (_) @injection.content (#set! injection.language lua))'
+ }
+ })
+
+ vim.treesitter.highlighter.new(parser)
+ ]]
+
+ screen:expect {
+ grid = [=[
+ {3:local} {4:s} {3:=} {5:[[} |
+ {5: }{3:local}{5: }{4:also}{5: }{3:=}{5: }{4:lua} |
+ {5:]]} |
+ ^ |
+ {2:~ }|
+ |
+ ]=],
+ }
+ end)
end)
describe('treesitter highlighting (nested injections)', function()
@@ -891,3 +1001,46 @@ vim.cmd([[
}
end)
end)
+
+describe('treesitter highlighting (markdown)', function()
+ local screen
+
+ before_each(function()
+ screen = Screen.new(40, 6)
+ screen:attach()
+ screen:set_default_attr_ids {
+ [1] = { foreground = Screen.colors.Blue1 },
+ [2] = { bold = true, foreground = Screen.colors.Blue1 },
+ [3] = { bold = true, foreground = Screen.colors.Brown },
+ [4] = { foreground = Screen.colors.Cyan4 },
+ [5] = { foreground = Screen.colors.Magenta1 },
+ }
+ end)
+
+ it('supports hyperlinks', function()
+ local url = 'https://example.com'
+ insert(string.format('[This link text](%s) is a hyperlink.', url))
+ exec_lua([[
+ vim.bo.filetype = 'markdown'
+ vim.treesitter.start()
+ ]])
+
+ screen:expect {
+ grid = [[
+ {4:[}{6:This link text}{4:](}{7:https://example.com}{4:)} is|
+ a hyperlink^. |
+ {2:~ }|*3
+ |
+ ]],
+ attr_ids = {
+ [1] = { foreground = Screen.colors.Blue1 },
+ [2] = { bold = true, foreground = Screen.colors.Blue1 },
+ [3] = { bold = true, foreground = Screen.colors.Brown },
+ [4] = { foreground = Screen.colors.Cyan4 },
+ [5] = { foreground = Screen.colors.Magenta },
+ [6] = { foreground = Screen.colors.Cyan4, url = url },
+ [7] = { underline = true, foreground = Screen.colors.SlateBlue },
+ },
+ }
+ end)
+end)
diff --git a/test/functional/treesitter/inspect_tree_spec.lua b/test/functional/treesitter/inspect_tree_spec.lua
index a3d44ff906..f5acfe7c4a 100644
--- a/test/functional/treesitter/inspect_tree_spec.lua
+++ b/test/functional/treesitter/inspect_tree_spec.lua
@@ -1,17 +1,19 @@
-local helpers = require('test.functional.helpers')(after_each)
-local clear = helpers.clear
-local insert = helpers.insert
-local dedent = helpers.dedent
-local eq = helpers.eq
-local exec_lua = helpers.exec_lua
-local feed = helpers.feed
+local t = require('test.testutil')
+local n = require('test.functional.testnvim')()
+
+local clear = n.clear
+local insert = n.insert
+local dedent = t.dedent
+local eq = t.eq
+local exec_lua = n.exec_lua
+local feed = n.feed
describe('vim.treesitter.inspect_tree', function()
before_each(clear)
local expect_tree = function(x)
local expected = vim.split(vim.trim(dedent(x)), '\n')
- local actual = helpers.buf_lines(0) ---@type string[]
+ local actual = n.buf_lines(0) ---@type string[]
eq(expected, actual)
end
diff --git a/test/functional/treesitter/language_spec.lua b/test/functional/treesitter/language_spec.lua
index 65d9e0e81c..40c974beee 100644
--- a/test/functional/treesitter/language_spec.lua
+++ b/test/functional/treesitter/language_spec.lua
@@ -1,12 +1,13 @@
-local helpers = require('test.functional.helpers')(after_each)
+local t = require('test.testutil')
+local n = require('test.functional.testnvim')()
-local clear = helpers.clear
-local eq = helpers.eq
-local command = helpers.command
-local exec_lua = helpers.exec_lua
-local pcall_err = helpers.pcall_err
-local matches = helpers.matches
-local insert = helpers.insert
+local clear = n.clear
+local eq = t.eq
+local command = n.command
+local exec_lua = n.exec_lua
+local pcall_err = t.pcall_err
+local matches = t.matches
+local insert = n.insert
before_each(clear)
@@ -120,6 +121,20 @@ describe('treesitter language API', function()
eq('<node translation_unit>', exec_lua('return tostring(tree:root())'))
end)
+ it('retrieve the tree given a range when range is out of bounds relative to buffer', function()
+ insert([[
+ int main() {
+ int x = 3;
+ }]])
+
+ exec_lua([[
+ langtree = vim.treesitter.get_parser(0, "c")
+ tree = langtree:tree_for_range({10, 10, 10, 10})
+ ]])
+
+ eq('<node translation_unit>', exec_lua('return tostring(tree:root())'))
+ end)
+
it('retrieve the node given a range', function()
insert([[
int main() {
diff --git a/test/functional/treesitter/node_spec.lua b/test/functional/treesitter/node_spec.lua
index f114d36823..96579f296b 100644
--- a/test/functional/treesitter/node_spec.lua
+++ b/test/functional/treesitter/node_spec.lua
@@ -1,10 +1,11 @@
-local helpers = require('test.functional.helpers')(after_each)
+local t = require('test.testutil')
+local n = require('test.functional.testnvim')()
-local clear = helpers.clear
-local eq = helpers.eq
-local exec_lua = helpers.exec_lua
-local insert = helpers.insert
-local assert_alive = helpers.assert_alive
+local clear = n.clear
+local eq = t.eq
+local exec_lua = n.exec_lua
+local insert = n.insert
+local assert_alive = n.assert_alive
before_each(clear)
@@ -142,4 +143,30 @@ describe('treesitter node API', function()
eq(28, lua_eval('root:byte_length()'))
eq(3, lua_eval('child:byte_length()'))
end)
+
+ it('child_containing_descendant() works', function()
+ insert([[
+ int main() {
+ int x = 3;
+ }]])
+
+ exec_lua([[
+ tree = vim.treesitter.get_parser(0, "c"):parse()[1]
+ root = tree:root()
+ main = root:child(0)
+ body = main:child(2)
+ statement = body:child(1)
+ declarator = statement:child(1)
+ value = declarator:child(1)
+ ]])
+
+ eq(lua_eval('main:type()'), lua_eval('root:child_containing_descendant(value):type()'))
+ eq(lua_eval('body:type()'), lua_eval('main:child_containing_descendant(value):type()'))
+ eq(lua_eval('statement:type()'), lua_eval('body:child_containing_descendant(value):type()'))
+ eq(
+ lua_eval('declarator:type()'),
+ lua_eval('statement:child_containing_descendant(value):type()')
+ )
+ eq(vim.NIL, lua_eval('declarator:child_containing_descendant(value)'))
+ end)
end)
diff --git a/test/functional/treesitter/parser_spec.lua b/test/functional/treesitter/parser_spec.lua
index 875b772fec..dbd6bb3c23 100644
--- a/test/functional/treesitter/parser_spec.lua
+++ b/test/functional/treesitter/parser_spec.lua
@@ -1,15 +1,13 @@
-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
-local api = helpers.api
-local fn = helpers.fn
+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 feed = n.feed
describe('treesitter parser API', function()
before_each(function()
@@ -121,7 +119,7 @@ void ui_refresh(void)
res = {}
for node, field in func_node:iter_children() do
- table.insert(res, {node:type(), field})
+ table.insert(res, { node:type(), field })
end
return res
]])
@@ -157,7 +155,7 @@ void ui_refresh(void)
local res = {}
for _, node in ipairs(func_node:field("type")) do
- table.insert(res, {node:type(), node:range()})
+ table.insert(res, { node:type(), node:range() })
end
return res
]])
@@ -173,185 +171,6 @@ void ui_refresh(void)
assert(res_fail)
end)
- 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()
- 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 = test_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
- ]],
- test_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, { all = true }) 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
- ]],
- test_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('support query and iter by capture for quantifiers', 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
- ]],
- '(expression_statement (assignment_expression (call_expression)))+ @funccall'
- )
-
- 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('support query and iter by match for quantifiers', 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, { all = true }) 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
- ]],
- '(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('supports getting text of multiline node', function()
insert(test_text)
local res = exec_lua([[
@@ -369,7 +188,7 @@ void ui_refresh(void)
eq('void', res2)
end)
- it('support getting text where start of node is one past EOF', function()
+ it('supports getting text where start of node is one past EOF', function()
local text = [[
def run
a = <<~E
@@ -378,25 +197,25 @@ end]]
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
+ local fake_node = {}
+ function fake_node:start()
+ return 3, 0, 23
end
- return 3, 0, 3, 0
- end
- return vim.treesitter.get_node_text(fake_node, 0)
- ]]
+ 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()
+ it('supports getting empty text if node range is zero width', function()
local text = [[
```lua
{}
@@ -418,387 +237,6 @@ end]]
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, 0, -1, { all = true }) 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
- ]])
-
- 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, 0, -1, { all = true }) 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
- ]])
-
- 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(
- [[
- 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")
-
- -- Time bomb: update this in 0.12
- if vim.fn.has('nvim-0.12') == 1 then
- return 'Update this test to remove this message and { all = true } from add_predicate'
- end
- query.add_predicate("is-main?", is_main, { all = true })
-
- 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)
- end
-
- -- Once with the old API. Remove this whole 'do' block in 0.12
- do
- local res = exec_lua(
- [[
- 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, true)
-
- 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
- )
-
- -- 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 [[
- local query = vim.treesitter.query
-
- local t = {}
- for _, v in ipairs(query.list_predicates()) do
- t[v] = true
- end
-
- return t
- ]]
-
- 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(
- [[
- local parser = vim.treesitter.get_parser(0, "lua")
- local query = vim.treesitter.query.parse("lua", ...)
- local nodes = {}
- for _, node in query:iter_captures(parser:parse()[1]:root(), 0) do
- nodes[#nodes+1] = { node:range() }
- end
- return nodes
- ]],
- query
- )
- 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 query = [[
- (((comment (comment_content))+) @bar
- (#lua-match? @bar "Comment"))
- ]]
-
- local result = exec_lua(
- [[
- local parser = vim.treesitter.get_parser(0, "lua")
- local query = vim.treesitter.query.parse("lua", ...)
- local nodes = {}
- for _, node in query:iter_captures(parser:parse()[1]:root(), 0) do
- nodes[#nodes+1] = { node:range() }
- end
- return nodes
- ]],
- query
- )
-
- 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(
- [[
- 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
- 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
- ]],
- test_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('allows to set simple ranges', function()
insert(test_text)
@@ -828,7 +266,7 @@ end]]
return parser:included_regions()
]]
- eq(range_tbl, { { { 0, 0, 0, 17, 1, 508 } } })
+ eq({ { { 0, 0, 0, 17, 1, 508 } } }, range_tbl)
end)
it('allows to set complex ranges', function()
@@ -882,56 +320,31 @@ end]]
local ret = exec_lua(
[[
- local str = ...
- local parser = vim.treesitter.get_string_parser(str, "c")
+ 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"))')
+ 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
+ for _, node in query:iter_captures(parser:parse()[1]:root(), str) do
+ table.insert(nodes, { node:range() })
+ end
- return nodes]],
+ 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
- ]])
+ return exec_lua [[
+ local result = {}
+ parser:for_each_tree(function(tree) table.insert(result, {tree:root():range()}) end)
+ return result
+ ]]
end
before_each(function()
@@ -948,10 +361,14 @@ int x = INT_MAX;
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)
+ 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)'))
@@ -965,7 +382,7 @@ int x = INT_MAX;
{ 2, 29, 2, 66 }, -- READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y))
}, get_ranges())
- helpers.feed('ggo<esc>')
+ n.feed('ggo<esc>')
eq(5, exec_lua('return #parser:children().c:trees()'))
eq({
{ 0, 0, 8, 0 }, -- root tree
@@ -981,10 +398,14 @@ int x = INT_MAX;
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)
+ 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)'))
@@ -998,7 +419,7 @@ int x = INT_MAX;
-- READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y))
}, get_ranges())
- helpers.feed('ggo<esc>')
+ n.feed('ggo<esc>')
eq('table', exec_lua('return type(parser:children().c)'))
eq(2, exec_lua('return #parser:children().c:trees()'))
eq({
@@ -1010,7 +431,7 @@ int x = INT_MAX;
-- READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y))
}, get_ranges())
- helpers.feed('7ggI//<esc>')
+ n.feed('7ggI//<esc>')
exec_lua([[parser:parse({6, 7})]])
eq('table', exec_lua('return type(parser:children().c)'))
eq(2, exec_lua('return #parser:children().c:trees()'))
@@ -1027,10 +448,14 @@ int x = INT_MAX;
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)
+ 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)'))
@@ -1044,7 +469,7 @@ int x = INT_MAX;
{ 2, 29, 2, 66 }, -- READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y))
}, get_ranges())
- helpers.feed('ggo<esc>')
+ n.feed('ggo<esc>')
eq(5, exec_lua('return #parser:children().c:trees()'))
eq({
{ 0, 0, 8, 0 }, -- root tree
@@ -1060,10 +485,14 @@ int x = INT_MAX;
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)
+ 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)'))
@@ -1078,13 +507,13 @@ int x = INT_MAX;
end)
it('should list all directives', function()
local res_list = exec_lua [[
- local query = vim.treesitter.query
+ local query = vim.treesitter.query
- local list = query.list_directives()
+ local list = query.list_directives()
- table.sort(list)
+ table.sort(list)
- return list
+ return list
]]
eq({ 'gsub!', 'offset!', 'set!', 'trim!' }, res_list)
@@ -1102,16 +531,49 @@ int x = INT_MAX;
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)
+ 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
+ ]])
- local sub_tree = parser:language_for_range({1, 18, 1, 19})
+ eq(true, result)
+ end)
+ end)
- return sub_tree == parser:children().c
+ describe('when setting the node for an injection', function()
+ before_each(function()
+ insert([[
+print()
]])
+ end)
- eq(result, true)
+ it('ignores optional captures #23100', function()
+ local result = exec_lua([[
+ parser = vim.treesitter.get_parser(0, "lua", {
+ injections = {
+ lua = (
+ '(function_call ' ..
+ '(arguments ' ..
+ '(string)? @injection.content ' ..
+ '(number)? @injection.content ' ..
+ '(#offset! @injection.content 0 1 0 -1) ' ..
+ '(#set! injection.language "c")))'
+ )
+ }
+ })
+ parser:parse(true)
+
+ return parser:is_valid()
+ ]])
+
+ eq(true, result)
end)
end)
@@ -1135,7 +597,7 @@ int x = INT_MAX;
return result
]])
- eq(result, 'value')
+ eq('value', result)
end)
describe('when setting a key on a capture', function()
@@ -1158,7 +620,7 @@ int x = INT_MAX;
end
]])
- eq(result, 'value')
+ eq('value', result)
end)
it('it should not overwrite the nested table', function()
@@ -1208,15 +670,15 @@ int x = INT_MAX;
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
- ]],
+ 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
@@ -1226,7 +688,7 @@ int x = INT_MAX;
{ 'declaration', 1, 2, 1, 12 },
}, run_query())
- helpers.command 'normal ggO'
+ n.command 'normal ggO'
insert('int a;')
eq({
@@ -1258,14 +720,14 @@ int x = INT_MAX;
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
- ]],
+ 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
)
@@ -1281,14 +743,14 @@ int x = INT_MAX;
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
- ]],
+ 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
)
@@ -1402,53 +864,7 @@ int x = INT_MAX;
)
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)
-
- describe('is_valid()', function()
+ describe('languagetree is_valid()', function()
before_each(function()
insert(dedent [[
Treesitter integration *treesitter*
diff --git a/test/functional/treesitter/query_spec.lua b/test/functional/treesitter/query_spec.lua
new file mode 100644
index 0000000000..c3a376cd71
--- /dev/null
+++ b/test/functional/treesitter/query_spec.lua
@@ -0,0 +1,808 @@
+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 get_query_result_code = [[
+ 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
+]]
+
+describe('treesitter query API', function()
+ before_each(function()
+ clear()
+ exec_lua [[
+ vim.g.__ts_debug = 1
+ ]]
+ 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 [[
+ return vim.treesitter.query.get("c", "highlights").captures
+ ]]
+
+ -- 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(
+ [[
+ local query, n = ...
+ local before = vim.api.nvim__stats().ts_query_parse_count
+ collectgarbage("stop")
+ for i=1, n, 1 do
+ cquery = vim.treesitter.query.parse("c", ...)
+ end
+ collectgarbage("restart")
+ collectgarbage("collect")
+ local after = vim.api.nvim__stats().ts_query_parse_count
+ return after - before
+ ]],
+ long_query,
+ _n
+ )
+ 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(
+ [[
+ 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
+ ]],
+ test_query
+ )
+
+ 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(
+ [[
+ 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, { all = true }) 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
+ ]],
+ test_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 query and iter by capture for quantifiers', 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
+ ]],
+ '(expression_statement (assignment_expression (call_expression)))+ @funccall'
+ )
+
+ 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(
+ [[
+ 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, { all = true }) 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
+ ]],
+ '(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('can match special regex characters like \\ * + ( with `vim-match?`', function()
+ insert('char* astring = "\\n"; (1 + 1) * 2 != 2;')
+
+ ---@type table
+ local res = exec_lua([[
+ query = (
+ '([_] @plus (#vim-match? @plus "^\\\\+$"))' ..
+ '([_] @times (#vim-match? @times "^\\\\*$"))' ..
+ '([_] @paren (#vim-match? @paren "^\\\\($"))' ..
+ '([_] @escape (#vim-match? @escape "^\\\\\\\\n$"))' ..
+ '([_] @string (#vim-match? @string "^\\"\\\\\\\\n\\"$"))'
+ )
+ cquery = vim.treesitter.query.parse("c", query)
+ parser = vim.treesitter.get_parser(0, "c")
+ tree = parser:parse()[1]
+ res = {}
+ for pattern, match in cquery:iter_matches(tree:root(), 0, 0, -1, { all = true }) 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
+ ]])
+
+ 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(get_query_result_code)
+
+ 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('supports builtin predicate has-ancestor?', function()
+ insert([[
+ int x = 123;
+ enum C { y = 124 };
+ int main() { int z = 125; }]])
+ exec_lua(get_query_result_code)
+
+ local result = exec_lua(
+ [[return get_query_result(...)]],
+ [[((number_literal) @literal (#has-ancestor? @literal "function_definition"))]]
+ )
+ eq({ { 'literal', 'number_literal', { 2, 21, 2, 24 }, '125' } }, result)
+
+ result = exec_lua(
+ [[return 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(
+ [[return 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)
+ end)
+
+ it('allows loading query with escaped quotes and capture them `#{lua,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, 0, -1, { all = true }) 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
+ ]])
+
+ 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(
+ [[
+ 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")
+
+ -- Time bomb: update this in 0.12
+ if vim.fn.has('nvim-0.12') == 1 then
+ return 'Update this test to remove this message and { all = true } from add_predicate'
+ end
+ query.add_predicate("is-main?", is_main, { all = true })
+
+ 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)
+ end
+
+ -- Once with the old API. Remove this whole 'do' block in 0.12
+ do
+ local res = exec_lua(
+ [[
+ 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, true)
+
+ 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
+ )
+
+ -- 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 [[
+ local query = vim.treesitter.query
+
+ local t = {}
+ for _, v in ipairs(query.list_predicates()) do
+ t[v] = true
+ end
+
+ return t
+ ]]
+
+ 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(
+ [[
+ local parser = vim.treesitter.get_parser(0, "lua")
+ local query = vim.treesitter.query.parse("lua", ...)
+ local nodes = {}
+ for _, node in query:iter_captures(parser:parse()[1]:root(), 0) do
+ nodes[#nodes+1] = { node:range() }
+ end
+ return nodes
+ ]],
+ query
+ )
+ 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 query = [[
+ (((comment (comment_content))+) @bar
+ (#lua-match? @bar "Comment"))
+ ]]
+
+ local result = exec_lua(
+ [[
+ local parser = vim.treesitter.get_parser(0, "lua")
+ local query = vim.treesitter.query.parse("lua", ...)
+ local nodes = {}
+ for _, node in query:iter_captures(parser:parse()[1]:root(), 0) do
+ nodes[#nodes+1] = { node:range() }
+ end
+ return nodes
+ ]],
+ query
+ )
+
+ 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(
+ [[
+ 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
+ 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
+ ]],
+ test_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('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)
+
+ 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)
+
+ 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 query = [[
+ (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 result = exec_lua(
+ [=[
+ local query = vim.treesitter.query.parse("c", ...)
+ local parser = vim.treesitter.get_parser(0, "c")
+ local root = parser:parse()[1]:root()
+ local t = {}
+ for id, node, metadata in query:iter_captures(root, 0) do
+ t[query.captures[id]] = metadata
+ end
+ return t
+ ]=],
+ query
+ )
+
+ 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(
+ [[
+ local query = vim.treesitter.query.parse("c", ...)
+ local match_preds = query.match_preds
+ local called = 0
+ function query:match_preds(...)
+ 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, node in query:iter_captures(root, 0) do
+ captures[#captures + 1] = id
+ end
+ return { called, captures }
+ ]],
+ query
+ )
+
+ eq({ 2, { 1, 1, 2, 2 } }, result)
+ end)
+ end)
+end)
diff --git a/test/functional/treesitter/utils_spec.lua b/test/functional/treesitter/utils_spec.lua
index 2734c22499..bca0aca0cb 100644
--- a/test/functional/treesitter/utils_spec.lua
+++ b/test/functional/treesitter/utils_spec.lua
@@ -1,9 +1,10 @@
-local helpers = require('test.functional.helpers')(after_each)
+local t = require('test.testutil')
+local n = require('test.functional.testnvim')()
-local clear = helpers.clear
-local insert = helpers.insert
-local eq = helpers.eq
-local exec_lua = helpers.exec_lua
+local clear = n.clear
+local insert = n.insert
+local eq = t.eq
+local exec_lua = n.exec_lua
before_each(clear)