[clang-tools-extra] [clang-tidy] Header check support for check_clang_tidy.py (PR #175735)
via cfe-commits
cfe-commits at lists.llvm.org
Fri Feb 13 07:44:00 PST 2026
https://github.com/zeyi2 updated https://github.com/llvm/llvm-project/pull/175735
>From 85c183395f01fd25bb299163735e4518d396dbf1 Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Thu, 25 Dec 2025 14:31:27 +0800
Subject: [PATCH 01/10] [clang-tidy] Header check support for
check_clang_tidy.py
---
.../test/clang-tidy/check_clang_tidy.py | 189 +++++++++++++++---
.../modernize-concat-nested-namespaces.h | 7 +-
.../Inputs/pass-by-value/header-with-fix.h | 1 +
.../modernize/Inputs/pass-by-value/header.h | 3 +
.../modernize/concat-nested-namespaces.cpp | 11 +-
.../modernize/pass-by-value-header.cpp | 14 +-
.../modernize/pass-by-value-multi-fixes.cpp | 15 +-
.../Inputs/unnecessary-value-param/header.h | 2 +
.../unnecessary-value-param-header.cpp | 9 +-
.../readability/duplicate-include.cpp | 5 +-
10 files changed, 189 insertions(+), 67 deletions(-)
diff --git a/clang-tools-extra/test/clang-tidy/check_clang_tidy.py b/clang-tools-extra/test/clang-tidy/check_clang_tidy.py
index b173ecf4fbdca..76899861c3a7c 100755
--- a/clang-tools-extra/test/clang-tidy/check_clang_tidy.py
+++ b/clang-tools-extra/test/clang-tidy/check_clang_tidy.py
@@ -49,7 +49,7 @@
import re
import subprocess
import sys
-from typing import List, Tuple
+from typing import Dict, List, Sequence, Tuple
def write_file(file_name: str, text: str) -> None:
@@ -93,6 +93,7 @@ def __init__(self, args: argparse.Namespace, extra_args: List[str]) -> None:
self.input_file_name = args.input_file_name
self.check_name = args.check_name
self.temp_file_name = args.temp_file_name
+ self.check_headers = args.check_headers
self.original_file_name = self.temp_file_name + ".orig"
self.expect_clang_tidy_error = args.expect_clang_tidy_error
self.std = args.std
@@ -121,6 +122,18 @@ def __init__(self, args: argparse.Namespace, extra_args: List[str]) -> None:
self.clang_extra_args = self.clang_tidy_extra_args[i + 1 :]
self.clang_tidy_extra_args = self.clang_tidy_extra_args[:i]
+ self.check_header_map: Dict[str, str] = {}
+ self.header_dir: str = self.temp_file_name + ".headers"
+ if self.check_headers:
+ self.check_header_map = {
+ os.path.abspath(
+ os.path.join(self.header_dir, os.path.basename(h))
+ ): os.path.abspath(h)
+ for h in self.check_headers
+ }
+
+ self.clang_extra_args.insert(0, "-I" + self.header_dir)
+
# If the test does not specify a config style, force an empty one; otherwise
# auto-detection logic can discover a ".clang-tidy" file that is not related to
# the test.
@@ -196,15 +209,36 @@ def get_prefixes(self) -> None:
)
assert expect_diagnosis or self.expect_no_diagnosis
+ def _sanitize_content(self, text: str) -> str:
+ return re.sub("// *CHECK-[A-Z0-9\\-]*:[^\r\n]*", "//", text)
+
+ def _filter_prefixes(self, prefixes: Sequence[str], check_file: str) -> List[str]:
+ if check_file == self.input_file_name:
+ content = self.input_text
+ else:
+ with open(check_file, "r", encoding="utf-8") as f:
+ content = f.read()
+ return [p for p in prefixes if p in content]
+
def prepare_test_inputs(self) -> None:
# Remove the contents of the CHECK lines to avoid CHECKs matching on
# themselves. We need to keep the comments to preserve line numbers while
# avoiding empty lines which could potentially trigger formatting-related
# checks.
- cleaned_test = re.sub("// *CHECK-[A-Z0-9\\-]*:[^\r\n]*", "//", self.input_text)
+ cleaned_test = self._sanitize_content(self.input_text)
write_file(self.temp_file_name, cleaned_test)
write_file(self.original_file_name, cleaned_test)
+ if self.check_headers:
+ os.makedirs(self.header_dir, exist_ok=True)
+
+ for temp_header_path, header in self.check_header_map.items():
+ with open(header, "r", encoding="utf-8") as f:
+ cleaned_header = self._sanitize_content(f.read())
+
+ write_file(temp_header_path, cleaned_header)
+ write_file(temp_header_path + ".orig", cleaned_header)
+
def run_clang_tidy(self) -> str:
args = (
[
@@ -241,6 +275,18 @@ def run_clang_tidy(self) -> str:
diff_output = try_run(
["diff", "-u", self.original_file_name, self.temp_file_name], False
)
+ if self.check_headers:
+ for temp_header_path in self.check_header_map:
+ diff_output += try_run(
+ [
+ "diff",
+ "-u",
+ temp_header_path + ".orig",
+ temp_header_path,
+ ],
+ False,
+ )
+
print("------------------------------ Fixes -----------------------------")
print(diff_output)
print("------------------------------------------------------------------")
@@ -250,35 +296,57 @@ def check_no_diagnosis(self, clang_tidy_output: str) -> None:
if clang_tidy_output != "":
sys.exit("No diagnostics were expected, but found the ones above")
- def check_fixes(self) -> None:
- if self.has_check_fixes:
- try_run(
- [
- "FileCheck",
- "--input-file=" + self.temp_file_name,
- self.input_file_name,
- "--check-prefixes=" + ",".join(self.fixes.prefixes),
- (
- "--match-full-lines"
- if not self.match_partial_fixes
- else "--strict-whitespace" # Keeping past behavior.
- ),
- ]
- )
+ def check_fixes(self, input_file: str = "", check_file: str = "") -> None:
+ if not check_file and not self.has_check_fixes:
+ return
- def check_messages(self, clang_tidy_output: str) -> None:
- if self.has_check_messages:
- messages_file = self.temp_file_name + ".msg"
- write_file(messages_file, clang_tidy_output)
- try_run(
- [
- "FileCheck",
- "-input-file=" + messages_file,
- self.input_file_name,
- "-check-prefixes=" + ",".join(self.messages.prefixes),
- "-implicit-check-not={{warning|error}}:",
- ]
- )
+ input_file = input_file or self.temp_file_name
+ check_file = check_file or self.input_file_name
+ active_prefixes = self._filter_prefixes(self.fixes.prefixes, check_file)
+
+ if not active_prefixes:
+ return
+
+ try_run(
+ [
+ "FileCheck",
+ "--input-file=" + input_file,
+ check_file,
+ "--check-prefixes=" + ",".join(active_prefixes),
+ (
+ "--match-full-lines"
+ if not self.match_partial_fixes
+ else "--strict-whitespace" # Keeping past behavior.
+ ),
+ ]
+ )
+
+ def check_messages(
+ self,
+ clang_tidy_output: str,
+ messages_file: str = "",
+ check_file: str = "",
+ ) -> None:
+ if not check_file and not self.has_check_messages:
+ return
+
+ messages_file = messages_file or (self.temp_file_name + ".msg")
+ check_file = check_file or self.input_file_name
+
+ active_prefixes = self._filter_prefixes(self.messages.prefixes, check_file)
+ if not active_prefixes:
+ return
+
+ write_file(messages_file, clang_tidy_output)
+ try_run(
+ [
+ "FileCheck",
+ "-input-file=" + messages_file,
+ check_file,
+ "-check-prefixes=" + ",".join(active_prefixes),
+ "-implicit-check-not={{warning|error}}:",
+ ]
+ )
def check_notes(self, clang_tidy_output: str) -> None:
if self.has_check_notes:
@@ -299,12 +367,60 @@ def check_notes(self, clang_tidy_output: str) -> None:
]
)
+ def check_header_messages(self, clang_tidy_output: str) -> str:
+ """
+ Filters and verifies clang-tidy diagnostics for headers.
+
+ - Input: The raw diagnostic output from clang-tidy.
+ - Output: The diagnostic output intended for the main file
+ verification.
+
+ This function separates messages belonging to headers specified by
+ `-check-header', verifies them using FileCheck against the header's
+ own rules, and returns the rest for further processing.
+ """
+ if not self.check_headers:
+ return clang_tidy_output
+
+ header_messages: Dict[str, List[str]] = {
+ t: [] for t in self.check_header_map.keys()
+ }
+ remaining_lines: List[str] = []
+ current_file: str = ""
+
+ for line in clang_tidy_output.splitlines(keepends=True):
+ if re.match(r"^\d+ warnings? generated\.", line):
+ continue
+ # Matches the beginning of a clang-tidy diagnostic line,
+ # which starts with "file_path:line:col: ".
+ match = re.match(r"^([^:]+):\d+:\d+: ", line)
+ if match:
+ abs_path = os.path.abspath(match.group(1))
+ current_file = abs_path if abs_path in header_messages else ""
+
+ header_messages.get(current_file, remaining_lines).append(line)
+
+ for temp_header, messages_list in header_messages.items():
+ original_header = self.check_header_map[temp_header]
+ messages = "".join(messages_list)
+ if not messages:
+ continue
+
+ self.check_messages(
+ messages,
+ messages_file=temp_header + ".msg",
+ check_file=original_header,
+ )
+
+ return "".join(remaining_lines)
+
def run(self) -> None:
self.read_input()
if self.export_fixes is None:
self.get_prefixes()
self.prepare_test_inputs()
clang_tidy_output = self.run_clang_tidy()
+ clang_tidy_output = self.check_header_messages(clang_tidy_output)
if self.expect_no_diagnosis:
self.check_no_diagnosis(clang_tidy_output)
elif self.export_fixes is None:
@@ -312,6 +428,12 @@ def run(self) -> None:
self.check_messages(clang_tidy_output)
self.check_notes(clang_tidy_output)
+ for temp_header, original_header in self.check_header_map.items():
+ self.check_fixes(
+ input_file=temp_header,
+ check_file=original_header,
+ )
+
CPP_STANDARDS = [
"c++98",
@@ -370,6 +492,13 @@ def parse_arguments() -> Tuple[argparse.Namespace, List[str]]:
type=csv,
help="comma-separated list of FileCheck suffixes",
)
+ parser.add_argument(
+ "-check-header",
+ action="append",
+ dest="check_headers",
+ default=[],
+ help="Header files to check",
+ )
parser.add_argument(
"-export-fixes",
default=None,
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/Inputs/concat-nested-namespaces/modernize-concat-nested-namespaces.h b/clang-tools-extra/test/clang-tidy/checkers/modernize/Inputs/concat-nested-namespaces/modernize-concat-nested-namespaces.h
index 703d7e05ca2e9..0d748a81c0067 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/modernize/Inputs/concat-nested-namespaces/modernize-concat-nested-namespaces.h
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/Inputs/concat-nested-namespaces/modernize-concat-nested-namespaces.h
@@ -1,8 +1,9 @@
+// CHECK-MESSAGES-NORMAL: :[[@LINE+1]]:1: warning: nested namespaces can be concatenated [modernize-concat-nested-namespaces]
namespace nn1 {
namespace nn2 {
-// CHECK-FIXES: namespace nn1::nn2
+// CHECK-FIXES-NORMAL: namespace nn1::nn2 {
void t();
} // namespace nn2
} // namespace nn1
-// CHECK-FIXES: void t();
-// CHECK-FIXES-NEXT: } // namespace nn1::nn2
+// CHECK-FIXES-NORMAL: void t();
+// CHECK-FIXES-NORMAL-NEXT: } // namespace nn1::nn2
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/Inputs/pass-by-value/header-with-fix.h b/clang-tools-extra/test/clang-tidy/checkers/modernize/Inputs/pass-by-value/header-with-fix.h
index 62609e25f682a..98512f411fe61 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/modernize/Inputs/pass-by-value/header-with-fix.h
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/Inputs/pass-by-value/header-with-fix.h
@@ -4,5 +4,6 @@ struct S {
};
struct Foo {
Foo(const S &s);
+ // CHECK-FIXES: Foo(S s);
S s;
};
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/Inputs/pass-by-value/header.h b/clang-tools-extra/test/clang-tidy/checkers/modernize/Inputs/pass-by-value/header.h
index c2103cb3fc7a1..e27104e70ac0b 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/modernize/Inputs/pass-by-value/header.h
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/Inputs/pass-by-value/header.h
@@ -6,5 +6,8 @@ class ThreadId {
struct A {
A(const ThreadId &tid) : threadid(tid) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: pass by value and use std::move [modernize-pass-by-value]
+ // CHECK-FIXES: #include <utility>
+ // CHECK-FIXES: A(ThreadId tid) : threadid(std::move(tid)) {}
ThreadId threadid;
};
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/concat-nested-namespaces.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/concat-nested-namespaces.cpp
index 35cb5503ba245..58ae66d0b389f 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/modernize/concat-nested-namespaces.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/concat-nested-namespaces.cpp
@@ -1,14 +1,7 @@
-// RUN: mkdir -p %t.dir
-// RUN: cp %S/Inputs/concat-nested-namespaces/modernize-concat-nested-namespaces.h %t.dir/modernize-concat-nested-namespaces.h
-// RUN: %check_clang_tidy -std=c++17 -check-suffix=NORMAL %s modernize-concat-nested-namespaces %t.dir/code -- -header-filter=".*" -- -I %t.dir
-// RUN: FileCheck -input-file=%t.dir/modernize-concat-nested-namespaces.h %S/Inputs/concat-nested-namespaces/modernize-concat-nested-namespaces.h -check-prefix=CHECK-FIXES
-// Restore header file and re-run with c++20:
-// RUN: cp %S/Inputs/concat-nested-namespaces/modernize-concat-nested-namespaces.h %t.dir/modernize-concat-nested-namespaces.h
-// RUN: %check_clang_tidy -std=c++20 -check-suffixes=NORMAL,CPP20 %s modernize-concat-nested-namespaces %t.dir/code -- -header-filter=".*" -- -I %t.dir
-// RUN: FileCheck -input-file=%t.dir/modernize-concat-nested-namespaces.h %S/Inputs/concat-nested-namespaces/modernize-concat-nested-namespaces.h -check-prefix=CHECK-FIXES
+// RUN: %check_clang_tidy -std=c++17 -check-suffix=NORMAL -check-header %S/Inputs/concat-nested-namespaces/modernize-concat-nested-namespaces.h %s modernize-concat-nested-namespaces %t -- -header-filter=".*" -- -I %S/Inputs/concat-nested-namespaces
+// RUN: %check_clang_tidy -std=c++20 -check-suffixes=NORMAL,CPP20 -check-header %S/Inputs/concat-nested-namespaces/modernize-concat-nested-namespaces.h %s modernize-concat-nested-namespaces %t -- -header-filter=".*" -- -I %S/Inputs/concat-nested-namespaces
#include "modernize-concat-nested-namespaces.h"
-// CHECK-MESSAGES-NORMAL-DAG: modernize-concat-nested-namespaces.h:1:1: warning: nested namespaces can be concatenated [modernize-concat-nested-namespaces]
namespace n1 {}
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/pass-by-value-header.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/pass-by-value-header.cpp
index 461a6378d99c0..79165e51f9e14 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/modernize/pass-by-value-header.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/pass-by-value-header.cpp
@@ -1,10 +1,6 @@
-// RUN: mkdir -p %t.dir
-// RUN: cp %S/Inputs/pass-by-value/header.h %t.dir/pass-by-value-header.h
-// RUN: clang-tidy %s -checks='-*,modernize-pass-by-value' -header-filter='.*' -fix -- -std=c++11 -I %t.dir | FileCheck %s -check-prefix=CHECK-MESSAGES -implicit-check-not="{{warning|error}}:"
-// RUN: FileCheck -input-file=%t.dir/pass-by-value-header.h %s -check-prefix=CHECK-FIXES
-// FIXME: Make the test work in all language modes.
+// RUN: %check_clang_tidy -check-header %S/Inputs/pass-by-value/header.h \
+// RUN: %s modernize-pass-by-value %t -- -header-filter='.*' \
+// RUN: -- -I %S/Inputs/pass-by-value -std=c++11
-#include "pass-by-value-header.h"
-// CHECK-MESSAGES: :8:5: warning: pass by value and use std::move [modernize-pass-by-value]
-// CHECK-FIXES: #include <utility>
-// CHECK-FIXES: A(ThreadId tid) : threadid(std::move(tid)) {}
+#include "header.h"
+// FIXME: Make the test work in all language modes.
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/pass-by-value-multi-fixes.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/pass-by-value-multi-fixes.cpp
index b77c74be02f5f..c5e6e0be30b8f 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/modernize/pass-by-value-multi-fixes.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/pass-by-value-multi-fixes.cpp
@@ -1,13 +1,10 @@
-// RUN: mkdir -p %t.dir
-// RUN: cat %S/Inputs/pass-by-value/header-with-fix.h > %t.dir/pass-by-value-header-with-fix.h
-// RUN: sed -e 's#//.*$##' %s > %t.dir/code.cpp
-// RUN: clang-tidy %t.dir/code.cpp -checks='-*,modernize-pass-by-value' -header-filter='.*' -fix -- -std=c++11 -I %t.dir | FileCheck %s -check-prefix=CHECK-MESSAGES -implicit-check-not="{{warning|error}}:"
-// RUN: FileCheck -input-file=%t.dir/code.cpp %s -check-prefix=CHECK-FIXES
-// RUN: FileCheck -input-file=%t.dir/pass-by-value-header-with-fix.h %s -check-prefix=CHECK-HEADER-FIXES
+// RUN: %check_clang_tidy -check-header %S/Inputs/pass-by-value/header-with-fix.h \
+// RUN: %s modernize-pass-by-value %t -- -header-filter='.*' \
+// RUN: -- -I %S/Inputs/pass-by-value -std=c++11
-#include "pass-by-value-header-with-fix.h"
-// CHECK-HEADER-FIXES: Foo(S s);
+#include "header-with-fix.h"
+
+// CHECK-MESSAGES: :[[@LINE+1]]:10: warning: pass by value and use std::move [modernize-pass-by-value]
Foo::Foo(const S &s) : s(s) {}
-// CHECK-MESSAGES: :10:10: warning: pass by value and use std::move [modernize-pass-by-value]
// CHECK-FIXES: #include <utility>
// CHECK-FIXES: Foo::Foo(S s) : s(std::move(s)) {}
diff --git a/clang-tools-extra/test/clang-tidy/checkers/performance/Inputs/unnecessary-value-param/header.h b/clang-tools-extra/test/clang-tidy/checkers/performance/Inputs/unnecessary-value-param/header.h
index d6f6e65ace79d..32b3d3374bc59 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/performance/Inputs/unnecessary-value-param/header.h
+++ b/clang-tools-extra/test/clang-tidy/checkers/performance/Inputs/unnecessary-value-param/header.h
@@ -7,9 +7,11 @@ struct ABC {
int f1(int n, ABC v1, ABC v2); // line 9
+// CHECK-FIXES: int f1(int n, const ABC& v1, const ABC& v2); // line 9
int f1(int n, ABC v1); // line 11
void f2( int n, ABC v2); // line 15
+// CHECK-FIXES: void f2( int n, const ABC& v2); // line 15
diff --git a/clang-tools-extra/test/clang-tidy/checkers/performance/unnecessary-value-param-header.cpp b/clang-tools-extra/test/clang-tidy/checkers/performance/unnecessary-value-param-header.cpp
index a7fc1eace67c5..ded66392e4af5 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/performance/unnecessary-value-param-header.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/performance/unnecessary-value-param-header.cpp
@@ -1,8 +1,7 @@
-// RUN: rm -rf %t
-// RUN: mkdir %t
-// RUN: cp %S/Inputs/unnecessary-value-param/header.h %t/header.h
-// RUN: %check_clang_tidy %s performance-unnecessary-value-param %t/temp -- -- -I %t
-// RUN: diff %t/header.h %S/Inputs/unnecessary-value-param/header-fixed.h
+// RUN: %check_clang_tidy -check-header %S/Inputs/unnecessary-value-param/header.h \
+// RUN: %s performance-unnecessary-value-param %t -- \
+// RUN: -header-filter='.*' \
+// RUN: -- -I %S/Inputs/unnecessary-value-param
#include "header.h"
diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability/duplicate-include.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability/duplicate-include.cpp
index c452f69fad07d..8483e895bb635 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/readability/duplicate-include.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/readability/duplicate-include.cpp
@@ -1,5 +1,6 @@
-// RUN: %check_clang_tidy %s readability-duplicate-include %t -- \
-// RUN: -header-filter='' \
+// RUN: %check_clang_tidy -check-header %S/Inputs/duplicate-include/duplicate-include.h \
+// RUN: %s readability-duplicate-include %t -- \
+// RUN: -header-filter='.*' \
// RUN: -- -isystem %S/Inputs/duplicate-include/system -I %S/Inputs/duplicate-include
int a;
>From 5d93fbb08b5768841fe1f3924c06be0e0419043b Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Tue, 13 Jan 2026 18:51:18 +0800
Subject: [PATCH 02/10] Try to make Windows FS happy, not sure whether it will
work :(
---
clang-tools-extra/test/clang-tidy/check_clang_tidy.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/clang-tools-extra/test/clang-tidy/check_clang_tidy.py b/clang-tools-extra/test/clang-tidy/check_clang_tidy.py
index 76899861c3a7c..1dcb47ec38603 100755
--- a/clang-tools-extra/test/clang-tidy/check_clang_tidy.py
+++ b/clang-tools-extra/test/clang-tidy/check_clang_tidy.py
@@ -126,8 +126,8 @@ def __init__(self, args: argparse.Namespace, extra_args: List[str]) -> None:
self.header_dir: str = self.temp_file_name + ".headers"
if self.check_headers:
self.check_header_map = {
- os.path.abspath(
- os.path.join(self.header_dir, os.path.basename(h))
+ os.path.normcase(
+ os.path.abspath(os.path.join(self.header_dir, os.path.basename(h)))
): os.path.abspath(h)
for h in self.check_headers
}
@@ -395,7 +395,7 @@ def check_header_messages(self, clang_tidy_output: str) -> str:
# which starts with "file_path:line:col: ".
match = re.match(r"^([^:]+):\d+:\d+: ", line)
if match:
- abs_path = os.path.abspath(match.group(1))
+ abs_path = os.path.normcase(os.path.abspath(match.group(1)))
current_file = abs_path if abs_path in header_messages else ""
header_messages.get(current_file, remaining_lines).append(line)
>From 5b9cb8e54a0afcbb1d0f8f192fa6301853805be1 Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Tue, 13 Jan 2026 19:08:04 +0800
Subject: [PATCH 03/10] Let's try backtracking..
---
clang-tools-extra/test/clang-tidy/check_clang_tidy.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang-tools-extra/test/clang-tidy/check_clang_tidy.py b/clang-tools-extra/test/clang-tidy/check_clang_tidy.py
index 1dcb47ec38603..2bbf04bbc143a 100755
--- a/clang-tools-extra/test/clang-tidy/check_clang_tidy.py
+++ b/clang-tools-extra/test/clang-tidy/check_clang_tidy.py
@@ -393,7 +393,7 @@ def check_header_messages(self, clang_tidy_output: str) -> str:
continue
# Matches the beginning of a clang-tidy diagnostic line,
# which starts with "file_path:line:col: ".
- match = re.match(r"^([^:]+):\d+:\d+: ", line)
+ match = re.match(r"^(.+):\d+:\d+: ", line)
if match:
abs_path = os.path.normcase(os.path.abspath(match.group(1)))
current_file = abs_path if abs_path in header_messages else ""
>From 298460d591b5d43d14a45e9496cfee492fbaf580 Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Fri, 16 Jan 2026 19:09:06 +0800
Subject: [PATCH 04/10] ~
---
.../test/clang-tidy/check_clang_tidy.py | 27 ++++++++++---------
1 file changed, 14 insertions(+), 13 deletions(-)
diff --git a/clang-tools-extra/test/clang-tidy/check_clang_tidy.py b/clang-tools-extra/test/clang-tidy/check_clang_tidy.py
index 2bbf04bbc143a..b0797cce8da60 100755
--- a/clang-tools-extra/test/clang-tidy/check_clang_tidy.py
+++ b/clang-tools-extra/test/clang-tidy/check_clang_tidy.py
@@ -49,6 +49,7 @@
import re
import subprocess
import sys
+from collections import defaultdict
from typing import Dict, List, Sequence, Tuple
@@ -123,7 +124,7 @@ def __init__(self, args: argparse.Namespace, extra_args: List[str]) -> None:
self.clang_tidy_extra_args = self.clang_tidy_extra_args[:i]
self.check_header_map: Dict[str, str] = {}
- self.header_dir: str = self.temp_file_name + ".headers"
+ self.header_dir = f"{self.temp_file_name}.headers"
if self.check_headers:
self.check_header_map = {
os.path.normcase(
@@ -314,9 +315,9 @@ def check_fixes(self, input_file: str = "", check_file: str = "") -> None:
check_file,
"--check-prefixes=" + ",".join(active_prefixes),
(
- "--match-full-lines"
- if not self.match_partial_fixes
- else "--strict-whitespace" # Keeping past behavior.
+ "--strict-whitespace" # Keeping past behavior.
+ if self.match_partial_fixes
+ else "--match-full-lines"
),
]
)
@@ -330,7 +331,7 @@ def check_messages(
if not check_file and not self.has_check_messages:
return
- messages_file = messages_file or (self.temp_file_name + ".msg")
+ messages_file = messages_file or f"{self.temp_file_name}.msg"
check_file = check_file or self.input_file_name
active_prefixes = self._filter_prefixes(self.messages.prefixes, check_file)
@@ -382,9 +383,7 @@ def check_header_messages(self, clang_tidy_output: str) -> str:
if not self.check_headers:
return clang_tidy_output
- header_messages: Dict[str, List[str]] = {
- t: [] for t in self.check_header_map.keys()
- }
+ header_messages = defaultdict(list)
remaining_lines: List[str] = []
current_file: str = ""
@@ -396,13 +395,15 @@ def check_header_messages(self, clang_tidy_output: str) -> str:
match = re.match(r"^(.+):\d+:\d+: ", line)
if match:
abs_path = os.path.normcase(os.path.abspath(match.group(1)))
- current_file = abs_path if abs_path in header_messages else ""
+ current_file = abs_path if abs_path in self.check_header_map else ""
- header_messages.get(current_file, remaining_lines).append(line)
+ dest_list = (
+ header_messages[current_file] if current_file else remaining_lines
+ )
+ dest_list.append(line)
- for temp_header, messages_list in header_messages.items():
- original_header = self.check_header_map[temp_header]
- messages = "".join(messages_list)
+ for temp_header, original_header in self.check_header_map.items():
+ messages = "".join(header_messages[temp_header])
if not messages:
continue
>From 7930f0455c9f526cbfd4befd0b7dd7aace4a834a Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Tue, 20 Jan 2026 20:20:50 +0800
Subject: [PATCH 05/10] Address review feedback
---
.../test/clang-tidy/check_clang_tidy.py | 44 ++++++++-----------
1 file changed, 18 insertions(+), 26 deletions(-)
diff --git a/clang-tools-extra/test/clang-tidy/check_clang_tidy.py b/clang-tools-extra/test/clang-tidy/check_clang_tidy.py
index b0797cce8da60..4ffce5a5463e3 100755
--- a/clang-tools-extra/test/clang-tidy/check_clang_tidy.py
+++ b/clang-tools-extra/test/clang-tidy/check_clang_tidy.py
@@ -95,7 +95,7 @@ def __init__(self, args: argparse.Namespace, extra_args: List[str]) -> None:
self.check_name = args.check_name
self.temp_file_name = args.temp_file_name
self.check_headers = args.check_headers
- self.original_file_name = self.temp_file_name + ".orig"
+ self.original_file_name = f"{self.temp_file_name}.orig"
self.expect_clang_tidy_error = args.expect_clang_tidy_error
self.std = args.std
self.check_suffix = args.check_suffix
@@ -133,7 +133,7 @@ def __init__(self, args: argparse.Namespace, extra_args: List[str]) -> None:
for h in self.check_headers
}
- self.clang_extra_args.insert(0, "-I" + self.header_dir)
+ self.clang_extra_args.insert(0, f"-I{self.header_dir}")
# If the test does not specify a config style, force an empty one; otherwise
# auto-detection logic can discover a ".clang-tidy" file that is not related to
@@ -150,7 +150,7 @@ def __init__(self, args: argparse.Namespace, extra_args: List[str]) -> None:
"-fblocks",
] + self.clang_extra_args
- self.clang_extra_args.append("-std=" + self.std)
+ self.clang_extra_args.append(f"-std={self.std}")
# Tests should not rely on STL being available, and instead provide mock
# implementations of relevant APIs.
@@ -172,8 +172,8 @@ def get_prefixes(self) -> None:
for suffix in self.check_suffix:
if suffix and not re.match("^[A-Z0-9\\-]+$", suffix):
sys.exit(
- 'Only A..Z, 0..9 and "-" are allowed in check suffixes list,'
- + ' but "%s" was given' % suffix
+ 'Only A..Z, 0..9 and "-" are allowed in check suffixes list, '
+ f'but "{suffix}" was given'
)
file_check_suffix = ("-" + suffix) if suffix else ""
@@ -238,7 +238,7 @@ def prepare_test_inputs(self) -> None:
cleaned_header = self._sanitize_content(f.read())
write_file(temp_header_path, cleaned_header)
- write_file(temp_header_path + ".orig", cleaned_header)
+ write_file(f"{temp_header_path}.orig", cleaned_header)
def run_clang_tidy(self) -> str:
args = (
@@ -251,11 +251,11 @@ def run_clang_tidy(self) -> str:
(
"-fix"
if self.export_fixes is None
- else "--export-fixes=" + self.export_fixes
+ else f"--export-fixes={self.export_fixes}"
)
]
+ [
- "--checks=-*," + self.check_name,
+ f"--checks=-*,{self.check_name}",
]
+ self.clang_tidy_extra_args
+ ["--"]
@@ -263,7 +263,7 @@ def run_clang_tidy(self) -> str:
)
if self.expect_clang_tidy_error:
args.insert(0, "not")
- print("Running " + repr(args) + "...")
+ print(f"Running {repr(args)}...")
clang_tidy_output = try_run(args)
print("------------------------ clang-tidy output -----------------------")
print(
@@ -279,13 +279,7 @@ def run_clang_tidy(self) -> str:
if self.check_headers:
for temp_header_path in self.check_header_map:
diff_output += try_run(
- [
- "diff",
- "-u",
- temp_header_path + ".orig",
- temp_header_path,
- ],
- False,
+ ["diff", "-u", f"{temp_header_path}.orig", temp_header_path], False
)
print("------------------------------ Fixes -----------------------------")
@@ -311,9 +305,9 @@ def check_fixes(self, input_file: str = "", check_file: str = "") -> None:
try_run(
[
"FileCheck",
- "--input-file=" + input_file,
+ f"--input-file={input_file}",
check_file,
- "--check-prefixes=" + ",".join(active_prefixes),
+ f"--check-prefixes={','.join(active_prefixes)}",
(
"--strict-whitespace" # Keeping past behavior.
if self.match_partial_fixes
@@ -342,16 +336,16 @@ def check_messages(
try_run(
[
"FileCheck",
- "-input-file=" + messages_file,
+ f"-input-file={messages_file}",
check_file,
- "-check-prefixes=" + ",".join(active_prefixes),
+ f"-check-prefixes={','.join(active_prefixes)}",
"-implicit-check-not={{warning|error}}:",
]
)
def check_notes(self, clang_tidy_output: str) -> None:
if self.has_check_notes:
- notes_file = self.temp_file_name + ".notes"
+ notes_file = f"{self.temp_file_name}.notes"
filtered_output = [
line
for line in clang_tidy_output.splitlines()
@@ -361,9 +355,9 @@ def check_notes(self, clang_tidy_output: str) -> None:
try_run(
[
"FileCheck",
- "-input-file=" + notes_file,
+ f"-input-file={notes_file}",
self.input_file_name,
- "-check-prefixes=" + ",".join(self.notes.prefixes),
+ f"-check-prefixes={','.join(self.notes.prefixes)}",
"-implicit-check-not={{note|warning|error}}:",
]
)
@@ -408,9 +402,7 @@ def check_header_messages(self, clang_tidy_output: str) -> str:
continue
self.check_messages(
- messages,
- messages_file=temp_header + ".msg",
- check_file=original_header,
+ messages, messages_file=f"{temp_header}.msg", check_file=original_header
)
return "".join(remaining_lines)
>From e82f5a9e7d76665a1a0af8eb3ea3219092f5c9a8 Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Wed, 21 Jan 2026 12:57:44 +0800
Subject: [PATCH 06/10] Address review feedback
---
clang-tools-extra/test/clang-tidy/check_clang_tidy.py | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/clang-tools-extra/test/clang-tidy/check_clang_tidy.py b/clang-tools-extra/test/clang-tidy/check_clang_tidy.py
index 4ffce5a5463e3..76edfe3060d62 100755
--- a/clang-tools-extra/test/clang-tidy/check_clang_tidy.py
+++ b/clang-tools-extra/test/clang-tidy/check_clang_tidy.py
@@ -292,7 +292,7 @@ def check_no_diagnosis(self, clang_tidy_output: str) -> None:
sys.exit("No diagnostics were expected, but found the ones above")
def check_fixes(self, input_file: str = "", check_file: str = "") -> None:
- if not check_file and not self.has_check_fixes:
+ if not check_file or self.has_check_fixes:
return
input_file = input_file or self.temp_file_name
@@ -379,15 +379,14 @@ def check_header_messages(self, clang_tidy_output: str) -> str:
header_messages = defaultdict(list)
remaining_lines: List[str] = []
- current_file: str = ""
+ current_file = ""
for line in clang_tidy_output.splitlines(keepends=True):
if re.match(r"^\d+ warnings? generated\.", line):
continue
# Matches the beginning of a clang-tidy diagnostic line,
# which starts with "file_path:line:col: ".
- match = re.match(r"^(.+):\d+:\d+: ", line)
- if match:
+ if match := re.match(r"^(.+):\d+:\d+: ", line):
abs_path = os.path.normcase(os.path.abspath(match.group(1)))
current_file = abs_path if abs_path in self.check_header_map else ""
>From fca79cab9d13c698b4e9c9c786433fa2d9ec5a62 Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Wed, 21 Jan 2026 13:40:03 +0800
Subject: [PATCH 07/10] Fix silly error
---
clang-tools-extra/test/clang-tidy/check_clang_tidy.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang-tools-extra/test/clang-tidy/check_clang_tidy.py b/clang-tools-extra/test/clang-tidy/check_clang_tidy.py
index 76edfe3060d62..28ea0708df97f 100755
--- a/clang-tools-extra/test/clang-tidy/check_clang_tidy.py
+++ b/clang-tools-extra/test/clang-tidy/check_clang_tidy.py
@@ -292,7 +292,7 @@ def check_no_diagnosis(self, clang_tidy_output: str) -> None:
sys.exit("No diagnostics were expected, but found the ones above")
def check_fixes(self, input_file: str = "", check_file: str = "") -> None:
- if not check_file or self.has_check_fixes:
+ if not (check_file or self.has_check_fixes):
return
input_file = input_file or self.temp_file_name
>From 530751b5cc8403f1d0f2006a9d7df477443943ac Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Fri, 13 Feb 2026 18:17:44 +0800
Subject: [PATCH 08/10] Address review feedback
---
.../test/clang-tidy/check_clang_tidy.py | 72 +++++++++++--------
.../modernize/concat-nested-namespaces.cpp | 4 +-
.../modernize/pass-by-value-header.cpp | 3 +-
.../modernize/pass-by-value-multi-fixes.cpp | 3 +-
.../unnecessary-value-param-header.cpp | 4 +-
.../readability/duplicate-include.cpp | 1 -
6 files changed, 47 insertions(+), 40 deletions(-)
diff --git a/clang-tools-extra/test/clang-tidy/check_clang_tidy.py b/clang-tools-extra/test/clang-tidy/check_clang_tidy.py
index 4c5ab2309b77e..62377a1ccbf1b 100755
--- a/clang-tools-extra/test/clang-tidy/check_clang_tidy.py
+++ b/clang-tools-extra/test/clang-tidy/check_clang_tidy.py
@@ -210,10 +210,28 @@ def get_prefixes(self) -> None:
)
assert expect_diagnosis or self.expect_no_diagnosis
- def _sanitize_content(self, text: str) -> str:
+ def _remove_filecheck_content(self, text: str) -> str:
+ # Remove the contents of the CHECK lines to avoid CHECKs matching on
+ # themselves. We need to keep the comments to preserve line numbers while
+ # avoiding empty lines which could potentially trigger formatting-related
+ # checks.
return re.sub("// *CHECK-[A-Z0-9\\-]*:[^\r\n]*", "//", text)
def _filter_prefixes(self, prefixes: Sequence[str], check_file: str) -> List[str]:
+ """
+ Filter prefixes to only those present in the check file.
+
+ - Input:
+ prefixes: A list of potential FileCheck prefixes.
+ check_file: The file to check for prefix presence.
+ - Output:
+ A list of prefixes found in the check file.
+
+ FileCheck fails if a specified prefix is not present. This is common
+ in header testing scenarios where expectations differ between the
+ main file and the header (e.g. the main file might verify code
+ changes while the header only verifies warnings).
+ """
if check_file == self.input_file_name:
content = self.input_text
else:
@@ -222,11 +240,7 @@ def _filter_prefixes(self, prefixes: Sequence[str], check_file: str) -> List[str
return [p for p in prefixes if p in content]
def prepare_test_inputs(self) -> None:
- # Remove the contents of the CHECK lines to avoid CHECKs matching on
- # themselves. We need to keep the comments to preserve line numbers while
- # avoiding empty lines which could potentially trigger formatting-related
- # checks.
- cleaned_test = self._sanitize_content(self.input_text)
+ cleaned_test = self._remove_filecheck_content(self.input_text)
write_file(self.temp_file_name, cleaned_test)
write_file(self.original_file_name, cleaned_test)
@@ -235,7 +249,7 @@ def prepare_test_inputs(self) -> None:
for temp_header_path, header in self.check_header_map.items():
with open(header, "r", encoding="utf-8") as f:
- cleaned_header = self._sanitize_content(f.read())
+ cleaned_header = self._remove_filecheck_content(f.read())
write_file(temp_header_path, cleaned_header)
write_file(f"{temp_header_path}.orig", cleaned_header)
@@ -362,20 +376,17 @@ def check_notes(self, clang_tidy_output: str) -> None:
]
)
- def check_header_messages(self, clang_tidy_output: str) -> str:
+ def _separate_messages(self, clang_tidy_output: str) -> Tuple[str, Dict[str, str]]:
"""
- Filters and verifies clang-tidy diagnostics for headers.
+ Separates diagnostics for the main file and headers.
- Input: The raw diagnostic output from clang-tidy.
- - Output: The diagnostic output intended for the main file
- verification.
-
- This function separates messages belonging to headers specified by
- `-check-header', verifies them using FileCheck against the header's
- own rules, and returns the rest for further processing.
+ - Output: A tuple containing:
+ 1. The diagnostic output for the main file.
+ 2. A dictionary mapping header files to their diagnostics.
"""
if not self.check_headers:
- return clang_tidy_output
+ return clang_tidy_output, {}
header_messages = defaultdict(list)
remaining_lines: List[str] = []
@@ -384,6 +395,7 @@ def check_header_messages(self, clang_tidy_output: str) -> str:
for line in clang_tidy_output.splitlines(keepends=True):
if re.match(r"^\d+ warnings? generated\.", line):
continue
+
# Matches the beginning of a clang-tidy diagnostic line,
# which starts with "file_path:line:col: ".
if match := re.match(r"^(.+):\d+:\d+: ", line):
@@ -395,16 +407,8 @@ def check_header_messages(self, clang_tidy_output: str) -> str:
)
dest_list.append(line)
- for temp_header, original_header in self.check_header_map.items():
- messages = "".join(header_messages[temp_header])
- if not messages:
- continue
-
- self.check_messages(
- messages, messages_file=f"{temp_header}.msg", check_file=original_header
- )
-
- return "".join(remaining_lines)
+ header_messages_str = {k: "".join(v) for k, v in header_messages.items()}
+ return "".join(remaining_lines), header_messages_str
def run(self) -> None:
self.read_input()
@@ -412,13 +416,14 @@ def run(self) -> None:
self.get_prefixes()
self.prepare_test_inputs()
clang_tidy_output = self.run_clang_tidy()
- clang_tidy_output = self.check_header_messages(clang_tidy_output)
+ main_output, header_messages = self._separate_messages(clang_tidy_output)
+
if self.expect_no_diagnosis:
- self.check_no_diagnosis(clang_tidy_output)
+ self.check_no_diagnosis(main_output)
elif self.export_fixes is None:
self.check_fixes()
- self.check_messages(clang_tidy_output)
- self.check_notes(clang_tidy_output)
+ self.check_messages(main_output)
+ self.check_notes(main_output)
for temp_header, original_header in self.check_header_map.items():
self.check_fixes(
@@ -426,6 +431,13 @@ def run(self) -> None:
check_file=original_header,
)
+ if temp_header in header_messages:
+ self.check_messages(
+ header_messages[temp_header],
+ messages_file=f"{temp_header}.msg",
+ check_file=original_header,
+ )
+
CPP_STANDARDS = [
"c++98",
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/concat-nested-namespaces.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/concat-nested-namespaces.cpp
index 58ae66d0b389f..4326fa6412021 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/modernize/concat-nested-namespaces.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/concat-nested-namespaces.cpp
@@ -1,5 +1,5 @@
-// RUN: %check_clang_tidy -std=c++17 -check-suffix=NORMAL -check-header %S/Inputs/concat-nested-namespaces/modernize-concat-nested-namespaces.h %s modernize-concat-nested-namespaces %t -- -header-filter=".*" -- -I %S/Inputs/concat-nested-namespaces
-// RUN: %check_clang_tidy -std=c++20 -check-suffixes=NORMAL,CPP20 -check-header %S/Inputs/concat-nested-namespaces/modernize-concat-nested-namespaces.h %s modernize-concat-nested-namespaces %t -- -header-filter=".*" -- -I %S/Inputs/concat-nested-namespaces
+// RUN: %check_clang_tidy -std=c++17 -check-suffix=NORMAL -check-header %S/Inputs/concat-nested-namespaces/modernize-concat-nested-namespaces.h %s modernize-concat-nested-namespaces %t
+// RUN: %check_clang_tidy -std=c++20 -check-suffixes=NORMAL,CPP20 -check-header %S/Inputs/concat-nested-namespaces/modernize-concat-nested-namespaces.h %s modernize-concat-nested-namespaces %t
#include "modernize-concat-nested-namespaces.h"
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/pass-by-value-header.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/pass-by-value-header.cpp
index 79165e51f9e14..e40c21f632d1a 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/modernize/pass-by-value-header.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/pass-by-value-header.cpp
@@ -1,6 +1,5 @@
// RUN: %check_clang_tidy -check-header %S/Inputs/pass-by-value/header.h \
-// RUN: %s modernize-pass-by-value %t -- -header-filter='.*' \
-// RUN: -- -I %S/Inputs/pass-by-value -std=c++11
+// RUN: %s modernize-pass-by-value %t -- -- -std=c++11
#include "header.h"
// FIXME: Make the test work in all language modes.
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/pass-by-value-multi-fixes.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/pass-by-value-multi-fixes.cpp
index c5e6e0be30b8f..bbe96d39793b0 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/modernize/pass-by-value-multi-fixes.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/pass-by-value-multi-fixes.cpp
@@ -1,6 +1,5 @@
// RUN: %check_clang_tidy -check-header %S/Inputs/pass-by-value/header-with-fix.h \
-// RUN: %s modernize-pass-by-value %t -- -header-filter='.*' \
-// RUN: -- -I %S/Inputs/pass-by-value -std=c++11
+// RUN: %s modernize-pass-by-value %t -- -- -std=c++11
#include "header-with-fix.h"
diff --git a/clang-tools-extra/test/clang-tidy/checkers/performance/unnecessary-value-param-header.cpp b/clang-tools-extra/test/clang-tidy/checkers/performance/unnecessary-value-param-header.cpp
index ded66392e4af5..4e53a385af629 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/performance/unnecessary-value-param-header.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/performance/unnecessary-value-param-header.cpp
@@ -1,7 +1,5 @@
// RUN: %check_clang_tidy -check-header %S/Inputs/unnecessary-value-param/header.h \
-// RUN: %s performance-unnecessary-value-param %t -- \
-// RUN: -header-filter='.*' \
-// RUN: -- -I %S/Inputs/unnecessary-value-param
+// RUN: %s performance-unnecessary-value-param %t
#include "header.h"
diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability/duplicate-include.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability/duplicate-include.cpp
index 8483e895bb635..13c54dac16e05 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/readability/duplicate-include.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/readability/duplicate-include.cpp
@@ -1,6 +1,5 @@
// RUN: %check_clang_tidy -check-header %S/Inputs/duplicate-include/duplicate-include.h \
// RUN: %s readability-duplicate-include %t -- \
-// RUN: -header-filter='.*' \
// RUN: -- -isystem %S/Inputs/duplicate-include/system -I %S/Inputs/duplicate-include
int a;
>From 44c44e7fc8f4691dd1f7ac302a93b4b071c7275d Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Fri, 13 Feb 2026 18:24:31 +0800
Subject: [PATCH 09/10] Add a releasenote
---
clang-tools-extra/docs/ReleaseNotes.rst | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 36626d7c1a9be..5d64bd0971d51 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -94,6 +94,11 @@ Improvements to clang-query
Improvements to clang-tidy
--------------------------
+- Improved :program:`check_clang_tidy.py` script by adding the ``-check-header``
+ argument to simplify testing of header files. This argument automatically
+ manages the creation of temporary header files and ensures that diagnostics
+ and fixes are verified for the specified headers.
+
New checks
^^^^^^^^^^
>From 80011942486911d7c51207b117e8567f40915d1b Mon Sep 17 00:00:00 2001
From: mitchell <mitchell.xu2 at gmail.com>
Date: Fri, 13 Feb 2026 23:43:45 +0800
Subject: [PATCH 10/10] Update clang-tools-extra/docs/ReleaseNotes.rst
Co-authored-by: EugeneZelenko <eugene.zelenko at gmail.com>
---
clang-tools-extra/docs/ReleaseNotes.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 5d64bd0971d51..5c0060877a67f 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -94,7 +94,7 @@ Improvements to clang-query
Improvements to clang-tidy
--------------------------
-- Improved :program:`check_clang_tidy.py` script by adding the ``-check-header``
+- Improved :program:`check_clang_tidy.py` script by adding the `-check-header`
argument to simplify testing of header files. This argument automatically
manages the creation of temporary header files and ensures that diagnostics
and fixes are verified for the specified headers.
More information about the cfe-commits
mailing list