blob: b0e1fbfa936b5c3f4be96c973ad91e74b1aebf72 [file] [log] [blame]
Benjamin Kramer6b236262016-04-20 12:43:43 +00001//===-- ClangIncludeFixer.cpp - Standalone include fixer ------------------===//
2//
3// The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9
Benjamin Kramera3d82332016-05-13 09:27:54 +000010#include "InMemorySymbolIndex.h"
Benjamin Kramer6b236262016-04-20 12:43:43 +000011#include "IncludeFixer.h"
Haojian Wu11e9bd22016-05-31 09:31:51 +000012#include "IncludeFixerContext.h"
Benjamin Kramera3d82332016-05-13 09:27:54 +000013#include "SymbolIndexManager.h"
14#include "YamlSymbolIndex.h"
Haojian Wu11e9bd22016-05-31 09:31:51 +000015#include "clang/Format/Format.h"
Benjamin Kramer6b236262016-04-20 12:43:43 +000016#include "clang/Frontend/TextDiagnosticPrinter.h"
17#include "clang/Rewrite/Core/Rewriter.h"
18#include "clang/Tooling/CommonOptionsParser.h"
Haojian Wu627ca962016-07-08 09:10:29 +000019#include "clang/Tooling/Core/Replacement.h"
Benjamin Kramer6b236262016-04-20 12:43:43 +000020#include "clang/Tooling/Tooling.h"
21#include "llvm/Support/CommandLine.h"
Benjamin Kramer8ff8fdf2016-07-18 19:21:22 +000022#include "llvm/Support/Path.h"
Haojian Wu17a54e32016-06-01 11:43:10 +000023#include "llvm/Support/YAMLTraits.h"
Haojian Wud8c12ba2016-04-29 09:23:38 +000024
Benjamin Kramer6b236262016-04-20 12:43:43 +000025using namespace clang;
Benjamin Kramerf412e902016-04-27 14:24:32 +000026using namespace llvm;
Haojian Wu17a54e32016-06-01 11:43:10 +000027using clang::include_fixer::IncludeFixerContext;
28
29LLVM_YAML_IS_DOCUMENT_LIST_VECTOR(IncludeFixerContext)
30LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(std::string)
Haojian Wu68c34a02016-07-13 16:43:54 +000031LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(IncludeFixerContext::HeaderInfo)
Haojian Wu62aee522016-07-21 13:47:09 +000032LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(IncludeFixerContext::QuerySymbolInfo)
Haojian Wu17a54e32016-06-01 11:43:10 +000033
34namespace llvm {
35namespace yaml {
Haojian Wu68c34a02016-07-13 16:43:54 +000036
37template <> struct MappingTraits<tooling::Range> {
38 struct NormalizedRange {
39 NormalizedRange(const IO &) : Offset(0), Length(0) {}
40
41 NormalizedRange(const IO &, const tooling::Range &R)
42 : Offset(R.getOffset()), Length(R.getLength()) {}
43
44 tooling::Range denormalize(const IO &) {
45 return tooling::Range(Offset, Length);
46 }
47
48 unsigned Offset;
49 unsigned Length;
50 };
51 static void mapping(IO &IO, tooling::Range &Info) {
52 MappingNormalization<NormalizedRange, tooling::Range> Keys(IO, Info);
53 IO.mapRequired("Offset", Keys->Offset);
54 IO.mapRequired("Length", Keys->Length);
55 }
56};
57
58template <> struct MappingTraits<IncludeFixerContext::HeaderInfo> {
59 static void mapping(IO &io, IncludeFixerContext::HeaderInfo &Info) {
60 io.mapRequired("Header", Info.Header);
61 io.mapRequired("QualifiedName", Info.QualifiedName);
62 }
63};
64
Haojian Wu9e4bd0c2016-07-19 14:49:04 +000065template <> struct MappingTraits<IncludeFixerContext::QuerySymbolInfo> {
66 static void mapping(IO &io, IncludeFixerContext::QuerySymbolInfo &Info) {
67 io.mapRequired("RawIdentifier", Info.RawIdentifier);
68 io.mapRequired("Range", Info.Range);
69 }
70};
71
Haojian Wu17a54e32016-06-01 11:43:10 +000072template <> struct MappingTraits<IncludeFixerContext> {
Haojian Wu68c34a02016-07-13 16:43:54 +000073 static void mapping(IO &IO, IncludeFixerContext &Context) {
Haojian Wu62aee522016-07-21 13:47:09 +000074 IO.mapRequired("QuerySymbolInfos", Context.QuerySymbolInfos);
Haojian Wu68c34a02016-07-13 16:43:54 +000075 IO.mapRequired("HeaderInfos", Context.HeaderInfos);
Haojian Wuc99f7282016-08-09 08:26:19 +000076 IO.mapRequired("FilePath", Context.FilePath);
Haojian Wu17a54e32016-06-01 11:43:10 +000077 }
78};
79} // namespace yaml
80} // namespace llvm
Benjamin Kramer6b236262016-04-20 12:43:43 +000081
Benjamin Kramerf412e902016-04-27 14:24:32 +000082namespace {
83cl::OptionCategory IncludeFixerCategory("Tool options");
84
85enum DatabaseFormatTy {
Simon Pilgrimb9f25582016-04-27 20:43:32 +000086 fixed, ///< Hard-coded mapping.
Haojian Wud8c12ba2016-04-29 09:23:38 +000087 yaml, ///< Yaml database created by find-all-symbols.
Benjamin Kramerf412e902016-04-27 14:24:32 +000088};
89
90cl::opt<DatabaseFormatTy> DatabaseFormat(
91 "db", cl::desc("Specify input format"),
Haojian Wud8c12ba2016-04-29 09:23:38 +000092 cl::values(clEnumVal(fixed, "Hard-coded mapping"),
93 clEnumVal(yaml, "Yaml database created by find-all-symbols"),
94 clEnumValEnd),
Benjamin Kramer8fd85a52016-05-10 11:35:47 +000095 cl::init(yaml), cl::cat(IncludeFixerCategory));
Benjamin Kramerf412e902016-04-27 14:24:32 +000096
97cl::opt<std::string> Input("input",
98 cl::desc("String to initialize the database"),
99 cl::cat(IncludeFixerCategory));
Benjamin Kramer3a45fab2016-04-28 11:21:29 +0000100
101cl::opt<bool>
102 MinimizeIncludePaths("minimize-paths",
103 cl::desc("Whether to minimize added include paths"),
104 cl::init(true), cl::cat(IncludeFixerCategory));
Benjamin Kramer6b236262016-04-20 12:43:43 +0000105
Benjamin Kramer54718292016-05-10 08:36:56 +0000106cl::opt<bool> Quiet("q", cl::desc("Reduce terminal output"), cl::init(false),
107 cl::cat(IncludeFixerCategory));
108
Eric Liuc7f3b102016-05-18 14:10:16 +0000109cl::opt<bool>
110 STDINMode("stdin",
111 cl::desc("Override source file's content (in the overlaying\n"
112 "virtual file system) with input from <stdin> and run\n"
113 "the tool on the new content with the compilation\n"
114 "options of the source file. This mode is currently\n"
115 "used for editor integration."),
116 cl::init(false), cl::cat(IncludeFixerCategory));
117
Haojian Wu11e9bd22016-05-31 09:31:51 +0000118cl::opt<bool> OutputHeaders(
119 "output-headers",
Haojian Wu17a54e32016-06-01 11:43:10 +0000120 cl::desc("Print the symbol being queried and all its relevant headers in\n"
121 "JSON format to stdout:\n"
122 " {\n"
Haojian Wuab372642016-08-17 11:31:19 +0000123 " \"FilePath\": \"/path/to/foo.cc\",\n"
Haojian Wu62aee522016-07-21 13:47:09 +0000124 " \"QuerySymbolInfos\": [\n"
125 " {\"RawIdentifier\": \"foo\",\n"
126 " \"Range\": {\"Offset\": 0, \"Length\": 3}}\n"
127 " ],\n"
Haojian Wu68c34a02016-07-13 16:43:54 +0000128 " \"HeaderInfos\": [ {\"Header\": \"\\\"foo_a.h\\\"\",\n"
129 " \"QualifiedName\": \"a::foo\"} ]\n"
Haojian Wu17a54e32016-06-01 11:43:10 +0000130 " }"),
Haojian Wu11e9bd22016-05-31 09:31:51 +0000131 cl::init(false), cl::cat(IncludeFixerCategory));
132
133cl::opt<std::string> InsertHeader(
134 "insert-header",
135 cl::desc("Insert a specific header. This should run with STDIN mode.\n"
136 "The result is written to stdout. It is currently used for\n"
Haojian Wu17a54e32016-06-01 11:43:10 +0000137 "editor integration. Support YAML/JSON format:\n"
Haojian Wu68c34a02016-07-13 16:43:54 +0000138 " -insert-header=\"{\n"
Haojian Wuab372642016-08-17 11:31:19 +0000139 " FilePath: \"/path/to/foo.cc\",\n"
Haojian Wu62aee522016-07-21 13:47:09 +0000140 " QuerySymbolInfos: [\n"
141 " {RawIdentifier: foo,\n"
142 " Range: {Offset: 0, Length: 3}}\n"
143 " ],\n"
Haojian Wu68c34a02016-07-13 16:43:54 +0000144 " HeaderInfos: [ {Headers: \"\\\"foo_a.h\\\"\",\n"
145 " QualifiedName: \"a::foo\"} ]}\""),
Haojian Wu11e9bd22016-05-31 09:31:51 +0000146 cl::init(""), cl::cat(IncludeFixerCategory));
147
Eric Liu702cfd12016-05-19 08:21:09 +0000148cl::opt<std::string>
149 Style("style",
Haojian Wuab372642016-08-17 11:31:19 +0000150 cl::desc("Fallback style for reformatting after inserting new\n"
Eric Liu702cfd12016-05-19 08:21:09 +0000151 "headers if there is no clang-format config file found."),
152 cl::init("llvm"), cl::cat(IncludeFixerCategory));
153
Haojian Wueb6ce062016-05-31 13:23:00 +0000154std::unique_ptr<include_fixer::SymbolIndexManager>
155createSymbolIndexManager(StringRef FilePath) {
156 auto SymbolIndexMgr = llvm::make_unique<include_fixer::SymbolIndexManager>();
157 switch (DatabaseFormat) {
158 case fixed: {
159 // Parse input and fill the database with it.
160 // <symbol>=<header><, header...>
161 // Multiple symbols can be given, separated by semicolons.
162 std::map<std::string, std::vector<std::string>> SymbolsMap;
163 SmallVector<StringRef, 4> SemicolonSplits;
164 StringRef(Input).split(SemicolonSplits, ";");
165 std::vector<find_all_symbols::SymbolInfo> Symbols;
166 for (StringRef Pair : SemicolonSplits) {
167 auto Split = Pair.split('=');
168 std::vector<std::string> Headers;
169 SmallVector<StringRef, 4> CommaSplits;
170 Split.second.split(CommaSplits, ",");
Benjamin Kramer658d2802016-05-31 14:33:28 +0000171 for (size_t I = 0, E = CommaSplits.size(); I != E; ++I)
Haojian Wueb6ce062016-05-31 13:23:00 +0000172 Symbols.push_back(find_all_symbols::SymbolInfo(
173 Split.first.trim(),
Benjamin Kramer658d2802016-05-31 14:33:28 +0000174 find_all_symbols::SymbolInfo::SymbolKind::Unknown,
175 CommaSplits[I].trim(), 1, {}, /*NumOccurrences=*/E - I));
Haojian Wueb6ce062016-05-31 13:23:00 +0000176 }
177 SymbolIndexMgr->addSymbolIndex(
178 llvm::make_unique<include_fixer::InMemorySymbolIndex>(Symbols));
179 break;
180 }
181 case yaml: {
182 llvm::ErrorOr<std::unique_ptr<include_fixer::YamlSymbolIndex>> DB(nullptr);
183 if (!Input.empty()) {
184 DB = include_fixer::YamlSymbolIndex::createFromFile(Input);
185 } else {
186 // If we don't have any input file, look in the directory of the first
187 // file and its parents.
188 SmallString<128> AbsolutePath(tooling::getAbsolutePath(FilePath));
189 StringRef Directory = llvm::sys::path::parent_path(AbsolutePath);
190 DB = include_fixer::YamlSymbolIndex::createFromDirectory(
191 Directory, "find_all_symbols_db.yaml");
192 }
193
194 if (!DB) {
195 llvm::errs() << "Couldn't find YAML db: " << DB.getError().message()
196 << '\n';
197 return nullptr;
198 }
199
200 SymbolIndexMgr->addSymbolIndex(std::move(*DB));
201 break;
202 }
203 }
204 return SymbolIndexMgr;
205}
206
Haojian Wu17a54e32016-06-01 11:43:10 +0000207void writeToJson(llvm::raw_ostream &OS, const IncludeFixerContext& Context) {
208 OS << "{\n"
Haojian Wuc99f7282016-08-09 08:26:19 +0000209 << " \"FilePath\": \""
210 << llvm::yaml::escape(Context.getFilePath()) << "\",\n"
211 << " \"QuerySymbolInfos\": [\n";
Haojian Wu62aee522016-07-21 13:47:09 +0000212 for (const auto &Info : Context.getQuerySymbolInfos()) {
213 OS << " {\"RawIdentifier\": \"" << Info.RawIdentifier << "\",\n";
214 OS << " \"Range\":{";
215 OS << "\"Offset\":" << Info.Range.getOffset() << ",";
216 OS << "\"Length\":" << Info.Range.getLength() << "}}";
217 if (&Info != &Context.getQuerySymbolInfos().back())
218 OS << ",\n";
219 }
220 OS << "\n ],\n";
Haojian Wu68c34a02016-07-13 16:43:54 +0000221 OS << " \"HeaderInfos\": [\n";
222 const auto &HeaderInfos = Context.getHeaderInfos();
223 for (const auto &Info : HeaderInfos) {
224 OS << " {\"Header\": \"" << llvm::yaml::escape(Info.Header) << "\",\n"
225 << " \"QualifiedName\": \"" << Info.QualifiedName << "\"}";
226 if (&Info != &HeaderInfos.back())
227 OS << ",\n";
Haojian Wu17a54e32016-06-01 11:43:10 +0000228 }
Haojian Wu68c34a02016-07-13 16:43:54 +0000229 OS << "\n";
230 OS << " ]\n";
231 OS << "}\n";
Haojian Wu17a54e32016-06-01 11:43:10 +0000232}
233
Haojian Wua315dcb2016-05-03 08:38:35 +0000234int includeFixerMain(int argc, const char **argv) {
Benjamin Kramerf412e902016-04-27 14:24:32 +0000235 tooling::CommonOptionsParser options(argc, argv, IncludeFixerCategory);
236 tooling::ClangTool tool(options.getCompilations(),
237 options.getSourcePathList());
238
Eric Liuc7f3b102016-05-18 14:10:16 +0000239 // In STDINMode, we override the file content with the <stdin> input.
240 // Since `tool.mapVirtualFile` takes `StringRef`, we define `Code` outside of
241 // the if-block so that `Code` is not released after the if-block.
242 std::unique_ptr<llvm::MemoryBuffer> Code;
243 if (STDINMode) {
244 assert(options.getSourcePathList().size() == 1 &&
245 "Expect exactly one file path in STDINMode.");
246 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> CodeOrErr =
247 MemoryBuffer::getSTDIN();
248 if (std::error_code EC = CodeOrErr.getError()) {
249 errs() << EC.message() << "\n";
250 return 1;
251 }
252 Code = std::move(CodeOrErr.get());
253 if (Code->getBufferSize() == 0)
254 return 0; // Skip empty files.
255
256 tool.mapVirtualFile(options.getSourcePathList().front(), Code->getBuffer());
257 }
258
Haojian Wu11e9bd22016-05-31 09:31:51 +0000259 if (!InsertHeader.empty()) {
260 if (!STDINMode) {
261 errs() << "Should be running in STDIN mode\n";
262 return 1;
263 }
264
Haojian Wu17a54e32016-06-01 11:43:10 +0000265 llvm::yaml::Input yin(InsertHeader);
266 IncludeFixerContext Context;
267 yin >> Context;
268
Haojian Wu68c34a02016-07-13 16:43:54 +0000269 const auto &HeaderInfos = Context.getHeaderInfos();
270 assert(!HeaderInfos.empty());
271 // We only accept one unique header.
272 // Check all elements in HeaderInfos have the same header.
273 bool IsUniqueHeader = std::equal(
274 HeaderInfos.begin()+1, HeaderInfos.end(), HeaderInfos.begin(),
275 [](const IncludeFixerContext::HeaderInfo &LHS,
276 const IncludeFixerContext::HeaderInfo &RHS) {
277 return LHS.Header == RHS.Header;
278 });
279 if (!IsUniqueHeader) {
280 errs() << "Expect exactly one unique header.\n";
Haojian Wu17a54e32016-06-01 11:43:10 +0000281 return 1;
282 }
283
Haojian Wu62aee522016-07-21 13:47:09 +0000284 // If a header has multiple symbols, we won't add the missing namespace
Haojian Wu68c34a02016-07-13 16:43:54 +0000285 // qualifiers because we don't know which one is exactly used.
286 //
287 // Check whether all elements in HeaderInfos have the same qualified name.
288 bool IsUniqueQualifiedName = std::equal(
289 HeaderInfos.begin() + 1, HeaderInfos.end(), HeaderInfos.begin(),
290 [](const IncludeFixerContext::HeaderInfo &LHS,
291 const IncludeFixerContext::HeaderInfo &RHS) {
292 return LHS.QualifiedName == RHS.QualifiedName;
293 });
Haojian Wuc99f7282016-08-09 08:26:19 +0000294 format::FormatStyle InsertStyle =
295 format::getStyle("file", Context.getFilePath(), Style);
Haojian Wu62aee522016-07-21 13:47:09 +0000296 auto Replacements = clang::include_fixer::createIncludeFixerReplacements(
Haojian Wuc99f7282016-08-09 08:26:19 +0000297 Code->getBuffer(), Context, InsertStyle,
Haojian Wu62aee522016-07-21 13:47:09 +0000298 /*AddQualifiers=*/IsUniqueQualifiedName);
299 if (!Replacements) {
300 errs() << "Failed to create replacements: "
301 << llvm::toString(Replacements.takeError()) << "\n";
302 return 1;
303 }
304
Eric Liua452db42016-07-11 13:53:21 +0000305 auto ChangedCode =
306 tooling::applyAllReplacements(Code->getBuffer(), *Replacements);
307 if (!ChangedCode) {
308 llvm::errs() << llvm::toString(ChangedCode.takeError()) << "\n";
309 return 1;
310 }
311 llvm::outs() << *ChangedCode;
Haojian Wu11e9bd22016-05-31 09:31:51 +0000312 return 0;
313 }
314
Eric Liu692aca62016-05-04 08:22:35 +0000315 // Set up data source.
Haojian Wueb6ce062016-05-31 13:23:00 +0000316 std::unique_ptr<include_fixer::SymbolIndexManager> SymbolIndexMgr =
317 createSymbolIndexManager(options.getSourcePathList().front());
318 if (!SymbolIndexMgr)
319 return 1;
Benjamin Kramer6b236262016-04-20 12:43:43 +0000320
321 // Now run our tool.
Haojian Wuc99f7282016-08-09 08:26:19 +0000322 std::vector<include_fixer::IncludeFixerContext> Contexts;
323 include_fixer::IncludeFixerActionFactory Factory(*SymbolIndexMgr, Contexts,
Haojian Wu11e9bd22016-05-31 09:31:51 +0000324 Style, MinimizeIncludePaths);
Benjamin Kramer6b236262016-04-20 12:43:43 +0000325
Benjamin Kramer6b5160a2016-05-18 13:32:38 +0000326 if (tool.run(&Factory) != 0) {
327 llvm::errs()
328 << "Clang died with a fatal error! (incorrect include paths?)\n";
329 return 1;
330 }
Benjamin Kramer6b236262016-04-20 12:43:43 +0000331
Haojian Wuc99f7282016-08-09 08:26:19 +0000332 assert(!Contexts.empty());
333
Haojian Wu11e9bd22016-05-31 09:31:51 +0000334 if (OutputHeaders) {
Haojian Wuc99f7282016-08-09 08:26:19 +0000335 // FIXME: Print contexts of all processing files instead of the first one.
336 writeToJson(llvm::outs(), Contexts.front());
Haojian Wu11e9bd22016-05-31 09:31:51 +0000337 return 0;
338 }
339
Haojian Wuc99f7282016-08-09 08:26:19 +0000340 std::vector<tooling::Replacements> FixerReplacements;
341 for (const auto &Context : Contexts) {
342 StringRef FilePath = Context.getFilePath();
343 format::FormatStyle InsertStyle = format::getStyle("file", FilePath, Style);
344 auto Buffer = llvm::MemoryBuffer::getFile(FilePath);
345 if (!Buffer) {
346 errs() << "Couldn't open file: " + FilePath.str() + ": "
347 << Buffer.getError().message() + "\n";
348 return 1;
349 }
Haojian Wu11e9bd22016-05-31 09:31:51 +0000350
Haojian Wuc99f7282016-08-09 08:26:19 +0000351 auto Replacements = clang::include_fixer::createIncludeFixerReplacements(
352 Buffer.get()->getBuffer(), Context, InsertStyle);
353 if (!Replacements) {
354 errs() << "Failed to create replacement: "
355 << llvm::toString(Replacements.takeError()) << "\n";
356 return 1;
357 }
358 FixerReplacements.push_back(*Replacements);
Haojian Wu11e9bd22016-05-31 09:31:51 +0000359 }
360
Haojian Wuc99f7282016-08-09 08:26:19 +0000361 if (!Quiet) {
362 for (const auto &Context : Contexts) {
363 if (!Context.getHeaderInfos().empty()) {
364 llvm::errs() << "Added #include "
365 << Context.getHeaderInfos().front().Header << " for "
366 << Context.getFilePath() << "\n";
367 }
368 }
Eric Liua452db42016-07-11 13:53:21 +0000369 }
Haojian Wu11e9bd22016-05-31 09:31:51 +0000370
Eric Liuc7f3b102016-05-18 14:10:16 +0000371 if (STDINMode) {
Haojian Wuc99f7282016-08-09 08:26:19 +0000372 assert(FixerReplacements.size() == 1);
373 auto ChangedCode = tooling::applyAllReplacements(Code->getBuffer(),
374 FixerReplacements.front());
Eric Liua452db42016-07-11 13:53:21 +0000375 if (!ChangedCode) {
376 llvm::errs() << llvm::toString(ChangedCode.takeError()) << "\n";
377 return 1;
378 }
379 llvm::outs() << *ChangedCode;
Eric Liuc7f3b102016-05-18 14:10:16 +0000380 return 0;
381 }
382
Haojian Wuc99f7282016-08-09 08:26:19 +0000383 // Set up a new source manager for applying the resulting replacements.
384 IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts(new DiagnosticOptions);
385 DiagnosticsEngine Diagnostics(new DiagnosticIDs, &*DiagOpts);
386 TextDiagnosticPrinter DiagnosticPrinter(outs(), &*DiagOpts);
387 SourceManager SM(Diagnostics, tool.getFiles());
388 Diagnostics.setClient(&DiagnosticPrinter, false);
389
Benjamin Kramer6b236262016-04-20 12:43:43 +0000390 // Write replacements to disk.
Benjamin Kramerf412e902016-04-27 14:24:32 +0000391 Rewriter Rewrites(SM, LangOptions());
Haojian Wuc99f7282016-08-09 08:26:19 +0000392 for (const auto Replacement : FixerReplacements) {
393 if (!tooling::applyAllReplacements(Replacement, Rewrites)) {
394 llvm::errs() << "Failed to apply replacements.\n";
395 return 1;
396 }
397 }
Benjamin Kramer6b236262016-04-20 12:43:43 +0000398 return Rewrites.overwriteChangedFiles();
399}
Haojian Wua315dcb2016-05-03 08:38:35 +0000400
401} // namespace
402
403int main(int argc, const char **argv) {
404 return includeFixerMain(argc, argv);
405}