[clang-tools-extra] [clang-tidy][NFC] Fix list.rst and add_new_check.py (PR #192228)

Zeyi Xu via cfe-commits cfe-commits at lists.llvm.org
Wed Apr 15 06:15:55 PDT 2026


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

>From e39476bdf63d3dd07074c587fd0248042e85f292 Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Wed, 15 Apr 2026 18:21:52 +0800
Subject: [PATCH 1/2] [clang-tidy][NFC] Fix list.rst and add_new_check.py

---
 clang-tools-extra/clang-tidy/add_new_check.py         | 11 ++++++++---
 .../docs/clang-tidy/checks/cert/err33-c.rst           |  2 ++
 clang-tools-extra/docs/clang-tidy/checks/list.rst     |  4 ++--
 3 files changed, 12 insertions(+), 5 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/add_new_check.py b/clang-tools-extra/clang-tidy/add_new_check.py
index 53a9e49f8e4f2..d5962a56b7ac8 100755
--- a/clang-tools-extra/clang-tidy/add_new_check.py
+++ b/clang-tools-extra/clang-tidy/add_new_check.py
@@ -536,10 +536,15 @@ def format_link_alias(doc_file: Tuple[str, str]) -> str:
                 ref_begin = ""
                 ref_end = "_"
             else:
-                redirect_parts = re.search(r"^\.\./([^/]*)/([^/]*)$", match.group(1))
+                # Match either "../modernize/use-nullptr" or a same-directory
+                # redirect like "prefer-single-char-overloads".
+                redirect_parts = re.search(
+                    r"^(?:\.\./([^/]+)/)?([^/]+)$", match.group(1)
+                )
                 assert redirect_parts
-                title = redirect_parts[1] + "-" + redirect_parts[2]
-                target = redirect_parts[1] + "/" + redirect_parts[2]
+                redirect_module = redirect_parts[1] or module
+                title = redirect_module + "-" + redirect_parts[2]
+                target = redirect_module + "/" + redirect_parts[2]
                 autofix = has_auto_fix(title)
                 ref_begin = ":doc:"
                 ref_end = ""
diff --git a/clang-tools-extra/docs/clang-tidy/checks/cert/err33-c.rst b/clang-tools-extra/docs/clang-tidy/checks/cert/err33-c.rst
index 75da669c0a2b3..f04a47321efad 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/cert/err33-c.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/cert/err33-c.rst
@@ -1,4 +1,6 @@
 .. title:: clang-tidy - cert-err33-c
+.. meta::
+   :http-equiv=refresh: 5;URL=../bugprone/unused-return-value.html
 
 cert-err33-c
 ============
diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst
index df869d422f1a1..c7c37e08d67ea 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -242,7 +242,6 @@ Clang-Tidy Checks
    :doc:`google-runtime-int <google/runtime-int>`,
    :doc:`google-runtime-operator <google/runtime-operator>`,
    :doc:`google-upgrade-googletest-case <google/upgrade-googletest-case>`, "Yes"
-   :doc:`hicpp-exception-baseclass <hicpp/exception-baseclass>`,
    :doc:`hicpp-multiway-paths-covered <hicpp/multiway-paths-covered>`,
    :doc:`linuxkernel-must-check-errs <linuxkernel/must-check-errs>`,
    :doc:`llvm-header-guard <llvm/header-guard>`,
@@ -352,7 +351,6 @@ Clang-Tidy Checks
    :doc:`openmp-use-default-none <openmp/use-default-none>`,
    :doc:`performance-avoid-endl <performance/avoid-endl>`, "Yes"
    :doc:`performance-enum-size <performance/enum-size>`,
-   :doc:`performance-faster-string-find <performance/faster-string-find>`, "Yes"
    :doc:`performance-for-range-copy <performance/for-range-copy>`, "Yes"
    :doc:`performance-implicit-conversion-in-loop <performance/implicit-conversion-in-loop>`,
    :doc:`performance-inefficient-algorithm <performance/inefficient-algorithm>`, "Yes"
@@ -466,6 +464,7 @@ Check aliases
    :doc:`cert-err60-cpp <cert/err60-cpp>`, :doc:`bugprone-exception-copy-constructor-throws <bugprone/exception-copy-constructor-throws>`,
    :doc:`cert-err61-cpp <cert/err61-cpp>`, :doc:`misc-throw-by-value-catch-by-reference <misc/throw-by-value-catch-by-reference>`,
    :doc:`cert-exp42-c <cert/exp42-c>`, :doc:`bugprone-suspicious-memory-comparison <bugprone/suspicious-memory-comparison>`,
+   :doc:`cert-exp45-c <cert/exp45-c>`, :doc:`bugprone-assignment-in-selection-statement <bugprone/assignment-in-selection-statement>`,
    :doc:`cert-fio38-c <cert/fio38-c>`, :doc:`misc-non-copyable-objects <misc/non-copyable-objects>`,
    :doc:`cert-flp30-c <cert/flp30-c>`, :doc:`bugprone-float-loop-counter <bugprone/float-loop-counter>`,
    :doc:`cert-flp37-c <cert/flp37-c>`, :doc:`bugprone-suspicious-memory-comparison <bugprone/suspicious-memory-comparison>`,
@@ -633,3 +632,4 @@ Check aliases
    :doc:`hicpp-vararg <hicpp/vararg>`, :doc:`cppcoreguidelines-pro-type-vararg <cppcoreguidelines/pro-type-vararg>`,
    :doc:`llvm-else-after-return <llvm/else-after-return>`, :doc:`readability-else-after-return <readability/else-after-return>`, "Yes"
    :doc:`llvm-qualified-auto <llvm/qualified-auto>`, :doc:`readability-qualified-auto <readability/qualified-auto>`, "Yes"
+   :doc:`performance-faster-string-find <performance/faster-string-find>`, :doc:`performance-prefer-single-char-overloads <performance/prefer-single-char-overloads>`, "Yes"

>From 036e5fdda0bc25a13d813057a5bd37a8d007ec9f Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Wed, 15 Apr 2026 21:15:33 +0800
Subject: [PATCH 2/2] better?

---
 clang-tools-extra/clang-tidy/add_new_check.py | 154 +++++++++++-------
 .../docs/clang-tidy/checks/cert/err33-c.rst   |   3 -
 2 files changed, 95 insertions(+), 62 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/add_new_check.py b/clang-tools-extra/clang-tidy/add_new_check.py
index d5962a56b7ac8..e40f673e753e7 100755
--- a/clang-tools-extra/clang-tidy/add_new_check.py
+++ b/clang-tools-extra/clang-tidy/add_new_check.py
@@ -494,7 +494,40 @@ def has_auto_fix(check_name: str) -> str:
 
         return ""
 
-    def process_doc(doc_file: Tuple[str, str]) -> Tuple[str, Optional[Match[str]]]:
+    def detect_alias_target(check_name: str, content: str) -> Optional[str]:
+        """Return the :doc: target for non-redirect alias pages.
+
+        This recognizes pages that keep their own documentation content, but
+        whose paragraph explicitly states that the current check is an
+        alias of another check.
+        """
+        paragraphs = [
+            re.sub(r"\s+", " ", paragraph.strip())
+            for paragraph in re.split(r"\n\s*\n", content)
+            if paragraph.strip()
+        ]
+
+        self_alias = re.compile(
+            r"^This check is an alias(?: of check| for)\b",
+            re.IGNORECASE,
+        )
+        named_alias = re.compile(
+            rf"^The\s+`?{re.escape(check_name)}(?:\s+check)?`?"
+            rf"(?:\s+check)?\s+is\s+an\s+alias,?\s+please\s+see\b",
+            re.IGNORECASE,
+        )
+
+        for paragraph in paragraphs:
+            if self_alias.search(paragraph) or named_alias.search(paragraph):
+                if match := re.search(r":doc:`[^`<]+?\s*<([^>]+)>`", paragraph):
+                    return match.group(1)
+                if match := re.search(
+                    r"`[^`<]+?\s*<(.+?)\.html(?:#[^>]+)?>`_", paragraph
+                ):
+                    return match.group(1)
+        return None
+
+    def process_doc(doc_file: Tuple[str, str]) -> Tuple[str, Optional[str]]:
         check_name = doc_file[0] + "-" + doc_file[1].replace(".rst", "")
 
         with open(os.path.join(docs_dir, *doc_file), "r", encoding="utf8") as doc:
@@ -504,9 +537,7 @@ def process_doc(doc_file: Tuple[str, str]) -> Tuple[str, Optional[Match[str]]]:
                 # Orphan page, don't list it.
                 return "", None
 
-            match = re.search(r".*:http-equiv=refresh: \d+;URL=(.*).html(.*)", content)
-            # Is it a redirect?
-            return check_name, match
+            return check_name, detect_alias_target(check_name, content)
 
     def format_link(doc_file: Tuple[str, str]) -> str:
         check_name, match = process_doc(doc_file)
@@ -522,61 +553,66 @@ def format_link(doc_file: Tuple[str, str]) -> str:
 
     def format_link_alias(doc_file: Tuple[str, str]) -> str:
         check_name, match = process_doc(doc_file)
-        if (match or (check_name.startswith("clang-analyzer-"))) and check_name:
-            module = doc_file[0]
-            check_file = doc_file[1].replace(".rst", "")
-            if (
-                not match
-                or match.group(1) == "https://clang.llvm.org/docs/analyzer/checkers"
-            ):
-                title = "Clang Static Analyzer " + check_file
-                # Preserve the anchor in checkers.html from group 2.
-                target = "" if not match else match.group(1) + ".html" + match.group(2)
-                autofix = ""
-                ref_begin = ""
-                ref_end = "_"
-            else:
-                # Match either "../modernize/use-nullptr" or a same-directory
-                # redirect like "prefer-single-char-overloads".
-                redirect_parts = re.search(
-                    r"^(?:\.\./([^/]+)/)?([^/]+)$", match.group(1)
-                )
-                assert redirect_parts
-                redirect_module = redirect_parts[1] or module
-                title = redirect_module + "-" + redirect_parts[2]
-                target = redirect_module + "/" + redirect_parts[2]
-                autofix = has_auto_fix(title)
-                ref_begin = ":doc:"
-                ref_end = ""
-
-            if target:
-                # The checker is just a redirect.
-                return (
-                    "   :doc:`%(check_name)s <%(module)s/%(check_file)s>`, %(ref_begin)s`%(title)s <%(target)s>`%(ref_end)s,%(autofix)s\n"
-                    % {
-                        "check_name": check_name,
-                        "module": module,
-                        "check_file": check_file,
-                        "target": target,
-                        "title": title,
-                        "autofix": autofix,
-                        "ref_begin": ref_begin,
-                        "ref_end": ref_end,
-                    }
-                )
-            else:
-                # The checker is just a alias without redirect.
-                return (
-                    "   :doc:`%(check_name)s <%(module)s/%(check_file)s>`, %(title)s,%(autofix)s\n"
-                    % {
-                        "check_name": check_name,
-                        "module": module,
-                        "check_file": check_file,
-                        "title": title,
-                        "autofix": autofix,
-                    }
-                )
-        return ""
+        is_clang_analyzer = check_name.startswith("clang-analyzer-")
+        if not check_name or (not match and not is_clang_analyzer):
+            return ""
+
+        module = doc_file[0]
+        check_file = doc_file[1].replace(".rst", "")
+        if is_clang_analyzer:
+            title = "Clang Static Analyzer " + check_file
+            # Clang Static Analyzer aliases still need the external redirect
+            # target so list.rst can link to the upstream analyzer docs.
+            with open(os.path.join(docs_dir, *doc_file), "r", encoding="utf8") as doc:
+                content = doc.read()
+            redirect = re.search(
+                r".*:http-equiv=refresh: \d+;URL=(.*).html(.*)", content
+            )
+            # Preserve the anchor in checkers.html from group 2.
+            target = (
+                "" if not redirect else redirect.group(1) + ".html" + redirect.group(2)
+            )
+            autofix = ""
+            ref_begin = ""
+            ref_end = "_"
+        else:
+            # Match neighbour or current-directory doc targets.
+            redirect_parts = re.search(r"^(?:\.\./([^/]+)/)?([^/]+)$", match)
+            assert redirect_parts
+            redirect_module = redirect_parts[1] or module
+            title = redirect_module + "-" + redirect_parts[2]
+            target = redirect_module + "/" + redirect_parts[2]
+            autofix = has_auto_fix(title)
+            ref_begin = ":doc:"
+            ref_end = ""
+
+        if target:
+            # The checker is just a redirect.
+            return (
+                "   :doc:`%(check_name)s <%(module)s/%(check_file)s>`, %(ref_begin)s`%(title)s <%(target)s>`%(ref_end)s,%(autofix)s\n"
+                % {
+                    "check_name": check_name,
+                    "module": module,
+                    "check_file": check_file,
+                    "target": target,
+                    "title": title,
+                    "autofix": autofix,
+                    "ref_begin": ref_begin,
+                    "ref_end": ref_end,
+                }
+            )
+
+        # The checker is just a alias without redirect.
+        return (
+            "   :doc:`%(check_name)s <%(module)s/%(check_file)s>`, %(title)s,%(autofix)s\n"
+            % {
+                "check_name": check_name,
+                "module": module,
+                "check_file": check_file,
+                "title": title,
+                "autofix": autofix,
+            }
+        )
 
     print("Updating %s..." % filename)
     with open(filename, "w", encoding="utf8", newline="\n") as f:
diff --git a/clang-tools-extra/docs/clang-tidy/checks/cert/err33-c.rst b/clang-tools-extra/docs/clang-tidy/checks/cert/err33-c.rst
index f04a47321efad..adfddc5c5838d 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/cert/err33-c.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/cert/err33-c.rst
@@ -1,7 +1,4 @@
 .. title:: clang-tidy - cert-err33-c
-.. meta::
-   :http-equiv=refresh: 5;URL=../bugprone/unused-return-value.html
-
 cert-err33-c
 ============
 



More information about the cfe-commits mailing list