https://github.com/zeyi2 updated 
https://github.com/llvm/llvm-project/pull/168827

>From 9552857532ca486931f0bcb247d1cd5df6a70c4d Mon Sep 17 00:00:00 2001
From: mtx <[email protected]>
Date: Thu, 20 Nov 2025 13:03:28 +0800
Subject: [PATCH 01/13] [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@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # 
v5.0.0
         with:
           fetch-depth: 2
-      
+
       - name: Get changed files
         id: changed-files
         uses: 
tj-actions/changed-files@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@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 <[email protected]>
Date: Thu, 20 Nov 2025 13:43:18 +0800
Subject: [PATCH 02/13] 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 <[email protected]>
Date: Thu, 20 Nov 2025 13:57:26 +0800
Subject: [PATCH 03/13] 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 <[email protected]>
Date: Thu, 20 Nov 2025 14:08:36 +0800
Subject: [PATCH 04/13] 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 <[email protected]>
Date: Fri, 21 Nov 2025 14:27:57 +0800
Subject: [PATCH 05/13] 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 <[email protected]>
Date: Fri, 21 Nov 2025 15:53:20 +0800
Subject: [PATCH 06/13] [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@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 <[email protected]>
Date: Fri, 21 Nov 2025 16:05:30 +0800
Subject: [PATCH 07/13] 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 <[email protected]>
Date: Fri, 21 Nov 2025 16:15:46 +0800
Subject: [PATCH 08/13] ~

---
 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 <[email protected]>
Date: Fri, 21 Nov 2025 21:13:01 +0800
Subject: [PATCH 09/13] ~

---
 .../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 <[email protected]>
Date: Fri, 21 Nov 2025 23:13:57 +0800
Subject: [PATCH 10/13] 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 <[email protected]>
Date: Fri, 21 Nov 2025 23:28:37 +0800
Subject: [PATCH 11/13] 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 <[email protected]>
Date: Sat, 22 Nov 2025 00:09:51 +0800
Subject: [PATCH 12/13] 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 }}

>From 29ebebe1daf14145e549329e469653670a49e420 Mon Sep 17 00:00:00 2001
From: mtx <[email protected]>
Date: Sat, 22 Nov 2025 00:11:08 +0800
Subject: [PATCH 13/13] ~

---
 .../docs/clang-tidy/checks/abseil/cleanup-ctad.rst              | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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 c874974b3ac24..b8afc8b8a8481 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
@@ -4,7 +4,7 @@ abseil-cleanup-ctad
 ===================
 
 Suggests switching the initialization pattern of ``absl::Cleanup``
-instances from the factor function to class template argument
+instances from the factory function to class template argument
 deduction (CTAD), in C++17 and higher.
 
 .. code-block:: c++

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to