478 lines · cpp
1//===-- ClangIncludeFixer.cpp - Standalone include fixer ------------------===//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-exception6//7//===----------------------------------------------------------------------===//8 9#include "FuzzySymbolIndex.h"10#include "InMemorySymbolIndex.h"11#include "IncludeFixer.h"12#include "IncludeFixerContext.h"13#include "SymbolIndexManager.h"14#include "YamlSymbolIndex.h"15#include "clang/Format/Format.h"16#include "clang/Frontend/TextDiagnosticPrinter.h"17#include "clang/Rewrite/Core/Rewriter.h"18#include "clang/Tooling/CommonOptionsParser.h"19#include "clang/Tooling/Core/Replacement.h"20#include "clang/Tooling/Tooling.h"21#include "llvm/Support/CommandLine.h"22#include "llvm/Support/Path.h"23#include "llvm/Support/YAMLTraits.h"24 25using namespace clang;26using namespace llvm;27using clang::include_fixer::IncludeFixerContext;28 29LLVM_YAML_IS_DOCUMENT_LIST_VECTOR(IncludeFixerContext)30LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(IncludeFixerContext::HeaderInfo)31LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(IncludeFixerContext::QuerySymbolInfo)32 33namespace llvm {34namespace yaml {35 36template <> struct MappingTraits<tooling::Range> {37 struct NormalizedRange {38 NormalizedRange(const IO &) : Offset(0), Length(0) {}39 40 NormalizedRange(const IO &, const tooling::Range &R)41 : Offset(R.getOffset()), Length(R.getLength()) {}42 43 tooling::Range denormalize(const IO &) {44 return tooling::Range(Offset, Length);45 }46 47 unsigned Offset;48 unsigned Length;49 };50 static void mapping(IO &IO, tooling::Range &Info) {51 MappingNormalization<NormalizedRange, tooling::Range> Keys(IO, Info);52 IO.mapRequired("Offset", Keys->Offset);53 IO.mapRequired("Length", Keys->Length);54 }55};56 57template <> struct MappingTraits<IncludeFixerContext::HeaderInfo> {58 static void mapping(IO &io, IncludeFixerContext::HeaderInfo &Info) {59 io.mapRequired("Header", Info.Header);60 io.mapRequired("QualifiedName", Info.QualifiedName);61 }62};63 64template <> struct MappingTraits<IncludeFixerContext::QuerySymbolInfo> {65 static void mapping(IO &io, IncludeFixerContext::QuerySymbolInfo &Info) {66 io.mapRequired("RawIdentifier", Info.RawIdentifier);67 io.mapRequired("Range", Info.Range);68 }69};70 71template <> struct MappingTraits<IncludeFixerContext> {72 static void mapping(IO &IO, IncludeFixerContext &Context) {73 IO.mapRequired("QuerySymbolInfos", Context.QuerySymbolInfos);74 IO.mapRequired("HeaderInfos", Context.HeaderInfos);75 IO.mapRequired("FilePath", Context.FilePath);76 }77};78} // namespace yaml79} // namespace llvm80 81namespace {82cl::OptionCategory IncludeFixerCategory("Tool options");83 84enum DatabaseFormatTy {85 fixed, ///< Hard-coded mapping.86 yaml, ///< Yaml database created by find-all-symbols.87 fuzzyYaml, ///< Yaml database with fuzzy-matched identifiers.88};89 90cl::opt<DatabaseFormatTy> DatabaseFormat(91 "db", cl::desc("Specify input format"),92 cl::values(clEnumVal(fixed, "Hard-coded mapping"),93 clEnumVal(yaml, "Yaml database created by find-all-symbols"),94 clEnumVal(fuzzyYaml, "Yaml database, with fuzzy-matched names")),95 cl::init(yaml), cl::cat(IncludeFixerCategory));96 97cl::opt<std::string> Input("input",98 cl::desc("String to initialize the database"),99 cl::cat(IncludeFixerCategory));100 101cl::opt<std::string>102 QuerySymbol("query-symbol",103 cl::desc("Query a given symbol (e.g. \"a::b::foo\") in\n"104 "database directly without parsing the file."),105 cl::cat(IncludeFixerCategory));106 107cl::opt<bool>108 MinimizeIncludePaths("minimize-paths",109 cl::desc("Whether to minimize added include paths"),110 cl::init(true), cl::cat(IncludeFixerCategory));111 112cl::opt<bool> Quiet("q", cl::desc("Reduce terminal output"), cl::init(false),113 cl::cat(IncludeFixerCategory));114 115cl::opt<bool>116 STDINMode("stdin",117 cl::desc("Override source file's content (in the overlaying\n"118 "virtual file system) with input from <stdin> and run\n"119 "the tool on the new content with the compilation\n"120 "options of the source file. This mode is currently\n"121 "used for editor integration."),122 cl::init(false), cl::cat(IncludeFixerCategory));123 124cl::opt<bool> OutputHeaders(125 "output-headers",126 cl::desc("Print the symbol being queried and all its relevant headers in\n"127 "JSON format to stdout:\n"128 " {\n"129 " \"FilePath\": \"/path/to/foo.cc\",\n"130 " \"QuerySymbolInfos\": [\n"131 " {\"RawIdentifier\": \"foo\",\n"132 " \"Range\": {\"Offset\": 0, \"Length\": 3}}\n"133 " ],\n"134 " \"HeaderInfos\": [ {\"Header\": \"\\\"foo_a.h\\\"\",\n"135 " \"QualifiedName\": \"a::foo\"} ]\n"136 " }"),137 cl::init(false), cl::cat(IncludeFixerCategory));138 139cl::opt<std::string> InsertHeader(140 "insert-header",141 cl::desc("Insert a specific header. This should run with STDIN mode.\n"142 "The result is written to stdout. It is currently used for\n"143 "editor integration. Support YAML/JSON format:\n"144 " -insert-header=\"{\n"145 " FilePath: \"/path/to/foo.cc\",\n"146 " QuerySymbolInfos: [\n"147 " {RawIdentifier: foo,\n"148 " Range: {Offset: 0, Length: 3}}\n"149 " ],\n"150 " HeaderInfos: [ {Headers: \"\\\"foo_a.h\\\"\",\n"151 " QualifiedName: \"a::foo\"} ]}\""),152 cl::init(""), cl::cat(IncludeFixerCategory));153 154cl::opt<std::string>155 Style("style",156 cl::desc("Fallback style for reformatting after inserting new\n"157 "headers if there is no clang-format config file found."),158 cl::init("llvm"), cl::cat(IncludeFixerCategory));159 160std::unique_ptr<include_fixer::SymbolIndexManager>161createSymbolIndexManager(StringRef FilePath) {162 using find_all_symbols::SymbolInfo;163 164 auto SymbolIndexMgr = std::make_unique<include_fixer::SymbolIndexManager>();165 switch (DatabaseFormat) {166 case fixed: {167 // Parse input and fill the database with it.168 // <symbol>=<header><, header...>169 // Multiple symbols can be given, separated by semicolons.170 SmallVector<StringRef, 4> SemicolonSplits;171 StringRef(Input).split(SemicolonSplits, ";");172 std::vector<find_all_symbols::SymbolAndSignals> Symbols;173 for (StringRef Pair : SemicolonSplits) {174 auto Split = Pair.split('=');175 SmallVector<StringRef, 4> CommaSplits;176 Split.second.split(CommaSplits, ",");177 for (size_t I = 0, E = CommaSplits.size(); I != E; ++I)178 Symbols.push_back(179 {SymbolInfo(Split.first.trim(), SymbolInfo::SymbolKind::Unknown,180 CommaSplits[I].trim(), {}),181 // Use fake "seen" signal for tests, so first header wins.182 SymbolInfo::Signals(/*Seen=*/static_cast<unsigned>(E - I),183 /*Used=*/0)});184 }185 SymbolIndexMgr->addSymbolIndex([=]() {186 return std::make_unique<include_fixer::InMemorySymbolIndex>(Symbols);187 });188 break;189 }190 case yaml: {191 auto CreateYamlIdx = [=]() -> std::unique_ptr<include_fixer::SymbolIndex> {192 llvm::ErrorOr<std::unique_ptr<include_fixer::YamlSymbolIndex>> DB(193 nullptr);194 if (!Input.empty()) {195 DB = include_fixer::YamlSymbolIndex::createFromFile(Input);196 } else {197 // If we don't have any input file, look in the directory of the198 // first199 // file and its parents.200 SmallString<128> AbsolutePath(tooling::getAbsolutePath(FilePath));201 StringRef Directory = llvm::sys::path::parent_path(AbsolutePath);202 DB = include_fixer::YamlSymbolIndex::createFromDirectory(203 Directory, "find_all_symbols_db.yaml");204 }205 206 if (!DB) {207 llvm::errs() << "Couldn't find YAML db: " << DB.getError().message()208 << '\n';209 return nullptr;210 }211 return std::move(*DB);212 };213 214 SymbolIndexMgr->addSymbolIndex(std::move(CreateYamlIdx));215 break;216 }217 case fuzzyYaml: {218 // This mode is not very useful, because we don't correct the identifier.219 // It's main purpose is to expose FuzzySymbolIndex to tests.220 SymbolIndexMgr->addSymbolIndex(221 []() -> std::unique_ptr<include_fixer::SymbolIndex> {222 auto DB = include_fixer::FuzzySymbolIndex::createFromYAML(Input);223 if (!DB) {224 llvm::errs() << "Couldn't load fuzzy YAML db: "225 << llvm::toString(DB.takeError()) << '\n';226 return nullptr;227 }228 return std::move(*DB);229 });230 break;231 }232 }233 return SymbolIndexMgr;234}235 236void writeToJson(llvm::raw_ostream &OS, const IncludeFixerContext& Context) {237 OS << "{\n"238 << " \"FilePath\": \""239 << llvm::yaml::escape(Context.getFilePath()) << "\",\n"240 << " \"QuerySymbolInfos\": [\n";241 for (const auto &Info : Context.getQuerySymbolInfos()) {242 OS << " {\"RawIdentifier\": \"" << Info.RawIdentifier << "\",\n";243 OS << " \"Range\":{";244 OS << "\"Offset\":" << Info.Range.getOffset() << ",";245 OS << "\"Length\":" << Info.Range.getLength() << "}}";246 if (&Info != &Context.getQuerySymbolInfos().back())247 OS << ",\n";248 }249 OS << "\n ],\n";250 OS << " \"HeaderInfos\": [\n";251 const auto &HeaderInfos = Context.getHeaderInfos();252 for (const auto &Info : HeaderInfos) {253 OS << " {\"Header\": \"" << llvm::yaml::escape(Info.Header) << "\",\n"254 << " \"QualifiedName\": \"" << Info.QualifiedName << "\"}";255 if (&Info != &HeaderInfos.back())256 OS << ",\n";257 }258 OS << "\n";259 OS << " ]\n";260 OS << "}\n";261}262 263int includeFixerMain(int argc, const char **argv) {264 auto ExpectedParser =265 tooling::CommonOptionsParser::create(argc, argv, IncludeFixerCategory);266 if (!ExpectedParser) {267 llvm::errs() << ExpectedParser.takeError();268 return 1;269 }270 tooling::CommonOptionsParser &options = ExpectedParser.get();271 tooling::ClangTool tool(options.getCompilations(),272 options.getSourcePathList());273 274 llvm::StringRef SourceFilePath = options.getSourcePathList().front();275 // In STDINMode, we override the file content with the <stdin> input.276 // Since `tool.mapVirtualFile` takes `StringRef`, we define `Code` outside of277 // the if-block so that `Code` is not released after the if-block.278 std::unique_ptr<llvm::MemoryBuffer> Code;279 if (STDINMode) {280 assert(options.getSourcePathList().size() == 1 &&281 "Expect exactly one file path in STDINMode.");282 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> CodeOrErr =283 MemoryBuffer::getSTDIN();284 if (std::error_code EC = CodeOrErr.getError()) {285 errs() << EC.message() << "\n";286 return 1;287 }288 Code = std::move(CodeOrErr.get());289 if (Code->getBufferSize() == 0)290 return 0; // Skip empty files.291 292 tool.mapVirtualFile(SourceFilePath, Code->getBuffer());293 }294 295 if (!InsertHeader.empty()) {296 if (!STDINMode) {297 errs() << "Should be running in STDIN mode\n";298 return 1;299 }300 301 llvm::yaml::Input yin(InsertHeader);302 IncludeFixerContext Context;303 yin >> Context;304 305 const auto &HeaderInfos = Context.getHeaderInfos();306 assert(!HeaderInfos.empty());307 // We only accept one unique header.308 // Check all elements in HeaderInfos have the same header.309 bool IsUniqueHeader = std::equal(310 HeaderInfos.begin()+1, HeaderInfos.end(), HeaderInfos.begin(),311 [](const IncludeFixerContext::HeaderInfo &LHS,312 const IncludeFixerContext::HeaderInfo &RHS) {313 return LHS.Header == RHS.Header;314 });315 if (!IsUniqueHeader) {316 errs() << "Expect exactly one unique header.\n";317 return 1;318 }319 320 // If a header has multiple symbols, we won't add the missing namespace321 // qualifiers because we don't know which one is exactly used.322 //323 // Check whether all elements in HeaderInfos have the same qualified name.324 bool IsUniqueQualifiedName = std::equal(325 HeaderInfos.begin() + 1, HeaderInfos.end(), HeaderInfos.begin(),326 [](const IncludeFixerContext::HeaderInfo &LHS,327 const IncludeFixerContext::HeaderInfo &RHS) {328 return LHS.QualifiedName == RHS.QualifiedName;329 });330 auto InsertStyle = format::getStyle(format::DefaultFormatStyle,331 Context.getFilePath(), Style);332 if (!InsertStyle) {333 llvm::errs() << llvm::toString(InsertStyle.takeError()) << "\n";334 return 1;335 }336 auto Replacements = clang::include_fixer::createIncludeFixerReplacements(337 Code->getBuffer(), Context, *InsertStyle,338 /*AddQualifiers=*/IsUniqueQualifiedName);339 if (!Replacements) {340 errs() << "Failed to create replacements: "341 << llvm::toString(Replacements.takeError()) << "\n";342 return 1;343 }344 345 auto ChangedCode =346 tooling::applyAllReplacements(Code->getBuffer(), *Replacements);347 if (!ChangedCode) {348 llvm::errs() << llvm::toString(ChangedCode.takeError()) << "\n";349 return 1;350 }351 llvm::outs() << *ChangedCode;352 return 0;353 }354 355 // Set up data source.356 std::unique_ptr<include_fixer::SymbolIndexManager> SymbolIndexMgr =357 createSymbolIndexManager(SourceFilePath);358 if (!SymbolIndexMgr)359 return 1;360 361 // Query symbol mode.362 if (!QuerySymbol.empty()) {363 auto MatchedSymbols = SymbolIndexMgr->search(364 QuerySymbol, /*IsNestedSearch=*/true, SourceFilePath);365 for (auto &Symbol : MatchedSymbols) {366 std::string HeaderPath = Symbol.getFilePath().str();367 Symbol.SetFilePath(((HeaderPath[0] == '"' || HeaderPath[0] == '<')368 ? HeaderPath369 : "\"" + HeaderPath + "\""));370 }371 372 // We leave an empty symbol range as we don't know the range of the symbol373 // being queried in this mode. clang-include-fixer won't add namespace374 // qualifiers if the symbol range is empty, which also fits this case.375 IncludeFixerContext::QuerySymbolInfo Symbol;376 Symbol.RawIdentifier = QuerySymbol;377 auto Context =378 IncludeFixerContext(SourceFilePath, {Symbol}, MatchedSymbols);379 writeToJson(llvm::outs(), Context);380 return 0;381 }382 383 // Now run our tool.384 std::vector<include_fixer::IncludeFixerContext> Contexts;385 include_fixer::IncludeFixerActionFactory Factory(*SymbolIndexMgr, Contexts,386 Style, MinimizeIncludePaths);387 388 if (tool.run(&Factory) != 0) {389 // We suppress all Clang diagnostics (because they would be wrong,390 // clang-include-fixer does custom recovery) but still want to give some391 // feedback in case there was a compiler error we couldn't recover from.392 // The most common case for this is a #include in the file that couldn't be393 // found.394 llvm::errs() << "Fatal compiler error occurred while parsing file!"395 " (incorrect include paths?)\n";396 return 1;397 }398 399 assert(!Contexts.empty());400 401 if (OutputHeaders) {402 // FIXME: Print contexts of all processing files instead of the first one.403 writeToJson(llvm::outs(), Contexts.front());404 return 0;405 }406 407 std::vector<tooling::Replacements> FixerReplacements;408 for (const auto &Context : Contexts) {409 StringRef FilePath = Context.getFilePath();410 auto InsertStyle =411 format::getStyle(format::DefaultFormatStyle, FilePath, Style);412 if (!InsertStyle) {413 llvm::errs() << llvm::toString(InsertStyle.takeError()) << "\n";414 return 1;415 }416 auto Buffer = llvm::MemoryBuffer::getFile(FilePath, /*IsText=*/true);417 if (!Buffer) {418 errs() << "Couldn't open file: " + FilePath.str() + ": "419 << Buffer.getError().message() + "\n";420 return 1;421 }422 423 auto Replacements = clang::include_fixer::createIncludeFixerReplacements(424 Buffer.get()->getBuffer(), Context, *InsertStyle);425 if (!Replacements) {426 errs() << "Failed to create replacement: "427 << llvm::toString(Replacements.takeError()) << "\n";428 return 1;429 }430 FixerReplacements.push_back(*Replacements);431 }432 433 if (!Quiet) {434 for (const auto &Context : Contexts) {435 if (!Context.getHeaderInfos().empty()) {436 llvm::errs() << "Added #include "437 << Context.getHeaderInfos().front().Header << " for "438 << Context.getFilePath() << "\n";439 }440 }441 }442 443 if (STDINMode) {444 assert(FixerReplacements.size() == 1);445 auto ChangedCode = tooling::applyAllReplacements(Code->getBuffer(),446 FixerReplacements.front());447 if (!ChangedCode) {448 llvm::errs() << llvm::toString(ChangedCode.takeError()) << "\n";449 return 1;450 }451 llvm::outs() << *ChangedCode;452 return 0;453 }454 455 // Set up a new source manager for applying the resulting replacements.456 DiagnosticOptions DiagOpts;457 DiagnosticsEngine Diagnostics(DiagnosticIDs::create(), DiagOpts);458 TextDiagnosticPrinter DiagnosticPrinter(outs(), DiagOpts);459 SourceManager SM(Diagnostics, tool.getFiles());460 Diagnostics.setClient(&DiagnosticPrinter, false);461 462 // Write replacements to disk.463 Rewriter Rewrites(SM, LangOptions());464 for (const auto &Replacement : FixerReplacements) {465 if (!tooling::applyAllReplacements(Replacement, Rewrites)) {466 llvm::errs() << "Failed to apply replacements.\n";467 return 1;468 }469 }470 return Rewrites.overwriteChangedFiles();471}472 473} // namespace474 475int main(int argc, const char **argv) {476 return includeFixerMain(argc, argv);477}478