local M = {} local tests_query = [[ (function_declaration name: (identifier) @testname parameters: (parameter_list . (parameter_declaration type: (pointer_type) @type) .) (#match? @type "*testing.(T|M)") (#match? @testname "^Test.+$")) @parent ]] local subtests_query = [[ (call_expression function: (selector_expression operand: (identifier) field: (field_identifier) @run) arguments: (argument_list (interpreted_string_literal) @testname [ (func_literal) (identifier) ]) (#eq? @run "Run")) @parent ]] local function format_subtest(testcase, test_tree) local parent if testcase.parent then for _, curr in pairs(test_tree) do if curr.name == testcase.parent then parent = curr break end end return string.format("%s/%s", format_subtest(parent, test_tree), testcase.name) else return testcase.name end end local function get_closest_above_cursor(test_tree) local result for _, curr in pairs(test_tree) do if not result then result = curr else local node_row1, _, _, _ = curr.node:range() local result_row1, _, _, _ = result.node:range() if node_row1 > result_row1 then result = curr end end end if result then return format_subtest(result, test_tree) end return nil end local function is_parent(dest, source) if not (dest and source) then return false end if dest == source then return false end local current = source while current ~= nil do if current == dest then return true end current = current:parent() end return false end local function get_closest_test() local stop_row = vim.api.nvim_win_get_cursor(0)[1] local ft = vim.api.nvim_buf_get_option(0, "filetype") assert(ft == "go", "can only find test in go files, not " .. ft) local parser = vim.treesitter.get_parser(0) local root = (parser:parse()[1]):root() local test_tree = {} local test_query = vim.treesitter.query.parse(ft, tests_query) assert(test_query, "could not parse test query") for _, match, _ in test_query:iter_matches(root, 0, 0, stop_row, { all = true }) do local test_match = {} for id, nodes in pairs(match) do for _, node in ipairs(nodes) do local capture = test_query.captures[id] if capture == "testname" then local name = vim.treesitter.get_node_text(node, 0) test_match.name = name end if capture == "parent" then test_match.node = node end end end table.insert(test_tree, test_match) end local subtest_query = vim.treesitter.query.parse(ft, subtests_query) assert(subtest_query, "could not parse test query") for _, match, _ in subtest_query:iter_matches(root, 0, 0, stop_row, { all = true }) do local test_match = {} for id, nodes in pairs(match) do for _, node in ipairs(nodes) do local capture = subtest_query.captures[id] if capture == "testname" then local name = vim.treesitter.get_node_text(node, 0) test_match.name = string.gsub(string.gsub(name, " ", "_"), '"', "") end if capture == "parent" then test_match.node = node end end end table.insert(test_tree, test_match) end table.sort(test_tree, function(a, b) return is_parent(a.node, b.node) end) for _, parent in ipairs(test_tree) do for _, child in ipairs(test_tree) do if is_parent(parent.node, child.node) then child.parent = parent.name end end end return get_closest_above_cursor(test_tree) end local function get_package_name() local test_dir = vim.fn.fnamemodify(vim.fn.expand("%:.:h"), ":r") return "./" .. test_dir end M.closest_test = function() local package_name = get_package_name() local test_case = get_closest_test() local test_scope if test_case then test_scope = "testcase" else test_scope = "package" end return { package = package_name, name = test_case, scope = test_scope, } end M.get_root_dir = function() local id, client = next(vim.lsp.buf_get_clients()) if id == nil then error({ error_msg = "lsp client not attached" }) end if not client.config.root_dir then error({ error_msg = "lsp root_dir not defined" }) end return client.config.root_dir end return M