Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
51c1d50
feat(linter): add zsh syntax checker
barrettruth Feb 4, 2026
254ddbd
fix(ci): add luarocks bin to PATH
barrettruth Feb 4, 2026
bbaccbb
fix: revert ci change
barrettruth Feb 4, 2026
4171703
feat(linter): add cpplint C++ linter
barrettruth Feb 4, 2026
2634e6f
Merge branch 'feat/zsh-linter'
barrettruth Feb 4, 2026
bd57f3a
Merge branch 'feat/cpplint-linter'
barrettruth Feb 4, 2026
5e0f479
feat(linter): add cpplint C++ linter
barrettruth Feb 4, 2026
3ee84b4
Merge branch 'feat/cpplint-linter'
barrettruth Feb 4, 2026
c91f1b2
ci: rewrite workflow with parallel jobs and modern actions
barrettruth Feb 4, 2026
0ede9be
test: update expectations for guard.nvim diagnostic format changes
barrettruth Feb 4, 2026
f9ee288
refactor(test): reorganize tests by package manager
barrettruth Feb 4, 2026
274c578
fix(test): update checkmake expected output for new phonydeclared rule
barrettruth Feb 4, 2026
1fd187c
fix(ci): run clang tests separately to avoid state pollution
barrettruth Feb 4, 2026
d045892
fix(test): revert checkmake linter, fix test order
barrettruth Feb 4, 2026
6154f8a
fix(test): restore swiftformat test with proper naming
barrettruth Feb 4, 2026
dbeae42
fix(ci): rename swiftformat binary after extraction
barrettruth Feb 4, 2026
f4faba1
fix(test): restore buf linter test and fix formatter expectation
barrettruth Feb 4, 2026
51d94b1
fix(buf): use fn approach for linter, fname for formatter
barrettruth Feb 4, 2026
73da25d
Revert "fix(buf): use fn approach for linter, fname for formatter"
barrettruth Feb 4, 2026
3b5c3af
fix(buf): simpler test input, formatter uses fname not stdin
barrettruth Feb 4, 2026
d5203af
Revert "fix(buf): simpler test input, formatter uses fname not stdin"
barrettruth Feb 4, 2026
823cd44
fix(ci): remove buf tests (fix in separate PR)
barrettruth Feb 4, 2026
a63a8e0
Merge branch 'fix/ci'
barrettruth Feb 4, 2026
be53149
fix: move cpplint to pip, zsh to binary
barrettruth Feb 4, 2026
84750dc
refactor(test)!: replace async test infra with synchronous execution
barrettruth Feb 6, 2026
70fd853
test: add remaining formatter and linter tests
barrettruth Feb 6, 2026
1772e4b
fix(ci): install zsh in test-binary job
barrettruth Feb 6, 2026
425a732
test: add batch 1 (isort, codespell, pylint, djhtml, mdformat, yamlfi…
barrettruth Feb 6, 2026
281de8e
fix(test): hadolint use file arg instead of stdin
barrettruth Feb 6, 2026
abe887a
test: add batch 2 (goimports, yamlfmt, shellcheck, typos, fish_indent…
barrettruth Feb 6, 2026
e209ce0
fix: update ruff CLI to current syntax, fix fish_indent expected output
barrettruth Feb 6, 2026
cb0fe3f
test: add batch 3 tests (buf, cbfmt, ktlint, eslint, eslint_d, stylel…
barrettruth Feb 6, 2026
1065b3a
fix(test): buf file-based, cbfmt extraction; add dart, zigfmt, google…
barrettruth Feb 6, 2026
c2ebbbc
fix: buf formatter definition (stdin->fname), cbfmt config, eslint cw…
barrettruth Feb 6, 2026
bdd0ae0
fix(test): stylelint explicit --config path, stderr fallback
barrettruth Feb 6, 2026
be33bc8
fix(test): buf uses cwd-relative tmpdir to avoid snap-private-tmp
barrettruth Feb 6, 2026
0ff6122
test: add remaining 11 tool tests and 6 new CI jobs
barrettruth Feb 6, 2026
7c53ec2
fix(ci): nix flake install, dotnet tools PATH
barrettruth Feb 6, 2026
609d7fc
fix(test): dprint plugins array, dotnet tools GITHUB_PATH
barrettruth Feb 6, 2026
60fa4f8
fix: update csharpier definition for v1.0+ CLI
barrettruth Feb 6, 2026
c9e439d
fix: update `Makefile` with new ci sources
barrettruth Feb 6, 2026
d331877
pls work
barrettruth Feb 6, 2026
ddc8b2b
refactor(ci): move npm-groovy-lint from test-npm to test-binary
barrettruth Feb 6, 2026
e98b980
revert: remove npm-groovy-lint test and CI setup
barrettruth Feb 6, 2026
fbc670e
test: add mypy linter test
barrettruth Feb 6, 2026
be42d99
refactor(ci): replace haskell toolchain with pre-built binaries
barrettruth Feb 6, 2026
8004df3
test: add mypyc linter test
barrettruth Feb 6, 2026
18073a1
test: add prettierd formatter test
barrettruth Feb 6, 2026
2f6efe1
fix
barrettruth Feb 6, 2026
bd3c23e
test: add test coverage check script and CI job
barrettruth Feb 6, 2026
1234b5a
fix: skip npm_groovy_lint in coverage check
barrettruth Feb 6, 2026
2cf3be9
fix: remove mypyc from coverage skip list
barrettruth Feb 6, 2026
692ffeb
refactor: use key-value table for coverage skip list
barrettruth Feb 6, 2026
6d795a3
fix: remove prettierd from coverage skip list
barrettruth Feb 6, 2026
45e08fa
docs: mention coverage check in CONTRIBUTING.md
barrettruth Feb 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
436 changes: 436 additions & 0 deletions .github/workflows/ci.yaml

Large diffs are not rendered by default.

75 changes: 0 additions & 75 deletions .github/workflows/test.yml

This file was deleted.

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CLAUDE.md
110 changes: 39 additions & 71 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,107 +1,75 @@
# Contributing to guard-collection

- Add your config to `formatter.lua` or `linter/<tool-name>.lua`, if it's a linter don't forget to export it in `linter/init.lua`
- Write a test. If it's a formatter, show that the config works by giving an example input and verify that the file did got formatted as intended. For example:
- Write a test in the appropriate `test/<category>/` directory. Tests run tools synchronously via `vim.system():wait()`, bypassing guard.nvim's async pipeline.

Formatter example:

```lua
describe('black', function()
it('can format', function()
-- pre-test setup
local ft = require('guard.filetype')
ft('python'):fmt('black')
-- Giving example input to the helper
-- the helper creates a new buffer with it, formats, and returns the formatted output
local formatted = require('test.formatter.helper').test_with('python', {
-- The input code should somewhat reflect the filetype
local formatted = require('test.helper').run_fmt('black', 'python', {
[[def foo(n):]],
[[ if n in (1,2,3):]],
[[ return n+1]],
[[a, b = 1, 2]],
[[b, a = a, b]],
[[print( f"The factorial of {a} is: {foo(a)}")]],
})
-- Show that the input is indeed formatted as intended
assert.are.same({
[[def foo(n):]],
[[ if n in (1, 2, 3):]],
[[ return n + 1]],
[[]],
[[]],
[[a, b = 1, 2]],
[[b, a = a, b]],
[[print(f"The factorial of {a} is: {foo(a)}")]],
}, formatted)
end)
end)
```

- Or if it's a linter, show that the linter's output is converted correctly into neovim diagnostics
Linter example:

```lua
describe('selene', function()
describe('flake8', function()
it('can lint', function()
-- pre-test setup
local helper = require('test.linter.helper')
local ns = helper.namespace
local ft = require('guard.filetype')
ft('lua'):lint('selene')
require('guard').setup()
-- Giving example input to the helper
-- the helper creates a new buffer with it, requires lint, and returns the diagnostics
local buf, diagnostics = require('test.linter.helper').test_with('lua', {
-- Make sure the input actually has some problems that the linter detects
[[local M = {}]],
[[function M.foo()]],
[[ print("foo")]],
[[end]],
[[U.bar()]],
[[return M]],
local helper = require('test.helper')
local buf, diagnostics = helper.run_lint('flake8', 'python', {
[[import os]],
})
-- Show that the diagnostics is indeed valid
assert.are.same({
{
bufnr = buf,
col = 0,
end_col = 0,
end_lnum = 4,
lnum = 4,
message = '`U` is not defined [undefined_variable]',
-- sometimes the namespace is not fixed
namespace = ns,
severity = 1,
source = 'selene',
},
}, diagnostics)
assert.is_true(#diagnostics > 0)
for _, d in ipairs(diagnostics) do
assert.equal(buf, d.bufnr)
assert.equal('flake8', d.source)
assert.is_number(d.lnum)
assert.is_string(d.message)
end
end)
end)

```

- To run the test you just created, install [vusted](https://github.com/notomo/vusted)
```shell
luarocks --lua-version=5.1 install vusted
```
- Create a symlink so that vusted recognizes guard.nvim namespaces
For linters with a custom `fn` (cpplint, checkmake, zsh), run the command directly and test `parse()`:

```shell
ln -s ~/.local/share/nvim/lazy/guard.nvim/lua/guard lua
```lua
describe('cpplint', function()
it('can lint', function()
local linter = require('test.helper').get_linter('cpplint')
local tmpfile = '/tmp/guard-test.cpp'
vim.fn.writefile({ [[int main(){int x=1;}]] }, tmpfile)
local bufnr = vim.api.nvim_create_buf(false, true)
local result = vim.system({ 'cpplint', '--filter=-legal/copyright', tmpfile }, {}):wait()
local diagnostics = linter.parse(result.stderr or '', bufnr)
assert.is_true(#diagnostics > 0)
end)
end)
```

- Run the test and make sure it passes

- Add your tool to CI in `.github/workflows/ci.yaml` under the appropriate job
- Run the test locally:
```shell

vusted ./test/formatter/<tool-name>_spec.lua
# or
vusted ./test/linter/<tool-name>\_spec.lua

ok 1 - <tool-name> can format
ok 1 - <tool-name> can lint
# requires: neovim, lua 5.1, luarocks, busted, nlua
# also requires guard.nvim cloned: git clone --depth 1 https://github.com/nvimdev/guard.nvim && mv guard.nvim/lua/guard lua/
make test-pip # or whichever category
```

- Modify `test/setup.sh` so that CI installs your tool

- Optionally, format the code with stylua
- Verify test coverage passes (CI enforces this):
```shell
make coverage
```
- Format with stylua before submitting:
```shell
stylua .
```
Expand Down
51 changes: 51 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
LUA_PATH := lua/?.lua;lua/?/init.lua;$(LUA_PATH)
export LUA_PATH

.PHONY: lint coverage test test-pip test-npm test-go test-rust test-lua test-binary test-clang test-dotnet test-ruby test-clojure test-elixir test-nix test-swift

lint:
stylua --check .

coverage:
nvim -l scripts/coverage.lua

test: test-pip test-npm test-go test-rust test-lua test-binary test-clang test-dotnet test-ruby test-clojure test-elixir test-nix test-swift

test-pip:
busted --lua nlua test/pip/*_spec.lua

test-npm:
busted --lua nlua test/npm/*_spec.lua

test-go:
busted --lua nlua test/go/*_spec.lua

test-rust:
busted --lua nlua test/rust/*_spec.lua

test-lua:
busted --lua nlua test/lua/*_spec.lua

test-binary:
busted --lua nlua test/binary/*_spec.lua

test-clang:
@for f in test/clang/*_spec.lua; do busted --lua nlua "$$f"; done

test-dotnet:
busted --lua nlua test/dotnet/*_spec.lua

test-ruby:
busted --lua nlua test/ruby/*_spec.lua

test-clojure:
busted --lua nlua test/clojure/*_spec.lua

test-elixir:
busted --lua nlua test/elixir/*_spec.lua

test-nix:
busted --lua nlua test/nix/*_spec.lua

test-swift:
busted --lua nlua test/swift/*_spec.lua
11 changes: 5 additions & 6 deletions lua/guard-collection/formatter.lua
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ M.cljfmt = {
}

M.csharpier = {
cmd = 'dotnet-csharpier',
args = { '--write-stdout' },
cmd = 'csharpier',
args = { 'format', '--write-stdout' },
stdin = true,
}

Expand Down Expand Up @@ -299,8 +299,7 @@ M.ruff = {

M.ruff_fix = {
cmd = 'ruff',
args = { '--fix', '-', '--stdin-filename' },
stdin = true,
args = { 'check', '--fix' },
fname = true,
}

Expand All @@ -319,8 +318,8 @@ M.biome = {

M.buf = {
cmd = 'buf',
args = { 'format' },
stdin = true,
args = { 'format', '-w' },
fname = true,
}

M.xmllint = {
Expand Down
33 changes: 33 additions & 0 deletions lua/guard-collection/linter/cpplint.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
local lint = require('guard.lint')

return {
fn = function(_, fname)
local co = assert(coroutine.running())
vim.system({ 'cpplint', '--filter=-legal/copyright', fname }, {}, function(result)
coroutine.resume(co, result.stderr or '')
end)
return coroutine.yield()
end,
parse = function(result, bufnr)
local diags = {}
for line in result:gmatch('[^\n]+') do
local lnum, msg, cat, sev = line:match('^[^:]+:(%d+):%s*(.-)%s+%[([^%]]+)%]%s+%[(%d+)%]$')
if lnum then
local severity = tonumber(sev) >= 3 and vim.diagnostic.severity.ERROR
or vim.diagnostic.severity.WARN
table.insert(
diags,
lint.diag_fmt(
bufnr,
tonumber(lnum) - 1,
0,
('[%s] %s'):format(cat, msg),
severity,
'cpplint'
)
)
end
end
return diags
end,
}
2 changes: 2 additions & 0 deletions lua/guard-collection/linter/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ return {
checkmake = require('guard-collection.linter.checkmake'),
['clang-tidy'] = require('guard-collection.linter.clang-tidy'),
codespell = require('guard-collection.linter.codespell'),
cpplint = require('guard-collection.linter.cpplint'),
detekt = require('guard-collection.linter.detekt'),
eslint = require('guard-collection.linter.eslint'),
eslint_d = require('guard-collection.linter.eslint_d'),
Expand All @@ -23,4 +24,5 @@ return {
mypy = require('guard-collection.linter.mypy').mypy,
mypyc = require('guard-collection.linter.mypy').mypyc,
dmypy = require('guard-collection.linter.mypy').dmypy,
zsh = require('guard-collection.linter.zsh'),
}
2 changes: 1 addition & 1 deletion lua/guard-collection/linter/ruff.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ local lint = require('guard.lint')
return {
cmd = 'ruff',
args = {
'check',
'-n',
'-e',
'--output-format',
'json',
'-',
Expand Down
24 changes: 24 additions & 0 deletions lua/guard-collection/linter/zsh.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
local lint = require('guard.lint')

return {
fn = function(_, fname)
local co = assert(coroutine.running())
vim.system({ 'zsh', '-n', fname }, {}, function(result)
coroutine.resume(co, result.stderr or '')
end)
return coroutine.yield()
end,
parse = function(result, bufnr)
local diags = {}
for line in result:gmatch('[^\n]+') do
local lnum, msg = line:match(':(%d+): (.+)$')
if lnum then
table.insert(
diags,
lint.diag_fmt(bufnr, tonumber(lnum) - 1, 0, msg, vim.diagnostic.severity.ERROR, 'zsh')
)
end
end
return diags
end,
}
Loading
Loading