-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: testify test suite support (#58)
- Loading branch information
1 parent
df4e6b3
commit d723241
Showing
20 changed files
with
878 additions
and
39 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
local M = {} | ||
|
||
M.lookup = require("neotest-golang.features.testify.lookup") | ||
M.query = require("neotest-golang.features.testify.query") | ||
M.tree_modification = | ||
require("neotest-golang.features.testify.tree_modification") | ||
|
||
return M |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
--- Lookup table for renaming Neotest namespaces (receiver type to testify suite function). | ||
|
||
local find = require("neotest-golang.find") | ||
local query = require("neotest-golang.features.testify.query") | ||
|
||
local M = {} | ||
|
||
--- TreeSitter query for identifying testify suites and their components. | ||
--- @type string | ||
M.query = [[ | ||
; query for the lookup between receiver and test suite. | ||
; package main // @package | ||
(package_clause | ||
(package_identifier) @package) | ||
; func TestSuite(t *testing.T) { // @test_function | ||
; suite.Run(t, new(testSuitestruct)) // @suite_lib, @run_method, @suite_receiver | ||
; } | ||
(function_declaration | ||
name: (identifier) @test_function (#match? @test_function "^Test") | ||
body: (block | ||
(expression_statement | ||
(call_expression | ||
function: (selector_expression | ||
operand: (identifier) @suite_lib (#eq? @suite_lib "suite") | ||
field: (field_identifier) @run_method (#eq? @run_method "Run")) | ||
arguments: (argument_list | ||
(identifier) | ||
(call_expression | ||
arguments: (argument_list | ||
(type_identifier) @suite_struct))))))) | ||
; func TestSuite(t *testing.T) { // @test_function | ||
; s := &testSuiteStruct{} // @suite_struct | ||
; suite.Run(t, s) // @suite_lib, @run_method | ||
; } | ||
(function_declaration | ||
name: (identifier) @test_function (#match? @test_function "^Test") | ||
parameters: (parameter_list | ||
(parameter_declaration | ||
name: (identifier) | ||
type: (pointer_type | ||
(qualified_type | ||
package: (package_identifier) | ||
name: (type_identifier))))) | ||
body: (block | ||
(short_var_declaration | ||
left: (expression_list | ||
(identifier)) | ||
right: (expression_list | ||
(unary_expression | ||
operand: (composite_literal | ||
type: (type_identifier) @suite_struct | ||
body: (literal_value))))) | ||
(expression_statement | ||
(call_expression | ||
function: (selector_expression | ||
operand: (identifier) @suite_lib (#eq? @suite_lib "suite") | ||
field: (field_identifier) @run_method (#eq? @run_method "Run")) | ||
arguments: (argument_list | ||
(identifier) | ||
(identifier)))))) | ||
]] | ||
|
||
--- The lookup table. | ||
--- @type table<string, table> | ||
local lookup_table = {} | ||
|
||
--- Get the current lookup table, generating it if empty. | ||
--- @return table<string, table> The lookup table containing testify suite information | ||
function M.get() | ||
if vim.tbl_isempty(lookup_table) then | ||
lookup_table = M.generate() | ||
end | ||
return lookup_table | ||
end | ||
|
||
--- Generate the lookup table for testify suites. | ||
--- @return table<string, table> The generated lookup table | ||
function M.generate() | ||
local cwd = vim.fn.getcwd() | ||
local filepaths = find.go_test_filepaths(cwd) | ||
local lookup = {} | ||
-- local global_suites = {} | ||
|
||
-- First pass: collect all data for the lookup table. | ||
for _, filepath in ipairs(filepaths) do | ||
local matches = query.run_query_on_file(filepath, M.query) | ||
|
||
local package_name = matches.package | ||
and matches.package[1] | ||
and matches.package[1].text | ||
or "unknown" | ||
|
||
lookup[filepath] = { | ||
package = package_name, | ||
replacements = {}, | ||
} | ||
|
||
for i, struct in ipairs(matches.suite_struct or {}) do | ||
local func = matches.test_function[i] | ||
if func then | ||
lookup[filepath].replacements[struct.text] = func.text | ||
end | ||
end | ||
end | ||
|
||
return lookup | ||
end | ||
|
||
--- Clear the lookup table. | ||
function M.clear() | ||
lookup_table = {} | ||
end | ||
|
||
return M |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
--- Helper functions around running Treesitter queries. | ||
|
||
local parsers = require("nvim-treesitter.parsers") | ||
|
||
local M = {} | ||
|
||
M.namespace_query = [[ | ||
; query for detecting receiver type and treat as Neotest namespace. | ||
; func (suite *testSuite) TestSomething() { // @namespace.name | ||
; // test code | ||
; } | ||
(method_declaration | ||
receiver: (parameter_list | ||
(parameter_declaration | ||
; name: (identifier) | ||
type: (pointer_type | ||
(type_identifier) @namespace.name )))) @namespace.definition | ||
name: (field_identifier) @test_function (#match? @test_function "^(Test|Example)") | ||
]] | ||
|
||
M.test_method_query = [[ | ||
; query for test method | ||
(method_declaration | ||
name: (field_identifier) @test.name (#match? @test.name "^(Test|Example)")) @test.definition | ||
]] | ||
|
||
--- Run a TreeSitter query on a file and return the matches. | ||
--- @param filepath string The path to the file to query | ||
--- @param query_string string The TreeSitter query string | ||
--- @return table<string, table> A table of matches, where each key is a capture name and the value is a table of nodes | ||
function M.run_query_on_file(filepath, query_string) | ||
local bufnr = vim.api.nvim_create_buf(false, true) | ||
local content = vim.fn.readfile(filepath) | ||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, content) | ||
|
||
vim.api.nvim_set_option_value("filetype", "go", { buf = bufnr }) | ||
|
||
if not parsers.has_parser("go") then | ||
error("Go parser is not available. Please ensure it's installed.") | ||
end | ||
|
||
local parser = parsers.get_parser(bufnr, "go") | ||
local tree = parser:parse()[1] | ||
local root = tree:root() | ||
|
||
---@type vim.treesitter.Query | ||
local query = vim.treesitter.query.parse("go", query_string) | ||
|
||
local matches = {} | ||
|
||
for pattern, match, metadata in | ||
query:iter_matches(root, bufnr, 0, -1, { all = true }) | ||
do | ||
for id, nodes in pairs(match) do | ||
local name = query.captures[id] | ||
for _, node in ipairs(nodes) do | ||
M.add_match(matches, name, node, bufnr, metadata[id]) | ||
end | ||
end | ||
end | ||
|
||
vim.api.nvim_buf_delete(bufnr, { force = true }) | ||
|
||
return matches | ||
end | ||
|
||
--- Add a match to the matches table | ||
--- @param matches table<string, table> The table of matches to add to | ||
--- @param name string The name of the capture | ||
--- @param node TSNode The TreeSitter node | ||
--- @param bufnr integer The buffer number | ||
--- @param metadata? table Optional metadata for the node | ||
function M.add_match(matches, name, node, bufnr, metadata) | ||
if not matches[name] then | ||
matches[name] = {} | ||
end | ||
table.insert(matches[name], { | ||
name = name, | ||
node = node, | ||
text = M.get_node_text(node, bufnr, { metadata = metadata }), | ||
}) | ||
end | ||
|
||
--- Get the text of a TreeSitter node. | ||
--- @param node TSNode The TreeSitter node | ||
--- @param bufnr integer|string The buffer number or content | ||
--- @param opts? table Optional parameters (e.g., metadata for a specific capture) | ||
--- @return string The text of the node | ||
function M.get_node_text(node, bufnr, opts) | ||
local text = vim.treesitter.get_node_text(node, bufnr, opts) -- NOTE: uses vim.treesitter | ||
if type(text) == "table" then | ||
return table.concat(text, "\n") | ||
end | ||
return text | ||
end | ||
|
||
return M |
Oops, something went wrong.