blob: 769d7f485043609a3a0175e8ad463694e3e94e5e [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 Wu62aee522016-07-21 13:47:09 +0000123 " \"QuerySymbolInfos\": [\n"
124 " {\"RawIdentifier\": \"foo\",\n"
125 " \"Range\": {\"Offset\": 0, \"Length\": 3}}\n"
126 " ],\n"
Haojian Wu68c34a02016-07-13 16:43:54 +0000127 " \"HeaderInfos\": [ {\"Header\": \"\\\"foo_a.h\\\"\",\n"
128 " \"QualifiedName\": \"a::foo\"} ]\n"
Haojian Wu17a54e32016-06-01 11:43:10 +0000129 " }"),
Haojian Wu11e9bd22016-05-31 09:31:51 +0000130 cl::init(false), cl::cat(IncludeFixerCategory));
131
132cl::opt<std::string> InsertHeader(
133 "insert-header",
134 cl::desc("Insert a specific header. This should run with STDIN mode.\n"
135 "The result is written to stdout. It is currently used for\n"
Haojian Wu17a54e32016-06-01 11:43:10 +0000136 "editor integration. Support YAML/JSON format:\n"
Haojian Wu68c34a02016-07-13 16:43:54 +0000137 " -insert-header=\"{\n"
Haojian Wu62aee522016-07-21 13:47:09 +0000138 " QuerySymbolInfos: [\n"
139 " {RawIdentifier: foo,\n"
140 " Range: {Offset: 0, Length: 3}}\n"
141 " ],\n"
Haojian Wu68c34a02016-07-13 16:43:54 +0000142 " HeaderInfos: [ {Headers: \"\\\"foo_a.h\\\"\",\n"
143 " QualifiedName: \"a::foo\"} ]}\""),
Haojian Wu11e9bd22016-05-31 09:31:51 +0000144 cl::init(""), cl::cat(IncludeFixerCategory));
145
Eric Liu702cfd12016-05-19 08:21:09 +0000146cl::opt<std::string>
147 Style("style",
148 cl::desc("Fallback style for reformatting after inserting new "
149 "headers if there is no clang-format config file found."),
150 cl::init("llvm"), cl::cat(IncludeFixerCategory));
151
Haojian Wueb6ce062016-05-31 13:23:00 +0000152std::unique_ptr<include_fixer::SymbolIndexManager>
153createSymbolIndexManager(StringRef FilePath) {
154 auto SymbolIndexMgr = llvm::make_unique<include_fixer::SymbolIndexManager>();
155 switch (DatabaseFormat) {
156 case fixed: {
157 // Parse input and fill the database with it.
158 // <symbol>=<header><, header...>
159 // Multiple symbols can be given, separated by semicolons.
160 std::map<std::string, std::vector<std::string>> SymbolsMap;
161 SmallVector<StringRef, 4> SemicolonSplits;
162 StringRef(Input).split(SemicolonSplits, ";");
163 std::vector<find_all_symbols::SymbolInfo> Symbols;
164 for (StringRef Pair : SemicolonSplits) {
165 auto Split = Pair.split('=');
166 std::vector<std::string> Headers;
167 SmallVector<StringRef, 4> CommaSplits;
168 Split.second.split(CommaSplits, ",");
Benjamin Kramer658d2802016-05-31 14:33:28 +0000169 for (size_t I = 0, E = CommaSplits.size(); I != E; ++I)
Haojian Wueb6ce062016-05-31 13:23:00 +0000170 Symbols.push_back(find_all_symbols::SymbolInfo(
171 Split.first.trim(),
Benjamin Kramer658d2802016-05-31 14:33:28 +0000172 find_all_symbols::SymbolInfo::SymbolKind::Unknown,
173 CommaSplits[I].trim(), 1, {}, /*NumOccurrences=*/E - I));
Haojian Wueb6ce062016-05-31 13:23:00 +0000174 }
175 SymbolIndexMgr->addSymbolIndex(
176 llvm::make_unique<include_fixer::InMemorySymbolIndex>(Symbols));
177 break;
178 }
179 case yaml: {
180 llvm::ErrorOr<std::unique_ptr<include_fixer::YamlSymbolIndex>> DB(nullptr);
181 if (!Input.empty()) {
182 DB = include_fixer::YamlSymbolIndex::createFromFile(Input);
183 } else {
184 // If we don't have any input file, look in the directory of the first
185 // file and its parents.
186 SmallString<128> AbsolutePath(tooling::getAbsolutePath(FilePath));
187 StringRef Directory = llvm::sys::path::parent_path(AbsolutePath);
188 DB = include_fixer::YamlSymbolIndex::createFromDirectory(
189 Directory, "find_all_symbols_db.yaml");
190 }
191
192 if (!DB) {
193 llvm::errs() << "Couldn't find YAML db: " << DB.getError().message()
194 << '\n';
195 return nullptr;
196 }
197
198 SymbolIndexMgr->addSymbolIndex(std::move(*DB));
199 break;
200 }
201 }
202 return SymbolIndexMgr;
203}
204
Haojian Wu17a54e32016-06-01 11:43:10 +0000205void writeToJson(llvm::raw_ostream &OS, const IncludeFixerContext& Context) {
206 OS << "{\n"
Haojian Wuc99f7282016-08-09 08:26:19 +0000207 << " \"FilePath\": \""
208 << llvm::yaml::escape(Context.getFilePath()) << "\",\n"
209 << " \"QuerySymbolInfos\": [\n";
Haojian Wu62aee522016-07-21 13:47:09 +0000210 for (const auto &Info : Context.getQuerySymbolInfos()) {
211 OS << " {\"RawIdentifier\": \"" << Info.RawIdentifier << "\",\n";
212 OS << " \"Range\":{";
213 OS << "\"Offset\":" << Info.Range.getOffset() << ",";
214 OS << "\"Length\":" << Info.Range.getLength() << "}}";
215 if (&Info != &Context.getQuerySymbolInfos().back())
216 OS << ",\n";
217 }
218 OS << "\n ],\n";
Haojian Wu68c34a02016-07-13 16:43:54 +0000219 OS << " \"HeaderInfos\": [\n";
220 const auto &HeaderInfos = Context.getHeaderInfos();
221 for (const auto &Info : HeaderInfos) {
222 OS << " {\"Header\": \"" << llvm::yaml::escape(Info.Header) << "\",\n"
223 << " \"QualifiedName\": \"" << Info.QualifiedName << "\"}";
224 if (&Info != &HeaderInfos.back())
225 OS << ",\n";
Haojian Wu17a54e32016-06-01 11:43:10 +0000226 }
Haojian Wu68c34a02016-07-13 16:43:54 +0000227 OS << "\n";
228 OS << " ]\n";
229 OS << "}\n";
Haojian Wu17a54e32016-06-01 11:43:10 +0000230}
231
Haojian Wua315dcb2016-05-03 08:38:35 +0000232int includeFixerMain(int argc, const char **argv) {
Benjamin Kramerf412e902016-04-27 14:24:32 +0000233 tooling::CommonOptionsParser options(argc, argv, IncludeFixerCategory);
234 tooling::ClangTool tool(options.getCompilations(),
235 options.getSourcePathList());
236
Eric Liuc7f3b102016-05-18 14:10:16 +0000237 // In STDINMode, we override the file content with the <stdin> input.
238 // Since `tool.mapVirtualFile` takes `StringRef`, we define `Code` outside of
239 // the if-block so that `Code` is not released after the if-block.
240 std::unique_ptr<llvm::MemoryBuffer> Code;
241 if (STDINMode) {
242 assert(options.getSourcePathList().size() == 1 &&
243 "Expect exactly one file path in STDINMode.");
244 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> CodeOrErr =
245 MemoryBuffer::getSTDIN();
246 if (std::error_code EC = CodeOrErr.getError()) {
247 errs() << EC.message() << "\n";
248 return 1;
249 }
250 Code = std::move(CodeOrErr.get());
251 if (Code->getBufferSize() == 0)
252 return 0; // Skip empty files.
253
254 tool.mapVirtualFile(options.getSourcePathList().front(), Code->getBuffer());
255 }
256
Haojian Wu11e9bd22016-05-31 09:31:51 +0000257 if (!InsertHeader.empty()) {
258 if (!STDINMode) {
259 errs() << "Should be running in STDIN mode\n";
260 return 1;
261 }
262
Haojian Wu17a54e32016-06-01 11:43:10 +0000263 llvm::yaml::Input yin(InsertHeader);
264 IncludeFixerContext Context;
265 yin >> Context;
266
Haojian Wu68c34a02016-07-13 16:43:54 +0000267 const auto &HeaderInfos = Context.getHeaderInfos();
268 assert(!HeaderInfos.empty());
269 // We only accept one unique header.
270 // Check all elements in HeaderInfos have the same header.
271 bool IsUniqueHeader = std::equal(
272 HeaderInfos.begin()+1, HeaderInfos.end(), HeaderInfos.begin(),
273 [](const IncludeFixerContext::HeaderInfo &LHS,
274 const IncludeFixerContext::HeaderInfo &RHS) {
275 return LHS.Header == RHS.Header;
276 });
277 if (!IsUniqueHeader) {
278 errs() << "Expect exactly one unique header.\n";
Haojian Wu17a54e32016-06-01 11:43:10 +0000279 return 1;
280 }
281
Haojian Wu62aee522016-07-21 13:47:09 +0000282 // If a header has multiple symbols, we won't add the missing namespace
Haojian Wu68c34a02016-07-13 16:43:54 +0000283 // qualifiers because we don't know which one is exactly used.
284 //
285 // Check whether all elements in HeaderInfos have the same qualified name.
286 bool IsUniqueQualifiedName = std::equal(
287 HeaderInfos.begin() + 1, HeaderInfos.end(), HeaderInfos.begin(),
288 [](const IncludeFixerContext::HeaderInfo &LHS,
289 const IncludeFixerContext::HeaderInfo &RHS) {
290 return LHS.QualifiedName == RHS.QualifiedName;
291 });
Haojian Wuc99f7282016-08-09 08:26:19 +0000292 format::FormatStyle InsertStyle =
293 format::getStyle("file", Context.getFilePath(), Style);
Haojian Wu62aee522016-07-21 13:47:09 +0000294 auto Replacements = clang::include_fixer::createIncludeFixerReplacements(
Haojian Wuc99f7282016-08-09 08:26:19 +0000295 Code->getBuffer(), Context, InsertStyle,
Haojian Wu62aee522016-07-21 13:47:09 +0000296 /*AddQualifiers=*/IsUniqueQualifiedName);
297 if (!Replacements) {
298 errs() << "Failed to create replacements: "
299 << llvm::toString(Replacements.takeError()) << "\n";
300 return 1;
301 }
302
Eric Liua452db42016-07-11 13:53:21 +0000303 auto ChangedCode =
304 tooling::applyAllReplacements(Code->getBuffer(), *Replacements);
305 if (!ChangedCode) {
306 llvm::errs() << llvm::toString(ChangedCode.takeError()) << "\n";
307 return 1;
308 }
309 llvm::outs() << *ChangedCode;
Haojian Wu11e9bd22016-05-31 09:31:51 +0000310 return 0;
311 }
312
Eric Liu692aca62016-05-04 08:22:35 +0000313 // Set up data source.
Haojian Wueb6ce062016-05-31 13:23:00 +0000314 std::unique_ptr<include_fixer::SymbolIndexManager> SymbolIndexMgr =
315 createSymbolIndexManager(options.getSourcePathList().front());
316 if (!SymbolIndexMgr)
317 return 1;
Benjamin Kramer6b236262016-04-20 12:43:43 +0000318
319 // Now run our tool.
Haojian Wuc99f7282016-08-09 08:26:19 +0000320 std::vector<include_fixer::IncludeFixerContext> Contexts;
321 include_fixer::IncludeFixerActionFactory Factory(*SymbolIndexMgr, Contexts,
Haojian Wu11e9bd22016-05-31 09:31:51 +0000322 Style, MinimizeIncludePaths);
Benjamin Kramer6b236262016-04-20 12:43:43 +0000323
Benjamin Kramer6b5160a2016-05-18 13:32:38 +0000324 if (tool.run(&Factory) != 0) {
325 llvm::errs()
326 << "Clang died with a fatal error! (incorrect include paths?)\n";
327 return 1;
328 }
Benjamin Kramer6b236262016-04-20 12:43:43 +0000329
Haojian Wuc99f7282016-08-09 08:26:19 +0000330 assert(!Contexts.empty());
331
Haojian Wu11e9bd22016-05-31 09:31:51 +0000332 if (OutputHeaders) {
Haojian Wuc99f7282016-08-09 08:26:19 +0000333 // FIXME: Print contexts of all processing files instead of the first one.
334 writeToJson(llvm::outs(), Contexts.front());
Haojian Wu11e9bd22016-05-31 09:31:51 +0000335 return 0;
336 }
337
Haojian Wuc99f7282016-08-09 08:26:19 +0000338 std::vector<tooling::Replacements> FixerReplacements;
339 for (const auto &Context : Contexts) {
340 StringRef FilePath = Context.getFilePath();
341 format::FormatStyle InsertStyle = format::getStyle("file", FilePath, Style);
342 auto Buffer = llvm::MemoryBuffer::getFile(FilePath);
343 if (!Buffer) {
344 errs() << "Couldn't open file: " + FilePath.str() + ": "
345 << Buffer.getError().message() + "\n";
346 return 1;
347 }
Haojian Wu11e9bd22016-05-31 09:31:51 +0000348
Haojian Wuc99f7282016-08-09 08:26:19 +0000349 auto Replacements = clang::include_fixer::createIncludeFixerReplacements(
350 Buffer.get()->getBuffer(), Context, InsertStyle);
351 if (!Replacements) {
352 errs() << "Failed to create replacement: "
353 << llvm::toString(Replacements.takeError()) << "\n";
354 return 1;
355 }
356 FixerReplacements.push_back(*Replacements);
Haojian Wu11e9bd22016-05-31 09:31:51 +0000357 }
358
Haojian Wuc99f7282016-08-09 08:26:19 +0000359 if (!Quiet) {
360 for (const auto &Context : Contexts) {
361 if (!Context.getHeaderInfos().empty()) {
362 llvm::errs() << "Added #include "
363 << Context.getHeaderInfos().front().Header << " for "
364 << Context.getFilePath() << "\n";
365 }
366 }
Eric Liua452db42016-07-11 13:53:21 +0000367 }
Haojian Wu11e9bd22016-05-31 09:31:51 +0000368
Eric Liuc7f3b102016-05-18 14:10:16 +0000369 if (STDINMode) {
Haojian Wuc99f7282016-08-09 08:26:19 +0000370 assert(FixerReplacements.size() == 1);
371 auto ChangedCode = tooling::applyAllReplacements(Code->getBuffer(),
372 FixerReplacements.front());
Eric Liua452db42016-07-11 13:53:21 +0000373 if (!ChangedCode) {
374 llvm::errs() << llvm::toString(ChangedCode.takeError()) << "\n";
375 return 1;
376 }
377 llvm::outs() << *ChangedCode;
Eric Liuc7f3b102016-05-18 14:10:16 +0000378 return 0;
379 }
380
Haojian Wuc99f7282016-08-09 08:26:19 +0000381 // Set up a new source manager for applying the resulting replacements.
382 IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts(new DiagnosticOptions);
383 DiagnosticsEngine Diagnostics(new DiagnosticIDs, &*DiagOpts);
384 TextDiagnosticPrinter DiagnosticPrinter(outs(), &*DiagOpts);
385 SourceManager SM(Diagnostics, tool.getFiles());
386 Diagnostics.setClient(&DiagnosticPrinter, false);
387
Benjamin Kramer6b236262016-04-20 12:43:43 +0000388 // Write replacements to disk.
Benjamin Kramerf412e902016-04-27 14:24:32 +0000389 Rewriter Rewrites(SM, LangOptions());
Haojian Wuc99f7282016-08-09 08:26:19 +0000390 for (const auto Replacement : FixerReplacements) {
391 if (!tooling::applyAllReplacements(Replacement, Rewrites)) {
392 llvm::errs() << "Failed to apply replacements.\n";
393 return 1;
394 }
395 }
Benjamin Kramer6b236262016-04-20 12:43:43 +0000396 return Rewrites.overwriteChangedFiles();
397}
Haojian Wua315dcb2016-05-03 08:38:35 +0000398
399} // namespace
400
401int main(int argc, const char **argv) {
402 return includeFixerMain(argc, argv);
403}