[clang-tools-extra] [llvm] [Github][CI] Add `doc8` for clang-tidy documentation formatting (PR #168827)
via cfe-commits
cfe-commits at lists.llvm.org
Fri Nov 21 08:12:39 PST 2025
https://github.com/zeyi2 updated https://github.com/llvm/llvm-project/pull/168827
>From 9552857532ca486931f0bcb247d1cd5df6a70c4d Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Thu, 20 Nov 2025 13:03:28 +0800
Subject: [PATCH 01/12] [Github][CI] Add `doc8` for clang-tidy documentation
formatting
---
.../github-action-ci-tooling/Dockerfile | 4 +
.github/workflows/pr-code-lint.yml | 25 ++-
llvm/utils/git/code-lint-helper.py | 201 ++++++++++++++++--
3 files changed, 203 insertions(+), 27 deletions(-)
diff --git a/.github/workflows/containers/github-action-ci-tooling/Dockerfile b/.github/workflows/containers/github-action-ci-tooling/Dockerfile
index b78c99efb9be3..8d02baa05f489 100644
--- a/.github/workflows/containers/github-action-ci-tooling/Dockerfile
+++ b/.github/workflows/containers/github-action-ci-tooling/Dockerfile
@@ -94,6 +94,10 @@ COPY --from=llvm-downloader /llvm-extract/LLVM-${LLVM_VERSION}-Linux-X64/bin/cla
COPY clang-tools-extra/clang-tidy/tool/clang-tidy-diff.py ${LLVM_SYSROOT}/bin/clang-tidy-diff.py
# Install dependencies for 'pr-code-lint.yml' job
+RUN apt-get update && \
+ DEBIAN_FRONTEND=noninteractive apt-get install -y python3-doc8 && \
+ apt-get clean && \
+ rm -rf /var/lib/apt/lists/*
COPY llvm/utils/git/requirements_linting.txt requirements_linting.txt
RUN pip install -r requirements_linting.txt --break-system-packages && \
rm requirements_linting.txt
diff --git a/.github/workflows/pr-code-lint.yml b/.github/workflows/pr-code-lint.yml
index 5444a29c22205..60c1900000e5e 100644
--- a/.github/workflows/pr-code-lint.yml
+++ b/.github/workflows/pr-code-lint.yml
@@ -30,7 +30,7 @@ jobs:
uses: actions/checkout at 08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
fetch-depth: 2
-
+
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files at 24d32ffd492484c1d75e0c0b894501ddb9d30d62 # v47.0.0
@@ -39,14 +39,14 @@ jobs:
skip_initial_fetch: true
base_sha: 'HEAD~1'
sha: 'HEAD'
-
+
- name: Listed files
env:
CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
run: |
echo "Changed files:"
echo "$CHANGED_FILES"
-
+
# TODO: create special mapping for 'codegen' targets, for now build predefined set
# TODO: add entrypoint in 'compute_projects.py' that only adds a project and its direct dependencies
- name: Configure and CodeGen
@@ -71,25 +71,38 @@ jobs:
-DLLVM_INCLUDE_TESTS=OFF \
-DCLANG_INCLUDE_TESTS=OFF \
-DCMAKE_BUILD_TYPE=Release
-
+
ninja -C build \
clang-tablegen-targets \
genconfusable # for "ConfusableIdentifierCheck.h"
- - name: Run code linter
+ - name: Run clang-tidy linter
env:
GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }}
CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
run: |
echo "[]" > comments &&
python3 llvm/utils/git/code-lint-helper.py \
+ --linter clang-tidy \
--token ${{ secrets.GITHUB_TOKEN }} \
--issue-number $GITHUB_PR_NUMBER \
--start-rev HEAD~1 \
--end-rev HEAD \
--verbose \
--changed-files "$CHANGED_FILES"
-
+
+ - name: Run doc8 linter
+ env:
+ GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }}
+ run: |
+ python3 llvm/utils/git/code-lint-helper.py \
+ --linter doc8 \
+ --token ${{ secrets.GITHUB_TOKEN }} \
+ --issue-number $GITHUB_PR_NUMBER \
+ --start-rev HEAD~1 \
+ --end-rev HEAD \
+ --verbose
+
- name: Upload results
uses: actions/upload-artifact at 330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
if: always()
diff --git a/llvm/utils/git/code-lint-helper.py b/llvm/utils/git/code-lint-helper.py
index 1232f3ab0d370..fc2068b438209 100755
--- a/llvm/utils/git/code-lint-helper.py
+++ b/llvm/utils/git/code-lint-helper.py
@@ -34,6 +34,8 @@ class LintArgs:
issue_number: int = 0
build_path: str = "build"
clang_tidy_binary: str = "clang-tidy"
+ doc8_binary: str = "doc8"
+ linter: str = None
def __init__(self, args: argparse.Namespace = None) -> None:
if not args is None:
@@ -46,9 +48,12 @@ def __init__(self, args: argparse.Namespace = None) -> None:
self.verbose = args.verbose
self.build_path = args.build_path
self.clang_tidy_binary = args.clang_tidy_binary
+ self.doc8_binary = args.doc8_binary
+ self.linter = args.linter
-COMMENT_TAG = "<!--LLVM CODE LINT COMMENT: clang-tidy-->"
+COMMENT_TAG_CLANG_TIDY = "<!--LLVM CODE LINT COMMENT: clang-tidy-->"
+COMMENT_TAG_DOC8 = "<!--LLVM CODE LINT COMMENT: doc8-->"
def get_instructions(cpp_files: List[str]) -> str:
@@ -135,13 +140,22 @@ def create_comment_text(warning: str, cpp_files: List[str]) -> str:
"""
-def find_comment(pr: any) -> any:
+def find_comment(pr: any, args: LintArgs) -> any:
+ comment_tag = get_comment_tag(args.linter)
for comment in pr.as_issue().get_comments():
- if COMMENT_TAG in comment.body:
+ if comment_tag in comment.body:
return comment
return None
+def get_comment_tag(linter: str) -> str:
+ if linter == "clang-tidy":
+ return COMMENT_TAG_CLANG_TIDY
+ elif linter == "doc8":
+ return COMMENT_TAG_DOC8
+ raise ValueError(f"Unknown linter: {linter}")
+
+
def create_comment(
comment_text: str, args: LintArgs, create_new: bool
) -> Optional[dict]:
@@ -150,9 +164,10 @@ def create_comment(
repo = github.Github(args.token).get_repo(args.repo)
pr = repo.get_issue(args.issue_number).as_pull_request()
- comment_text = COMMENT_TAG + "\n\n" + comment_text
+ comment_tag = get_comment_tag(args.linter)
+ comment_text = comment_tag + "\n\n" + comment_text
- existing_comment = find_comment(pr)
+ existing_comment = find_comment(pr, args)
comment = None
if create_new or existing_comment:
@@ -215,7 +230,126 @@ def run_clang_tidy(changed_files: List[str], args: LintArgs) -> Optional[str]:
return clean_clang_tidy_output(proc.stdout.strip())
-def run_linter(changed_files: List[str], args: LintArgs) -> tuple[bool, Optional[dict]]:
+
+def clean_doc8_output(output: str) -> Optional[str]:
+ if not output:
+ return None
+
+ lines = output.split("\n")
+ cleaned_lines = []
+ in_summary = False
+
+ for line in lines:
+ if line.startswith("Scanning...") or line.startswith("Validating..."):
+ continue
+ if line.startswith("========"):
+ in_summary = True
+ continue
+ if in_summary:
+ continue
+ if line.strip():
+ cleaned_lines.append(line)
+
+ if cleaned_lines:
+ return "\n".join(cleaned_lines)
+ return None
+
+
+def get_doc8_instructions() -> str:
+ # TODO: use git diff
+ return "doc8 ./clang-tools-extra/docs/clang-tidy/checks/"
+
+
+def create_doc8_comment_text(doc8_output: str) -> str:
+ instructions = get_doc8_instructions()
+ return f"""
+:warning: Documentation linter doc8 found issues in your code. :warning:
+
+<details>
+<summary>
+You can test this locally with the following command:
+</summary>
+
+```bash
+{instructions}
+```
+
+</details>
+
+<details>
+<summary>
+View the output from doc8 here.
+</summary>
+
+```
+{doc8_output}
+```
+
+</details>
+"""
+
+
+def run_doc8(args: LintArgs) -> tuple[int, Optional[str]]:
+ doc8_cmd = [args.doc8_binary, "./clang-tools-extra/docs/clang-tidy/checks/"]
+
+ if args.verbose:
+ print(f"Running doc8: {' '.join(doc8_cmd)}")
+
+ proc = subprocess.run(
+ doc8_cmd,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ text=True,
+ check=False,
+ )
+
+ cleaned_output = clean_doc8_output(proc.stdout.strip())
+ if proc.returncode != 0 and cleaned_output is None:
+ # Infrastructure failure
+ return proc.returncode, proc.stderr.strip()
+
+ return proc.returncode, cleaned_output
+
+
+def run_doc8_linter(args: LintArgs) -> tuple[bool, Optional[dict]]:
+ returncode, result = run_doc8(args)
+ should_update_gh = args.token is not None and args.repo is not None
+ comment = None
+
+ if returncode == 0:
+ if should_update_gh:
+ comment_text = (
+ ":white_check_mark: With the latest revision "
+ "this PR passed the documentation linter."
+ )
+ comment = create_comment(comment_text, args, create_new=False)
+ return True, comment
+ else:
+ if should_update_gh:
+ if result:
+ comment_text = create_doc8_comment_text(result)
+ comment = create_comment(comment_text, args, create_new=True)
+ else:
+ comment_text = (
+ ":warning: The documentation linter failed without printing "
+ "an output. Check the logs for output. :warning:"
+ )
+ comment = create_comment(comment_text, args, create_new=False)
+ else:
+ if result:
+ print(
+ "Warning: Documentation linter, doc8 detected "
+ "some issues with your code..."
+ )
+ print(result)
+ else:
+ print("Warning: Documentation linter, doc8 failed to run.")
+ return False, comment
+
+
+def run_clang_tidy_linter(
+ changed_files: List[str], args: LintArgs
+) -> tuple[bool, Optional[dict]]:
changed_files = [arg for arg in changed_files if "third-party" not in arg]
cpp_files = filter_changed_files(changed_files)
@@ -255,6 +389,13 @@ def run_linter(changed_files: List[str], args: LintArgs) -> tuple[bool, Optional
if __name__ == "__main__":
parser = argparse.ArgumentParser()
+ parser.add_argument(
+ "--linter",
+ type=str,
+ choices=["clang-tidy", "doc8"],
+ required=True,
+ help="The linter to run.",
+ )
parser.add_argument(
"--token", type=str, required=True, help="GitHub authentication token"
)
@@ -291,6 +432,12 @@ def run_linter(changed_files: List[str], args: LintArgs) -> tuple[bool, Optional
default="clang-tidy",
help="Path to clang-tidy binary",
)
+ parser.add_argument(
+ "--doc8-binary",
+ type=str,
+ default="doc8",
+ help="Path to doc8 binary",
+ )
parser.add_argument(
"--verbose", action="store_true", default=True, help="Verbose output"
)
@@ -298,32 +445,44 @@ def run_linter(changed_files: List[str], args: LintArgs) -> tuple[bool, Optional
parsed_args = parser.parse_args()
args = LintArgs(parsed_args)
- changed_files = []
- if args.changed_files:
- changed_files = args.changed_files.split(",")
-
- if args.verbose:
- print(f"got changed files: {changed_files}")
-
if args.verbose:
- print("running linter clang-tidy")
+ print(f"running linter {args.linter}")
- success, comment = run_linter(changed_files, args)
+ success, comment = False, None
+ if args.linter == "clang-tidy":
+ changed_files = []
+ if args.changed_files:
+ changed_files = args.changed_files.split(",")
+ if args.verbose:
+ print(f"got changed files: {changed_files}")
+ success, comment = run_clang_tidy_linter(changed_files, args)
+ elif args.linter == "doc8":
+ success, comment = run_doc8_linter(args)
if not success:
if args.verbose:
- print("linter clang-tidy failed")
+ print(f"linter {args.linter} failed")
# Write comments file if we have a comment
if comment:
+ import json
if args.verbose:
- print(f"linter clang-tidy has comment: {comment}")
+ print(f"linter {args.linter} has comment: {comment}")
- with open("comments", "w") as f:
- import json
+ existing_comments = []
+ if os.path.exists("comments"):
+ with open("comments", "r") as f:
+ try:
+ existing_comments = json.load(f)
+ except json.JSONDecodeError:
+ # File might be empty or invalid, start fresh
+ pass
- json.dump([comment], f)
+ existing_comments.append(comment)
+
+ with open("comments", "w") as f:
+ json.dump(existing_comments, f)
if not success:
- print("error: some linters failed: clang-tidy")
+ print(f"error: linter {args.linter} failed")
sys.exit(1)
>From 8fc630014edd5046c39d9da8831cad5a4f0d23b2 Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Thu, 20 Nov 2025 13:43:18 +0800
Subject: [PATCH 02/12] Try to make CI work
---
.github/workflows/pr-code-lint.yml | 3 +++
1 file changed, 3 insertions(+)
diff --git a/.github/workflows/pr-code-lint.yml b/.github/workflows/pr-code-lint.yml
index 60c1900000e5e..33b84117fea8b 100644
--- a/.github/workflows/pr-code-lint.yml
+++ b/.github/workflows/pr-code-lint.yml
@@ -76,6 +76,9 @@ jobs:
clang-tablegen-targets \
genconfusable # for "ConfusableIdentifierCheck.h"
+ - name: Install linter dependencies
+ run: pip install doc8 --break-system-packages
+
- name: Run clang-tidy linter
env:
GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }}
>From 3a5c436a2efa0bb2bc8128ccd3e11b53b30cb611 Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Thu, 20 Nov 2025 13:57:26 +0800
Subject: [PATCH 03/12] Dirty fix again
---
.github/workflows/pr-code-lint.yml | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/pr-code-lint.yml b/.github/workflows/pr-code-lint.yml
index 33b84117fea8b..337a033f4848c 100644
--- a/.github/workflows/pr-code-lint.yml
+++ b/.github/workflows/pr-code-lint.yml
@@ -77,7 +77,9 @@ jobs:
genconfusable # for "ConfusableIdentifierCheck.h"
- name: Install linter dependencies
- run: pip install doc8 --break-system-packages
+ run: |
+ pip install doc8 --break-system-packages
+ echo "$HOME/.local/bin" >> $GITHUB_PATH
- name: Run clang-tidy linter
env:
>From 27c30abf7464821af5f94a0b45c9f8871e7d2648 Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Thu, 20 Nov 2025 14:08:36 +0800
Subject: [PATCH 04/12] Fix comment issue
---
llvm/utils/git/code-lint-helper.py | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/llvm/utils/git/code-lint-helper.py b/llvm/utils/git/code-lint-helper.py
index fc2068b438209..17cdd02cfceb6 100755
--- a/llvm/utils/git/code-lint-helper.py
+++ b/llvm/utils/git/code-lint-helper.py
@@ -141,9 +141,14 @@ def create_comment_text(warning: str, cpp_files: List[str]) -> str:
def find_comment(pr: any, args: LintArgs) -> any:
- comment_tag = get_comment_tag(args.linter)
+ linter_tag = get_comment_tag(args.linter)
+ other_linter = "doc8" if args.linter == "clang-tidy" else "clang-tidy"
+ other_tag = get_comment_tag(other_linter)
+
for comment in pr.as_issue().get_comments():
- if comment_tag in comment.body:
+ body = comment.body
+ if linter_tag in body and other_tag not in body:
+ # Found a comment that is exclusively for this linter.
return comment
return None
@@ -230,7 +235,6 @@ def run_clang_tidy(changed_files: List[str], args: LintArgs) -> Optional[str]:
return clean_clang_tidy_output(proc.stdout.strip())
-
def clean_doc8_output(output: str) -> Optional[str]:
if not output:
return None
>From 75acf3ba9a2174ff92062cb7d74016a9c13ac296 Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Fri, 21 Nov 2025 14:27:57 +0800
Subject: [PATCH 05/12] Update scripts
---
llvm/utils/git/code-lint-helper.py | 78 ++++++++++++++++--------------
1 file changed, 41 insertions(+), 37 deletions(-)
diff --git a/llvm/utils/git/code-lint-helper.py b/llvm/utils/git/code-lint-helper.py
index 17cdd02cfceb6..7172c07b91f23 100755
--- a/llvm/utils/git/code-lint-helper.py
+++ b/llvm/utils/git/code-lint-helper.py
@@ -111,6 +111,19 @@ def filter_changed_files(changed_files: List[str]) -> List[str]:
return filtered_files
+def filter_doc_files(changed_files: List[str]) -> List[str]:
+ filtered_files = []
+ for filepath in changed_files:
+ _, ext = os.path.splitext(filepath)
+ if ext not in (".rst"):
+ continue
+ if not filepath.startswith("clang-tools-extra/docs/clang-tidy/checks/"):
+ continue
+ if os.path.exists(filepath):
+ filtered_files.append(filepath)
+ return filtered_files
+
+
def create_comment_text(warning: str, cpp_files: List[str]) -> str:
instructions = get_instructions(cpp_files)
return f"""
@@ -235,37 +248,13 @@ def run_clang_tidy(changed_files: List[str], args: LintArgs) -> Optional[str]:
return clean_clang_tidy_output(proc.stdout.strip())
-def clean_doc8_output(output: str) -> Optional[str]:
- if not output:
- return None
+def get_doc8_instructions(doc_files: List[str]) -> str:
+ files_str = " ".join(doc_files)
+ return f"doc8 -q {files_str}"
- lines = output.split("\n")
- cleaned_lines = []
- in_summary = False
- for line in lines:
- if line.startswith("Scanning...") or line.startswith("Validating..."):
- continue
- if line.startswith("========"):
- in_summary = True
- continue
- if in_summary:
- continue
- if line.strip():
- cleaned_lines.append(line)
-
- if cleaned_lines:
- return "\n".join(cleaned_lines)
- return None
-
-
-def get_doc8_instructions() -> str:
- # TODO: use git diff
- return "doc8 ./clang-tools-extra/docs/clang-tidy/checks/"
-
-
-def create_doc8_comment_text(doc8_output: str) -> str:
- instructions = get_doc8_instructions()
+def create_doc8_comment_text(doc8_output: str, doc_files: List[str]) -> str:
+ instructions = get_doc8_instructions(doc_files)
return f"""
:warning: Documentation linter doc8 found issues in your code. :warning:
@@ -293,8 +282,11 @@ def create_doc8_comment_text(doc8_output: str) -> str:
"""
-def run_doc8(args: LintArgs) -> tuple[int, Optional[str]]:
- doc8_cmd = [args.doc8_binary, "./clang-tools-extra/docs/clang-tidy/checks/"]
+def run_doc8(doc_files: List[str], args: LintArgs) -> tuple[int, Optional[str]]:
+ if not doc_files:
+ return 0, None
+
+ doc8_cmd = [args.doc8_binary, "-q"] + doc_files
if args.verbose:
print(f"Running doc8: {' '.join(doc8_cmd)}")
@@ -307,20 +299,32 @@ def run_doc8(args: LintArgs) -> tuple[int, Optional[str]]:
check=False,
)
- cleaned_output = clean_doc8_output(proc.stdout.strip())
- if proc.returncode != 0 and cleaned_output is None:
+ output = proc.stdout.strip()
+ if proc.returncode != 0 and not output:
# Infrastructure failure
return proc.returncode, proc.stderr.strip()
- return proc.returncode, cleaned_output
+ return proc.returncode, output if output else None
def run_doc8_linter(args: LintArgs) -> tuple[bool, Optional[dict]]:
- returncode, result = run_doc8(args)
+ changed_files = []
+ if args.changed_files:
+ changed_files = args.changed_files.split(',')
+ doc_files = filter_doc_files(changed_files)
+
+ is_success = True
+ result = None
+
+ if doc_files:
+ returncode, result = run_doc8(doc_files, args)
+ if returncode != 0:
+ is_success = False
+
should_update_gh = args.token is not None and args.repo is not None
comment = None
- if returncode == 0:
+ if is_success:
if should_update_gh:
comment_text = (
":white_check_mark: With the latest revision "
@@ -331,7 +335,7 @@ def run_doc8_linter(args: LintArgs) -> tuple[bool, Optional[dict]]:
else:
if should_update_gh:
if result:
- comment_text = create_doc8_comment_text(result)
+ comment_text = create_doc8_comment_text(result, doc_files)
comment = create_comment(comment_text, args, create_new=True)
else:
comment_text = (
>From 09a61e4c17e4f0a1d1cace501c77b83d7e00e117 Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Fri, 21 Nov 2025 15:53:20 +0800
Subject: [PATCH 06/12] [WIP] Unified script
---
.github/workflows/pr-code-lint.yml | 15 +-
llvm/utils/git/code-lint-helper.py | 552 ++++++++++++-----------------
2 files changed, 232 insertions(+), 335 deletions(-)
diff --git a/.github/workflows/pr-code-lint.yml b/.github/workflows/pr-code-lint.yml
index 337a033f4848c..4f016b42d180c 100644
--- a/.github/workflows/pr-code-lint.yml
+++ b/.github/workflows/pr-code-lint.yml
@@ -81,14 +81,13 @@ jobs:
pip install doc8 --break-system-packages
echo "$HOME/.local/bin" >> $GITHUB_PATH
- - name: Run clang-tidy linter
+ - name: Run linters
env:
GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }}
CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
run: |
echo "[]" > comments &&
python3 llvm/utils/git/code-lint-helper.py \
- --linter clang-tidy \
--token ${{ secrets.GITHUB_TOKEN }} \
--issue-number $GITHUB_PR_NUMBER \
--start-rev HEAD~1 \
@@ -96,18 +95,6 @@ jobs:
--verbose \
--changed-files "$CHANGED_FILES"
- - name: Run doc8 linter
- env:
- GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }}
- run: |
- python3 llvm/utils/git/code-lint-helper.py \
- --linter doc8 \
- --token ${{ secrets.GITHUB_TOKEN }} \
- --issue-number $GITHUB_PR_NUMBER \
- --start-rev HEAD~1 \
- --end-rev HEAD \
- --verbose
-
- name: Upload results
uses: actions/upload-artifact at 330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
if: always()
diff --git a/llvm/utils/git/code-lint-helper.py b/llvm/utils/git/code-lint-helper.py
index 7172c07b91f23..ca4cf481ade2f 100755
--- a/llvm/utils/git/code-lint-helper.py
+++ b/llvm/utils/git/code-lint-helper.py
@@ -35,7 +35,6 @@ class LintArgs:
build_path: str = "build"
clang_tidy_binary: str = "clang-tidy"
doc8_binary: str = "doc8"
- linter: str = None
def __init__(self, args: argparse.Namespace = None) -> None:
if not args is None:
@@ -43,91 +42,45 @@ def __init__(self, args: argparse.Namespace = None) -> None:
self.end_rev = args.end_rev
self.repo = args.repo
self.token = args.token
- self.changed_files = args.changed_files
+ if args.changed_files:
+ self.changed_files = args.changed_files.split(",")
+ else:
+ self.changed_files = []
self.issue_number = args.issue_number
self.verbose = args.verbose
self.build_path = args.build_path
self.clang_tidy_binary = args.clang_tidy_binary
self.doc8_binary = args.doc8_binary
- self.linter = args.linter
-
-
-COMMENT_TAG_CLANG_TIDY = "<!--LLVM CODE LINT COMMENT: clang-tidy-->"
-COMMENT_TAG_DOC8 = "<!--LLVM CODE LINT COMMENT: doc8-->"
-
-
-def get_instructions(cpp_files: List[str]) -> str:
- files_str = " ".join(cpp_files)
- return f"""
-git diff -U0 origin/main...HEAD -- {files_str} |
-python3 clang-tools-extra/clang-tidy/tool/clang-tidy-diff.py \\
- -path build -p1 -quiet"""
-
-
-def clean_clang_tidy_output(output: str) -> Optional[str]:
- """
- - Remove 'Running clang-tidy in X threads...' line
- - Remove 'N warnings generated.' line
- - Strip leading workspace path from file paths
- """
- if not output or output == "No relevant changes found.":
- return None
- lines = output.split("\n")
- cleaned_lines = []
- for line in lines:
- if line.startswith("Running clang-tidy in") or line.endswith("generated."):
- continue
+class LintHelper:
+ COMMENT_TAG = "<!--LLVM CODE LINT COMMENT: {linter}-->"
+ name: str
+ friendly_name: str
+ comment: dict = None
- # Remove everything up to rightmost "llvm-project/" for correct files names
- idx = line.rfind("llvm-project/")
- if idx != -1:
- line = line[idx + len("llvm-project/") :]
+ @property
+ def comment_tag(self) -> str:
+ return self.COMMENT_TAG.format(linter=self.name)
- cleaned_lines.append(line)
+ @property
+ def instructions(self) -> str:
+ raise NotImplementedError()
- if cleaned_lines:
- return "\n".join(cleaned_lines)
- return None
+ def filter_changed_files(self, changed_files: List[str]) -> List[str]:
+ raise NotImplementedError()
+ def run_linter_tool(
+ self, files_to_lint: List[str], args: LintArgs
+ ) -> Optional[str]:
+ raise NotImplementedError()
-# TODO: Add more rules when enabling other projects to use clang-tidy in CI.
-def should_lint_file(filepath: str) -> bool:
- return filepath.startswith("clang-tools-extra/clang-tidy/")
-
-
-def filter_changed_files(changed_files: List[str]) -> List[str]:
- filtered_files = []
- for filepath in changed_files:
- _, ext = os.path.splitext(filepath)
- if ext not in (".cpp", ".c", ".h", ".hpp", ".hxx", ".cxx"):
- continue
- if not should_lint_file(filepath):
- continue
- if os.path.exists(filepath):
- filtered_files.append(filepath)
-
- return filtered_files
-
-
-def filter_doc_files(changed_files: List[str]) -> List[str]:
- filtered_files = []
- for filepath in changed_files:
- _, ext = os.path.splitext(filepath)
- if ext not in (".rst"):
- continue
- if not filepath.startswith("clang-tools-extra/docs/clang-tidy/checks/"):
- continue
- if os.path.exists(filepath):
- filtered_files.append(filepath)
- return filtered_files
-
-
-def create_comment_text(warning: str, cpp_files: List[str]) -> str:
- instructions = get_instructions(cpp_files)
- return f"""
-:warning: C/C++ code linter clang-tidy found issues in your code. :warning:
+ def pr_comment_text_for_diff(
+ self, linter_output: str, files_to_lint: List[str], args: LintArgs
+ ) -> str:
+ instructions = self.instructions(files_to_lint, args)
+ return f"""
+:warning: {self.friendly_name}, {self.name} found issues in your code. :warning:
<details>
<summary>
@@ -142,268 +95,238 @@ def create_comment_text(warning: str, cpp_files: List[str]) -> str:
<details>
<summary>
-View the output from clang-tidy here.
+View the output from {self.name} here.
</summary>
```
-{warning}
+{linter_output}
```
</details>
"""
+ def find_comment(self, pr: any) -> any:
+ for comment in pr.as_issue().get_comments():
+ if self.comment_tag in comment.body:
+ return comment
+ return None
-def find_comment(pr: any, args: LintArgs) -> any:
- linter_tag = get_comment_tag(args.linter)
- other_linter = "doc8" if args.linter == "clang-tidy" else "clang-tidy"
- other_tag = get_comment_tag(other_linter)
-
- for comment in pr.as_issue().get_comments():
- body = comment.body
- if linter_tag in body and other_tag not in body:
- # Found a comment that is exclusively for this linter.
- return comment
- return None
-
-
-def get_comment_tag(linter: str) -> str:
- if linter == "clang-tidy":
- return COMMENT_TAG_CLANG_TIDY
- elif linter == "doc8":
- return COMMENT_TAG_DOC8
- raise ValueError(f"Unknown linter: {linter}")
-
-
-def create_comment(
- comment_text: str, args: LintArgs, create_new: bool
-) -> Optional[dict]:
- import github
-
- repo = github.Github(args.token).get_repo(args.repo)
- pr = repo.get_issue(args.issue_number).as_pull_request()
-
- comment_tag = get_comment_tag(args.linter)
- comment_text = comment_tag + "\n\n" + comment_text
+ def update_pr(self, comment_text: str, args: LintArgs, create_new: bool) -> None:
+ import github
+ from github import IssueComment, PullRequest
- existing_comment = find_comment(pr, args)
+ repo = github.Github(args.token).get_repo(args.repo)
+ pr = repo.get_issue(args.issue_number).as_pull_request()
- comment = None
- if create_new or existing_comment:
- comment = {"body": comment_text}
- if existing_comment:
- comment["id"] = existing_comment.id
- return comment
+ comment_text = self.comment_tag + "\n\n" + comment_text
+ existing_comment = self.find_comment(pr)
-def run_clang_tidy(changed_files: List[str], args: LintArgs) -> Optional[str]:
- if not changed_files:
- print("no c/c++ files found")
- return None
+ if create_new or existing_comment:
+ self.comment = {"body": comment_text}
+ if existing_comment:
+ self.comment["id"] = existing_comment.id
- git_diff_cmd = [
- "git",
- "diff",
- "-U0",
- f"{args.start_rev}...{args.end_rev}",
- "--",
- ] + changed_files
-
- diff_proc = subprocess.run(
- git_diff_cmd,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- text=True,
- check=False,
- )
- if diff_proc.returncode != 0:
- print(f"Git diff failed: {diff_proc.stderr}")
- return None
+ def run(self, args: LintArgs) -> bool:
+ files_to_lint = self.filter_changed_files(args.changed_files)
- diff_content = diff_proc.stdout
- if not diff_content.strip():
- print("No diff content found")
- return None
+ is_success = True
+ linter_output = None
- tidy_diff_cmd = [
- "clang-tools-extra/clang-tidy/tool/clang-tidy-diff.py",
- "-path",
- args.build_path,
- "-p1",
- "-quiet",
- ]
+ if files_to_lint:
+ linter_output = self.run_linter_tool(files_to_lint, args)
+ if linter_output:
+ is_success = False
- if args.verbose:
- print(f"Running clang-tidy-diff: {' '.join(tidy_diff_cmd)}")
-
- proc = subprocess.run(
- tidy_diff_cmd,
- input=diff_content,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- text=True,
- check=False,
- )
+ should_update_gh = args.token is not None and args.repo is not None
- return clean_clang_tidy_output(proc.stdout.strip())
+ if is_success:
+ if should_update_gh:
+ comment_text = (
+ ":white_check_mark: With the latest revision "
+ f"this PR passed the {self.friendly_name}."
+ )
+ self.update_pr(comment_text, args, create_new=False)
+ return True
+ else:
+ if should_update_gh:
+ if linter_output:
+ comment_text = self.pr_comment_text_for_diff(
+ linter_output, files_to_lint, args
+ )
+ self.update_pr(comment_text, args, create_new=True)
+ else:
+ comment_text = (
+ f":warning: The {self.friendly_name} failed without printing "
+ "an output. Check the logs for output. :warning:"
+ )
+ self.update_pr(comment_text, args, create_new=False)
+ else:
+ if linter_output:
+ print(
+ f"Warning: {self.friendly_name}, {self.name} detected "
+ "some issues with your code..."
+ )
+ print(linter_output)
+ else:
+ print(f"Warning: {self.friendly_name}, {self.name} failed to run.")
+ return False
+
+
+class ClangTidyLintHelper(LintHelper):
+ name = "clang-tidy"
+ friendly_name = "C/C++ code linter"
+
+ def instructions(self, cpp_files: List[str], args: LintArgs) -> str:
+ files_str = " ".join(cpp_files)
+ return f"""
+git diff -U0 origin/main...HEAD -- {files_str} |
+python3 clang-tools-extra/clang-tidy/tool/clang-tidy-diff.py \\
+ -path {args.build_path} -p1 -quiet"""
+
+ def filter_changed_files(self, changed_files: List[str]) -> List[str]:
+ clang_tidy_changed_files = [
+ arg for arg in changed_files if "third-party" not in arg
+ ]
+
+ filtered_files = []
+ for filepath in clang_tidy_changed_files:
+ _, ext = os.path.splitext(filepath)
+ if ext not in (".cpp", ".c", ".h", ".hpp", ".hxx", ".cxx"):
+ continue
+ if not self._should_lint_file(filepath):
+ continue
+ if os.path.exists(filepath):
+ filtered_files.append(filepath)
+ return filtered_files
+
+ def _should_lint_file(self, filepath: str) -> bool:
+ # TODO: Add more rules when enabling other projects to use clang-tidy in CI.
+ return filepath.startswith("clang-tools-extra/clang-tidy/")
+
+ def run_linter_tool(self, cpp_files: List[str], args: LintArgs) -> Optional[str]:
+ if not cpp_files:
+ return None
+
+ git_diff_cmd = [
+ "git",
+ "diff",
+ "-U0",
+ f"{args.start_rev}...{args.end_rev}",
+ "--",
+ ] + cpp_files
+
+ diff_proc = subprocess.run(
+ git_diff_cmd,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ text=True,
+ check=False,
+ )
+ if diff_proc.returncode != 0:
+ print(f"Git diff failed: {diff_proc.stderr}")
+ return "Git diff failed"
-def get_doc8_instructions(doc_files: List[str]) -> str:
- files_str = " ".join(doc_files)
- return f"doc8 -q {files_str}"
+ diff_content = diff_proc.stdout
+ if not diff_content.strip():
+ return None
+ tidy_diff_cmd = [
+ "clang-tools-extra/clang-tidy/tool/clang-tidy-diff.py",
+ "-path",
+ args.build_path,
+ "-p1",
+ "-quiet",
+ ]
-def create_doc8_comment_text(doc8_output: str, doc_files: List[str]) -> str:
- instructions = get_doc8_instructions(doc_files)
- return f"""
-:warning: Documentation linter doc8 found issues in your code. :warning:
+ if args.verbose:
+ print(f"Running clang-tidy-diff: {' '.join(tidy_diff_cmd)}")
+
+ proc = subprocess.run(
+ tidy_diff_cmd,
+ input=diff_content,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ text=True,
+ check=False,
+ )
-<details>
-<summary>
-You can test this locally with the following command:
-</summary>
+ clean_output = self._clean_clang_tidy_output(proc.stdout.strip())
+ return clean_output
-```bash
-{instructions}
-```
-</details>
+ def _clean_clang_tidy_output(self, output: str) -> Optional[str]:
+ if not output or output == "No relevant changes found.":
+ return None
-<details>
-<summary>
-View the output from doc8 here.
-</summary>
+ lines = output.split("\n")
+ cleaned_lines = []
-```
-{doc8_output}
-```
+ for line in lines:
+ if line.startswith("Running clang-tidy in") or line.endswith("generated."):
+ continue
-</details>
-"""
+ idx = line.rfind("llvm-project/")
+ if idx != -1:
+ line = line[idx + len("llvm-project/") :]
+ cleaned_lines.append(line)
-def run_doc8(doc_files: List[str], args: LintArgs) -> tuple[int, Optional[str]]:
- if not doc_files:
- return 0, None
+ if cleaned_lines:
+ return "\n".join(cleaned_lines)
+ return None
- doc8_cmd = [args.doc8_binary, "-q"] + doc_files
- if args.verbose:
- print(f"Running doc8: {' '.join(doc8_cmd)}")
-
- proc = subprocess.run(
- doc8_cmd,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- text=True,
- check=False,
- )
+class Doc8LintHelper(LintHelper):
+ name = "doc8"
+ friendly_name = "Documentation linter"
- output = proc.stdout.strip()
- if proc.returncode != 0 and not output:
- # Infrastructure failure
- return proc.returncode, proc.stderr.strip()
+ def instructions(self, doc_files: List[str], args: LintArgs) -> str:
+ files_str = " ".join(doc_files)
+ return f"doc8 -q {files_str}"
- return proc.returncode, output if output else None
+ def filter_changed_files(self, changed_files: List[str]) -> List[str]:
+ filtered_files = []
+ for filepath in changed_files:
+ _, ext = os.path.splitext(filepath)
+ if ext not in (".rst"):
+ continue
+ if not filepath.startswith("clang-tools-extra/docs/clang-tidy/checks/"):
+ continue
+ if os.path.exists(filepath):
+ filtered_files.append(filepath)
+ return filtered_files
+ def run_linter_tool(self, doc_files: List[str], args: LintArgs) -> Optional[str]:
+ if not doc_files:
+ return None
-def run_doc8_linter(args: LintArgs) -> tuple[bool, Optional[dict]]:
- changed_files = []
- if args.changed_files:
- changed_files = args.changed_files.split(',')
- doc_files = filter_doc_files(changed_files)
+ doc8_cmd = [args.doc8_binary, "-q"] + doc_files
- is_success = True
- result = None
+ if args.verbose:
+ print(f"Running doc8: {' '.join(doc8_cmd)}")
+
+ proc = subprocess.run(
+ doc8_cmd,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ text=True,
+ check=False,
+ )
- if doc_files:
- returncode, result = run_doc8(doc_files, args)
- if returncode != 0:
- is_success = False
+ output = proc.stdout.strip()
+ if proc.returncode != 0 and not output:
+ return proc.stderr.strip()
- should_update_gh = args.token is not None and args.repo is not None
- comment = None
- if is_success:
- if should_update_gh:
- comment_text = (
- ":white_check_mark: With the latest revision "
- "this PR passed the documentation linter."
- )
- comment = create_comment(comment_text, args, create_new=False)
- return True, comment
- else:
- if should_update_gh:
- if result:
- comment_text = create_doc8_comment_text(result, doc_files)
- comment = create_comment(comment_text, args, create_new=True)
- else:
- comment_text = (
- ":warning: The documentation linter failed without printing "
- "an output. Check the logs for output. :warning:"
- )
- comment = create_comment(comment_text, args, create_new=False)
- else:
- if result:
- print(
- "Warning: Documentation linter, doc8 detected "
- "some issues with your code..."
- )
- print(result)
- else:
- print("Warning: Documentation linter, doc8 failed to run.")
- return False, comment
-
-
-def run_clang_tidy_linter(
- changed_files: List[str], args: LintArgs
-) -> tuple[bool, Optional[dict]]:
- changed_files = [arg for arg in changed_files if "third-party" not in arg]
-
- cpp_files = filter_changed_files(changed_files)
-
- tidy_result = run_clang_tidy(cpp_files, args)
- should_update_gh = args.token is not None and args.repo is not None
-
- comment = None
- if tidy_result is None:
- if should_update_gh:
- comment_text = (
- ":white_check_mark: With the latest revision "
- "this PR passed the C/C++ code linter."
- )
- comment = create_comment(comment_text, args, create_new=False)
- return True, comment
- elif len(tidy_result) > 0:
- if should_update_gh:
- comment_text = create_comment_text(tidy_result, cpp_files)
- comment = create_comment(comment_text, args, create_new=True)
- else:
- print(
- "Warning: C/C++ code linter, clang-tidy detected "
- "some issues with your code..."
- )
- return False, comment
- else:
- # The linter failed but didn't output a result (e.g. some sort of
- # infrastructure failure).
- comment_text = (
- ":warning: The C/C++ code linter failed without printing "
- "an output. Check the logs for output. :warning:"
- )
- comment = create_comment(comment_text, args, create_new=False)
- return False, comment
+ALL_LINTERS = (ClangTidyLintHelper(), Doc8LintHelper())
if __name__ == "__main__":
+
parser = argparse.ArgumentParser()
- parser.add_argument(
- "--linter",
- type=str,
- choices=["clang-tidy", "doc8"],
- required=True,
- help="The linter to run.",
- )
parser.add_argument(
"--token", type=str, required=True, help="GitHub authentication token"
)
@@ -454,43 +377,30 @@ def run_clang_tidy_linter(
args = LintArgs(parsed_args)
if args.verbose:
- print(f"running linter {args.linter}")
+ print("Running all linters.")
- success, comment = False, None
- if args.linter == "clang-tidy":
- changed_files = []
- if args.changed_files:
- changed_files = args.changed_files.split(",")
- if args.verbose:
- print(f"got changed files: {changed_files}")
- success, comment = run_clang_tidy_linter(changed_files, args)
- elif args.linter == "doc8":
- success, comment = run_doc8_linter(args)
+ overall_success = True
+ all_comments = []
- if not success:
+ for linter in ALL_LINTERS:
if args.verbose:
- print(f"linter {args.linter} failed")
+ print(f"Running linter: {linter.name}")
- # Write comments file if we have a comment
- if comment:
- import json
- if args.verbose:
- print(f"linter {args.linter} has comment: {comment}")
+ linter_passed = linter.run(args)
+ if not linter_passed:
+ overall_success = False
- existing_comments = []
- if os.path.exists("comments"):
- with open("comments", "r") as f:
- try:
- existing_comments = json.load(f)
- except json.JSONDecodeError:
- # File might be empty or invalid, start fresh
- pass
+ if linter.comment:
+ all_comments.append(linter.comment)
- existing_comments.append(comment)
+ if len(all_comments):
+ import json
with open("comments", "w") as f:
- json.dump(existing_comments, f)
+ json.dump(all_comments, f)
- if not success:
- print(f"error: linter {args.linter} failed")
+ if not overall_success:
+ print("error: Some linters failed.")
sys.exit(1)
+ else:
+ print("All linters passed.")
>From f3cb4bbc5ce17eac9264b7426b37fec95fddf2c6 Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Fri, 21 Nov 2025 16:05:30 +0800
Subject: [PATCH 07/12] Force doc8 check to run
---
.../docs/clang-tidy/checks/bugprone/unsafe-functions.rst | 1 +
llvm/utils/git/code-lint-helper.py | 2 --
2 files changed, 1 insertion(+), 2 deletions(-)
diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-functions.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-functions.rst
index cb7ea415c54b2..6f9f0df8ba3d5 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-functions.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-functions.rst
@@ -3,6 +3,7 @@
bugprone-unsafe-functions
=========================
+
Checks for functions that have safer, more secure replacements available, or
are considered deprecated due to design flaws.
The check heavily relies on the functions from the
diff --git a/llvm/utils/git/code-lint-helper.py b/llvm/utils/git/code-lint-helper.py
index ca4cf481ade2f..0d8bfa0432efe 100755
--- a/llvm/utils/git/code-lint-helper.py
+++ b/llvm/utils/git/code-lint-helper.py
@@ -256,7 +256,6 @@ def run_linter_tool(self, cpp_files: List[str], args: LintArgs) -> Optional[str]
clean_output = self._clean_clang_tidy_output(proc.stdout.strip())
return clean_output
-
def _clean_clang_tidy_output(self, output: str) -> Optional[str]:
if not output or output == "No relevant changes found.":
return None
@@ -325,7 +324,6 @@ def run_linter_tool(self, doc_files: List[str], args: LintArgs) -> Optional[str]
if __name__ == "__main__":
-
parser = argparse.ArgumentParser()
parser.add_argument(
"--token", type=str, required=True, help="GitHub authentication token"
>From 342cae403efc43f2efa79cfbf3cea179c17dbfbb Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Fri, 21 Nov 2025 16:15:46 +0800
Subject: [PATCH 08/12] ~
---
llvm/utils/git/code-lint-helper.py | 15 ++++++++++++---
1 file changed, 12 insertions(+), 3 deletions(-)
diff --git a/llvm/utils/git/code-lint-helper.py b/llvm/utils/git/code-lint-helper.py
index 0d8bfa0432efe..0821c6830deb2 100755
--- a/llvm/utils/git/code-lint-helper.py
+++ b/llvm/utils/git/code-lint-helper.py
@@ -36,7 +36,7 @@ class LintArgs:
clang_tidy_binary: str = "clang-tidy"
doc8_binary: str = "doc8"
- def __init__(self, args: argparse.Namespace = None) -> None:
+ def __init__(self, args: argparse.Namespace) -> None:
if not args is None:
self.start_rev = args.start_rev
self.end_rev = args.end_rev
@@ -315,9 +315,18 @@ def run_linter_tool(self, doc_files: List[str], args: LintArgs) -> Optional[str]
check=False,
)
+ if proc.returncode == 0:
+ return None
+
output = proc.stdout.strip()
- if proc.returncode != 0 and not output:
- return proc.stderr.strip()
+ if output:
+ return output
+
+ error_output = proc.stderr.strip()
+ if error_output:
+ return error_output
+
+ return f"doc8 exited with return code {proc.returncode} but no output."
ALL_LINTERS = (ClangTidyLintHelper(), Doc8LintHelper())
>From f8a1c2353e8d1793c92b0ffc84f8cf28182434bc Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Fri, 21 Nov 2025 21:13:01 +0800
Subject: [PATCH 09/12] ~
---
.../checks/bugprone/unsafe-functions.rst | 1 +
llvm/utils/git/code-lint-helper.py | 14 ++++++++++++--
2 files changed, 13 insertions(+), 2 deletions(-)
diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-functions.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-functions.rst
index 6f9f0df8ba3d5..7e5938dbc1cf0 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-functions.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-functions.rst
@@ -17,6 +17,7 @@ The check implements the following rules from the CERT C Coding Standard:
`cert-msc24-c` and `cert-msc33-c` redirect here as aliases of this check.
+
Unsafe functions
----------------
diff --git a/llvm/utils/git/code-lint-helper.py b/llvm/utils/git/code-lint-helper.py
index 0821c6830deb2..58c1eccd4ddb0 100755
--- a/llvm/utils/git/code-lint-helper.py
+++ b/llvm/utils/git/code-lint-helper.py
@@ -105,10 +105,20 @@ def pr_comment_text_for_diff(
</details>
"""
+ # TODO: Refactor this
def find_comment(self, pr: any) -> any:
+ all_linter_names = [l.name for l in ALL_LINTERS]
+ other_linter_names = [name for name in all_linter_names if name != self.name]
+
+ other_tags = [
+ self.COMMENT_TAG.format(linter=name) for name in other_linter_names
+ ]
+
for comment in pr.as_issue().get_comments():
- if self.comment_tag in comment.body:
- return comment
+ body = comment.body
+ if self.comment_tag in body:
+ if not any(other_tag in body for other_tag in other_tags):
+ return comment
return None
def update_pr(self, comment_text: str, args: LintArgs, create_new: bool) -> None:
>From f553d45b4ae95c79f67857d896e4c48fae468326 Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Fri, 21 Nov 2025 23:13:57 +0800
Subject: [PATCH 10/12] Fix script logic and intentionally break another rst
file
---
.../clang-tidy/checks/abseil/cleanup-ctad.rst | 3 +-
llvm/utils/git/code-lint-helper.py | 52 ++++++++++++++-----
2 files changed, 39 insertions(+), 16 deletions(-)
diff --git a/clang-tools-extra/docs/clang-tidy/checks/abseil/cleanup-ctad.rst b/clang-tools-extra/docs/clang-tidy/checks/abseil/cleanup-ctad.rst
index b8afc8b8a8481..1aecf9af64a00 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/abseil/cleanup-ctad.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/abseil/cleanup-ctad.rst
@@ -3,8 +3,7 @@
abseil-cleanup-ctad
===================
-Suggests switching the initialization pattern of ``absl::Cleanup``
-instances from the factory function to class template argument
+Suggests switching the initialization pattern of ``absl::Cleanup`` instances from the factor function to class template argument
deduction (CTAD), in C++17 and higher.
.. code-block:: c++
diff --git a/llvm/utils/git/code-lint-helper.py b/llvm/utils/git/code-lint-helper.py
index 58c1eccd4ddb0..411231c772215 100755
--- a/llvm/utils/git/code-lint-helper.py
+++ b/llvm/utils/git/code-lint-helper.py
@@ -18,6 +18,7 @@
"""
import argparse
+from operator import attrgetter
import os
import subprocess
import sys
@@ -107,7 +108,7 @@ def pr_comment_text_for_diff(
# TODO: Refactor this
def find_comment(self, pr: any) -> any:
- all_linter_names = [l.name for l in ALL_LINTERS]
+ all_linter_names = list(map(attrgetter("name"), ALL_LINTERS))
other_linter_names = [name for name in all_linter_names if name != self.name]
other_tags = [
@@ -116,9 +117,10 @@ def find_comment(self, pr: any) -> any:
for comment in pr.as_issue().get_comments():
body = comment.body
- if self.comment_tag in body:
- if not any(other_tag in body for other_tag in other_tags):
- return comment
+ if self.comment_tag in body and not any(
+ other_tag in body for other_tag in other_tags
+ ):
+ return comment
return None
def update_pr(self, comment_text: str, args: LintArgs, create_new: bool) -> None:
@@ -139,8 +141,14 @@ def update_pr(self, comment_text: str, args: LintArgs, create_new: bool) -> None
def run(self, args: LintArgs) -> bool:
+ if args.verbose:
+ print(f"got changed files: {args.changed_files}")
+
files_to_lint = self.filter_changed_files(args.changed_files)
+ if not files_to_lint and args.verbose:
+ print("no modified files found")
+
is_success = True
linter_output = None
@@ -237,10 +245,12 @@ def run_linter_tool(self, cpp_files: List[str], args: LintArgs) -> Optional[str]
if diff_proc.returncode != 0:
print(f"Git diff failed: {diff_proc.stderr}")
- return "Git diff failed"
+ return None
diff_content = diff_proc.stdout
if not diff_content.strip():
+ if args.verbose:
+ print("No diff content found")
return None
tidy_diff_cmd = [
@@ -290,7 +300,7 @@ def _clean_clang_tidy_output(self, output: str) -> Optional[str]:
class Doc8LintHelper(LintHelper):
name = "doc8"
- friendly_name = "Documentation linter"
+ friendly_name = "documentation linter"
def instructions(self, doc_files: List[str], args: LintArgs) -> str:
files_str = " ".join(doc_files)
@@ -393,31 +403,45 @@ def run_linter_tool(self, doc_files: List[str], args: LintArgs) -> Optional[str]
parsed_args = parser.parse_args()
args = LintArgs(parsed_args)
- if args.verbose:
- print("Running all linters.")
-
overall_success = True
+ failed_linters = []
all_comments = []
for linter in ALL_LINTERS:
if args.verbose:
- print(f"Running linter: {linter.name}")
+ print(f"running linter {linter.name}")
linter_passed = linter.run(args)
if not linter_passed:
overall_success = False
+ failed_linters.append(linter.name)
+ if args.verbose:
+ print(f"linter {linter.name} failed")
if linter.comment:
+ if args.verbose:
+ print(f"linter {linter.name} has comment: {linter.comment}")
all_comments.append(linter.comment)
if len(all_comments):
import json
+ existing_comments = []
+ if os.path.exists("comments"):
+ try:
+ with open("comments", "r") as f:
+ existing_comments = json.load(f)
+ if not isinstance(existing_comments, list):
+ existing_comments = []
+ except Exception:
+ existing_comments = []
+
+ existing_comments.extend(all_comments)
+
with open("comments", "w") as f:
- json.dump(all_comments, f)
+ json.dump(existing_comments, f)
if not overall_success:
- print("error: Some linters failed.")
+ for name in failed_linters:
+ print(f"error: linter {name} failed")
sys.exit(1)
- else:
- print("All linters passed.")
>From 2ded50d93bd30dca3e822965c5cb62fefd790dc3 Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Fri, 21 Nov 2025 23:28:37 +0800
Subject: [PATCH 11/12] Test comment update
---
.../docs/clang-tidy/checks/abseil/cleanup-ctad.rst | 3 ++-
.../docs/clang-tidy/checks/bugprone/unsafe-functions.rst | 2 --
2 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/clang-tools-extra/docs/clang-tidy/checks/abseil/cleanup-ctad.rst b/clang-tools-extra/docs/clang-tidy/checks/abseil/cleanup-ctad.rst
index 1aecf9af64a00..c874974b3ac24 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/abseil/cleanup-ctad.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/abseil/cleanup-ctad.rst
@@ -3,7 +3,8 @@
abseil-cleanup-ctad
===================
-Suggests switching the initialization pattern of ``absl::Cleanup`` instances from the factor function to class template argument
+Suggests switching the initialization pattern of ``absl::Cleanup``
+instances from the factor function to class template argument
deduction (CTAD), in C++17 and higher.
.. code-block:: c++
diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-functions.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-functions.rst
index 7e5938dbc1cf0..cb7ea415c54b2 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-functions.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-functions.rst
@@ -3,7 +3,6 @@
bugprone-unsafe-functions
=========================
-
Checks for functions that have safer, more secure replacements available, or
are considered deprecated due to design flaws.
The check heavily relies on the functions from the
@@ -17,7 +16,6 @@ The check implements the following rules from the CERT C Coding Standard:
`cert-msc24-c` and `cert-msc33-c` redirect here as aliases of this check.
-
Unsafe functions
----------------
>From a462bbe2f1a492b3b5054d66484c293bde192ecd Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Sat, 22 Nov 2025 00:09:51 +0800
Subject: [PATCH 12/12] Remove temp dependency installing
---
.github/workflows/pr-code-lint.yml | 7 +------
1 file changed, 1 insertion(+), 6 deletions(-)
diff --git a/.github/workflows/pr-code-lint.yml b/.github/workflows/pr-code-lint.yml
index 4f016b42d180c..f4d2b7f6b4662 100644
--- a/.github/workflows/pr-code-lint.yml
+++ b/.github/workflows/pr-code-lint.yml
@@ -20,7 +20,7 @@ jobs:
run:
shell: bash
container:
- image: 'ghcr.io/llvm/ci-ubuntu-24.04-lint'
+ image: 'ghcr.io/llvm/ci-ubuntu-24.04-lint-doc8'
timeout-minutes: 60
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -76,11 +76,6 @@ jobs:
clang-tablegen-targets \
genconfusable # for "ConfusableIdentifierCheck.h"
- - name: Install linter dependencies
- run: |
- pip install doc8 --break-system-packages
- echo "$HOME/.local/bin" >> $GITHUB_PATH
-
- name: Run linters
env:
GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }}
More information about the cfe-commits
mailing list