11#include "IncludeFixer.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"
36template <>
struct MappingTraits<
tooling::Range> {
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);
59 io.mapRequired(
"Header", Info.Header);
60 io.mapRequired(
"QualifiedName", Info.QualifiedName);
66 io.mapRequired(
"RawIdentifier", Info.RawIdentifier);
67 io.mapRequired(
"Range", Info.Range);
73 IO.mapRequired(
"QuerySymbolInfos", Context.QuerySymbolInfos);
74 IO.mapRequired(
"HeaderInfos", Context.HeaderInfos);
75 IO.mapRequired(
"FilePath", Context.FilePath);
82cl::OptionCategory IncludeFixerCategory(
"Tool options");
84enum DatabaseFormatTy {
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));
97cl::opt<std::string> Input(
"input",
98 cl::desc(
"String to initialize the database"),
99 cl::cat(IncludeFixerCategory));
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));
108 MinimizeIncludePaths(
"minimize-paths",
109 cl::desc(
"Whether to minimize added include paths"),
110 cl::init(
true), cl::cat(IncludeFixerCategory));
112cl::opt<bool>
Quiet(
"q", cl::desc(
"Reduce terminal output"), cl::init(
false),
113 cl::cat(IncludeFixerCategory));
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));
124cl::opt<bool> OutputHeaders(
126 cl::desc(
"Print the symbol being queried and all its relevant headers in\n"
127 "JSON format to stdout:\n"
129 " \"FilePath\": \"/path/to/foo.cc\",\n"
130 " \"QuerySymbolInfos\": [\n"
131 " {\"RawIdentifier\": \"foo\",\n"
132 " \"Range\": {\"Offset\": 0, \"Length\": 3}}\n"
134 " \"HeaderInfos\": [ {\"Header\": \"\\\"foo_a.h\\\"\",\n"
135 " \"QualifiedName\": \"a::foo\"} ]\n"
137 cl::init(
false), cl::cat(IncludeFixerCategory));
139cl::opt<std::string> InsertHeader(
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"
150 " HeaderInfos: [ {Headers: \"\\\"foo_a.h\\\"\",\n"
151 " QualifiedName: \"a::foo\"} ]}\""),
152 cl::init(
""), cl::cat(IncludeFixerCategory));
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));
160std::unique_ptr<include_fixer::SymbolIndexManager>
161createSymbolIndexManager(StringRef FilePath) {
164 auto SymbolIndexMgr = std::make_unique<include_fixer::SymbolIndexManager>();
165 switch (DatabaseFormat) {
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)
180 CommaSplits[I].trim(), {}),
185 SymbolIndexMgr->addSymbolIndex([=]() {
186 return std::make_unique<include_fixer::InMemorySymbolIndex>(Symbols);
191 auto CreateYamlIdx = [=]() -> std::unique_ptr<include_fixer::SymbolIndex> {
192 llvm::ErrorOr<std::unique_ptr<include_fixer::YamlSymbolIndex>> DB(
194 if (!Input.empty()) {
200 SmallString<128> AbsolutePath(tooling::getAbsolutePath(FilePath));
201 StringRef
Directory = llvm::sys::path::parent_path(AbsolutePath);
207 llvm::errs() <<
"Couldn't find YAML db: " << DB.getError().message()
211 return std::move(*DB);
214 SymbolIndexMgr->addSymbolIndex(std::move(CreateYamlIdx));
220 SymbolIndexMgr->addSymbolIndex(
221 []() -> std::unique_ptr<include_fixer::SymbolIndex> {
224 llvm::errs() <<
"Couldn't load fuzzy YAML db: "
225 << llvm::toString(DB.takeError()) <<
'\n';
228 return std::move(*DB);
233 return SymbolIndexMgr;
238 <<
" \"FilePath\": \""
239 << llvm::yaml::escape(Context.
getFilePath()) <<
"\",\n"
240 <<
" \"QuerySymbolInfos\": [\n";
242 OS <<
" {\"RawIdentifier\": \"" <<
Info.RawIdentifier <<
"\",\n";
243 OS <<
" \"Range\":{";
244 OS <<
"\"Offset\":" <<
Info.Range.getOffset() <<
",";
245 OS <<
"\"Length\":" <<
Info.Range.getLength() <<
"}}";
250 OS <<
" \"HeaderInfos\": [\n";
252 for (
const auto &Info : HeaderInfos) {
253 OS <<
" {\"Header\": \"" << llvm::yaml::escape(
Info.Header) <<
"\",\n"
254 <<
" \"QualifiedName\": \"" <<
Info.QualifiedName <<
"\"}";
255 if (&Info != &HeaderInfos.back())
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();
270 tooling::CommonOptionsParser &options = ExpectedParser.get();
271 tooling::ClangTool tool(options.getCompilations(),
272 options.getSourcePathList());
274 llvm::StringRef SourceFilePath = options.getSourcePathList().front();
278 std::unique_ptr<llvm::MemoryBuffer> Code;
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";
288 Code = std::move(CodeOrErr.get());
289 if (Code->getBufferSize() == 0)
292 tool.mapVirtualFile(SourceFilePath, Code->getBuffer());
295 if (!InsertHeader.empty()) {
297 errs() <<
"Should be running in STDIN mode\n";
301 llvm::yaml::Input yin(InsertHeader);
306 assert(!HeaderInfos.empty());
309 bool IsUniqueHeader = std::equal(
310 HeaderInfos.begin()+1, HeaderInfos.end(), HeaderInfos.begin(),
313 return LHS.Header == RHS.Header;
315 if (!IsUniqueHeader) {
316 errs() <<
"Expect exactly one unique header.\n";
324 bool IsUniqueQualifiedName = std::equal(
325 HeaderInfos.begin() + 1, HeaderInfos.end(), HeaderInfos.begin(),
328 return LHS.QualifiedName == RHS.QualifiedName;
330 auto InsertStyle = format::getStyle(format::DefaultFormatStyle,
333 llvm::errs() << llvm::toString(InsertStyle.takeError()) <<
"\n";
337 Code->getBuffer(), Context, *InsertStyle,
338 IsUniqueQualifiedName);
340 errs() <<
"Failed to create replacements: "
341 << llvm::toString(Replacements.takeError()) <<
"\n";
346 tooling::applyAllReplacements(Code->getBuffer(), *Replacements);
348 llvm::errs() << llvm::toString(ChangedCode.takeError()) <<
"\n";
351 llvm::outs() << *ChangedCode;
356 std::unique_ptr<include_fixer::SymbolIndexManager> SymbolIndexMgr =
357 createSymbolIndexManager(SourceFilePath);
362 if (!QuerySymbol.empty()) {
363 auto MatchedSymbols = SymbolIndexMgr->search(
364 QuerySymbol,
true, SourceFilePath);
365 for (
auto &Symbol : MatchedSymbols) {
366 std::string HeaderPath = Symbol.getFilePath().str();
367 Symbol.SetFilePath(((HeaderPath[0] ==
'"' || HeaderPath[0] ==
'<')
369 :
"\"" + HeaderPath +
"\""));
379 writeToJson(llvm::outs(), Context);
384 std::vector<include_fixer::IncludeFixerContext> Contexts;
386 Style, MinimizeIncludePaths);
388 if (tool.run(&Factory) != 0) {
394 llvm::errs() <<
"Fatal compiler error occurred while parsing file!"
395 " (incorrect include paths?)\n";
399 assert(!Contexts.empty());
403 writeToJson(llvm::outs(), Contexts.front());
407 std::vector<tooling::Replacements> FixerReplacements;
408 for (
const auto &Context : Contexts) {
411 format::getStyle(format::DefaultFormatStyle, FilePath, Style);
413 llvm::errs() << llvm::toString(InsertStyle.takeError()) <<
"\n";
416 auto Buffer = llvm::MemoryBuffer::getFile(FilePath,
true);
418 errs() <<
"Couldn't open file: " + FilePath.str() +
": "
419 << Buffer.getError().message() +
"\n";
424 Buffer.get()->getBuffer(), Context, *InsertStyle);
426 errs() <<
"Failed to create replacement: "
427 << llvm::toString(Replacements.takeError()) <<
"\n";
430 FixerReplacements.push_back(*Replacements);
434 for (
const auto &Context : Contexts) {
436 llvm::errs() <<
"Added #include "
444 assert(FixerReplacements.size() == 1);
445 auto ChangedCode = tooling::applyAllReplacements(Code->getBuffer(),
446 FixerReplacements.front());
448 llvm::errs() << llvm::toString(ChangedCode.takeError()) <<
"\n";
451 llvm::outs() << *ChangedCode;
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);
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";
470 return Rewrites.overwriteChangedFiles();
475int main(
int argc,
const char **argv) {
476 return includeFixerMain(argc, argv);
static cl::opt< std::string > Directory(cl::Positional, cl::Required, cl::desc("<Search Root Directory>"))
int main(int argc, const char **argv)
static cl::opt< bool > Quiet("quiet", desc(R"(
Run clang-tidy in quiet mode. This suppresses
printing statistics about ignored warnings and
warnings treated as errors if the respective
options are specified.
)"), cl::init(false), cl::cat(ClangTidyCategory))
clang::find_all_symbols::SymbolInfo SymbolInfo
Describes a named symbol from a header.
static llvm::Expected< std::unique_ptr< FuzzySymbolIndex > > createFromYAML(llvm::StringRef File)
A context for a file being processed.
const std::vector< HeaderInfo > & getHeaderInfos() const
Get header information.
StringRef getFilePath() const
Get the file path to the file being processed.
const std::vector< QuerySymbolInfo > & getQuerySymbolInfos() const
Get information of symbols being querid.
static llvm::ErrorOr< std::unique_ptr< YamlSymbolIndex > > createFromFile(llvm::StringRef FilePath)
Create a new Yaml db from a file.
static llvm::ErrorOr< std::unique_ptr< YamlSymbolIndex > > createFromDirectory(llvm::StringRef Directory, llvm::StringRef Name)
Look for a file called Name in Directory and all parent directories.
@ Info
An information message.
llvm::Expected< tooling::Replacements > createIncludeFixerReplacements(StringRef Code, const IncludeFixerContext &Context, const clang::format::FormatStyle &Style, bool AddQualifiers)
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Some operations such as code completion produce a set of candidates.
std::string RawIdentifier
The raw symbol name being queried in database.
static void mapping(IO &IO, IncludeFixerContext &Context)
static void mapping(IO &io, IncludeFixerContext::QuerySymbolInfo &Info)