Skip to content

Commit 218b369

Browse files
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>
1 parent 6cb5cfb commit 218b369

3 files changed

Lines changed: 81 additions & 4 deletions

File tree

README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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.2.0
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
@@ -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: 11 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,17 @@ def run_clang_tidy(args=None) -> Tuple[int, str]:
154155

155156
clang_tidy_args, source_files = _split_source_files(other_args)
156157

158+
if hook_args.fix:
159+
clang_tidy_args.append("-fix")
160+
157161
# 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.
162+
# shared output path (e.g., --export-fixes fixes.yaml) or that apply in-place
163+
# fixes (-fix, -fix-errors), since multiple clang-tidy processes may attempt
164+
# to modify the same header file concurrently.
160165
unsafe_parallel = any(
161-
arg == "--export-fixes" or arg.startswith("--export-fixes=")
166+
arg == "--export-fixes"
167+
or arg.startswith("--export-fixes=")
168+
or arg in ("-fix", "-fix-errors")
162169
for arg in clang_tidy_args
163170
)
164171

@@ -167,7 +174,7 @@ def run_clang_tidy(args=None) -> Tuple[int, str]:
167174
["clang-tidy"] + clang_tidy_args, source_files, hook_args.jobs
168175
)
169176

170-
return _exec_clang_tidy(["clang-tidy"] + other_args)
177+
return _exec_clang_tidy(["clang-tidy"] + clang_tidy_args + source_files)
171178

172179

173180
def main() -> int:

tests/test_clang_tidy.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,3 +324,50 @@ 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+
cmd = mock_exec.call_args[0][0]
339+
assert "-fix" in cmd
340+
341+
342+
def test_fix_flag_forces_serial_execution():
343+
with (
344+
patch(
345+
"cpp_linter_hooks.clang_tidy._exec_clang_tidy", return_value=(0, "")
346+
) as mock_exec,
347+
patch("cpp_linter_hooks.clang_tidy.resolve_install"),
348+
):
349+
run_clang_tidy(["--fix", "--jobs=4", "-p", "./build", "a.cpp", "b.cpp"])
350+
351+
mock_exec.assert_called_once()
352+
cmd = mock_exec.call_args[0][0]
353+
assert "-fix" in cmd
354+
assert "a.cpp" in cmd
355+
assert "b.cpp" in cmd
356+
357+
358+
def test_fix_errors_in_args_forces_serial_execution():
359+
with (
360+
patch(
361+
"cpp_linter_hooks.clang_tidy._exec_clang_tidy", return_value=(0, "")
362+
) as mock_exec,
363+
patch("cpp_linter_hooks.clang_tidy.resolve_install"),
364+
):
365+
run_clang_tidy(
366+
["--jobs=4", "-p", "./build", "-fix-errors", "a.cpp", "b.cpp"]
367+
)
368+
369+
mock_exec.assert_called_once()
370+
cmd = mock_exec.call_args[0][0]
371+
assert "-fix-errors" in cmd
372+
assert "a.cpp" in cmd
373+
assert "b.cpp" in cmd

0 commit comments

Comments
 (0)