This is a pre-commit hooks repo that integrates five C/C++ linters:
Some of these linters will always appear to “pass” if run directly from pre-commit; the hooks in this repo use wrapper scripts to detect errors accurately. They also work around pre-commit core’s inability to put the files to check anywhere but the end of the command line (see “The '--' doubledash option” below).
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, --, -I., --]
- 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.
If you need to give clang-tidy or oclint options after --
(instead of using a compilation database),
you need to put a second --
marker in args:
,
after all of those options.
This is because pre-commit core always runs hooks
with the arguments in args:
followed by the list of files to check
(see pre-commit issue #1000).
The second --
allows the hook to tell
where the trailing options end
and the file list begins.
For example, this .pre-commit-config.yaml
stanza
hooks:
- id: clang-tidy
args: [-checks=clang-diagnostic-return-type, --, -I., --]
will run clang-tidy like this:
clang-tidy -checks=clang-diagnostic-return-type [files to check] -- -I.
If you leave out the second --
the hook will attempt to guess where the trailing options end,
but this guess cannot be made 100% accurate.
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