Skip to content

Commit 0d4c4db

Browse files
Copilotshenxianpengpre-commit-ci[bot]
authored
feat(clang-tidy): add --fix opt-in flag for auto-applying fixes in place (#206)
* feat: add --fix argument to clang-tidy hook for auto-fix support Agent-Logs-Url: https://github.com/cpp-linter/cpp-linter-hooks/sessions/c28acf77-7f8c-44a2-9082-569253e178c9 Co-authored-by: shenxianpeng <3353385+shenxianpeng@users.noreply.github.com> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * feat: update README and code to support clang-tidy auto-fix with version v1.4.0 --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: shenxianpeng <3353385+shenxianpeng@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: shenxianpeng <xianpeng.shen@gmail.com>
1 parent ed85e29 commit 0d4c4db

3 files changed

Lines changed: 92 additions & 12 deletions

File tree

README.md

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ Add this configuration to your `.pre-commit-config.yaml` file:
3232
```yaml
3333
repos:
3434
- repo: https://github.com/cpp-linter/cpp-linter-hooks
35-
rev: v1.2.0 # Use the tag or commit you want
35+
rev: v1.4.0 # Use the tag or commit you want
3636
hooks:
3737
- id: clang-format
3838
args: [--style=Google] # Other coding style: LLVM, GNU, Chromium, Microsoft, Mozilla, WebKit.
@@ -47,7 +47,7 @@ To use custom configurations like `.clang-format` and `.clang-tidy`:
4747
```yaml
4848
repos:
4949
- repo: https://github.com/cpp-linter/cpp-linter-hooks
50-
rev: v1.2.0
50+
rev: v1.4.0
5151
hooks:
5252
- id: clang-format
5353
args: [--style=file] # Loads style from .clang-format file
@@ -56,7 +56,7 @@ repos:
5656
```
5757

5858
> [!TIP]
59-
> The `rev` tag (e.g. `v1.2.0`) is the **project** version, not the clang tool version. Each release bundles a default version of `clang-format` and `clang-tidy` — check the [release notes](https://github.com/cpp-linter/cpp-linter-hooks/releases) to see which tool version a given `rev` ships with. To pin an exact tool version independently of the project release, use `--version` as shown below.
59+
> The `rev` tag (e.g. `v1.4.0`) is the **project** version, not the clang tool version. Each release bundles a default version of `clang-format` and `clang-tidy` — check the [release notes](https://github.com/cpp-linter/cpp-linter-hooks/releases) to see which tool version a given `rev` ships with. To pin an exact tool version independently of the project release, use `--version` as shown below.
6060

6161
### Custom Clang Tool Version
6262

@@ -65,7 +65,7 @@ To use specific versions of clang-format and clang-tidy (using Python wheel pack
6565
```yaml
6666
repos:
6767
- repo: https://github.com/cpp-linter/cpp-linter-hooks
68-
rev: v1.2.0
68+
rev: v1.4.0
6969
hooks:
7070
- id: clang-format
7171
args: [--style=file, --version=21] # Specifies version
@@ -89,7 +89,7 @@ automatically — no configuration needed for most projects:
8989
```yaml
9090
repos:
9191
- repo: https://github.com/cpp-linter/cpp-linter-hooks
92-
rev: v1.2.0
92+
rev: v1.4.0
9393
hooks:
9494
- id: clang-tidy
9595
args: [--checks=.clang-tidy]
@@ -192,6 +192,28 @@ Use -header-filter=.* to display errors from all non-system headers. Use -system
192192
193193
```
194194

195+
> [!NOTE]
196+
> Add `--fix` to `args` to automatically apply clang-tidy fixes in place (equivalent to
197+
> passing `-fix` to clang-tidy directly). This is **opt-in** and **not the default** because
198+
> auto-fixing can modify source files in unexpected ways. A valid `compile_commands.json` is
199+
> strongly recommended when using `--fix`.
200+
>
201+
> For cases where compiler errors exist alongside style issues, pass `-fix-errors` directly
202+
> in `args` instead (clang-tidy native flag).
203+
204+
```yaml
205+
repos:
206+
- repo: https://github.com/cpp-linter/cpp-linter-hooks
207+
rev: v1.4.0 # requires the version that introduced --fix
208+
hooks:
209+
- id: clang-tidy
210+
args: [--checks=.clang-tidy, --fix]
211+
```
212+
213+
> [!WARNING]
214+
> When `--fix` (or `-fix-errors`) is active, parallel execution via `--jobs`/`-j` is
215+
> automatically disabled to prevent concurrent writes to the same header file.
216+
195217
## Troubleshooting
196218

197219
### Performance Optimization
@@ -201,7 +223,7 @@ Use -header-filter=.* to display errors from all non-system headers. Use -system
201223

202224
```yaml
203225
- repo: https://github.com/cpp-linter/cpp-linter-hooks
204-
rev: v1.2.0
226+
rev: v1.4.0
205227
hooks:
206228
- id: clang-format
207229
args: [--style=file, --version=21]
@@ -216,7 +238,7 @@ or `-j`:
216238

217239
```yaml
218240
- repo: https://github.com/cpp-linter/cpp-linter-hooks
219-
rev: v1.2.0
241+
rev: v1.4.0
220242
hooks:
221243
- id: clang-tidy
222244
args: [--checks=.clang-tidy, --version=21, --jobs=4]
@@ -245,7 +267,7 @@ This approach ensures that only modified files are checked, further speeding up
245267
```yaml
246268
repos:
247269
- repo: https://github.com/cpp-linter/cpp-linter-hooks
248-
rev: v1.2.0
270+
rev: v1.4.0
249271
hooks:
250272
- id: clang-format
251273
args: [--style=file, --version=21, --verbose] # Shows processed files
@@ -266,6 +288,7 @@ repos:
266288
| Supports passing format style string | ✅ via `--style` | ❌ |
267289
| Verbose output | ✅ via `--verbose` | ❌ |
268290
| Dry-run mode | ✅ via `--dry-run` | ❌ |
291+
| Auto-fix mode | ✅ via `--fix` (clang-tidy only) | ❌ |
269292
| Compilation database support | ✅ auto-detect or `--compile-commands` | ❌ |
270293

271294

cpp_linter_hooks/clang_tidy.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ def _positive_int(value: str) -> int:
4545
)
4646
parser.add_argument("-j", "--jobs", type=_positive_int, default=1)
4747
parser.add_argument("-v", "--verbose", action="store_true")
48+
parser.add_argument("--fix", action="store_true", help="Apply fixes in place (-fix)")
4849

4950

5051
def _find_compile_commands() -> Optional[str]:
@@ -154,11 +155,21 @@ def run_clang_tidy(args=None) -> Tuple[int, str]:
154155

155156
clang_tidy_args, source_files = _split_source_files(other_args)
156157

158+
if (
159+
hook_args.fix
160+
and "-fix" not in clang_tidy_args
161+
and "-fix-errors" not in clang_tidy_args
162+
):
163+
clang_tidy_args.append("-fix")
164+
157165
# Parallel execution is unsafe when arguments include flags that write to a
158-
# shared output path (e.g., --export-fixes fixes.yaml). In that case, force
159-
# serial execution to avoid concurrent writes/overwrites.
166+
# shared output path (e.g., --export-fixes fixes.yaml) or that apply in-place
167+
# fixes (-fix, -fix-errors), since multiple clang-tidy processes may attempt
168+
# to modify the same header file concurrently.
160169
unsafe_parallel = any(
161-
arg == "--export-fixes" or arg.startswith("--export-fixes=")
170+
arg == "--export-fixes"
171+
or arg.startswith("--export-fixes=")
172+
or arg in ("-fix", "-fix-errors")
162173
for arg in clang_tidy_args
163174
)
164175

@@ -167,7 +178,7 @@ def run_clang_tidy(args=None) -> Tuple[int, str]:
167178
["clang-tidy"] + clang_tidy_args, source_files, hook_args.jobs
168179
)
169180

170-
return _exec_clang_tidy(["clang-tidy"] + other_args)
181+
return _exec_clang_tidy(["clang-tidy"] + clang_tidy_args + source_files)
171182

172183

173184
def main() -> int:

tests/test_clang_tidy.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,3 +324,49 @@ def test_jobs_with_export_fixes_forces_serial_execution():
324324
"b.cpp",
325325
]
326326
)
327+
328+
329+
def test_fix_flag_appends_fix_to_command():
330+
with (
331+
patch(
332+
"cpp_linter_hooks.clang_tidy._exec_clang_tidy", return_value=(0, "")
333+
) as mock_exec,
334+
patch("cpp_linter_hooks.clang_tidy.resolve_install"),
335+
):
336+
run_clang_tidy(["--fix", "-p", "./build", "dummy.cpp"])
337+
338+
mock_exec.assert_called_once()
339+
cmd = mock_exec.call_args[0][0]
340+
assert "-fix" in cmd
341+
342+
343+
def test_fix_flag_forces_serial_execution():
344+
with (
345+
patch(
346+
"cpp_linter_hooks.clang_tidy._exec_clang_tidy", return_value=(0, "")
347+
) as mock_exec,
348+
patch("cpp_linter_hooks.clang_tidy.resolve_install"),
349+
):
350+
run_clang_tidy(["--fix", "--jobs=4", "-p", "./build", "a.cpp", "b.cpp"])
351+
352+
mock_exec.assert_called_once()
353+
cmd = mock_exec.call_args[0][0]
354+
assert "-fix" in cmd
355+
assert "a.cpp" in cmd
356+
assert "b.cpp" in cmd
357+
358+
359+
def test_fix_errors_in_args_forces_serial_execution():
360+
with (
361+
patch(
362+
"cpp_linter_hooks.clang_tidy._exec_clang_tidy", return_value=(0, "")
363+
) as mock_exec,
364+
patch("cpp_linter_hooks.clang_tidy.resolve_install"),
365+
):
366+
run_clang_tidy(["--jobs=4", "-p", "./build", "-fix-errors", "a.cpp", "b.cpp"])
367+
368+
mock_exec.assert_called_once()
369+
cmd = mock_exec.call_args[0][0]
370+
assert "-fix-errors" in cmd
371+
assert "a.cpp" in cmd
372+
assert "b.cpp" in cmd

0 commit comments

Comments
 (0)