clang-tools 20.0.0git
ClangIncludeFixer.cpp
Go to the documentation of this file.
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-exception
6//
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;
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 yaml
79} // namespace llvm
80
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) {
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 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)
180 Symbols.push_back(
181 {SymbolInfo(Split.first.trim(), SymbolInfo::SymbolKind::Unknown,
182 CommaSplits[I].trim(), {}),
183 // Use fake "seen" signal for tests, so first header wins.
184 SymbolInfo::Signals(/*Seen=*/static_cast<unsigned>(E - I),
185 /*Used=*/0)});
186 }
187 SymbolIndexMgr->addSymbolIndex([=]() {
188 return std::make_unique<include_fixer::InMemorySymbolIndex>(Symbols);
189 });
190 break;
191 }
192 case yaml: {
193 auto CreateYamlIdx = [=]() -> std::unique_ptr<include_fixer::SymbolIndex> {
194 llvm::ErrorOr<std::unique_ptr<include_fixer::YamlSymbolIndex>> DB(
195 nullptr);
196 if (!Input.empty()) {
198 } else {
199 // If we don't have any input file, look in the directory of the
200 // first
201 // file and its parents.
202 SmallString<128> AbsolutePath(tooling::getAbsolutePath(FilePath));
203 StringRef Directory = llvm::sys::path::parent_path(AbsolutePath);
205 Directory, "find_all_symbols_db.yaml");
206 }
207
208 if (!DB) {
209 llvm::errs() << "Couldn't find YAML db: " << DB.getError().message()
210 << '\n';
211 return nullptr;
212 }
213 return std::move(*DB);
214 };
215
216 SymbolIndexMgr->addSymbolIndex(std::move(CreateYamlIdx));
217 break;
218 }
219 case fuzzyYaml: {
220 // This mode is not very useful, because we don't correct the identifier.
221 // It's main purpose is to expose FuzzySymbolIndex to tests.
222 SymbolIndexMgr->addSymbolIndex(
223 []() -> std::unique_ptr<include_fixer::SymbolIndex> {
225 if (!DB) {
226 llvm::errs() << "Couldn't load fuzzy YAML db: "
227 << llvm::toString(DB.takeError()) << '\n';
228 return nullptr;
229 }
230 return std::move(*DB);
231 });
232 break;
233 }
234 }
235 return SymbolIndexMgr;
236}
237
238void writeToJson(llvm::raw_ostream &OS, const IncludeFixerContext& Context) {
239 OS << "{\n"
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())
249 OS << ",\n";
250 }
251 OS << "\n ],\n";
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())
258 OS << ",\n";
259 }
260 OS << "\n";
261 OS << " ]\n";
262 OS << "}\n";
263}
264
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();
270 return 1;
271 }
272 tooling::CommonOptionsParser &options = ExpectedParser.get();
273 tooling::ClangTool tool(options.getCompilations(),
274 options.getSourcePathList());
275
276 llvm::StringRef SourceFilePath = options.getSourcePathList().front();
277 // In STDINMode, we override the file content with the <stdin> input.
278 // Since `tool.mapVirtualFile` takes `StringRef`, we define `Code` outside of
279 // the if-block so that `Code` is not released after the if-block.
280 std::unique_ptr<llvm::MemoryBuffer> Code;
281 if (STDINMode) {
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";
288 return 1;
289 }
290 Code = std::move(CodeOrErr.get());
291 if (Code->getBufferSize() == 0)
292 return 0; // Skip empty files.
293
294 tool.mapVirtualFile(SourceFilePath, Code->getBuffer());
295 }
296
297 if (!InsertHeader.empty()) {
298 if (!STDINMode) {
299 errs() << "Should be running in STDIN mode\n";
300 return 1;
301 }
302
303 llvm::yaml::Input yin(InsertHeader);
304 IncludeFixerContext Context;
305 yin >> Context;
306
307 const auto &HeaderInfos = Context.getHeaderInfos();
308 assert(!HeaderInfos.empty());
309 // We only accept one unique header.
310 // Check all elements in HeaderInfos have the same header.
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;
316 });
317 if (!IsUniqueHeader) {
318 errs() << "Expect exactly one unique header.\n";
319 return 1;
320 }
321
322 // If a header has multiple symbols, we won't add the missing namespace
323 // qualifiers because we don't know which one is exactly used.
324 //
325 // Check whether all elements in HeaderInfos have the same qualified name.
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;
331 });
332 auto InsertStyle = format::getStyle(format::DefaultFormatStyle,
333 Context.getFilePath(), Style);
334 if (!InsertStyle) {
335 llvm::errs() << llvm::toString(InsertStyle.takeError()) << "\n";
336 return 1;
337 }
339 Code->getBuffer(), Context, *InsertStyle,
340 /*AddQualifiers=*/IsUniqueQualifiedName);
341 if (!Replacements) {
342 errs() << "Failed to create replacements: "
343 << llvm::toString(Replacements.takeError()) << "\n";
344 return 1;
345 }
346
347 auto ChangedCode =
348 tooling::applyAllReplacements(Code->getBuffer(), *Replacements);
349 if (!ChangedCode) {
350 llvm::errs() << llvm::toString(ChangedCode.takeError()) << "\n";
351 return 1;
352 }
353 llvm::outs() << *ChangedCode;
354 return 0;
355 }
356
357 // Set up data source.
358 std::unique_ptr<include_fixer::SymbolIndexManager> SymbolIndexMgr =
359 createSymbolIndexManager(SourceFilePath);
360 if (!SymbolIndexMgr)
361 return 1;
362
363 // Query symbol mode.
364 if (!QuerySymbol.empty()) {
365 auto MatchedSymbols = SymbolIndexMgr->search(
366 QuerySymbol, /*IsNestedSearch=*/true, SourceFilePath);
367 for (auto &Symbol : MatchedSymbols) {
368 std::string HeaderPath = Symbol.getFilePath().str();
369 Symbol.SetFilePath(((HeaderPath[0] == '"' || HeaderPath[0] == '<')
370 ? HeaderPath
371 : "\"" + HeaderPath + "\""));
372 }
373
374 // We leave an empty symbol range as we don't know the range of the symbol
375 // being queried in this mode. clang-include-fixer won't add namespace
376 // qualifiers if the symbol range is empty, which also fits this case.
377 IncludeFixerContext::QuerySymbolInfo Symbol;
378 Symbol.RawIdentifier = QuerySymbol;
379 auto Context =
380 IncludeFixerContext(SourceFilePath, {Symbol}, MatchedSymbols);
381 writeToJson(llvm::outs(), Context);
382 return 0;
383 }
384
385 // Now run our tool.
386 std::vector<include_fixer::IncludeFixerContext> Contexts;
387 include_fixer::IncludeFixerActionFactory Factory(*SymbolIndexMgr, Contexts,
388 Style, MinimizeIncludePaths);
389
390 if (tool.run(&Factory) != 0) {
391 // We suppress all Clang diagnostics (because they would be wrong,
392 // clang-include-fixer does custom recovery) but still want to give some
393 // feedback in case there was a compiler error we couldn't recover from.
394 // The most common case for this is a #include in the file that couldn't be
395 // found.
396 llvm::errs() << "Fatal compiler error occurred while parsing file!"
397 " (incorrect include paths?)\n";
398 return 1;
399 }
400
401 assert(!Contexts.empty());
402
403 if (OutputHeaders) {
404 // FIXME: Print contexts of all processing files instead of the first one.
405 writeToJson(llvm::outs(), Contexts.front());
406 return 0;
407 }
408
409 std::vector<tooling::Replacements> FixerReplacements;
410 for (const auto &Context : Contexts) {
411 StringRef FilePath = Context.getFilePath();
412 auto InsertStyle =
413 format::getStyle(format::DefaultFormatStyle, FilePath, Style);
414 if (!InsertStyle) {
415 llvm::errs() << llvm::toString(InsertStyle.takeError()) << "\n";
416 return 1;
417 }
418 auto Buffer = llvm::MemoryBuffer::getFile(FilePath);
419 if (!Buffer) {
420 errs() << "Couldn't open file: " + FilePath.str() + ": "
421 << Buffer.getError().message() + "\n";
422 return 1;
423 }
424
426 Buffer.get()->getBuffer(), Context, *InsertStyle);
427 if (!Replacements) {
428 errs() << "Failed to create replacement: "
429 << llvm::toString(Replacements.takeError()) << "\n";
430 return 1;
431 }
432 FixerReplacements.push_back(*Replacements);
433 }
434
435 if (!Quiet) {
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";
441 }
442 }
443 }
444
445 if (STDINMode) {
446 assert(FixerReplacements.size() == 1);
447 auto ChangedCode = tooling::applyAllReplacements(Code->getBuffer(),
448 FixerReplacements.front());
449 if (!ChangedCode) {
450 llvm::errs() << llvm::toString(ChangedCode.takeError()) << "\n";
451 return 1;
452 }
453 llvm::outs() << *ChangedCode;
454 return 0;
455 }
456
457 // Set up a new source manager for applying the resulting replacements.
458 IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts(new DiagnosticOptions);
459 DiagnosticsEngine Diagnostics(new DiagnosticIDs, &*DiagOpts);
460 TextDiagnosticPrinter DiagnosticPrinter(outs(), &*DiagOpts);
461 SourceManager SM(Diagnostics, tool.getFiles());
462 Diagnostics.setClient(&DiagnosticPrinter, false);
463
464 // Write replacements to disk.
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";
469 return 1;
470 }
471 }
472 return Rewrites.overwriteChangedFiles();
473}
474
475} // namespace
476
477int main(int argc, const char **argv) {
478 return includeFixerMain(argc, argv);
479}
const Expr * E
@ yaml
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))
size_t Offset
std::string Code
FunctionInfo Info
CharSourceRange Range
SourceRange for the file name.
llvm::StringRef Directory
unsigned Length
WantDiagnostics Diagnostics
llvm::raw_string_ostream OS
Definition: TraceTests.cpp:160
Describes a named symbol from a header.
Definition: SymbolInfo.h:26
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::HeaderInfo &Info)
static void mapping(IO &io, IncludeFixerContext::QuerySymbolInfo &Info)
static void mapping(IO &IO, tooling::Range &Info)