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
