199 lines
6.2 KiB
C++
199 lines
6.2 KiB
C++
//===--- IncludeCleaner.cpp - standalone tool for include analysis --------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "AnalysisInternal.h"
|
|
#include "clang-include-cleaner/Analysis.h"
|
|
#include "clang-include-cleaner/Record.h"
|
|
#include "clang/Frontend/CompilerInstance.h"
|
|
#include "clang/Frontend/FrontendAction.h"
|
|
#include "clang/Lex/Preprocessor.h"
|
|
#include "clang/Tooling/CommonOptionsParser.h"
|
|
#include "clang/Tooling/Tooling.h"
|
|
#include "llvm/ADT/StringRef.h"
|
|
#include "llvm/Support/CommandLine.h"
|
|
#include "llvm/Support/Signals.h"
|
|
#include "llvm/Support/raw_ostream.h"
|
|
|
|
namespace clang {
|
|
namespace include_cleaner {
|
|
namespace {
|
|
namespace cl = llvm::cl;
|
|
|
|
llvm::StringRef Overview = llvm::StringLiteral(R"(
|
|
clang-include-cleaner analyzes the #include directives in source code.
|
|
|
|
It suggests removing headers that the code is not using.
|
|
It suggests inserting headers that the code relies on, but does not include.
|
|
These changes make the file more self-contained and (at scale) make the codebase
|
|
easier to reason about and modify.
|
|
|
|
The tool operates on *working* source code. This means it can suggest including
|
|
headers that are only indirectly included, but cannot suggest those that are
|
|
missing entirely. (clang-include-fixer can do this).
|
|
)")
|
|
.trim();
|
|
|
|
cl::OptionCategory IncludeCleaner("clang-include-cleaner");
|
|
|
|
cl::opt<std::string> HTMLReportPath{
|
|
"html",
|
|
cl::desc("Specify an output filename for an HTML report. "
|
|
"This describes both recommendations and reasons for changes."),
|
|
cl::cat(IncludeCleaner),
|
|
};
|
|
|
|
enum class PrintStyle { Changes, Final };
|
|
cl::opt<PrintStyle> Print{
|
|
"print",
|
|
cl::values(
|
|
clEnumValN(PrintStyle::Changes, "changes", "Print symbolic changes"),
|
|
clEnumValN(PrintStyle::Final, "", "Print final code")),
|
|
cl::ValueOptional,
|
|
cl::init(PrintStyle::Final),
|
|
cl::desc("Print the list of headers to insert and remove"),
|
|
cl::cat(IncludeCleaner),
|
|
};
|
|
|
|
cl::opt<bool> Edit{
|
|
"edit",
|
|
cl::desc("Apply edits to analyzed source files"),
|
|
cl::cat(IncludeCleaner),
|
|
};
|
|
|
|
cl::opt<bool> Insert{
|
|
"insert",
|
|
cl::desc("Allow header insertions"),
|
|
cl::init(true),
|
|
};
|
|
cl::opt<bool> Remove{
|
|
"remove",
|
|
cl::desc("Allow header removals"),
|
|
cl::init(true),
|
|
};
|
|
|
|
std::atomic<unsigned> Errors = ATOMIC_VAR_INIT(0);
|
|
|
|
format::FormatStyle getStyle(llvm::StringRef Filename) {
|
|
auto S = format::getStyle(format::DefaultFormatStyle, Filename,
|
|
format::DefaultFallbackStyle);
|
|
if (!S || !S->isCpp()) {
|
|
consumeError(S.takeError());
|
|
return format::getLLVMStyle();
|
|
}
|
|
return std::move(*S);
|
|
}
|
|
|
|
class Action : public clang::ASTFrontendAction {
|
|
RecordedAST AST;
|
|
RecordedPP PP;
|
|
PragmaIncludes PI;
|
|
|
|
void ExecuteAction() override {
|
|
auto &P = getCompilerInstance().getPreprocessor();
|
|
P.addPPCallbacks(PP.record(P));
|
|
PI.record(getCompilerInstance());
|
|
ASTFrontendAction::ExecuteAction();
|
|
}
|
|
|
|
std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
|
|
StringRef File) override {
|
|
return AST.record();
|
|
}
|
|
|
|
void EndSourceFile() override {
|
|
if (!HTMLReportPath.empty())
|
|
writeHTML();
|
|
|
|
const auto &SM = getCompilerInstance().getSourceManager();
|
|
auto &HS = getCompilerInstance().getPreprocessor().getHeaderSearchInfo();
|
|
llvm::StringRef Path =
|
|
SM.getFileEntryForID(SM.getMainFileID())->tryGetRealPathName();
|
|
assert(!Path.empty() && "Main file path not known?");
|
|
llvm::StringRef Code = SM.getBufferData(SM.getMainFileID());
|
|
|
|
auto Results =
|
|
analyze(AST.Roots, PP.MacroReferences, PP.Includes, &PI, SM, HS);
|
|
if (!Insert)
|
|
Results.Missing.clear();
|
|
if (!Remove)
|
|
Results.Unused.clear();
|
|
std::string Final = fixIncludes(Results, Code, getStyle(Path));
|
|
|
|
if (Print.getNumOccurrences()) {
|
|
switch (Print) {
|
|
case PrintStyle::Changes:
|
|
for (const Include *I : Results.Unused)
|
|
llvm::outs() << "- " << I->quote() << "\n";
|
|
for (const auto &I : Results.Missing)
|
|
llvm::outs() << "+ " << I << "\n";
|
|
break;
|
|
case PrintStyle::Final:
|
|
llvm::outs() << Final;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (Edit) {
|
|
if (auto Err = llvm::writeToOutput(
|
|
Path, [&](llvm::raw_ostream &OS) -> llvm::Error {
|
|
OS << Final;
|
|
return llvm::Error::success();
|
|
})) {
|
|
llvm::errs() << "Failed to apply edits to " << Path << ": "
|
|
<< toString(std::move(Err)) << "\n";
|
|
++Errors;
|
|
}
|
|
}
|
|
}
|
|
|
|
void writeHTML() {
|
|
std::error_code EC;
|
|
llvm::raw_fd_ostream OS(HTMLReportPath, EC);
|
|
if (EC) {
|
|
llvm::errs() << "Unable to write HTML report to " << HTMLReportPath
|
|
<< ": " << EC.message() << "\n";
|
|
++Errors;
|
|
return;
|
|
}
|
|
writeHTMLReport(
|
|
AST.Ctx->getSourceManager().getMainFileID(), PP.Includes, AST.Roots,
|
|
PP.MacroReferences, *AST.Ctx,
|
|
getCompilerInstance().getPreprocessor().getHeaderSearchInfo(), &PI, OS);
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
} // namespace include_cleaner
|
|
} // namespace clang
|
|
|
|
int main(int argc, const char **argv) {
|
|
using namespace clang::include_cleaner;
|
|
|
|
llvm::sys::PrintStackTraceOnErrorSignal(argv[0]);
|
|
auto OptionsParser =
|
|
clang::tooling::CommonOptionsParser::create(argc, argv, IncludeCleaner);
|
|
if (!OptionsParser) {
|
|
llvm::errs() << toString(OptionsParser.takeError());
|
|
return 1;
|
|
}
|
|
|
|
if (OptionsParser->getSourcePathList().size() != 1) {
|
|
std::vector<cl::Option *> IncompatibleFlags = {&HTMLReportPath, &Print};
|
|
for (const auto *Flag : IncompatibleFlags) {
|
|
if (Flag->getNumOccurrences())
|
|
llvm::errs() << "-" << Flag->ArgStr << " requires a single input file";
|
|
return 1;
|
|
}
|
|
}
|
|
auto Factory = clang::tooling::newFrontendActionFactory<Action>();
|
|
return clang::tooling::ClangTool(OptionsParser->getCompilations(),
|
|
OptionsParser->getSourcePathList())
|
|
.run(Factory.get()) ||
|
|
Errors != 0;
|
|
}
|