Background
qjson has strong Rust-side quality gates today: release-mode tests, scalar-only tests, panic-barrier tests, fuzzing, Miri/ASan coverage, cargo-audit, Lua busted integration tests, and LuaRocks package validation.
The Lua side currently does not have an equivalent static analysis gate. The public Lua wrapper and lazy table/encoder logic live in:
lua/qjson.lua
lua/qjson/lib.lua
lua/qjson/table.lua
The test suite also contains non-trivial Lua helpers and mocks under tests/lua/. Runtime tests cover behavior, but they do not reliably catch all accidental globals, misspelled locals, unused variables, duplicate definitions, or low-level mistakes in rarely exercised branches.
Kong/lua-resty-simdjson uses luacheck as a lightweight Lua static analysis gate. qjson should add a similarly scoped gate, without turning this into a formatting or style-enforcement project.
The lint workflow should be local-first: developers and AI coding agents need a quick local command that finds Lua lint problems before pushing. CI should run the same command as final enforcement, rather than hiding the lint logic only inside GitHub Actions.
Goal
Add a lightweight Lua lint quality gate that catches accidental globals and common Lua mistakes in both production Lua code and Lua tests, with an easy local entrypoint and CI enforcement using that same entrypoint.
Proposed Scope
Add luacheck coverage for:
The initial implementation should include:
- A repository
.luacheckrc configured for LuaJIT-oriented code.
- Allowed globals for the busted test environment, such as
describe, it, assert, before_each, after_each, if needed.
- Any explicit allowances required for LuaJIT FFI idioms used by this project.
- A local Makefile target, for example
make lua-lint, that runs the Lua lint check directly.
- The target should be listed by
make help and should fail clearly when luacheck is missing.
- A GitHub Actions lint step/job that installs
luacheck and runs the local target, rather than duplicating the raw lint command inline.
A reasonable local command shape is:
The underlying check should be equivalent to:
The configuration should be strict enough to catch meaningful mistakes, but conservative enough to avoid large unrelated churn.
Non-Goals
- Do not introduce a Lua formatter such as
stylua.
- Do not add formatting or whitespace-only requirements.
- Do not refactor Lua code for style reasons.
- Do not change Rust formatting policy or add
cargo fmt --check.
- Do not require the first implementation to fold Lua lint into
make lint. It is acceptable to keep make lint as Rust clippy-only and add a separate make lua-lint target. If make lint is changed to include Lua lint, update AGENTS.md, CONTRIBUTING.md, and any other command documentation in the same PR.
- Do not use a changed-files-only lint strategy in the first version. The Lua codebase is small enough that full linting is simpler and gives clearer local and CI behavior.
Acceptance Criteria
- The repository has a
.luacheckrc checked in.
make lua-lint works locally and runs Lua lint over lua/ and tests/lua/.
make help lists the Lua lint target with a clear description.
- If
luacheck is not installed locally, the failure mode is clear enough for a developer or AI agent to install the missing tool and retry.
- CI runs the same local lint entrypoint on pull requests.
- The lint job fails on accidental global writes or undefined globals in project Lua files, except for explicitly allowed test/runtime globals.
- The lint configuration supports LuaJIT FFI usage in the existing codebase.
- The initial PR either passes with minimal code cleanup or documents any narrowly scoped suppressions in
.luacheckrc.
- The change does not introduce a Lua formatting gate.
- The change does not require broad style-only rewrites.
Implementation Notes
A reasonable first pass is:
- Add
.luacheckrc with LuaJIT/busted-aware settings.
- Add
make lua-lint as the local developer entrypoint.
- Run
make lua-lint locally and inspect warnings.
- Prefer fixing real issues over suppressing them.
- Suppress only warnings that are intentional for this project, and keep suppressions specific.
- Add a CI job or step that installs
luacheck through LuaRocks and runs make lua-lint.
Keep the local target simple and readable. It should be easy for a contributor or AI coding agent to run, see failures, fix the Lua files, and rerun without needing to reproduce the full CI environment.
Follow-Ups
Potential future improvements, intentionally excluded from the first PR:
- Fold
lua-lint into make lint if the project later wants make lint to represent all language linters.
- Add changed-files optimization if the Lua codebase grows substantially.
- Consider stricter warning categories after the initial gate has been stable for a while.
Background
qjsonhas strong Rust-side quality gates today: release-mode tests, scalar-only tests, panic-barrier tests, fuzzing, Miri/ASan coverage, cargo-audit, Lua busted integration tests, and LuaRocks package validation.The Lua side currently does not have an equivalent static analysis gate. The public Lua wrapper and lazy table/encoder logic live in:
lua/qjson.lualua/qjson/lib.lualua/qjson/table.luaThe test suite also contains non-trivial Lua helpers and mocks under
tests/lua/. Runtime tests cover behavior, but they do not reliably catch all accidental globals, misspelled locals, unused variables, duplicate definitions, or low-level mistakes in rarely exercised branches.Kong/lua-resty-simdjson uses
luacheckas a lightweight Lua static analysis gate.qjsonshould add a similarly scoped gate, without turning this into a formatting or style-enforcement project.The lint workflow should be local-first: developers and AI coding agents need a quick local command that finds Lua lint problems before pushing. CI should run the same command as final enforcement, rather than hiding the lint logic only inside GitHub Actions.
Goal
Add a lightweight Lua lint quality gate that catches accidental globals and common Lua mistakes in both production Lua code and Lua tests, with an easy local entrypoint and CI enforcement using that same entrypoint.
Proposed Scope
Add
luacheckcoverage for:lua/tests/lua/The initial implementation should include:
.luacheckrcconfigured for LuaJIT-oriented code.describe,it,assert,before_each,after_each, if needed.make lua-lint, that runs the Lua lint check directly.make helpand should fail clearly whenluacheckis missing.luacheckand runs the local target, rather than duplicating the raw lint command inline.A reasonable local command shape is:
The underlying check should be equivalent to:
The configuration should be strict enough to catch meaningful mistakes, but conservative enough to avoid large unrelated churn.
Non-Goals
stylua.cargo fmt --check.make lint. It is acceptable to keepmake lintas Rust clippy-only and add a separatemake lua-linttarget. Ifmake lintis changed to include Lua lint, updateAGENTS.md,CONTRIBUTING.md, and any other command documentation in the same PR.Acceptance Criteria
.luacheckrcchecked in.make lua-lintworks locally and runs Lua lint overlua/andtests/lua/.make helplists the Lua lint target with a clear description.luacheckis not installed locally, the failure mode is clear enough for a developer or AI agent to install the missing tool and retry..luacheckrc.Implementation Notes
A reasonable first pass is:
.luacheckrcwith LuaJIT/busted-aware settings.make lua-lintas the local developer entrypoint.make lua-lintlocally and inspect warnings.luacheckthrough LuaRocks and runsmake lua-lint.Keep the local target simple and readable. It should be easy for a contributor or AI coding agent to run, see failures, fix the Lua files, and rerun without needing to reproduce the full CI environment.
Follow-Ups
Potential future improvements, intentionally excluded from the first PR:
lua-lintintomake lintif the project later wantsmake lintto represent all language linters.