This repository provides a small collection of pre-commit hooks to build, test, and lint Go code.
These hooks use the native Go environment that pre-commit provides. As a consequence, pre-commit
automatically installs and updates them into an isolated environment. This plays to pre-commit's
strengths as being a package manager for deterministic, portable hooks. Most other existing hooks
for Go do not leverage pre-commit's builtin Go support and depend on a system-wide or $GOBIN
installation of the tools instead.
The following hooks are currently supported:
buildto make sure commits always compile, runsgo build ./...by default.testto ensure the tests pass, runsgo test ./...by default.vetfor running Go's integrated linter, runsgo vet ./...by default.mod-tidyto rungo mod tidy.goimportsfor formatting and managing packageimports. By default, this hook mutates the content of files.staticcheck, a popular, configurable linter with many checks.errcheck, which enforces error checking.godot, which ensures API comments end with a dot.revive, an establishedgo lintsuccessor.go-criticfor additional checks that are not provided by other common tools.errnameto enforce naming conventions around error variables and types.ineffassignto complain about ineffectual assignments.
If you favourite linter is missing, please open an issue or file a PR!
Make sure pre-commit is installed on your system according to the installation
instructions. Then, pull in the hooks by editing
.pre-commit-config.yaml in your repository root. For example:
repos:
- repo: https://github.com/lubgr/go-pre-commit-hooks
rev: v0.1.0
hooks:
- id: goimports
- id: build
- id: test
args: [-cover, -race, ./...]
- id: vet
- id: mod-tidy
- id: staticcheck
- id: errcheck
args: [-ignoretests, -blank, ./...]
- id: godot
- id: go-critic
args: [check, '-enable=#diagnostic,#performance', ./...]
- id: errname
- id: ineffassignRun pre-commit install to pull in the hooks into their own isolated and cached environment. You
probably want to run pre-commit run --all-files next, to get an idea what this configuration
complains about.
The hooks are configured to run the tools with their default behaviour. You can override this by
specifying the arguments args: [...]. For example, to pass a configuration file to revive:
- id: revive
args: [-config, .revive-config.toml, ./...]Or, to have goimports list non-compliant files instead of overwriting them:
- id: goimports
args: [-l]Go tooling works with packages, and individual files are mostly irrelevant. This contrasts
pre-commit's approach, where a hook is expected to operate on one or more files. In fact,
pre-commit can end up passing files from different packages to a hook - most Go tools will refuse
to process these. Considering this mismatch, the default of the hooks in this repository is to
ignore specific files (pass_filenames: false) and run the tool on the entire repository, most
often using the ./... argument. This is simple enough, but has downsides (they can partly be
addressed, but not in a very satisfying way):
- It is not suitable for large repositories. One way to address this is to duplicate hooks in
.pre-commit-config.yamlwith different per-package filters based on thefilesregex, and passing the relevant package through theargsarray rather than the default./.... When the number of packages grow, the hook entries for.pre-commit-config.yamlfile should be generated. - Untracked files impact the tools, e.g. issues are found in untracked files meant for later
commits, or warnings about unused symbols are omitted because they are used in untracked files. In
order to reproduce the exact outcome of a
pre-commitrun e.g. in CI, one can automate a shallow clone of the repository into a temporary directory, port staged changes but not untracked files, and runpre-committhere.
The number of hooks provided here is not exhaustive, and is not meant to be exhaustive (issues/PRs are welcome, though!).
As a reference, golangci-lint provides many more linters along with
pleasant usability and a unified configuration approach. However, the number of integrated tools
seem to complicate builds, and using go install is discouraged
from (the executable can crash
straight away). Hence, golangci-lint requires system-wide or otherwise external installation and
cannot be integrated as a native Go pre-commit hook.