https://github.com/unterumarmung created 
https://github.com/llvm/llvm-project/pull/196765

None

>From 738e2e77e62e456ea6943e6410a7851fe22f21db Mon Sep 17 00:00:00 2001
From: Daniil Dudkin <[email protected]>
Date: Sat, 9 May 2026 16:59:00 +0300
Subject: [PATCH] [clang-include-cleaner] expose fragment headers in the tool

---
 clang-tools-extra/docs/ReleaseNotes.rst       |  6 ++
 .../include-cleaner/test/Inputs/a.inc         |  4 ++
 .../include-cleaner/test/Inputs/b.inc         |  4 ++
 .../include-cleaner/test/Inputs/gen.inc       |  4 ++
 .../test/Inputs/generated/gen.inc             |  4 ++
 .../include-cleaner/test/Inputs/inner.inc     |  4 ++
 .../include-cleaner/test/Inputs/outer.inc     |  2 +
 .../include-cleaner/test/Inputs/vector        |  4 ++
 .../include-cleaner/test/fragments-multi.cpp  |  8 +++
 .../test/fragments-nonrecursive.cpp           |  5 ++
 .../test/fragments-spelled-path.cpp           |  6 ++
 .../include-cleaner/test/fragments.cpp        |  6 ++
 .../include-cleaner/tool/IncludeCleaner.cpp   | 67 +++++++++++++++----
 13 files changed, 110 insertions(+), 14 deletions(-)
 create mode 100644 clang-tools-extra/include-cleaner/test/Inputs/a.inc
 create mode 100644 clang-tools-extra/include-cleaner/test/Inputs/b.inc
 create mode 100644 clang-tools-extra/include-cleaner/test/Inputs/gen.inc
 create mode 100644 
clang-tools-extra/include-cleaner/test/Inputs/generated/gen.inc
 create mode 100644 clang-tools-extra/include-cleaner/test/Inputs/inner.inc
 create mode 100644 clang-tools-extra/include-cleaner/test/Inputs/outer.inc
 create mode 100644 clang-tools-extra/include-cleaner/test/Inputs/vector
 create mode 100644 clang-tools-extra/include-cleaner/test/fragments-multi.cpp
 create mode 100644 
clang-tools-extra/include-cleaner/test/fragments-nonrecursive.cpp
 create mode 100644 
clang-tools-extra/include-cleaner/test/fragments-spelled-path.cpp
 create mode 100644 clang-tools-extra/include-cleaner/test/fragments.cpp

diff --git a/clang-tools-extra/docs/ReleaseNotes.rst 
b/clang-tools-extra/docs/ReleaseNotes.rst
index 51251eacbcd5e..22f760f616473 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -650,6 +650,12 @@ Miscellaneous
 Improvements to include-fixer
 -----------------------------
 
+Improvements to clang-include-cleaner
+-------------------------------------
+
+- Added :program:`clang-include-cleaner` support for treating matching direct
+  includes as fragments of the main file with ``--fragment-headers``.
+
 Improvements to clang-include-fixer
 -----------------------------------
 
diff --git a/clang-tools-extra/include-cleaner/test/Inputs/a.inc 
b/clang-tools-extra/include-cleaner/test/Inputs/a.inc
new file mode 100644
index 0000000000000..1d44a1554bcb8
--- /dev/null
+++ b/clang-tools-extra/include-cleaner/test/Inputs/a.inc
@@ -0,0 +1,4 @@
+#pragma once
+struct A {
+  std::vector<int> Values;
+};
diff --git a/clang-tools-extra/include-cleaner/test/Inputs/b.inc 
b/clang-tools-extra/include-cleaner/test/Inputs/b.inc
new file mode 100644
index 0000000000000..a36c95d1eb829
--- /dev/null
+++ b/clang-tools-extra/include-cleaner/test/Inputs/b.inc
@@ -0,0 +1,4 @@
+#pragma once
+struct B {
+  std::vector<int> Values;
+};
diff --git a/clang-tools-extra/include-cleaner/test/Inputs/gen.inc 
b/clang-tools-extra/include-cleaner/test/Inputs/gen.inc
new file mode 100644
index 0000000000000..966fff546162e
--- /dev/null
+++ b/clang-tools-extra/include-cleaner/test/Inputs/gen.inc
@@ -0,0 +1,4 @@
+#pragma once
+struct Gen {
+  std::vector<int> Values;
+};
diff --git a/clang-tools-extra/include-cleaner/test/Inputs/generated/gen.inc 
b/clang-tools-extra/include-cleaner/test/Inputs/generated/gen.inc
new file mode 100644
index 0000000000000..051180bcc7a83
--- /dev/null
+++ b/clang-tools-extra/include-cleaner/test/Inputs/generated/gen.inc
@@ -0,0 +1,4 @@
+#pragma once
+struct SpelledGen {
+  std::vector<int> Values;
+};
diff --git a/clang-tools-extra/include-cleaner/test/Inputs/inner.inc 
b/clang-tools-extra/include-cleaner/test/Inputs/inner.inc
new file mode 100644
index 0000000000000..2f52678c091ca
--- /dev/null
+++ b/clang-tools-extra/include-cleaner/test/Inputs/inner.inc
@@ -0,0 +1,4 @@
+#pragma once
+struct Inner {
+  std::vector<int> Values;
+};
diff --git a/clang-tools-extra/include-cleaner/test/Inputs/outer.inc 
b/clang-tools-extra/include-cleaner/test/Inputs/outer.inc
new file mode 100644
index 0000000000000..4f68e88d1730a
--- /dev/null
+++ b/clang-tools-extra/include-cleaner/test/Inputs/outer.inc
@@ -0,0 +1,2 @@
+#pragma once
+#include "inner.inc"
diff --git a/clang-tools-extra/include-cleaner/test/Inputs/vector 
b/clang-tools-extra/include-cleaner/test/Inputs/vector
new file mode 100644
index 0000000000000..a2956ae8e54da
--- /dev/null
+++ b/clang-tools-extra/include-cleaner/test/Inputs/vector
@@ -0,0 +1,4 @@
+#pragma once
+namespace std {
+template <typename T> struct vector {};
+} // namespace std
diff --git a/clang-tools-extra/include-cleaner/test/fragments-multi.cpp 
b/clang-tools-extra/include-cleaner/test/fragments-multi.cpp
new file mode 100644
index 0000000000000..5aa1814df7326
--- /dev/null
+++ b/clang-tools-extra/include-cleaner/test/fragments-multi.cpp
@@ -0,0 +1,8 @@
+#include <vector>
+#include "a.inc"
+#include "b.inc"
+
+A AValue;
+B BValue;
+
+// RUN: clang-include-cleaner -print=changes %s --fragment-headers='.*\.inc$' 
-- -I%S/Inputs/ | count 0
diff --git a/clang-tools-extra/include-cleaner/test/fragments-nonrecursive.cpp 
b/clang-tools-extra/include-cleaner/test/fragments-nonrecursive.cpp
new file mode 100644
index 0000000000000..e5a3d3be257b9
--- /dev/null
+++ b/clang-tools-extra/include-cleaner/test/fragments-nonrecursive.cpp
@@ -0,0 +1,5 @@
+#include <vector>
+#include "outer.inc"
+
+// RUN: clang-include-cleaner -print=changes %s --fragment-headers='.*\.inc$' 
-- -I%S/Inputs/ | FileCheck --check-prefix=CHANGES %s
+// CHANGES: - <vector> @Line:1
diff --git a/clang-tools-extra/include-cleaner/test/fragments-spelled-path.cpp 
b/clang-tools-extra/include-cleaner/test/fragments-spelled-path.cpp
new file mode 100644
index 0000000000000..82d0d27c34df9
--- /dev/null
+++ b/clang-tools-extra/include-cleaner/test/fragments-spelled-path.cpp
@@ -0,0 +1,6 @@
+#include <vector>
+#include "generated/gen.inc"
+
+SpelledGen G;
+
+// RUN: clang-include-cleaner -print=changes %s 
--fragment-headers='generated/gen\.inc' -- -I%S/Inputs/ | count 0
diff --git a/clang-tools-extra/include-cleaner/test/fragments.cpp 
b/clang-tools-extra/include-cleaner/test/fragments.cpp
new file mode 100644
index 0000000000000..0e7ee5615e7d3
--- /dev/null
+++ b/clang-tools-extra/include-cleaner/test/fragments.cpp
@@ -0,0 +1,6 @@
+#include <vector>
+#include "gen.inc"
+
+Gen G;
+
+// RUN: clang-include-cleaner -print=changes %s --fragment-headers='.*\.inc$' 
-- -I%S/Inputs/ | count 0
diff --git a/clang-tools-extra/include-cleaner/tool/IncludeCleaner.cpp 
b/clang-tools-extra/include-cleaner/tool/IncludeCleaner.cpp
index 49bd5495bcc13..fbc14be848b5f 100644
--- a/clang-tools-extra/include-cleaner/tool/IncludeCleaner.cpp
+++ b/clang-tools-extra/include-cleaner/tool/IncludeCleaner.cpp
@@ -34,6 +34,12 @@ namespace include_cleaner {
 namespace {
 namespace cl = llvm::cl;
 
+llvm::SmallVector<Decl *> topLevelDecls(ASTContext &Ctx);
+std::function<bool(llvm::StringRef)> matchesAny(llvm::StringRef RegexFlag,
+                                                bool AnchorToSuffix);
+std::function<bool(const Header &)> headerFilter();
+std::function<bool(llvm::StringRef)> fragmentHeaderFilter();
+
 llvm::StringRef Overview = llvm::StringLiteral(R"(
 clang-include-cleaner analyzes the #include directives in source code.
 
@@ -73,6 +79,15 @@ cl::opt<std::string> IgnoreHeaders{
     cl::cat(IncludeCleaner),
 };
 
+cl::opt<std::string> FragmentHeaders{
+    "fragment-headers",
+    cl::desc("A comma-separated list of regular expressions matched against "
+             "normalized include paths. Matching direct includes are treated "
+             "as fragments of the main file."),
+    cl::init(""),
+    cl::cat(IncludeCleaner),
+};
+
 enum class PrintStyle { Changes, Final };
 cl::opt<PrintStyle> Print{
     "print",
@@ -131,14 +146,18 @@ format::FormatStyle getStyle(llvm::StringRef Filename) {
 class Action : public clang::ASTFrontendAction {
 public:
   Action(std::function<bool(const Header &)> HeaderFilter,
+         std::function<bool(llvm::StringRef)> FragmentHeaderFilter,
          llvm::StringMap<std::string> &EditedFiles)
-      : HeaderFilter(std::move(HeaderFilter)), EditedFiles(EditedFiles) {}
+      : HeaderFilter(std::move(HeaderFilter)),
+        FragmentHeaderFilter(std::move(FragmentHeaderFilter)),
+        EditedFiles(EditedFiles) {}
 
 private:
   RecordedAST AST;
   RecordedPP PP;
   PragmaIncludes PI;
   std::function<bool(const Header &)> HeaderFilter;
+  std::function<bool(llvm::StringRef)> FragmentHeaderFilter;
   llvm::StringMap<std::string> &EditedFiles;
 
   bool BeginInvocation(CompilerInstance &CI) override {
@@ -192,9 +211,10 @@ class Action : public clang::ASTFrontendAction {
     SM.getFileManager().makeAbsolutePath(AbsPath);
     llvm::StringRef Code = SM.getBufferData(SM.getMainFileID());
 
-    AnalysisOptions AnalyzeOptions{HeaderFilter, {}};
+    AnalysisOptions AnalyzeOptions{HeaderFilter, FragmentHeaderFilter};
+    llvm::SmallVector<Decl *> RootDecls = topLevelDecls(*AST.Ctx);
     auto Results =
-        analyze(AST.Roots, PP.MacroReferences, PP.Includes, &PI,
+        analyze(RootDecls, PP.MacroReferences, PP.Includes, &PI,
                 getCompilerInstance().getPreprocessor(), AnalyzeOptions);
 
     if (!Insert) {
@@ -253,11 +273,14 @@ class Action : public clang::ASTFrontendAction {
 };
 class ActionFactory : public tooling::FrontendActionFactory {
 public:
-  ActionFactory(std::function<bool(const Header &)> HeaderFilter)
-      : HeaderFilter(std::move(HeaderFilter)) {}
+  ActionFactory(std::function<bool(const Header &)> HeaderFilter,
+                std::function<bool(llvm::StringRef)> FragmentHeaderFilter)
+      : HeaderFilter(std::move(HeaderFilter)),
+        FragmentHeaderFilter(std::move(FragmentHeaderFilter)) {}
 
   std::unique_ptr<clang::FrontendAction> create() override {
-    return std::make_unique<Action>(HeaderFilter, EditedFiles);
+    return std::make_unique<Action>(HeaderFilter, FragmentHeaderFilter,
+                                    EditedFiles);
   }
 
   const llvm::StringMap<std::string> &editedFiles() const {
@@ -266,18 +289,29 @@ class ActionFactory : public 
tooling::FrontendActionFactory {
 
 private:
   std::function<bool(const Header &)> HeaderFilter;
+  std::function<bool(llvm::StringRef)> FragmentHeaderFilter;
   // Map from file name to final code with the include edits applied.
   llvm::StringMap<std::string> EditedFiles;
 };
 
+llvm::SmallVector<Decl *> topLevelDecls(ASTContext &Ctx) {
+  llvm::SmallVector<Decl *> Decls;
+  for (Decl *D : Ctx.getTranslationUnitDecl()->decls())
+    Decls.push_back(D);
+  return Decls;
+}
+
 // Compiles a regex list into a function that return true if any match a 
header.
 // Prints and returns nullptr if any regexes are invalid.
-std::function<bool(llvm::StringRef)> matchesAny(llvm::StringRef RegexFlag) {
+std::function<bool(llvm::StringRef)> matchesAny(llvm::StringRef RegexFlag,
+                                                bool AnchorToSuffix) {
   auto FilterRegs = std::make_shared<std::vector<llvm::Regex>>();
   llvm::SmallVector<llvm::StringRef> Headers;
   RegexFlag.split(Headers, ',', -1, /*KeepEmpty=*/false);
   for (auto HeaderPattern : Headers) {
-    std::string AnchoredPattern = "(" + HeaderPattern.str() + ")$";
+    std::string AnchoredPattern = HeaderPattern.str();
+    if (AnchorToSuffix)
+      AnchoredPattern = "(" + AnchoredPattern + ")$";
     llvm::Regex CompiledRegex(AnchoredPattern);
     std::string RegexError;
     if (!CompiledRegex.isValid(RegexError)) {
@@ -297,13 +331,13 @@ std::function<bool(llvm::StringRef)> 
matchesAny(llvm::StringRef RegexFlag) {
 }
 
 std::function<bool(const Header &)> headerFilter() {
-  auto OnlyMatches = matchesAny(OnlyHeaders);
-  auto IgnoreMatches = matchesAny(IgnoreHeaders);
+  auto OnlyMatches = matchesAny(OnlyHeaders, /*AnchorToSuffix=*/true);
+  auto IgnoreMatches = matchesAny(IgnoreHeaders, /*AnchorToSuffix=*/true);
   if (!OnlyMatches || !IgnoreMatches)
     return nullptr;
 
-  return [OnlyMatches, IgnoreMatches](const Header &H) {
-    llvm::StringRef Path = H.resolvedPath();
+  return [OnlyMatches, IgnoreMatches](const Header &Header) {
+    llvm::StringRef Path = Header.resolvedPath();
     if (!OnlyHeaders.empty() && !OnlyMatches(Path))
       return true;
     if (!IgnoreHeaders.empty() && IgnoreMatches(Path))
@@ -312,6 +346,10 @@ std::function<bool(const Header &)> headerFilter() {
   };
 }
 
+std::function<bool(llvm::StringRef)> fragmentHeaderFilter() {
+  return matchesAny(FragmentHeaders, /*AnchorToSuffix=*/false);
+}
+
 // Maps absolute path of each files of each compilation commands to the
 // absolute path of the input file.
 llvm::Expected<std::map<std::string, std::string, std::less<>>>
@@ -390,9 +428,10 @@ int main(int argc, const char **argv) {
   clang::tooling::ClangTool Tool(CDB, OptionsParser->getSourcePathList());
 
   auto HeaderFilter = headerFilter();
-  if (!HeaderFilter)
+  auto FragmentHeaderFilter = fragmentHeaderFilter();
+  if (!HeaderFilter || !FragmentHeaderFilter)
     return 1; // error already reported.
-  ActionFactory Factory(HeaderFilter);
+  ActionFactory Factory(HeaderFilter, FragmentHeaderFilter);
   auto ErrorCode = Tool.run(&Factory);
   if (Edit) {
     for (const auto &NameAndContent : Factory.editedFiles()) {

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

Reply via email to