From 2f816da95455623639672eca806a576b710d6973 Mon Sep 17 00:00:00 2001
From: Xianpeng Shen <3353385+shenxianpeng@users.noreply.github.com>
Date: Fri, 23 Oct 2026 20:16:01 +0000
Subject: [PATCH 1/5] feat: install clang-format with the hook environment
clang-format is now an unpinned dependency, so pre-commit installs the
newest clang-format wheel when it creates the hook environment and the
hook runs offline from then on. `additional_dependencies` and
`--version` still pin a version, as before.
clang-tidy 22+ enables no checks by default and printed its whole help
text; the hook now prints a short hint on how to enable checks.
---
README.md | 14 ++++++++++----
cpp_linter_hooks/clang_tidy.py | 10 ++++++++++
pyproject.toml | 5 ++++-
tests/test_clang_tidy.py | 18 ++++++++++++++++++
uv.lock | 27 +++++++++++++++++++++++++++
5 files changed, 69 insertions(+), 5 deletions(-)
diff --git a/README.md b/README.md
index 923e601..47a003a 100644
--- a/README.md
+++ b/README.md
@@ -46,10 +46,12 @@ them once, with the hook (see
> [!TIP]
> The `rev` tag (e.g. `v1.8.0`) is the **project** version, not the clang tool version. Without
-> `additional_dependencies` or `--version`, each hook installs the newest clang-format or
-> clang-tidy wheel on PyPI the first time it runs and keeps it until pre-commit rebuilds the hook
-> environment, so two machines can run different LLVM versions with the same configuration. Pin
-> the version in one of the two places, not both.
+> `additional_dependencies` or `--version`, the hooks use the newest wheel on PyPI: pre-commit
+> installs the newest clang-format together with the hook environment, and the clang-tidy hook
+> installs the newest clang-tidy wheel the first time it runs. Both are kept until pre-commit
+> rebuilds the hook environment (a new `rev` or `pre-commit clean`), so two machines can run
+> different LLVM versions with the same configuration. Pin the version in one of the two places,
+> not both.
Instead of `additional_dependencies`, you can give the version in `--version`. The hook then
picks and installs the wheel the first time it runs:
@@ -152,6 +154,10 @@ int main() {for (;;) break; printf("Hello world!\n");return 0;}
### clang-tidy
+clang-tidy 22 and newer enable no checks by themselves. Without a `.clang-tidy` file or
+`--checks`, the hook fails and says how to choose checks, for example with a `.clang-tidy` file
+that contains `Checks: 'bugprone-*,performance-*,readability-*'`.
+
To set the checks in `args` instead of your `.clang-tidy` file, quote the whole option: inside
`[...]`, YAML splits an unquoted value at each comma. When that happens, the hook joins the pieces
again and adds a note on how to fix the configuration. `--checks` takes check names, not a file:
diff --git a/cpp_linter_hooks/clang_tidy.py b/cpp_linter_hooks/clang_tidy.py
index 39b7ca4..5dbf41e 100644
--- a/cpp_linter_hooks/clang_tidy.py
+++ b/cpp_linter_hooks/clang_tidy.py
@@ -60,6 +60,13 @@
The hook finds it in build/ and builddir/. For another directory, pass
--compile-commands=
."""
+NO_CHECKS_ERROR = "Error: no checks enabled."
+NO_CHECKS_HINT = """\
+clang-tidy found no checks to run. LLVM 22 and newer enable no checks by
+default. Add a .clang-tidy file with the checks you want, for example
+ Checks: 'bugprone-*,performance-*,readability-*'
+or set them in args: ["--checks=bugprone-*,performance-*"]."""
+
MSVC_HINT = """\
Windows/MSVC clang-tidy hints:
- Run from a Visual Studio Developer Command Prompt, or call vcvars64.bat first.
@@ -705,6 +712,9 @@ def run_clang_tidy(args=None) -> tuple[int, str]:
[executable] + clang_tidy_args, source_files, lines
)
+ if NO_CHECKS_ERROR in output:
+ # Instead of clang-tidy's error followed by its whole --help text.
+ return 1, _append_notes(NO_CHECKS_HINT, notes)
if not hook_args.verbose:
output = _strip_noise(output)
return retval, _append_notes(output, notes)
diff --git a/pyproject.toml b/pyproject.toml
index 89225d7..1b290da 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -31,8 +31,11 @@ classifiers = [
"Topic :: Software Development :: Build Tools",
]
dependencies = [
+ # Unpinned, so a new hook environment gets the latest clang-format at install
+ # time. Pin it with additional_dependencies or --version.
+ "clang-format",
"filelock>=3.16", # One hook process at a time installs clang tools
- "pip>=26.1", # Required to install clang-format and clang-tidy
+ "pip>=26.1", # Required to install other clang-format and clang-tidy versions
]
dynamic = ["version"]
diff --git a/tests/test_clang_tidy.py b/tests/test_clang_tidy.py
index 7a432f7..962a772 100644
--- a/tests/test_clang_tidy.py
+++ b/tests/test_clang_tidy.py
@@ -13,6 +13,7 @@
COMPILE_COMMANDS_HINT,
COMPILE_DB_SEARCH_DIRS,
MSVC_HINT,
+ NO_CHECKS_HINT,
_append_guidance,
_combine_outputs,
_exec_clang_tidy,
@@ -880,6 +881,23 @@ def test_main_reads_arguments_from_command_line(monkeypatch, mock_resolve, mock_
mock_exec.assert_called_once_with(["clang-tidy", "-quiet", "a.cpp"])
+# --- no checks enabled (LLVM 22+ without a Checks setting) ---
+
+
+@pytest.mark.parametrize("verbose", [[], ["--verbose"]])
+def test_no_checks_enabled_shows_hint_instead_of_help(mock_exec, verbose):
+ mock_exec.return_value = (
+ 1,
+ "Error: no checks enabled.\nUSAGE: clang-tidy [options] \n...",
+ )
+
+ retval, output = run_clang_tidy(verbose + ["--no-compile-commands", "a.cpp"])
+
+ assert retval == 1
+ assert output == NO_CHECKS_HINT
+ assert "USAGE" not in output
+
+
# --- quiet output ---
diff --git a/uv.lock b/uv.lock
index b5de557..cbe703e 100644
--- a/uv.lock
+++ b/uv.lock
@@ -11,6 +11,31 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" },
]
+[[package]]
+name = "clang-format"
+version = "23.1.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/33/c1/76dcec575f5fa6598e32cc66731c499349610ebdcb0393e76cceaa74c3bb/clang_format-23.1.3.tar.gz", hash = "sha256:9f7a4e7a2b8eb766bdef11da742fa93fc58c2b0e960e6a6041716618326a07c5", size = 11506, upload-time = "2026-10-06T20:27:32.799Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/05/0f/a1813ede1e149e700ee810baf22dd9c863ca0bd719d73f86e8c10b67a6bb/clang_format-23.1.3-py2.py3-none-macosx_10_9_x86_64.whl", hash = "sha256:0b8e86cc5a870ca42989bd1ccfe5220fbab96e386922d0a8f0ca350db30593df", size = 1565137, upload-time = "2026-10-06T20:27:05.231Z" },
+ { url = "https://files.pythonhosted.org/packages/47/a5/2007b7a7a53dfe417e66d0a6b38b2f87f16391bfe6fe522d436d1e50be1c/clang_format-23.1.3-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:79f53ac55434af6dda88eb90eeb65d903ff920c910f78041de41e3ca666dfba5", size = 1552313, upload-time = "2026-10-06T20:27:07.196Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/00/a0f99e24dc4892fb120e82fa9cf6e8fcb92714181ffd1165ab7eaf5ba1fd/clang_format-23.1.3-py2.py3-none-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:072113527b49a9c0ab9058006d9fe20e583823e6482608a34e1883dd2c49ae7d", size = 1820841, upload-time = "2026-10-06T20:27:08.675Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/d4/c74dbdaaa72dcb447007c6611ca1a50751c451a75c1d58d1965728001b32/clang_format-23.1.3-py2.py3-none-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:8736b2cd4ed510ceeb1cac2ed0687fd03ce32f82ea960c3c3e62020c5ec88b40", size = 1954844, upload-time = "2026-10-06T20:27:10.441Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/51/0517ba621f554044201e44a61abb2bf64821fac79e3b6260670ff4ef2398/clang_format-23.1.3-py2.py3-none-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b1ab2973aa6d2b72289709df1b2969e0a3f1e09dccb665745b18ca265d4419f3", size = 2192373, upload-time = "2026-10-06T20:27:11.911Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/9a/7c3dd660c8655e2c8d96d7684b05557f0a407a7e6338749d1d47355a7d41/clang_format-23.1.3-py2.py3-none-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9d4bf253feb752a3a52cc5ca595bdae88c9e41de7b35a0f470c8b26c316c2643", size = 2161068, upload-time = "2026-10-06T20:27:13.367Z" },
+ { url = "https://files.pythonhosted.org/packages/11/d1/658d3da4dcbca98620956868c7cf6f4ba19fccecbdfc30cc055e19c9d4a7/clang_format-23.1.3-py2.py3-none-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:58bf1804216501669832a39f2415e14284337438a062ff564729792adec664ac", size = 1906888, upload-time = "2026-10-06T20:27:14.79Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/b6/80d700f3f02a158e6510a1c16ea70a2d9c55e072d3388081152c03cbf44d/clang_format-23.1.3-py2.py3-none-manylinux_2_31_armv7l.whl", hash = "sha256:4e55a20db2d6bc7e9a3341f818a2638f269d45d5a92d2121dfe1c64d47d5b22a", size = 1738515, upload-time = "2026-10-06T20:27:16.193Z" },
+ { url = "https://files.pythonhosted.org/packages/19/f1/2680a4545159dae13f96668dcfcc04d49127f3a4bc04e232cd3f5d67e9d8/clang_format-23.1.3-py2.py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:fdb92131587b1ec71e587d35de552b5a9cfe32c55c5faa9c7e0956c08ee2667f", size = 2804820, upload-time = "2026-10-06T20:27:17.628Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/eb/3d80137f08f6c3ae5789e63fca26c3f5f06246b611529593012a32e77898/clang_format-23.1.3-py2.py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:07070f0f5aaa584eb0f7613c4608709adc01c34f14a3b7efb75152d8dcfcf948", size = 2580768, upload-time = "2026-10-06T20:27:19.136Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/0e/bf8e7202082d2d00c1a4a79833f28725bff715eebcc505bb48d33b837bf9/clang_format-23.1.3-py2.py3-none-musllinux_1_2_i686.whl", hash = "sha256:6b87338ce8862dde24d9844970a2638d5f7fb2c5cd9d868062ccfcbd6e39f4fd", size = 3058043, upload-time = "2026-10-06T20:27:20.938Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/ce/db9b28062cdf54b01bc2eb7545f1368ca231545a712e7090d59327f1c4ac/clang_format-23.1.3-py2.py3-none-musllinux_1_2_ppc64le.whl", hash = "sha256:a92fac41b6a553205f69892b99c49e84195ad9b48055dbca53ec62305f2d7275", size = 3250030, upload-time = "2026-10-06T20:27:22.729Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/65/cee67955f7f4e3dba78e6eb243b76ce32c1d915d31760ba10abb63ea9ef8/clang_format-23.1.3-py2.py3-none-musllinux_1_2_s390x.whl", hash = "sha256:22095e50d7aa66b0336dba712dbaec76ad79b54f95bd1d4bd4cffa2aadbdab75", size = 3287158, upload-time = "2026-10-06T20:27:24.219Z" },
+ { url = "https://files.pythonhosted.org/packages/81/9d/eb9c0d0d7188929a6c07ca5bc83775e3457a63123515f7692a0d310095bf/clang_format-23.1.3-py2.py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ae316062f0266f4f1c914dff24158fafc6009bef0e407bdebb6a2e2308a40a70", size = 2920230, upload-time = "2026-10-06T20:27:26.512Z" },
+ { url = "https://files.pythonhosted.org/packages/32/af/51bb1f77e54863e769d5527a606298da2988520e7d43328648b6d9c6b7b5/clang_format-23.1.3-py2.py3-none-win32.whl", hash = "sha256:1560b5d7b12c9fe6ace14c659b8fae16d580dfe4abfe18eb02583ba290f01ed0", size = 1363845, upload-time = "2026-10-06T20:27:28.259Z" },
+ { url = "https://files.pythonhosted.org/packages/23/a6/90148f91544e3c056e78c514f0e764c1b44d50a33344c83fd03011ea97c8/clang_format-23.1.3-py2.py3-none-win_amd64.whl", hash = "sha256:6c2b74c3a3e942ec172d768e7b945f8e136db05a7b735d49ecfc69bda97643ad", size = 1537541, upload-time = "2026-10-06T20:27:29.654Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/ed/47bda876e360ca71f366f256939dd674e7a5f4f8deae6eddf7819ea21832/clang_format-23.1.3-py2.py3-none-win_arm64.whl", hash = "sha256:583af22e6593b8ea463412cf3411f55731f117546ad13e88b9ce585e7c47475f", size = 1442230, upload-time = "2026-10-06T20:27:31.241Z" },
+]
+
[[package]]
name = "colorama"
version = "0.4.6"
@@ -152,6 +177,7 @@ wheels = [
name = "cpp-linter-hooks"
source = { editable = "." }
dependencies = [
+ { name = "clang-format" },
{ name = "filelock" },
{ name = "pip" },
]
@@ -166,6 +192,7 @@ dev = [
[package.metadata]
requires-dist = [
+ { name = "clang-format" },
{ name = "filelock", specifier = ">=3.16" },
{ name = "pip", specifier = ">=26.1" },
]
From 5c136a85cec5ed8eb17fd3a734a22aba17d28004 Mon Sep 17 00:00:00 2001
From: Xianpeng Shen <3353385+shenxianpeng@users.noreply.github.com>
Date: Fri, 23 Oct 2026 20:17:42 +0000
Subject: [PATCH 2/5] docs: choose the clang tool version with --version only
---
README.md | 69 ++++++++++++++++------------------------
examples/cmake/README.md | 2 --
examples/meson/README.md | 2 --
3 files changed, 27 insertions(+), 46 deletions(-)
diff --git a/README.md b/README.md
index 47a003a..9fadd84 100644
--- a/README.md
+++ b/README.md
@@ -23,9 +23,7 @@ repos:
hooks:
- id: clang-format
args: [--style=file]
- additional_dependencies: [clang-format==21.1.8]
- id: clang-tidy
- additional_dependencies: [clang-tidy==21.1.6]
```
Run `pre-commit install` once in each clone. `--style=file` loads the style from your
@@ -36,62 +34,49 @@ a `compile_commands.json`, which it looks for in `build/` and a few other direct
it skips clang-tidy on your machine and fails in CI (see
[Compilation database](https://github.com/cpp-linter/cpp-linter-hooks#compilation-database)).
-`additional_dependencies` pins the clang-format and clang-tidy versions, and pre-commit installs
-them once, with the hook (see
-[Install the tool with the hook](https://github.com/cpp-linter/cpp-linter-hooks#install-the-tool-with-the-hook)).
+This uses the newest clang-format and clang-tidy. To choose a version, add `--version` (see
+[Clang tool version](https://github.com/cpp-linter/cpp-linter-hooks#clang-tool-version)).
## Usage
-### Custom clang tool version
-
-> [!TIP]
-> The `rev` tag (e.g. `v1.8.0`) is the **project** version, not the clang tool version. Without
-> `additional_dependencies` or `--version`, the hooks use the newest wheel on PyPI: pre-commit
-> installs the newest clang-format together with the hook environment, and the clang-tidy hook
-> installs the newest clang-tidy wheel the first time it runs. Both are kept until pre-commit
-> rebuilds the hook environment (a new `rev` or `pre-commit clean`), so two machines can run
-> different LLVM versions with the same configuration. Pin the version in one of the two places,
-> not both.
+### Clang tool version
-Instead of `additional_dependencies`, you can give the version in `--version`. The hook then
-picks and installs the wheel the first time it runs:
+The `rev` tag (e.g. `v1.8.0`) is the version of these hooks, not of clang-format or clang-tidy.
+Choose the clang tool version with `--version`:
```yaml
- id: clang-format
args: [--style=file, --version=21]
+ - id: clang-tidy
+ args: [--version=21]
```
-- `--version=21` installs the newest 21.x wheel, and `--version=21.1.8` pins an exact release.
+- Without `--version`, the hooks use the newest wheel on PyPI: pre-commit installs the newest
+ clang-format together with the hook, and the clang-tidy hook installs the newest clang-tidy the
+ first time it runs. Both are kept until pre-commit rebuilds the hook (a new `rev` or
+ `pre-commit clean`), so two machines can end up with different versions. Teams and CI should
+ set `--version`.
+- `--version=21` installs the newest 21.x wheel, and `--version=21.1.8` an exact release.
clang-tidy wheels are released separately from clang-format wheels and skip some releases, so
give clang-tidy the major version.
- clang-format wheels cover LLVM 6 to 23 and clang-tidy wheels LLVM 13 to 23. For a version without
a wheel, the hook fails and lists some of the versions that exist.
-- Each `--version` is installed once, into its own directory in the hook environment, and later
- runs use it without contacting pypi.org. pre-commit shares the hook environment between all
- your projects that use the same `rev`, so projects that ask for different versions keep their
- own copy instead of reinstalling. A wheel from `additional_dependencies` that matches
- `--version` is used as well.
-- Only a run that finds no matching wheel installed looks the version up on pypi.org and
- installs it, so that run needs network access. pre-commit runs the hook on groups of files in
- parallel; one of them installs the wheel and the others wait for it.
-
-### Install the tool with the hook
+- Each version is installed once, the first time the hook runs with it, and later runs use it
+ without contacting pypi.org. pre-commit shares the hook between all your projects that use the
+ same `rev`, and projects that ask for different versions each keep their own copy.
-We recommend giving the version in `additional_dependencies`, as in the Quick start. pre-commit
-then installs the clang tool once, when it creates the hook environment, the same way
-[mirrors-clang-format](https://github.com/pre-commit/mirrors-clang-format) does. The hook uses
-that wheel, so do not set `--version` as well.
+#### Without network access
-pre-commit installs `additional_dependencies` with the hook (for example on
-`pre-commit install-hooks`), through the same package index it uses for the hook itself. The hooks
-then never install anything or contact pypi.org when they run, so this works:
+A `--version` that is not installed yet needs network access the first time the hook runs. On
+[pre-commit.ci](https://pre-commit.ci/), which runs hooks without network access, or where
+pypi.org is blocked, pin the version with pre-commit's own `additional_dependencies` instead of
+`--version`. pre-commit installs it together with the hook, through the package index it uses for
+the hook itself:
-- on [pre-commit.ci](https://pre-commit.ci/), which runs hooks without network access,
-- behind a firewall or with a company PyPI mirror, where pypi.org may be blocked or slow,
-- for teams and CI, where every machine gets exactly the same wheel.
-
-If you set both and they do not match, `--version` wins and the hook installs that wheel at run
-time, which needs network access.
+```yaml
+ - id: clang-tidy
+ additional_dependencies: [clang-tidy==21.1.6]
+```
### clang-format
@@ -372,7 +357,7 @@ mirror of the clang-format wheel.
|----------------------------------|-------------------------------------------|----------------------------------------|
| Supports `clang-format` and `clang-tidy` | Both | `clang-format` only |
| Custom configuration files | `.clang-format`, `.clang-tidy` | `.clang-format` |
-| Specify tool version | `additional_dependencies` or `--version` | via `rev` tag (e.g. `rev: v21.1.8`) |
+| Specify tool version | `--version` | via `rev` tag (e.g. `rev: v21.1.8`) |
| `rev` tag meaning | Project version, not the tool version | Equals the clang-format version directly |
| Default file types | C, C++ | C, C++, C#, CUDA, Java, JavaScript, JSON, Objective-C, proto, textproto, Metal |
| Supports passing format style string | via `--style` | via `--style` |
diff --git a/examples/cmake/README.md b/examples/cmake/README.md
index 105ee52..5b3a680 100644
--- a/examples/cmake/README.md
+++ b/examples/cmake/README.md
@@ -11,11 +11,9 @@ repos:
hooks:
- id: clang-format
args: [--style=file]
- additional_dependencies: [clang-format==21.1.8]
files: ^(src|include)/.*\.(cpp|cc|cxx|h|hpp)$
- id: clang-tidy
- additional_dependencies: [clang-tidy==21.1.6]
files: ^(src|include)/.*\.(cpp|cc|cxx)$
```
diff --git a/examples/meson/README.md b/examples/meson/README.md
index 97efd80..eb51ad8 100644
--- a/examples/meson/README.md
+++ b/examples/meson/README.md
@@ -11,11 +11,9 @@ repos:
hooks:
- id: clang-format
args: [--style=file]
- additional_dependencies: [clang-format==21.1.8]
files: ^(src|include)/.*\.(cpp|cc|cxx|h|hpp)$
- id: clang-tidy
- additional_dependencies: [clang-tidy==21.1.6]
files: ^(src|include)/.*\.(cpp|cc|cxx)$
```
From b763a0ca7dcff3f7d0bf8a97a5818f9f26692550 Mon Sep 17 00:00:00 2001
From: Xianpeng Shen <3353385+shenxianpeng@users.noreply.github.com>
Date: Fri, 23 Oct 2026 20:21:00 +0000
Subject: [PATCH 3/5] docs: use rev v1.8.1 in examples
---
README.md | 14 +++++++-------
examples/cmake/README.md | 2 +-
examples/meson/README.md | 2 +-
3 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/README.md b/README.md
index 9fadd84..1352766 100644
--- a/README.md
+++ b/README.md
@@ -19,7 +19,7 @@ Add this configuration to your `.pre-commit-config.yaml` file:
```yaml
repos:
- repo: https://github.com/cpp-linter/cpp-linter-hooks
- rev: v1.8.0
+ rev: v1.8.1
hooks:
- id: clang-format
args: [--style=file]
@@ -41,7 +41,7 @@ This uses the newest clang-format and clang-tidy. To choose a version, add `--ve
### Clang tool version
-The `rev` tag (e.g. `v1.8.0`) is the version of these hooks, not of clang-format or clang-tidy.
+The `rev` tag (e.g. `v1.8.1`) is the version of these hooks, not of clang-format or clang-tidy.
Choose the clang tool version with `--version`:
```yaml
@@ -211,7 +211,7 @@ See [Performance optimization](https://github.com/cpp-linter/cpp-linter-hooks#pe
```yaml
repos:
- repo: https://github.com/cpp-linter/cpp-linter-hooks
- rev: v1.8.0 # includes --fix support
+ rev: v1.8.1 # includes --fix support
hooks:
- id: clang-tidy
args: [--fix]
@@ -234,7 +234,7 @@ repository root works too.
```yaml
repos:
- repo: https://github.com/cpp-linter/cpp-linter-hooks
- rev: v1.8.0
+ rev: v1.8.1
hooks:
- id: clang-tidy
# Auto-detects ./build/compile_commands.json if present
@@ -289,7 +289,7 @@ Three self-contained templates plus quick snippets for other common setups.
```yaml
- repo: https://github.com/cpp-linter/cpp-linter-hooks
- rev: v1.8.0
+ rev: v1.8.1
hooks:
- id: clang-format
args: [--style=file]
@@ -303,7 +303,7 @@ or `-j`:
```yaml
- repo: https://github.com/cpp-linter/cpp-linter-hooks
- rev: v1.8.0
+ rev: v1.8.1
hooks:
- id: clang-tidy
args: [--jobs=4]
@@ -340,7 +340,7 @@ This approach ensures that only modified files are checked, further speeding up
```yaml
repos:
- repo: https://github.com/cpp-linter/cpp-linter-hooks
- rev: v1.8.0
+ rev: v1.8.1
hooks:
- id: clang-format
args: [--style=file, --verbose] # Shows processed files
diff --git a/examples/cmake/README.md b/examples/cmake/README.md
index 5b3a680..22ce9bd 100644
--- a/examples/cmake/README.md
+++ b/examples/cmake/README.md
@@ -7,7 +7,7 @@ If you already have a CMake project, you need **two files**:
```yaml
repos:
- repo: https://github.com/cpp-linter/cpp-linter-hooks
- rev: v1.8.0
+ rev: v1.8.1
hooks:
- id: clang-format
args: [--style=file]
diff --git a/examples/meson/README.md b/examples/meson/README.md
index eb51ad8..4c18134 100644
--- a/examples/meson/README.md
+++ b/examples/meson/README.md
@@ -7,7 +7,7 @@ If you already have a Meson project, configure the hooks and generate a compilat
```yaml
repos:
- repo: https://github.com/cpp-linter/cpp-linter-hooks
- rev: v1.8.0
+ rev: v1.8.1
hooks:
- id: clang-format
args: [--style=file]
From 26467665258beb5f89bdaca5a60b40acd014276c Mon Sep 17 00:00:00 2001
From: Xianpeng Shen <3353385+shenxianpeng@users.noreply.github.com>
Date: Sat, 24 Oct 2026 11:25:30 +0000
Subject: [PATCH 4/5] fix: keep other files' warnings when clang-tidy has no
checks
With --jobs or line-filter batches, one run that printed "Error: no
checks enabled." replaced the whole output with the hint, so a
sub-directory .clang-tidy with `Checks: '-*'` hid every other file's
warnings. Each run now keeps only the error line instead of the help
text, and the hint is added once after the other runs' output. The
hint also covers `Checks: '-*'`.
Docs: the Quick start says clang-tidy 22+ needs a .clang-tidy file, and
"Without network access" says unpinned clang-tidy also installs on its
first run.
---
README.md | 13 ++++++++-----
cpp_linter_hooks/clang_tidy.py | 18 +++++++++++++-----
pyproject.toml | 2 +-
tests/test_clang_tidy.py | 32 ++++++++++++++++++++++++++------
4 files changed, 48 insertions(+), 17 deletions(-)
diff --git a/README.md b/README.md
index 1352766..c9c3207 100644
--- a/README.md
+++ b/README.md
@@ -27,7 +27,9 @@ repos:
```
Run `pre-commit install` once in each clone. `--style=file` loads the style from your
-`.clang-format` file, and clang-tidy reads your `.clang-tidy` file by itself. The clang-tidy hook
+`.clang-format` file, and clang-tidy reads your `.clang-tidy` file by itself. clang-tidy 22 and
+newer run no checks without one, so add a `.clang-tidy` file that contains, for example,
+`Checks: 'bugprone-*,performance-*,readability-*'`. The clang-tidy hook
reports only problems on the lines that your commit adds or changes (see
[Only changed lines](https://github.com/cpp-linter/cpp-linter-hooks#only-changed-lines)). It needs
a `compile_commands.json`, which it looks for in `build/` and a few other directories; without one
@@ -67,10 +69,11 @@ Choose the clang tool version with `--version`:
#### Without network access
-A `--version` that is not installed yet needs network access the first time the hook runs. On
-[pre-commit.ci](https://pre-commit.ci/), which runs hooks without network access, or where
-pypi.org is blocked, pin the version with pre-commit's own `additional_dependencies` instead of
-`--version`. pre-commit installs it together with the hook, through the package index it uses for
+clang-format without `--version` is installed together with the hook and runs offline. clang-tidy
+without `--version`, and any `--version` that is not installed yet, is installed the first time
+the hook runs, which needs network access. On [pre-commit.ci](https://pre-commit.ci/), which runs
+hooks without network access, or where pypi.org is blocked, pin the version with pre-commit's own
+`additional_dependencies` instead of `--version`. pre-commit installs it together with the hook, through the package index it uses for
the hook itself:
```yaml
diff --git a/cpp_linter_hooks/clang_tidy.py b/cpp_linter_hooks/clang_tidy.py
index 5dbf41e..b35ca92 100644
--- a/cpp_linter_hooks/clang_tidy.py
+++ b/cpp_linter_hooks/clang_tidy.py
@@ -62,10 +62,12 @@
NO_CHECKS_ERROR = "Error: no checks enabled."
NO_CHECKS_HINT = """\
-clang-tidy found no checks to run. LLVM 22 and newer enable no checks by
-default. Add a .clang-tidy file with the checks you want, for example
+clang-tidy found no checks to run for some files. Their .clang-tidy file turns
+every check off (for example Checks: '-*'), or they have none and clang-tidy 22
+and newer enable no checks by default. To check them, set the checks you want
+in a .clang-tidy file, for example
Checks: 'bugprone-*,performance-*,readability-*'
-or set them in args: ["--checks=bugprone-*,performance-*"]."""
+or in args: ["--checks=bugprone-*,performance-*"]."""
MSVC_HINT = """\
Windows/MSVC clang-tidy hints:
@@ -400,6 +402,9 @@ def _exec_clang_tidy(command) -> tuple[int, str]:
try:
sp = subprocess.run(command, capture_output=True, encoding="utf-8", check=False)
output = (sp.stdout or "") + (sp.stderr or "")
+ if NO_CHECKS_ERROR in output:
+ # Keep the error line, not the whole --help text that follows it.
+ return 1, NO_CHECKS_ERROR
output = _append_guidance(output)
retval = (
1 if sp.returncode != 0 or "warning:" in output or "error:" in output else 0
@@ -713,8 +718,11 @@ def run_clang_tidy(args=None) -> tuple[int, str]:
)
if NO_CHECKS_ERROR in output:
- # Instead of clang-tidy's error followed by its whole --help text.
- return 1, _append_notes(NO_CHECKS_HINT, notes)
+ # Other runs' warnings stay; the hint explains the error once.
+ output = "\n".join(
+ line for line in output.splitlines() if line != NO_CHECKS_ERROR
+ )
+ notes.append(NO_CHECKS_HINT)
if not hook_args.verbose:
output = _strip_noise(output)
return retval, _append_notes(output, notes)
diff --git a/pyproject.toml b/pyproject.toml
index 1b290da..c0481dd 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -32,7 +32,7 @@ classifiers = [
]
dependencies = [
# Unpinned, so a new hook environment gets the latest clang-format at install
- # time. Pin it with additional_dependencies or --version.
+ # time; additional_dependencies can pin it, and --version runs another version.
"clang-format",
"filelock>=3.16", # One hook process at a time installs clang tools
"pip>=26.1", # Required to install other clang-format and clang-tidy versions
diff --git a/tests/test_clang_tidy.py b/tests/test_clang_tidy.py
index 962a772..dbc3c69 100644
--- a/tests/test_clang_tidy.py
+++ b/tests/test_clang_tidy.py
@@ -13,6 +13,7 @@
COMPILE_COMMANDS_HINT,
COMPILE_DB_SEARCH_DIRS,
MSVC_HINT,
+ NO_CHECKS_ERROR,
NO_CHECKS_HINT,
_append_guidance,
_combine_outputs,
@@ -884,18 +885,37 @@ def test_main_reads_arguments_from_command_line(monkeypatch, mock_resolve, mock_
# --- no checks enabled (LLVM 22+ without a Checks setting) ---
+def test_exec_keeps_only_the_no_checks_error():
+ help_text = "Error: no checks enabled.\nUSAGE: clang-tidy [options] \n..."
+ with patch("cpp_linter_hooks.clang_tidy.subprocess.run") as mock_run:
+ mock_run.return_value = subprocess.CompletedProcess(
+ ["clang-tidy"], 1, stdout="", stderr=help_text
+ )
+ assert _exec_clang_tidy(["clang-tidy", "a.cpp"]) == (1, NO_CHECKS_ERROR)
+
+
@pytest.mark.parametrize("verbose", [[], ["--verbose"]])
-def test_no_checks_enabled_shows_hint_instead_of_help(mock_exec, verbose):
- mock_exec.return_value = (
- 1,
- "Error: no checks enabled.\nUSAGE: clang-tidy [options] \n...",
- )
+def test_no_checks_enabled_shows_hint(mock_exec, verbose):
+ mock_exec.return_value = (1, NO_CHECKS_ERROR)
retval, output = run_clang_tidy(verbose + ["--no-compile-commands", "a.cpp"])
assert retval == 1
assert output == NO_CHECKS_HINT
- assert "USAGE" not in output
+
+
+def test_no_checks_hint_keeps_other_files_warnings(mock_exec):
+ warning = "b.cpp:1:5: warning: use a trailing return type [modernize-x]"
+ mock_exec.side_effect = lambda command: (
+ (1, NO_CHECKS_ERROR) if command[-1] == "sub/c.cpp" else (1, warning)
+ )
+
+ retval, output = run_clang_tidy(
+ ["--jobs=2", "--no-compile-commands", "b.cpp", "sub/c.cpp"]
+ )
+
+ assert retval == 1
+ assert output == warning + "\n\n" + NO_CHECKS_HINT
# --- quiet output ---
From 0a564eef31d3aab39ed327c21ad40dcdc29219c3 Mon Sep 17 00:00:00 2001
From: Xianpeng Shen <3353385+shenxianpeng@users.noreply.github.com>
Date: Sat, 24 Oct 2026 11:32:29 +0000
Subject: [PATCH 5/5] docs: add a .clang-tidy file to the CMake and Meson
examples
---
examples/cmake/README.md | 10 +++++++++-
examples/meson/README.md | 11 ++++++++++-
2 files changed, 19 insertions(+), 2 deletions(-)
diff --git a/examples/cmake/README.md b/examples/cmake/README.md
index 22ce9bd..b26e80a 100644
--- a/examples/cmake/README.md
+++ b/examples/cmake/README.md
@@ -1,6 +1,6 @@
# CMake Project
-If you already have a CMake project, you need **two files**:
+If you already have a CMake project, you need **three files**:
## .pre-commit-config.yaml
@@ -17,6 +17,14 @@ repos:
files: ^(src|include)/.*\.(cpp|cc|cxx)$
```
+## .clang-tidy
+
+clang-tidy 22 and newer run no checks without one. Choose the checks you want, for example:
+
+```yaml
+Checks: 'bugprone-*,performance-*,readability-*'
+```
+
## CMakeLists.txt
Add one line so `clang-tidy` can see your include paths:
diff --git a/examples/meson/README.md b/examples/meson/README.md
index 4c18134..e3e7816 100644
--- a/examples/meson/README.md
+++ b/examples/meson/README.md
@@ -1,6 +1,7 @@
# Meson Project
-If you already have a Meson project, configure the hooks and generate a compilation database.
+If you already have a Meson project, configure the hooks and the clang-tidy checks, and generate a compilation
+database.
## .pre-commit-config.yaml
@@ -17,6 +18,14 @@ repos:
files: ^(src|include)/.*\.(cpp|cc|cxx)$
```
+## .clang-tidy
+
+clang-tidy 22 and newer run no checks without one. Choose the checks you want, for example:
+
+```yaml
+Checks: 'bugprone-*,performance-*,readability-*'
+```
+
## Generate the compilation database
Run from the project root, where `meson.build` lives: