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"
30LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(IncludeFixerContext::HeaderInfo)
31LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(IncludeFixerContext::QuerySymbolInfo)
36template <>
struct MappingTraits<tooling::
Range> {
37 struct NormalizedRange {
51 MappingNormalization<NormalizedRange, tooling::Range> Keys(IO,
Info);
52 IO.mapRequired(
"Offset", Keys->Offset);
53 IO.mapRequired(
"Length", Keys->Length);
58 static void mapping(IO &io, IncludeFixerContext::HeaderInfo &
Info) {
59 io.mapRequired(
"Header",
Info.Header);
60 io.mapRequired(
"QualifiedName",
Info.QualifiedName);
65 static void mapping(IO &io, IncludeFixerContext::QuerySymbolInfo &
Info) {
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 std::map<std::string, std::vector<std::string>> SymbolsMap;
171 SmallVector<StringRef, 4> SemicolonSplits;
172 StringRef(Input).split(SemicolonSplits,
";");
173 std::vector<find_all_symbols::SymbolAndSignals> Symbols;
174 for (StringRef Pair : SemicolonSplits) {
175 auto Split = Pair.split(
'=');
176 std::vector<std::string> Headers;
177 SmallVector<StringRef, 4> CommaSplits;
178 Split.second.split(CommaSplits,
",");
179 for (
size_t I = 0,
E = CommaSplits.size(); I !=
E; ++I)
182 CommaSplits[I].trim(), {}),
187 SymbolIndexMgr->addSymbolIndex([=]() {
188 return std::make_unique<include_fixer::InMemorySymbolIndex>(Symbols);
193 auto CreateYamlIdx = [=]() -> std::unique_ptr<include_fixer::SymbolIndex> {
194 llvm::ErrorOr<std::unique_ptr<include_fixer::YamlSymbolIndex>> DB(
196 if (!Input.empty()) {
202 SmallString<128> AbsolutePath(tooling::getAbsolutePath(FilePath));
203 StringRef
Directory = llvm::sys::path::parent_path(AbsolutePath);
209 llvm::errs() <<
"Couldn't find YAML db: " << DB.getError().message()
213 return std::move(*DB);
216 SymbolIndexMgr->addSymbolIndex(std::move(CreateYamlIdx));
222 SymbolIndexMgr->addSymbolIndex(
223 []() -> std::unique_ptr<include_fixer::SymbolIndex> {
226 llvm::errs() <<
"Couldn't load fuzzy YAML db: "
227 << llvm::toString(DB.takeError()) <<
'\n';
230 return std::move(*DB);
235 return SymbolIndexMgr;
240 <<
" \"FilePath\": \""
241 << llvm::yaml::escape(Context.getFilePath()) <<
"\",\n"
242 <<
" \"QuerySymbolInfos\": [\n";
243 for (
const auto &
Info : Context.getQuerySymbolInfos()) {
244 OS <<
" {\"RawIdentifier\": \"" <<
Info.RawIdentifier <<
"\",\n";
245 OS <<
" \"Range\":{";
246 OS <<
"\"Offset\":" <<
Info.Range.getOffset() <<
",";
247 OS <<
"\"Length\":" <<
Info.Range.getLength() <<
"}}";
248 if (&
Info != &Context.getQuerySymbolInfos().back())
252 OS <<
" \"HeaderInfos\": [\n";
253 const auto &HeaderInfos = Context.getHeaderInfos();
254 for (
const auto &
Info : HeaderInfos) {
255 OS <<
" {\"Header\": \"" << llvm::yaml::escape(
Info.Header) <<
"\",\n"
256 <<
" \"QualifiedName\": \"" <<
Info.QualifiedName <<
"\"}";
257 if (&
Info != &HeaderInfos.back())
265int includeFixerMain(
int argc,
const char **argv) {
266 auto ExpectedParser =
267 tooling::CommonOptionsParser::create(argc, argv, IncludeFixerCategory);
268 if (!ExpectedParser) {
269 llvm::errs() << ExpectedParser.takeError();
272 tooling::CommonOptionsParser &options = ExpectedParser.get();
273 tooling::ClangTool tool(options.getCompilations(),
274 options.getSourcePathList());
276 llvm::StringRef SourceFilePath = options.getSourcePathList().front();
280 std::unique_ptr<llvm::MemoryBuffer>
Code;
282 assert(options.getSourcePathList().size() == 1 &&
283 "Expect exactly one file path in STDINMode.");
284 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> CodeOrErr =
285 MemoryBuffer::getSTDIN();
286 if (std::error_code EC = CodeOrErr.getError()) {
287 errs() << EC.message() <<
"\n";
290 Code = std::move(CodeOrErr.get());
291 if (
Code->getBufferSize() == 0)
294 tool.mapVirtualFile(SourceFilePath,
Code->getBuffer());
297 if (!InsertHeader.empty()) {
299 errs() <<
"Should be running in STDIN mode\n";
303 llvm::yaml::Input yin(InsertHeader);
307 const auto &HeaderInfos = Context.getHeaderInfos();
308 assert(!HeaderInfos.empty());
311 bool IsUniqueHeader = std::equal(
312 HeaderInfos.begin()+1, HeaderInfos.end(), HeaderInfos.begin(),
313 [](
const IncludeFixerContext::HeaderInfo &LHS,
314 const IncludeFixerContext::HeaderInfo &RHS) {
315 return LHS.Header == RHS.Header;
317 if (!IsUniqueHeader) {
318 errs() <<
"Expect exactly one unique header.\n";
326 bool IsUniqueQualifiedName = std::equal(
327 HeaderInfos.begin() + 1, HeaderInfos.end(), HeaderInfos.begin(),
328 [](
const IncludeFixerContext::HeaderInfo &LHS,
329 const IncludeFixerContext::HeaderInfo &RHS) {
330 return LHS.QualifiedName == RHS.QualifiedName;
332 auto InsertStyle = format::getStyle(format::DefaultFormatStyle,
333 Context.getFilePath(), Style);
335 llvm::errs() << llvm::toString(InsertStyle.takeError()) <<
"\n";
339 Code->getBuffer(), Context, *InsertStyle,
340 IsUniqueQualifiedName);
342 errs() <<
"Failed to create replacements: "
343 << llvm::toString(Replacements.takeError()) <<
"\n";
348 tooling::applyAllReplacements(
Code->getBuffer(), *Replacements);
350 llvm::errs() << llvm::toString(ChangedCode.takeError()) <<
"\n";
353 llvm::outs() << *ChangedCode;
358 std::unique_ptr<include_fixer::SymbolIndexManager> SymbolIndexMgr =
359 createSymbolIndexManager(SourceFilePath);
364 if (!QuerySymbol.empty()) {
365 auto MatchedSymbols = SymbolIndexMgr->search(
366 QuerySymbol,
true, SourceFilePath);
367 for (
auto &Symbol : MatchedSymbols) {
368 std::string HeaderPath = Symbol.getFilePath().str();
369 Symbol.SetFilePath(((HeaderPath[0] ==
'"' || HeaderPath[0] ==
'<')
371 :
"\"" + HeaderPath +
"\""));
377 IncludeFixerContext::QuerySymbolInfo Symbol;
378 Symbol.RawIdentifier = QuerySymbol;
381 writeToJson(llvm::outs(), Context);
386 std::vector<include_fixer::IncludeFixerContext> Contexts;
388 Style, MinimizeIncludePaths);
390 if (tool.run(&Factory) != 0) {
396 llvm::errs() <<
"Fatal compiler error occurred while parsing file!"
397 " (incorrect include paths?)\n";
401 assert(!Contexts.empty());
405 writeToJson(llvm::outs(), Contexts.front());
409 std::vector<tooling::Replacements> FixerReplacements;
410 for (
const auto &Context : Contexts) {
411 StringRef FilePath = Context.getFilePath();
413 format::getStyle(format::DefaultFormatStyle, FilePath, Style);
415 llvm::errs() << llvm::toString(InsertStyle.takeError()) <<
"\n";
418 auto Buffer = llvm::MemoryBuffer::getFile(FilePath);
420 errs() <<
"Couldn't open file: " + FilePath.str() +
": "
421 << Buffer.getError().message() +
"\n";
426 Buffer.get()->getBuffer(), Context, *InsertStyle);
428 errs() <<
"Failed to create replacement: "
429 << llvm::toString(Replacements.takeError()) <<
"\n";
432 FixerReplacements.push_back(*Replacements);
436 for (
const auto &Context : Contexts) {
437 if (!Context.getHeaderInfos().empty()) {
438 llvm::errs() <<
"Added #include "
439 << Context.getHeaderInfos().front().Header <<
" for "
440 << Context.getFilePath() <<
"\n";
446 assert(FixerReplacements.size() == 1);
447 auto ChangedCode = tooling::applyAllReplacements(
Code->getBuffer(),
448 FixerReplacements.front());
450 llvm::errs() << llvm::toString(ChangedCode.takeError()) <<
"\n";
453 llvm::outs() << *ChangedCode;
458 IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts(
new DiagnosticOptions);
459 DiagnosticsEngine
Diagnostics(
new DiagnosticIDs, &*DiagOpts);
460 TextDiagnosticPrinter DiagnosticPrinter(outs(), &*DiagOpts);
465 Rewriter Rewrites(SM, LangOptions());
466 for (
const auto &Replacement : FixerReplacements) {
467 if (!tooling::applyAllReplacements(Replacement, Rewrites)) {
468 llvm::errs() <<
"Failed to apply replacements.\n";
472 return Rewrites.overwriteChangedFiles();
477int main(
int argc,
const char **argv) {
478 return includeFixerMain(argc, argv);
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))
CharSourceRange Range
SourceRange for the file name.
llvm::StringRef Directory
WantDiagnostics Diagnostics
llvm::raw_string_ostream OS
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.
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.
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.
static void mapping(IO &IO, IncludeFixerContext &Context)
static void mapping(IO &io, IncludeFixerContext::QuerySymbolInfo &Info)