1 //===--- IncludeCleaner.cpp - standalone tool for include analysis --------===//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
9 #include "AnalysisInternal.h"
10 #include "clang-include-cleaner/Analysis.h"
11 #include "clang-include-cleaner/Record.h"
12 #include "clang/Frontend/CompilerInstance.h"
13 #include "clang/Frontend/FrontendAction.h"
14 #include "clang/Lex/Preprocessor.h"
15 #include "clang/Tooling/CommonOptionsParser.h"
16 #include "clang/Tooling/Tooling.h"
17 #include "llvm/ADT/STLFunctionalExtras.h"
18 #include "llvm/ADT/SmallVector.h"
19 #include "llvm/ADT/StringRef.h"
20 #include "llvm/Support/CommandLine.h"
21 #include "llvm/Support/FormatVariadic.h"
22 #include "llvm/Support/MemoryBuffer.h"
23 #include "llvm/Support/Regex.h"
24 #include "llvm/Support/Signals.h"
25 #include "llvm/Support/raw_ostream.h"
33 namespace include_cleaner
{
35 namespace cl
= llvm::cl
;
37 llvm::StringRef Overview
= llvm::StringLiteral(R
"(
38 clang-include-cleaner analyzes the #include directives in source code.
40 It suggests removing headers that the code is not using.
41 It suggests inserting headers that the code relies on, but does not include.
42 These changes make the file more self-contained and (at scale) make the codebase
43 easier to reason about and modify.
45 The tool operates on *working* source code. This means it can suggest including
46 headers that are only indirectly included, but cannot suggest those that are
47 missing entirely. (clang-include-fixer can do this).
51 cl::OptionCategory
IncludeCleaner("clang-include-cleaner");
53 cl::opt
<std::string
> HTMLReportPath
{
55 cl::desc("Specify an output filename for an HTML report. "
56 "This describes both recommendations and reasons for changes."),
57 cl::cat(IncludeCleaner
),
60 cl::opt
<std::string
> IgnoreHeaders
{
62 cl::desc("A comma-separated list of regexes to match against suffix of a "
63 "header, and disable analysis if matched."),
65 cl::cat(IncludeCleaner
),
68 enum class PrintStyle
{ Changes
, Final
};
69 cl::opt
<PrintStyle
> Print
{
72 clEnumValN(PrintStyle::Changes
, "changes", "Print symbolic changes"),
73 clEnumValN(PrintStyle::Final
, "", "Print final code")),
75 cl::init(PrintStyle::Final
),
76 cl::desc("Print the list of headers to insert and remove"),
77 cl::cat(IncludeCleaner
),
82 cl::desc("Apply edits to analyzed source files"),
83 cl::cat(IncludeCleaner
),
88 cl::desc("Allow header insertions"),
90 cl::cat(IncludeCleaner
),
94 cl::desc("Allow header removals"),
96 cl::cat(IncludeCleaner
),
99 std::atomic
<unsigned> Errors
= ATOMIC_VAR_INIT(0);
101 format::FormatStyle
getStyle(llvm::StringRef Filename
) {
102 auto S
= format::getStyle(format::DefaultFormatStyle
, Filename
,
103 format::DefaultFallbackStyle
);
104 if (!S
|| !S
->isCpp()) {
105 consumeError(S
.takeError());
106 return format::getLLVMStyle();
108 return std::move(*S
);
111 class Action
: public clang::ASTFrontendAction
{
113 Action(llvm::function_ref
<bool(llvm::StringRef
)> HeaderFilter
)
114 : HeaderFilter(HeaderFilter
){};
120 llvm::function_ref
<bool(llvm::StringRef
)> HeaderFilter
;
122 bool BeginInvocation(CompilerInstance
&CI
) override
{
123 // We only perform include-cleaner analysis. So we disable diagnostics that
124 // won't affect our analysis to make the tool more robust against
125 // in-development code.
126 CI
.getLangOpts().ModulesDeclUse
= false;
127 CI
.getLangOpts().ModulesStrictDeclUse
= false;
131 void ExecuteAction() override
{
132 auto &P
= getCompilerInstance().getPreprocessor();
133 P
.addPPCallbacks(PP
.record(P
));
134 PI
.record(getCompilerInstance());
135 ASTFrontendAction::ExecuteAction();
138 std::unique_ptr
<ASTConsumer
> CreateASTConsumer(CompilerInstance
&CI
,
139 StringRef File
) override
{
143 void EndSourceFile() override
{
144 const auto &SM
= getCompilerInstance().getSourceManager();
145 if (SM
.getDiagnostics().hasUncompilableErrorOccurred()) {
147 << "Skipping file " << getCurrentFile()
148 << " due to compiler errors. clang-include-cleaner expects to "
149 "work on compilable source code.\n";
153 if (!HTMLReportPath
.empty())
156 llvm::StringRef Path
=
157 SM
.getFileEntryForID(SM
.getMainFileID())->tryGetRealPathName();
158 assert(!Path
.empty() && "Main file path not known?");
159 llvm::StringRef Code
= SM
.getBufferData(SM
.getMainFileID());
162 analyze(AST
.Roots
, PP
.MacroReferences
, PP
.Includes
, &PI
,
163 getCompilerInstance().getPreprocessor(), HeaderFilter
);
165 Results
.Missing
.clear();
167 Results
.Unused
.clear();
168 std::string Final
= fixIncludes(Results
, Path
, Code
, getStyle(Path
));
170 if (Print
.getNumOccurrences()) {
172 case PrintStyle::Changes
:
173 for (const Include
*I
: Results
.Unused
)
174 llvm::outs() << "- " << I
->quote() << " @Line:" << I
->Line
<< "\n";
175 for (const auto &I
: Results
.Missing
)
176 llvm::outs() << "+ " << I
<< "\n";
178 case PrintStyle::Final
:
179 llvm::outs() << Final
;
184 if (Edit
&& (!Results
.Missing
.empty() || !Results
.Unused
.empty())) {
185 if (auto Err
= llvm::writeToOutput(
186 Path
, [&](llvm::raw_ostream
&OS
) -> llvm::Error
{
188 return llvm::Error::success();
190 llvm::errs() << "Failed to apply edits to " << Path
<< ": "
191 << toString(std::move(Err
)) << "\n";
199 llvm::raw_fd_ostream
OS(HTMLReportPath
, EC
);
201 llvm::errs() << "Unable to write HTML report to " << HTMLReportPath
202 << ": " << EC
.message() << "\n";
207 AST
.Ctx
->getSourceManager().getMainFileID(), PP
.Includes
, AST
.Roots
,
208 PP
.MacroReferences
, *AST
.Ctx
,
209 getCompilerInstance().getPreprocessor().getHeaderSearchInfo(), &PI
, OS
);
212 class ActionFactory
: public tooling::FrontendActionFactory
{
214 ActionFactory(llvm::function_ref
<bool(llvm::StringRef
)> HeaderFilter
)
215 : HeaderFilter(HeaderFilter
) {}
217 std::unique_ptr
<clang::FrontendAction
> create() override
{
218 return std::make_unique
<Action
>(HeaderFilter
);
222 llvm::function_ref
<bool(llvm::StringRef
)> HeaderFilter
;
225 std::function
<bool(llvm::StringRef
)> headerFilter() {
226 auto FilterRegs
= std::make_shared
<std::vector
<llvm::Regex
>>();
228 llvm::SmallVector
<llvm::StringRef
> Headers
;
229 llvm::StringRef(IgnoreHeaders
).split(Headers
, ',', -1, /*KeepEmpty=*/false);
230 for (auto HeaderPattern
: Headers
) {
231 std::string AnchoredPattern
= "(" + HeaderPattern
.str() + ")$";
232 llvm::Regex
CompiledRegex(AnchoredPattern
);
233 std::string RegexError
;
234 if (!CompiledRegex
.isValid(RegexError
)) {
235 llvm::errs() << llvm::formatv("Invalid regular expression '{0}': {1}\n",
236 HeaderPattern
, RegexError
);
239 FilterRegs
->push_back(std::move(CompiledRegex
));
241 return [FilterRegs
](llvm::StringRef Path
) {
242 for (const auto &F
: *FilterRegs
) {
251 } // namespace include_cleaner
254 int main(int argc
, const char **argv
) {
255 using namespace clang::include_cleaner
;
257 llvm::sys::PrintStackTraceOnErrorSignal(argv
[0]);
259 clang::tooling::CommonOptionsParser::create(argc
, argv
, IncludeCleaner
);
260 if (!OptionsParser
) {
261 llvm::errs() << toString(OptionsParser
.takeError());
265 if (OptionsParser
->getSourcePathList().size() != 1) {
266 std::vector
<cl::Option
*> IncompatibleFlags
= {&HTMLReportPath
, &Print
};
267 for (const auto *Flag
: IncompatibleFlags
) {
268 if (Flag
->getNumOccurrences()) {
269 llvm::errs() << "-" << Flag
->ArgStr
<< " requires a single input file";
275 clang::tooling::ClangTool
Tool(OptionsParser
->getCompilations(),
276 OptionsParser
->getSourcePathList());
277 std::vector
<std::unique_ptr
<llvm::MemoryBuffer
>> Buffers
;
278 for (const auto &File
: OptionsParser
->getSourcePathList()) {
279 auto Content
= llvm::MemoryBuffer::getFile(File
);
281 llvm::errs() << "Error: can't read file '" << File
282 << "': " << Content
.getError().message() << "\n";
285 Buffers
.push_back(std::move(Content
.get()));
286 Tool
.mapVirtualFile(File
, Buffers
.back()->getBuffer());
289 auto HeaderFilter
= headerFilter();
291 return 1; // error already reported.
292 ActionFactory
Factory(HeaderFilter
);
293 return Tool
.run(&Factory
) || Errors
!= 0;