[flang] Use object before converts in fir.dispatch (#68589)
[llvm-project.git] / clang-tools-extra / include-cleaner / tool / IncludeCleaner.cpp
blob30aaee29b9a397a947f7ca124db79da6da8874d0
1 //===--- IncludeCleaner.cpp - standalone tool for include analysis --------===//
2 //
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
6 //
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"
26 #include <functional>
27 #include <memory>
28 #include <string>
29 #include <utility>
30 #include <vector>
32 namespace clang {
33 namespace include_cleaner {
34 namespace {
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).
48 )")
49 .trim();
51 cl::OptionCategory IncludeCleaner("clang-include-cleaner");
53 cl::opt<std::string> HTMLReportPath{
54 "html",
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{
61 "ignore-headers",
62 cl::desc("A comma-separated list of regexes to match against suffix of a "
63 "header, and disable analysis if matched."),
64 cl::init(""),
65 cl::cat(IncludeCleaner),
68 enum class PrintStyle { Changes, Final };
69 cl::opt<PrintStyle> Print{
70 "print",
71 cl::values(
72 clEnumValN(PrintStyle::Changes, "changes", "Print symbolic changes"),
73 clEnumValN(PrintStyle::Final, "", "Print final code")),
74 cl::ValueOptional,
75 cl::init(PrintStyle::Final),
76 cl::desc("Print the list of headers to insert and remove"),
77 cl::cat(IncludeCleaner),
80 cl::opt<bool> Edit{
81 "edit",
82 cl::desc("Apply edits to analyzed source files"),
83 cl::cat(IncludeCleaner),
86 cl::opt<bool> Insert{
87 "insert",
88 cl::desc("Allow header insertions"),
89 cl::init(true),
90 cl::cat(IncludeCleaner),
92 cl::opt<bool> Remove{
93 "remove",
94 cl::desc("Allow header removals"),
95 cl::init(true),
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 {
112 public:
113 Action(llvm::function_ref<bool(llvm::StringRef)> HeaderFilter)
114 : HeaderFilter(HeaderFilter){};
116 private:
117 RecordedAST AST;
118 RecordedPP PP;
119 PragmaIncludes PI;
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;
128 return true;
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 {
140 return AST.record();
143 void EndSourceFile() override {
144 const auto &SM = getCompilerInstance().getSourceManager();
145 if (SM.getDiagnostics().hasUncompilableErrorOccurred()) {
146 llvm::errs()
147 << "Skipping file " << getCurrentFile()
148 << " due to compiler errors. clang-include-cleaner expects to "
149 "work on compilable source code.\n";
150 return;
153 if (!HTMLReportPath.empty())
154 writeHTML();
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());
161 auto Results =
162 analyze(AST.Roots, PP.MacroReferences, PP.Includes, &PI,
163 getCompilerInstance().getPreprocessor(), HeaderFilter);
164 if (!Insert)
165 Results.Missing.clear();
166 if (!Remove)
167 Results.Unused.clear();
168 std::string Final = fixIncludes(Results, Path, Code, getStyle(Path));
170 if (Print.getNumOccurrences()) {
171 switch (Print) {
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";
177 break;
178 case PrintStyle::Final:
179 llvm::outs() << Final;
180 break;
184 if (Edit && (!Results.Missing.empty() || !Results.Unused.empty())) {
185 if (auto Err = llvm::writeToOutput(
186 Path, [&](llvm::raw_ostream &OS) -> llvm::Error {
187 OS << Final;
188 return llvm::Error::success();
189 })) {
190 llvm::errs() << "Failed to apply edits to " << Path << ": "
191 << toString(std::move(Err)) << "\n";
192 ++Errors;
197 void writeHTML() {
198 std::error_code EC;
199 llvm::raw_fd_ostream OS(HTMLReportPath, EC);
200 if (EC) {
201 llvm::errs() << "Unable to write HTML report to " << HTMLReportPath
202 << ": " << EC.message() << "\n";
203 ++Errors;
204 return;
206 writeHTMLReport(
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 {
213 public:
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);
221 private:
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);
237 return nullptr;
239 FilterRegs->push_back(std::move(CompiledRegex));
241 return [FilterRegs](llvm::StringRef Path) {
242 for (const auto &F : *FilterRegs) {
243 if (F.match(Path))
244 return true;
246 return false;
250 } // namespace
251 } // namespace include_cleaner
252 } // namespace clang
254 int main(int argc, const char **argv) {
255 using namespace clang::include_cleaner;
257 llvm::sys::PrintStackTraceOnErrorSignal(argv[0]);
258 auto OptionsParser =
259 clang::tooling::CommonOptionsParser::create(argc, argv, IncludeCleaner);
260 if (!OptionsParser) {
261 llvm::errs() << toString(OptionsParser.takeError());
262 return 1;
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";
270 return 1;
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);
280 if (!Content) {
281 llvm::errs() << "Error: can't read file '" << File
282 << "': " << Content.getError().message() << "\n";
283 return 1;
285 Buffers.push_back(std::move(Content.get()));
286 Tool.mapVirtualFile(File, Buffers.back()->getBuffer());
289 auto HeaderFilter = headerFilter();
290 if (!HeaderFilter)
291 return 1; // error already reported.
292 ActionFactory Factory(HeaderFilter);
293 return Tool.run(&Factory) || Errors != 0;