feat: add --fix CLI option to auto-apply clang-format fixes - #202
feat: add --fix CLI option to auto-apply clang-format fixes#202shenxianpeng wants to merge 9 commits into
Conversation
Adds a --fix flag that runs clang-format -i on files with detected style issues, clearing the format advice so no issues are reported. Related to cpp-linter/cpp-linter-action#439
|
test coverage failed to upload to codecov because their GPG key is now invalid; they're in the middle of a complete migration. Currently workaround is to use the |
|
There was a problem hiding this comment.
- This patch runs clang-format serially, not in parallel. This will add a significant amount of time for events with a large number of changed files.
- This patch ignores the lines-changed-only feature. Formatting the entire file may not be the behavior that users expect.
I think it would be better to handle this inside run_clang_format() because that happens in parallel and the same clang-format args that are already assembled can be reused with -i (which is also done for PR reviews I think).
On a side note, it would be nice to switch from parsing XML to parsing the diff between original source and -i output. But that is a separate issue: #168.
The merge conflict resolution lost the closing `)` of the
_parser_args[("-F", "--fix")] = dict(...) block, producing
"SyntaxError: '(' was never closed" at import time and breaking all
test collection.
clang_tools/__init__.py imports `Path`, not `PurePath`; the auto-fix block referenced the latter, which would fail lint/mypy and raise a NameError at runtime. Use `Path`, consistent with the rest of the module.
Business-logic fixes for the --fix feature: - The in-place clang-format call used subprocess.run(check=True) with no output capture, so a single file's failure aborted the whole run with an unhandled traceback and clang-format's stderr leaked into the logs. Capture the output and, on failure, log an error and continue with the remaining files — consistent with run_clang_format()'s handling. - Add tests/capture_tools_output/test_fix.py covering the previously untested fix path: a malformed file is reformatted in place and its format advice is cleared, and a second pass is a no-op on the already-formatted file.
WalkthroughChangesClang-format fixing
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #202 +/- ##
==========================================
- Coverage 98.31% 98.10% -0.22%
==========================================
Files 24 25 +1
Lines 1958 2000 +42
==========================================
+ Hits 1925 1962 +37
- Misses 33 38 +5 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Address review feedback: the previous implementation applied clang-format fixes in a serial post-loop in capture_clang_tools_output(), which was slow for many files and always reformatted the whole file. Move the in-place fix into run_clang_format(), where it runs per-file in the existing ProcessPoolExecutor worker and reuses the already-assembled, range-aware clang-format args. As a result --fix now honors --lines-changed-only (only the selected lines are reformatted) instead of always rewriting the entire file. On success the file's advice is cleared so nothing is reported as unformatted. Also removes the standalone post-loop subprocess call and updates the --fix CLI help text to match the new behavior.
Document in the README that the --fix option applies clang-format fixes in-place and that clang-tidy fixes are intentionally out of scope.
# Conflicts: # .github/workflows/tests.yml
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
tests/capture_tools_output/test_fix.py (1)
27-60: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd changed-line range coverage.
This test only exercises
lines_changed_only=0. Add a case with formatting issues inside and outside the changed range. Assert that--fixchanges only the selected range.As per PR objectives,
--fixmust honor--lines-changed-only.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/capture_tools_output/test_fix.py` around lines 27 - 60, Extend test_fix_applies_clang_format to cover a nonzero lines_changed_only range, using a file with formatting issues both inside and outside the selected lines. Invoke --fix with that range and assert only the selected lines are reformatted while outside content remains unchanged, preserving the existing full-file and second-pass assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@cpp_linter/clang_tools/clang_format.py`:
- Around line 210-213: Constrain the in-place clang-format flow so it cannot
modify symlinks or targets outside args.repo_root. Update the file-selection
validation or the logic before constructing fix_cmds in the clang-format fix
path to reject C/C++ symlinks, or resolve FileObj.name and require it remains
under args.repo_root before adding -i.
---
Nitpick comments:
In `@tests/capture_tools_output/test_fix.py`:
- Around line 27-60: Extend test_fix_applies_clang_format to cover a nonzero
lines_changed_only range, using a file with formatting issues both inside and
outside the selected lines. Invoke --fix with that range and assert only the
selected lines are reformatted while outside content remains unchanged,
preserving the existing full-file and second-pass assertions.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 11c695d6-7edf-4afe-a284-9736fbf3b322
📒 Files selected for processing (6)
README.rstcpp_linter/clang_tools/__init__.pycpp_linter/clang_tools/clang_format.pycpp_linter/cli.pytests/capture_tools_output/test_fix.pytests/test_cli_args.py
| fix_cmds = [arg for arg in cmds if arg != "--output-replacements-xml"] | ||
| fix_cmds.insert(-1, "-i") # apply edits in-place, just before the file path | ||
| logger.info('Applying formatting fixes with "%s"', " ".join(fix_cmds)) | ||
| fix_results = subprocess.run(fix_cmds, capture_output=True) |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate FileObj construction and identify file-path validation before formatting.
ast-grep outline cpp_linter/common_fs.py --items all --match FileObj
rg -n -C 5 --glob '*.py' \
'FileObj\(|is_symlink\(|resolve\(|relative_to\(|repo_root|lstat\(' \
cpp_linter tests
# Trace the file list passed into the formatter worker.
rg -n -C 5 --glob '*.py' \
'capture_clang_tools_output\(|_run_on_single_file\(' \
cpp_linter testsRepository: cpp-linter/cpp-linter
Length of output: 29770
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== common_fs files =="
git ls-files cpp_linter/common_fs
echo
echo "== locate FileObj definition =="
rg -n --glob '*.py' 'class FileObj|def __init__|def lstat|def is_symlink|def resolve|relative_to|repo_root|files_in_list|get_list' cpp_linter cpp_linter/common_fs tests || true
echo
echo "== FileObj definitions =="
for f in $(rg -l --glob '*.py' 'class FileObj' cpp_linter cpp_linter/common_fs tests || true); do
echo "--- ${f#"$PWD/"}"
sed -n '1,240p' "$f"
done
echo
echo "== file selection factories =="
sed -n '115,220p' cpp_linter/common_fs/file_filter.py
sed -n '110,150p' cpp_linter/__init__.py
sed -n '230,255p' cpp_linter/rest_api/github_api.py
echo
echo "== clang-format file argument handling =="
sed -n '160,235p' cpp_linter/clang_tools/clang_format.pyRepository: cpp-linter/cpp-linter
Length of output: 30977
🌐 Web query:
clang-format -i symlink behavior documentation follows symlink in-place
💡 Result:
The behavior of clang-format regarding symbolic links depends on the specific tool being used: 1. clang-format (the core command-line tool): When you run clang-format with the in-place editing flag (-i) on a file that is a symbolic link, it does not safely preserve the symlink. Instead, it effectively overwrites the symlink by replacing it with a regular file containing the formatted content [1]. It does not follow the symlink to format the target file while leaving the symlink itself intact; rather, it breaks the symlink connection [1]. 2. git-clang-format: This wrapper tool explicitly avoids this issue. Following a reported bug where it would break symlinks, git-clang-format was updated to detect and ignore symbolic links entirely, ensuring they are not formatted [2][3][1]. In summary, there is no built-in documentation or feature that makes clang-format "follow" a symlink to format the target in-place while respecting the symlink structure. If you need to format a target file pointed to by a symlink using the base clang-format tool, you must resolve the path to the actual target file manually before running the command [1].
Citations:
- 1: clang-format breaks symlinks llvm/llvm-project#46336
- 2: https://github.com/llvm/llvm-project/blob/main/clang/tools/clang-format/git-clang-format
- 3: https://lists.llvm.org/pipermail/cfe-commits/Week-of-Mon-20210510/369509.html
🌐 Web query:
site:clang.llvm.org/extra/clangd/clang_format.html or clang-format man page -i symbolic link behavior
💡 Result:
clang-format -i file edits the path you passed, but the official documentation does not specify special symbolic-link behavior—it only says -i performs an in-place edit. (clang.llvm.org)
On POSIX systems, this normally means:
- The symlink is followed when reading.
- The target file’s contents are updated.
- The symlink itself remains intact.
Example:
ln -s real.cpp alias.cpp
clang-format -i alias.cppreal.cpp is formatted; alias.cpp remains a symlink. This follows standard pathname symlink semantics. (man7.org)
However, this is inferred behavior rather than a documented clang-format guarantee; test it if portability across platforms or versions matters.
Citations:
- 1: https://clang.llvm.org/docs/ClangFormat.html?utm_source=openai
- 2: https://man7.org/linux/man-pages/man7/symlink.7.html?utm_source=openai
🌐 Web query:
llvm-project clang-format tool.cpp argv[0] replace symlink in place rename operation -i
💡 Result:
The clang-format tool does not perform in-place rename operations on itself via symlink manipulation [1][2]. While the LLVM project uses symbolic links for managing versions of tools (e.g., creating unversioned clang-format links pointing to versioned binaries like clang-format-18) [3][4][5], these are installation-time or system-management artifacts, not runtime operations performed by the clang-format source code [1][2]. In the context of clang-format's behavior regarding symlinks and file processing: 1. File Handling: There have been reports of clang-format improperly handling symbolic links during in-place formatting (the -i flag), where it might replace a symlink with a regular file containing the formatted content [6]. This is generally considered an issue rather than an intended feature [6]. 2. argv Usage: In the broader LLVM Tooling library, argv is often used to locate the resource directory (e.g., standard library headers), but the project has moved toward injecting the resource directory explicitly rather than relying on or overwriting argv [7][8]. 3. Tooling Logic: The source code for clang-format (e.g., ClangFormat.cpp) and related tooling does not contain logic to rename or replace its own binary executable through symlink manipulation [1][2]. If you are seeing an issue where a symlink is being replaced, it is likely an artifact of how the operating system or file system handles the write operation during an in-place edit, rather than an explicit rename operation executed by the clang-format binary [6].
Citations:
- 1: https://llvm.googlesource.com/llvm-project/+/9a553d3766aacb69e884823da92dedff264e3f0f/clang/tools/clang-format
- 2: https://github.com/llvm/llvm-project/blob/main/clang/tools/clang-format/git-clang-format
- 3: https://cpp-linter.github.io/clang-tools-pip/
- 4: https://developer.lsst.io/cpp/clang-format.html
- 5: https://lists.llvm.org/pipermail/llvm-commits/Week-of-Mon-20161003/394387.html
- 6: clang-format breaks symlinks llvm/llvm-project#46336
- 7: https://github.com/llvm/llvm-project/blob/96196e25fd49e3d0ecec9550e81365ce122679cb/clang/lib/Tooling/Tooling.cpp
- 8: https://lists.llvm.org/pipermail/cfe-commits/Week-of-Mon-20160418/156225.html
Security And Privacy (CWE-59)
Exploitability: Moderate
Reachability path
● Entry
tests/capture_tools_output/test_fix.py
│
▼
● Hop
cpp_linter/clang_tools/__init__.py:37
_run_on_single_file: pragma: no cover
│
▼
● Sink
cpp_linter/clang_tools/clang_format.py
Keep clang-format --fix files constrained to --repo-root.
file_obj.name is passed directly to clang-format -i from file-selection paths. If a selected source file is a C/C++ symlink, clang-format --fix can modify the symlink or its writable target outside the checkout.
Reject C/C++ symlinks during file selection, or require the resolved FileObj.name to remain under args.repo_root before constructing the in-place clang-format command.
🧰 Tools
🪛 ast-grep (0.45.0)
[error] 212-212: Use of unsanitized data to create processes
Context: subprocess.run(fix_cmds, capture_output=True)
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').
(os-system-unsanitized-data)
[error] 212-212: Command coming from incoming request
Context: subprocess.run(fix_cmds, capture_output=True)
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').
(subprocess-from-request)
🪛 Ruff (0.16.1)
[error] 213-213: subprocess call: check for execution of untrusted input
(S603)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@cpp_linter/clang_tools/clang_format.py` around lines 210 - 213, Constrain the
in-place clang-format flow so it cannot modify symlinks or targets outside
args.repo_root. Update the file-selection validation or the logic before
constructing fix_cmds in the clang-format fix path to reject C/C++ symlinks, or
resolve FileObj.name and require it remains under args.repo_root before adding
-i.
Summary
Adds a
--fixCLI flag tocpp-linterthat appliesclang-formatfixes in-place to files with detected style issues, then clears the format advice so no issues are reported.This is part 1 of implementing
auto-fixforcpp-linter-action(see cpp-linter/cpp-linter-action#439).Changes
cpp_linter/cli.pyfix: bool = Falseattribute to theArgsclass--fix/-FCLI argument (store_true)cpp_linter/clang_tools/clang_format.pyrun_clang_format()gained afixparameter. After computing the XML advice, iffixis enabled and the file has style issues, it re-runs clang-format with-ireusing the same range-aware args that were already assembled, then returns a clearedFormatAdviceso the file reports no outstanding issues.cpp_linter/clang_tools/__init__.py_run_on_single_file()passesfix=args.fix, so fixes are applied per-file inside the existingProcessPoolExecutorworker (in parallel) instead of a serial post-loop.Tests
tests/test_cli_args.py: added a case for--fixflag parsingtests/capture_tools_output/test_fix.py: verifies a malformed file is reformatted in place and its advice is cleared, and that a second pass is a no-opDesign Decisions
run_clang_format()in the parallel worker and reuses the assembled range-aware args, so it honors--lines-changed-only— only the selected lines are reformatted (the whole file when--lines-changed-onlyisfalse).format_advicecleared, so the existing comment/review logic sees zero format issues.Related
auto-fixoption to fix code format automatically in PR? cpp-linter-action#439Summary by CodeRabbit
New Features
-F/--fixoption to automatically apply clang-format fixes to changed lines.Documentation
--fixoption.Tests