diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..dd6cca9 --- /dev/null +++ b/.envrc @@ -0,0 +1,3 @@ +dotenv + +env_vars_required GOPHER_DIR diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml index a78c3c3..389b72f 100644 --- a/.github/workflows/linters.yml +++ b/.github/workflows/linters.yml @@ -1,5 +1,11 @@ name: linters -on: [push, pull_request] + +on: + push: + branches: + - main + - develop + pull_request: jobs: linters: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9b16b5d..8564381 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,25 +1,20 @@ name: tests -on: [push, pull_request] + +on: + push: + branches: + - main + - develop + pull_request: jobs: tests: strategy: matrix: os: [ubuntu-latest] - nvim_version: + version: + - v0.10.4 - nightly - - v0.7.0 - - v0.7.2 - - v0.8.0 - - v0.8.1 - - v0.8.2 - - v0.8.3 - - v0.9.0 - - v0.9.1 - - v0.9.2 - - v0.9.4 - - v0.9.5 - - v0.10.0 runs-on: ${{ matrix.os }} steps: - name: Install Task @@ -28,18 +23,33 @@ jobs: version: 3.x repo-token: ${{ secrets.GITHUB_TOKEN }} + - name: Install Go + uses: actions/setup-go@v5 + with: + go-version: "1.24.0" + check-latest: false + + - name: Install NeoVim + uses: rhysd/action-setup-vim@v1 + with: + neovim: true + version: ${{ matrix.version }} + - uses: actions/checkout@v3 - - name: Install Neovim - run: | - mkdir -p /tmp/nvim - wget -q https://github.com/neovim/neovim/releases/download/${{ matrix.nvim_version }}/nvim.appimage -O /tmp/nvim/nvim.appimage - cd /tmp/nvim - chmod a+x ./nvim.appimage - ./nvim.appimage --appimage-extract - echo "/tmp/nvim/squashfs-root/usr/bin/" >> $GITHUB_PATH + - name: Cache .tests + uses: actions/cache@v4 + with: + path: | + ${{ github.workspace }}/.tests + ~/.cache/go-build + ~/go/pkg/mod + key: ${{ runner.os }}-tests-${{ hashFiles('${{ github.workspace }}/.tests') }} + + - name: Install Go bins + run: nvim --headless -u "./scripts/minimal_init.lua" -c "GoInstallDeps" -c "qa!" - name: Run Tests run: | nvim --version - task test + task tests diff --git a/.gitignore b/.gitignore index df7d859..3a9d44e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /playground/ /.tests/ +/.env diff --git a/README.md b/README.md index d256e5f..cd66f74 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,11 @@ It's **NOT** an LSP tool, the main goal of this plugin is to add go tooling supp ## Install (using [lazy.nvim](https://github.com/folke/lazy.nvim)) -Pre-dependency: +Requirements: -- [Go](https://github.com/golang/go) +- **Neovim 0.10** or later - `go` treesitter parser, install by `:TSInstall go` +- [Go](https://github.com/golang/go) installed (tested on 1.23) ```lua { diff --git a/Taskfile.yml b/Taskfile.yml index 581f11c..c6f5339 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -26,17 +26,13 @@ tasks: cmds: - stylua . - test: - desc: runs all tests - aliases: [tests, spec] + tests: + desc: run all tests cmds: - | nvim --headless \ - -u ./scripts/minimal_init.lua \ - -c "PlenaryBustedDirectory spec \ - {minimal_init='./scripts/minimal_init.lua' \ - ,sequential=true}" \ - -c ":qa!" + -u ./scripts/minimal_init.lua \ + -c "lua MiniTest.run()" docgen: desc: generate vimhelp diff --git a/doc/gopher.nvim.txt b/doc/gopher.nvim.txt index d4a82d0..78b316d 100644 --- a/doc/gopher.nvim.txt +++ b/doc/gopher.nvim.txt @@ -116,7 +116,6 @@ simple example: } < - ============================================================================== ------------------------------------------------------------------------------ *gopher.nvim-impl* @@ -147,7 +146,6 @@ simple example: } < - ============================================================================== ------------------------------------------------------------------------------ *gopher.nvim-gotests* @@ -167,7 +165,6 @@ Usage ~ you can also specify the template to use for generating the tests. see |gopher.nvim-config| more details about templates can be found at: https://github.com/cweill/gotests - ------------------------------------------------------------------------------ *gopher.nvim-gotests-named* diff --git a/lua/gopher/_utils/init.lua b/lua/gopher/_utils/init.lua index a3b567c..96a9134 100644 --- a/lua/gopher/_utils/init.lua +++ b/lua/gopher/_utils/init.lua @@ -23,12 +23,4 @@ function utils.notify(msg, lvl) log.debug(msg) end --- safe require ----@param module string module name -function utils.sreq(module) - local ok, m = pcall(require, module) - assert(ok, string.format("gopher.nvim dependency error: %s not installed", module)) - return m -end - return utils diff --git a/lua/gopher/config.lua b/lua/gopher/config.lua index 9c20843..8265838 100644 --- a/lua/gopher/config.lua +++ b/lua/gopher/config.lua @@ -23,7 +23,7 @@ local config = {} --minidoc_replace_start { ---@tag gopher.nvim-config-defaults ----@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section):gsub(">", ">lua") +---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section) --- ---@class gopher.Config local default_config = { diff --git a/nvim.toml b/nvim.toml index fa09a88..50dc11f 100644 --- a/nvim.toml +++ b/nvim.toml @@ -1,6 +1,9 @@ [vim] any = true +[MiniTest] +any = true + [describe] any = true [[describe.args]] diff --git a/scripts/minimal_init.lua b/scripts/minimal_init.lua index 49a606e..e59f06a 100644 --- a/scripts/minimal_init.lua +++ b/scripts/minimal_init.lua @@ -6,7 +6,7 @@ end local function install_plug(plugin) local name = plugin:match ".*/(.*)" local package_root = root ".tests/site/pack/deps/start/" - if not vim.loop.fs_stat(package_root .. name) then + if not vim.uv.fs_stat(package_root .. name) then print("Installing " .. plugin) vim.fn.mkdir(package_root, "p") vim.fn.system { @@ -19,6 +19,11 @@ local function install_plug(plugin) end end +vim.env.XDG_CONFIG_HOME = root ".tests/config" +vim.env.XDG_DATA_HOME = root ".tests/data" +vim.env.XDG_STATE_HOME = root ".tests/state" +vim.env.XDG_CACHE_HOME = root ".tests/cache" + vim.cmd [[set runtimepath=$VIMRUNTIME]] vim.opt.runtimepath:append(root()) vim.opt.packpath = { root ".tests/site" } @@ -27,8 +32,18 @@ vim.notify = print install_plug "nvim-lua/plenary.nvim" install_plug "nvim-treesitter/nvim-treesitter" install_plug "echasnovski/mini.doc" -- used for docs generation +install_plug "echasnovski/mini.test" -vim.env.XDG_CONFIG_HOME = root ".tests/config" -vim.env.XDG_DATA_HOME = root ".tests/data" -vim.env.XDG_STATE_HOME = root ".tests/state" -vim.env.XDG_CACHE_HOME = root ".tests/cache" +-- install go treesitter parse +require("nvim-treesitter.install").ensure_installed_sync "go" + +-- setup mini.test only when running headless nvim +if #vim.api.nvim_list_uis() == 0 then + require("mini.test").setup { + collect = { + find_files = function() + return vim.fn.globpath("spec", "**/*_test.lua", true, true) + end, + }, + } +end diff --git a/spec/fixtures/comment/package_input.go b/spec/fixtures/comment/package_input.go new file mode 100644 index 0000000..06ab7d0 --- /dev/null +++ b/spec/fixtures/comment/package_input.go @@ -0,0 +1 @@ +package main diff --git a/spec/fixtures/comment/package_output.go b/spec/fixtures/comment/package_output.go new file mode 100644 index 0000000..66d106a --- /dev/null +++ b/spec/fixtures/comment/package_output.go @@ -0,0 +1,2 @@ +// Package main provides main +package main diff --git a/spec/fixtures/iferr/iferr_input.go b/spec/fixtures/iferr/iferr_input.go new file mode 100644 index 0000000..b94f158 --- /dev/null +++ b/spec/fixtures/iferr/iferr_input.go @@ -0,0 +1,9 @@ +package main + +func test() error { + return nil +} + +func main() { + err := test() +} diff --git a/spec/fixtures/iferr/iferr_output.go b/spec/fixtures/iferr/iferr_output.go new file mode 100644 index 0000000..8ab181a --- /dev/null +++ b/spec/fixtures/iferr/iferr_output.go @@ -0,0 +1,12 @@ +package main + +func test() error { + return nil +} + +func main() { + err := test() + if err != nil { + return + } +} diff --git a/spec/fixtures/impl/closer_input.go b/spec/fixtures/impl/closer_input.go new file mode 100644 index 0000000..e4d5f52 --- /dev/null +++ b/spec/fixtures/impl/closer_input.go @@ -0,0 +1,3 @@ +package main + +type CloserTest struct{} diff --git a/spec/fixtures/impl/closer_output.go b/spec/fixtures/impl/closer_output.go new file mode 100644 index 0000000..5e976f3 --- /dev/null +++ b/spec/fixtures/impl/closer_output.go @@ -0,0 +1,8 @@ +package main + +type CloserTest2 struct{} + +func (closertest *CloserTest2) Close() error { + panic("not implemented") // TODO: Implement +} + diff --git a/spec/fixtures/impl/reader_input.go b/spec/fixtures/impl/reader_input.go new file mode 100644 index 0000000..ebc8eff --- /dev/null +++ b/spec/fixtures/impl/reader_input.go @@ -0,0 +1,3 @@ +package main + +type Read struct{} diff --git a/spec/fixtures/impl/reader_output.go b/spec/fixtures/impl/reader_output.go new file mode 100644 index 0000000..26df873 --- /dev/null +++ b/spec/fixtures/impl/reader_output.go @@ -0,0 +1,8 @@ +package main + +func (r Read2) Read(p []byte) (n int, err error) { + panic("not implemented") // TODO: Implement +} + + +type Read2 struct{} diff --git a/spec/fixtures/impl/writer_input.go b/spec/fixtures/impl/writer_input.go new file mode 100644 index 0000000..ef034cc --- /dev/null +++ b/spec/fixtures/impl/writer_input.go @@ -0,0 +1,3 @@ +package main + +type WriterTest struct{} diff --git a/spec/fixtures/impl/writer_output.go b/spec/fixtures/impl/writer_output.go new file mode 100644 index 0000000..19e8f6e --- /dev/null +++ b/spec/fixtures/impl/writer_output.go @@ -0,0 +1,8 @@ +package main + +type WriterTest2 struct{} + +func (w *WriterTest2) Write(p []byte) (n int, err error) { + panic("not implemented") // TODO: Implement +} + diff --git a/spec/fixtures/tags/remove_output.go b/spec/fixtures/tags/remove_output.go index 7e27a27..6d9fe70 100644 --- a/spec/fixtures/tags/remove_output.go +++ b/spec/fixtures/tags/remove_output.go @@ -1,11 +1,11 @@ package main type Test struct { - ID int - Name string - Num int64 + ID int + Name string + Num int64 Another struct { - First int - Second string - } + First int + Second string + } } diff --git a/spec/fixtures/tests/function_input.go b/spec/fixtures/tests/function_input.go new file mode 100644 index 0000000..bde08df --- /dev/null +++ b/spec/fixtures/tests/function_input.go @@ -0,0 +1,5 @@ +package fortest + +func Add(x, y int) int { + return 2 + x + y +} diff --git a/spec/fixtures/tests/function_output.go b/spec/fixtures/tests/function_output.go new file mode 100644 index 0000000..42ebd4c --- /dev/null +++ b/spec/fixtures/tests/function_output.go @@ -0,0 +1,24 @@ +package fortest + +import "testing" + +func TestAdd(t *testing.T) { + type args struct { + x int + y int + } + tests := []struct { + name string + args args + want int + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := Add(tt.args.x, tt.args.y); got != tt.want { + t.Errorf("Add() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/spec/fixtures/tests/method_input.go b/spec/fixtures/tests/method_input.go new file mode 100644 index 0000000..fe04124 --- /dev/null +++ b/spec/fixtures/tests/method_input.go @@ -0,0 +1,7 @@ +package fortest + +type ForTest struct{} + +func (t *ForTest) Add(x, y int) int { + return 2 + x + y +} diff --git a/spec/fixtures/tests/method_output.go b/spec/fixtures/tests/method_output.go new file mode 100644 index 0000000..7f927b5 --- /dev/null +++ b/spec/fixtures/tests/method_output.go @@ -0,0 +1,26 @@ +package fortest + +import "testing" + +func TestForTest_Add(t *testing.T) { + type args struct { + x int + y int + } + tests := []struct { + name string + tr *ForTest + args args + want int + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tr := &ForTest{} + if got := tr.Add(tt.args.x, tt.args.y); got != tt.want { + t.Errorf("ForTest.Add() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/spec/integration/comment_test.lua b/spec/integration/comment_test.lua new file mode 100644 index 0000000..8276688 --- /dev/null +++ b/spec/integration/comment_test.lua @@ -0,0 +1,27 @@ +local t = require "spec.testutils" + +local child = MiniTest.new_child_neovim() +local T = MiniTest.new_set { + hooks = { + post_once = child.stop, + pre_case = function() + MiniTest.skip "This module should be fixed first" + child.restart { "-u", t.mininit_path } + end, + }, +} +T["comment"] = MiniTest.new_set {} + +T["comment"]["should add comment to package"] = function() end + +T["comment"]["should add comment to struct"] = function() end + +T["comment"]["should add comment to function"] = function() end + +T["comment"]["should add comment to method"] = function() end + +T["comment"]["should add comment to interface"] = function() end + +T["comment"]["otherwise should add // above cursor"] = function() end + +return T diff --git a/spec/integration/gotests_test.lua b/spec/integration/gotests_test.lua new file mode 100644 index 0000000..57f3142 --- /dev/null +++ b/spec/integration/gotests_test.lua @@ -0,0 +1,47 @@ +local t = require "spec.testutils" + +local child = MiniTest.new_child_neovim() +local T = MiniTest.new_set { + hooks = { + post_once = child.stop, + pre_case = function() + child.restart { "-u", t.mininit_path } + end, + }, +} +T["gotests"] = MiniTest.new_set {} + +--- NOTE: :GoTestAdd is the only place that has actual logic +--- All other parts are handled `gotests` itself. + +---@param fpath string +---@return string +local function read_testfile(fpath) + return t.readfile(fpath:gsub(".go", "_test.go")) +end + +T["gotests"]["should add test for function under cursor"] = function() + local tmp = t.tmpfile() + local fixtures = t.get_fixtures "tests/function" + t.writefile(tmp, fixtures.input) + + child.cmd("silent edit " .. tmp) + child.fn.setpos(".", { child.fn.bufnr "%", 3, 6 }) + child.cmd "GoTestAdd" + + t.eq(fixtures.output, read_testfile(tmp)) +end + +T["gotests"]["should add test for method under cursor"] = function() + local tmp = t.tmpfile() + local fixtures = t.get_fixtures "tests/method" + t.writefile(tmp, fixtures.input) + + child.cmd("silent edit " .. tmp) + child.fn.setpos(".", { child.fn.bufnr "%", 5, 19 }) + child.cmd "GoTestAdd" + + t.eq(fixtures.output, read_testfile(tmp)) +end + +return T diff --git a/spec/integration/iferr_test.lua b/spec/integration/iferr_test.lua new file mode 100644 index 0000000..14ee08e --- /dev/null +++ b/spec/integration/iferr_test.lua @@ -0,0 +1,26 @@ +local t = require "spec.testutils" + +local child = MiniTest.new_child_neovim() +local T = MiniTest.new_set { + hooks = { + post_once = child.stop, + pre_case = function() + child.restart { "-u", t.mininit_path } + end, + }, +} +T["iferr"] = MiniTest.new_set {} +T["iferr"]["works"] = function() + local tmp = t.tmpfile() + local fixtures = t.get_fixtures "iferr/iferr" + t.writefile(tmp, fixtures.input) + + child.cmd("silent edit " .. tmp) + child.fn.setpos(".", { child.fn.bufnr "%", 8, 2, 0 }) + child.cmd "GoIfErr" + child.cmd "write" + + t.eq(t.readfile(tmp), fixtures.output) +end + +return T diff --git a/spec/integration/impl_test.lua b/spec/integration/impl_test.lua new file mode 100644 index 0000000..5376fa5 --- /dev/null +++ b/spec/integration/impl_test.lua @@ -0,0 +1,55 @@ +local t = require "spec.testutils" + +local child = MiniTest.new_child_neovim() +local T = MiniTest.new_set { + hooks = { + post_once = child.stop, + pre_case = function() + child.restart { "-u", t.mininit_path } + end, + }, +} +T["impl"] = MiniTest.new_set {} +T["impl"]["works w io.Writer"] = function() + local tmp = t.tmpfile() + local fixtures = t.get_fixtures "impl/writer" + t.writefile(tmp, fixtures.input) + + child.cmd("silent edit " .. tmp) + child.fn.setpos(".", { child.fn.bufnr(tmp), 3, 6 }) + child.cmd "GoImpl w io.Writer" + child.cmd "write" + + -- NOTE: since "impl" won't implement interface if it's already implemented i went with this hack + local rhs = fixtures.output:gsub("Test2", "Test") + t.eq(t.readfile(tmp), rhs) +end + +T["impl"]["works r Read io.Reader"] = function() + local tmp = t.tmpfile() + local fixtures = t.get_fixtures "impl/reader" + t.writefile(tmp, fixtures.input) + + child.cmd("silent edit " .. tmp) + child.cmd "GoImpl r Read io.Reader" + child.cmd "write" + + local rhs = fixtures.output:gsub("Read2", "Read") + t.eq(t.readfile(tmp), rhs) +end + +T["impl"]["works io.Closer"] = function() + local tmp = t.tmpfile() + local fixtures = t.get_fixtures "impl/closer" + t.writefile(tmp, fixtures.input) + + child.cmd("silent edit " .. tmp) + child.fn.setpos(".", { child.fn.bufnr(tmp), 3, 6 }) + child.cmd "GoImpl io.Closer" + child.cmd "write" + + local rhs = fixtures.output:gsub("Test2", "Test") + t.eq(t.readfile(tmp), rhs) +end + +return T diff --git a/spec/integration/struct_tags_test.lua b/spec/integration/struct_tags_test.lua new file mode 100644 index 0000000..8fb4aa3 --- /dev/null +++ b/spec/integration/struct_tags_test.lua @@ -0,0 +1,37 @@ +local t = require "spec.testutils" + +local child = MiniTest.new_child_neovim() +local T = MiniTest.new_set { + hooks = { + post_once = child.stop, + pre_case = function() + child.restart { "-u", t.mininit_path } + end, + }, +} +T["struct_tags"] = MiniTest.new_set {} +T["struct_tags"]["works add"] = function() + local tmp = t.tmpfile() + local fixtures = t.get_fixtures "tags/add" + t.writefile(tmp, fixtures.input) + + child.cmd("silent edit " .. tmp) + child.fn.setpos(".", { child.fn.bufnr "%", 3, 6, 0 }) + child.cmd "GoTagAdd json" + + t.eq(t.readfile(tmp), fixtures.output) +end + +T["struct_tags"]["works remove"] = function() + local tmp = t.tmpfile() + local fixtures = t.get_fixtures "tags/remove" + t.writefile(tmp, fixtures.input) + + child.cmd("silent edit " .. tmp) + child.fn.setpos(".", { child.fn.bufnr "%", 4, 6, 0 }) + child.cmd "GoTagRm json" + + t.eq(t.readfile(tmp), fixtures.output) +end + +return T diff --git a/spec/testutils.lua b/spec/testutils.lua new file mode 100644 index 0000000..e5ab9a2 --- /dev/null +++ b/spec/testutils.lua @@ -0,0 +1,43 @@ +local base_dir = vim.env.GOPHER_DIR or vim.fn.expand "%:p:h" + +---@class gopher.TestUtils +local testutils = {} + +testutils.mininit_path = vim.fs.joinpath(base_dir, "scripts", "minimal_init.lua") +testutils.fixtures_dir = vim.fs.joinpath(base_dir, "spec/fixtures") + +---@generic T +---@param a T +---@param b T +---@return boolean +function testutils.eq(a, b) + return MiniTest.expect.equality(a, b) +end + +---@return string +function testutils.tmpfile() + return vim.fn.tempname() .. ".go" +end + +---@param path string +---@return string +function testutils.readfile(path) + return vim.fn.join(vim.fn.readfile(path), "\n") +end + +---@param fpath string +---@param contents string +function testutils.writefile(fpath, contents) + vim.fn.writefile(vim.split(contents, "\n"), fpath) +end + +---@param fixture string +---@return {input: string, output: string} +function testutils.get_fixtures(fixture) + return { + input = testutils.readfile(vim.fs.joinpath(testutils.fixtures_dir, fixture) .. "_input.go"), + output = testutils.readfile(vim.fs.joinpath(testutils.fixtures_dir, fixture) .. "_output.go"), + } +end + +return testutils diff --git a/spec/units/config_spec.lua b/spec/units/config_spec.lua deleted file mode 100644 index 1fd91dd..0000000 --- a/spec/units/config_spec.lua +++ /dev/null @@ -1,29 +0,0 @@ -describe("gopher.config", function() - it(".setup() should provide default when .setup() is not called", function() - local c = require "gopher.config" - - assert.are.same(c.commands.go, "go") - assert.are.same(c.commands.gomodifytags, "gomodifytags") - assert.are.same(c.commands.gotests, "gotests") - assert.are.same(c.commands.impl, "impl") - assert.are.same(c.commands.iferr, "iferr") - assert.are.same(c.commands.dlv, "dlv") - end) - - it(".setup() should change options on users config", function() - local c = require "gopher.config" - c.setup { - commands = { - go = "go1.420", - gomodifytags = "iDontUseRustBtw", - }, - } - - assert.are.same(c.commands.go, "go1.420") - assert.are.same(c.commands.gomodifytags, "iDontUseRustBtw") - assert.are.same(c.commands.gotests, "gotests") - assert.are.same(c.commands.impl, "impl") - assert.are.same(c.commands.iferr, "iferr") - assert.are.same(c.commands.dlv, "dlv") - end) -end) diff --git a/spec/units/utils_spec.lua b/spec/units/utils_spec.lua deleted file mode 100644 index ea2f30c..0000000 --- a/spec/units/utils_spec.lua +++ /dev/null @@ -1,15 +0,0 @@ -describe("gopher._utils", function() - local u = require "gopher._utils" - - describe(".sreq()", function() - it("can require existing module", function() - assert.are.same(require "gopher", u.sreq "gopher") - end) - - it("cannot require non-existing module", function() - assert.has.errors(function() - u.sreq "iDontExistBtw" - end) - end) - end) -end)