Skip to content

feat: add --fix CLI option to auto-apply clang-format fixes - #202

Open
shenxianpeng wants to merge 9 commits into
mainfrom
feature/auto-fix
Open

feat: add --fix CLI option to auto-apply clang-format fixes#202
shenxianpeng wants to merge 9 commits into
mainfrom
feature/auto-fix

Conversation

@shenxianpeng

@shenxianpeng shenxianpeng commented Jun 8, 2026

Copy link
Copy Markdown
Member

Summary

Adds a --fix CLI flag to cpp-linter that applies clang-format fixes 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-fix for cpp-linter-action (see cpp-linter/cpp-linter-action#439).

Changes

cpp_linter/cli.py

  • Added fix: bool = False attribute to the Args class
  • Added --fix / -F CLI argument (store_true)

cpp_linter/clang_tools/clang_format.py

  • run_clang_format() gained a fix parameter. After computing the XML advice, if fix is enabled and the file has style issues, it re-runs clang-format with -i reusing the same range-aware args that were already assembled, then returns a cleared FormatAdvice so the file reports no outstanding issues.

cpp_linter/clang_tools/__init__.py

  • _run_on_single_file() passes fix=args.fix, so fixes are applied per-file inside the existing ProcessPoolExecutor worker (in parallel) instead of a serial post-loop.

Tests

  • tests/test_cli_args.py: added a case for --fix flag parsing
  • tests/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-op

Design Decisions

  • Fixing runs inside 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-only is false).
  • Only clang-format fixes are applied; clang-tidy fixes are explicitly out of scope for now (documented in the CLI help).
  • Files that get fixed have their format_advice cleared, so the existing comment/review logic sees zero format issues.
  • Tidy issues are unaffected and still reported normally.

Related

Summary by CodeRabbit

  • New Features

    • Added a -F/--fix option to automatically apply clang-format fixes to changed lines.
    • Successfully formatted files no longer report formatting advice.
    • The option is disabled by default, and clang-tidy fixes remain unapplied.
  • Documentation

    • Documented the behavior and limitations of the --fix option.
  • Tests

    • Added coverage for formatting files in place and confirming subsequent runs are clean.

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
@2bndy5

2bndy5 commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

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 use_pypi: true option or upgrade the codecov-action

@2bndy5

2bndy5 commented Jun 9, 2026

Copy link
Copy Markdown
Contributor
  1. So, no clang-tidy support? You only want to roll out auto-fix for clang-format?
  2. Should we instead use a auto-fix-format if we're going to allow fixes from only certain tools?

@2bndy5 2bndy5 removed the bug Something isn't working label Jun 9, 2026

@2bndy5 2bndy5 Jun 9, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. 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.
  2. 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.

@github-actions github-actions Bot added the bug Something isn't working label Jul 24, 2026
Comment thread cpp_linter/clang_tools/__init__.py Fixed
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.
@shenxianpeng
shenxianpeng marked this pull request as ready for review July 24, 2026 07:44
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.
Comment thread cpp_linter/clang_tools/__init__.py Fixed
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Changes

Clang-format fixing

Layer / File(s) Summary
Fix CLI contract
cpp_linter/cli.py, tests/test_cli_args.py, README.rst
Adds Args.fix and the -F/--fix flag. Documents in-place clang-format fixes and excludes automatic clang-tidy fixes.
In-place format application
cpp_linter/clang_tools/clang_format.py, cpp_linter/clang_tools/__init__.py
Passes the CLI setting to run_clang_format. When replacements exist, clang-format reruns with -i and preserves configured line ranges.
Format fix integration coverage
tests/capture_tools_output/test_fix.py
Tests LLVM and file styles, verifies formatting advice is cleared, and confirms a second pass makes no changes.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding a CLI option to apply clang-format fixes automatically.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jul 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.09524% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 98.10%. Comparing base (85a9fd7) to head (4c6e8be).

Files with missing lines Patch % Lines
cpp_linter/clang_tools/clang_format.py 16.66% 5 Missing ⚠️
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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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.
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Jul 24, 2026
@shenxianpeng shenxianpeng removed bug Something isn't working documentation Improvements or additions to documentation labels Jul 24, 2026
# Conflicts:
#	.github/workflows/tests.yml
@github-actions github-actions Bot added bug Something isn't working documentation Improvements or additions to documentation labels Aug 5, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/capture_tools_output/test_fix.py (1)

27-60: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add 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 --fix changes only the selected range.

As per PR objectives, --fix must 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

📥 Commits

Reviewing files that changed from the base of the PR and between 85a9fd7 and 4c6e8be.

📒 Files selected for processing (6)
  • README.rst
  • cpp_linter/clang_tools/__init__.py
  • cpp_linter/clang_tools/clang_format.py
  • cpp_linter/cli.py
  • tests/capture_tools_output/test_fix.py
  • tests/test_cli_args.py

Comment on lines +210 to +213
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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 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 tests

Repository: 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.py

Repository: 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:


🌐 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.cpp

real.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:


🌐 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:


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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working documentation Improvements or additions to documentation enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

how to commit changes if checks fail (without using PR review suggestions)

3 participants