This is a pre-commit hooks repo that integrates five C/C++ linters:
Many of these linters will return 0 on error, which pre-commit will then
mark as passing. Additionally, pre-commit has
a bug
where arguments after --
are dropped. This repo's hooks for each command
will fail correctly and honor all --
arguments.
This repo is available in both python and bash. To use a language, use rev: $lang
in your .pre-commit-config.yaml
. Master is set to python as the default as it is
easier to maintain and troubleshoot.
With int main() { int i; return 10; }
in a file err.cpp
, all five linters should fail on commit:
Using clang-format 8.0.0
; clang-tidy 8.0.0
; oclint 0.13
The above uses this .pre-commit-config.yaml
:
fail_fast: false
repos:
- repo: https://github.com/pocc/pre-commit-hooks
rev: python
hooks:
- id: clang-format
args: [--style=Google]
- id: clang-tidy
args: [-checks=clang-diagnostic-return-type]
- id: oclint
args: [-enable-clang-static-analyzer, -enable-global-analysis]
- id: uncrustify
- id: cppcheck
args: [--enable=all]
Note that for your config yaml, you can supply your own args or remove the args line entirely, depending on your use case.
Python3.6+ is required to use these hooks as all 5 invoking scripts are written in it. As this is also the minimum version of pre-commit, this should not be an issue.
You will need to install these utilities in order to use them. Your package manager may already have them. Below are the package names for each package manager, if available:
apt install clang clang-format clang-tidy uncrustify cppcheck
[1] [2]yum install llvm uncrustify cppcheck
[2]brew install llvm oclint uncrustify cppcheck
[3]choco install llvm uncrustify cppcheck
[4]
[1]: clang
is a required install for clang-format
or clang-tidy
to work.
[2]: oclint takes a couple hours to compile. I've compiled and tarred oclint-v0.15 for those using linux who want to skip the wait (built on Ubuntu-18.04). You can also download the older oclint-v0.13.1 for linux from oclint's github page (see releases).
[3]: Depending on your brew installation, you may need to install
oclint with brew cask install oclint
.
[4]: oclint is not available on windows.
If your package manager is not listed here, it will have similar names for these tools. You can build all of these from source.
Hook Info | Type | Languages |
---|---|---|
clang-format | Formatter | C, C++, ObjC |
clang-tidy | Static code analyzer | C, C++, ObjC |
oclint | Static code analyzer | C, C++, ObjC |
uncrustify | Formatter | C, C++, C#, ObjC, D, Java, Pawn, VALA |
cppcheck | Static code analyzer | C, C++ |
Hook Options | Fix In Place | Enable all Checks | Set key/value |
---|---|---|---|
clang-format | -i |
||
clang-tidy | --fix-errors [1] |
-checks=* -warnings-as-errors=* [2] |
|
oclint | -enable-global-analysis -enable-clang-static-analyzer [3] |
-rc=<key>=<value> |
|
uncrustify | --replace --no-backup [4] |
--set key=value |
|
cppcheck | -enable=all |
[1]: -fix
will fail if there are compiler errors. -fix-errors
will -fix
and fix compiler errors if it can, like missing semicolons.
[2]: Be careful with -checks=*
. can have self-contradictory rules in newer versions of llvm (9+):
modernize wants to use trailing return type
but Fuchsia disallows it.
Thanks to @rambo.
[3]: The oclint pre-commit hook does the equivalent of -max-priority-3 0
by default, which returns an error code when any check fails.
If you use the -max-priority-3
flag to only catch some errors, feel free to reopen #23.
See oclint error codes for more info on partially catching failed checks.
[4]: By definition, if you are using pre-commit
, you are using version control.
Therefore, it is recommended to avoid needless backup creation by using --no-backup
.
Some linters change behavior between versions. To enforce a linter version
8.0.0, for example, add --version=8.0.0
to args:
for that linter. Note that
this is a pre-commit hook arg and will be filtered before args are passed to the linter.
clang-tidy
and oclint
both expect a
compilation database.
Both of the hooks for them will ignore the error for not having one.
You can generate with one cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON ...
if you
have a cmake-based project.
Options after --
like -std=c++11
will be interpreted correctly for
clang-tidy
and oclint
. Make sure they sequentially follow the --
argument
in the hook's args list.
If you want to have predictable return codes for your C linters outside of pre-commit,
these hooks are available via PyPI.
Install it with pip install CLinters
.
They are named as $cmd-hook
, so clang-format
becomes clang-format-hook
.
If you want to run the tests below, you will need to install them from PyPI
or locally with pip install .
.
To run the tests and verify clang-format
, clang-tidy
, and oclint
are
working as expected on your system, use pytest --runslow --internal -vvv
.
This will work on both bash and python branches.
Testing is done by using pytest to generate 76 table tests (python branch) based on combinations of args, files, and expected results.
The default is to skip most (41/76) tests as to run them all takes ~60s. These pytest options are available to add test types:
--runslow
: oclint tests, which take extra time--internal
: Internal class tests to ensure internal/shell APIs match
Note: You can parallelize these tests with pytest-xdist
. Adding -n 32
to the command creates 32 workers and divides runtime by ~6x in my testing.
To run all tests serially, run pytest -x -vvv --internal --runslow
like so:
pre-commit-hooks$ pytest -x -vvv --internal --runslow
============================= test session starts ==============================
platform darwin -- Python 3.7.6, pytest-5.4.1, py-1.7.0, pluggy-0.13.1 -- /usr/local/opt/python/bin/python3.7
cachedir: .pytest_cache
rootdir: /Users/pre-commit-hooks/code/pre-commit-hooks, inifile: pytest.ini
collected 76 items
tests/test_hooks.py::TestHooks::test_run[run_cmd_class clang-format on /Users/pre-commit-hooks/code/pre-commit-hooks/tests/files/ok.c] PASSED [ 3%]
tests/test_hooks.py::TestHooks::test_run[run_cmd_class clang-tidy on /Users/pre-commit-hooks/code/pre-commit-hooks/tests/files/ok.c] PASSED [ 7%]
...
============================= 89 passed in 61.86s ==============================
shellcheck keeps things simple by relaying arguments as shellcheck "$@"
.
This is not possible with several C/C++ linters because they exit 0 when
there are errors. pre-commit registers failures by non-zero exit codes,
which results in false "passes".
- Official Docs
- clang-format Guide - a good overview and a great place to get started
- clang-format Configurator - Website to interactively design your config while
- clang-format Options Explorer - Website to interactively understand various options
- Source Code
- Official Docs
- clang-tidy guide - Good place to start
- Example usage - Explanation of how to use clang-tidy by the creators of Kratos
- Add your own checks - Function names must be awesome!
- Source Code
Apache 2.0