[llvm-branch-commits] [clang-tools-extra] [clang-include-cleaner] expose fragment headers in the tool (PR #196765)
via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Sat May 9 15:45:50 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang-tools-extra
Author: Daniil Dudkin (unterumarmung)
<details>
<summary>Changes</summary>
---
Full diff: https://github.com/llvm/llvm-project/pull/196765.diff
13 Files Affected:
- (modified) clang-tools-extra/docs/ReleaseNotes.rst (+6)
- (added) clang-tools-extra/include-cleaner/test/Inputs/a.inc (+4)
- (added) clang-tools-extra/include-cleaner/test/Inputs/b.inc (+4)
- (added) clang-tools-extra/include-cleaner/test/Inputs/gen.inc (+4)
- (added) clang-tools-extra/include-cleaner/test/Inputs/generated/gen.inc (+4)
- (added) clang-tools-extra/include-cleaner/test/Inputs/inner.inc (+4)
- (added) clang-tools-extra/include-cleaner/test/Inputs/outer.inc (+2)
- (added) clang-tools-extra/include-cleaner/test/Inputs/vector (+4)
- (added) clang-tools-extra/include-cleaner/test/fragments-multi.cpp (+8)
- (added) clang-tools-extra/include-cleaner/test/fragments-nonrecursive.cpp (+5)
- (added) clang-tools-extra/include-cleaner/test/fragments-spelled-path.cpp (+6)
- (added) clang-tools-extra/include-cleaner/test/fragments.cpp (+6)
- (modified) clang-tools-extra/include-cleaner/tool/IncludeCleaner.cpp (+53-14)
``````````diff
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()) {
``````````
</details>
https://github.com/llvm/llvm-project/pull/196765
More information about the llvm-branch-commits
mailing list