[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 02:26:23 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 1/9] [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 2/9] 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 3/9] 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 4/9] ~
---
.../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 5/9] 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 6/9] 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 7/9] 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 8/9] 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 9/9] 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
^^^^^^^^^^
More information about the cfe-commits
mailing list