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" 25 using namespace clang;
30 LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(IncludeFixerContext::HeaderInfo)
31 LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(IncludeFixerContext::QuerySymbolInfo)
36 template <>
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);
82 cl::OptionCategory IncludeFixerCategory(
"Tool options");
90 cl::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));
97 cl::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));
112 cl::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));
124 cl::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));
139 cl::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));
160 std::unique_ptr<include_fixer::SymbolIndexManager>
161 createSymbolIndexManager(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)
181 {SymbolInfo(Split.first.trim(), SymbolInfo::SymbolKind::Unknown,
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);
205 Directory,
"find_all_symbols_db.yaml");
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: " 230 return std::move(*DB);
235 return SymbolIndexMgr;
240 <<
" \"FilePath\": \"" 241 << llvm::yaml::escape(Context.
getFilePath()) <<
"\",\n" 242 <<
" \"QuerySymbolInfos\": [\n";
244 OS <<
" {\"RawIdentifier\": \"" <<
Info.RawIdentifier <<
"\",\n";
245 OS <<
" \"Range\":{";
246 OS <<
"\"Offset\":" <<
Info.Range.getOffset() <<
",";
247 OS <<
"\"Length\":" <<
Info.Range.getLength() <<
"}}";
252 OS <<
" \"HeaderInfos\": [\n";
254 for (
const auto &
Info : HeaderInfos) {
255 OS <<
" {\"Header\": \"" << llvm::yaml::escape(
Info.Header) <<
"\",\n" 256 <<
" \"QualifiedName\": \"" <<
Info.QualifiedName <<
"\"}";
257 if (&
Info != &HeaderInfos.back())
265 int includeFixerMain(
int argc,
const char **argv) {
266 tooling::CommonOptionsParser options(argc, argv, IncludeFixerCategory);
267 tooling::ClangTool tool(options.getCompilations(),
268 options.getSourcePathList());
270 llvm::StringRef SourceFilePath = options.getSourcePathList().front();
274 std::unique_ptr<llvm::MemoryBuffer>
Code;
276 assert(options.getSourcePathList().size() == 1 &&
277 "Expect exactly one file path in STDINMode.");
278 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> CodeOrErr =
279 MemoryBuffer::getSTDIN();
280 if (std::error_code EC = CodeOrErr.getError()) {
281 errs() << EC.message() <<
"\n";
284 Code = std::move(CodeOrErr.get());
285 if (Code->getBufferSize() == 0)
288 tool.mapVirtualFile(SourceFilePath, Code->getBuffer());
291 if (!InsertHeader.empty()) {
293 errs() <<
"Should be running in STDIN mode\n";
297 llvm::yaml::Input yin(InsertHeader);
302 assert(!HeaderInfos.empty());
305 bool IsUniqueHeader = std::equal(
306 HeaderInfos.begin()+1, HeaderInfos.end(), HeaderInfos.begin(),
307 [](
const IncludeFixerContext::HeaderInfo &LHS,
308 const IncludeFixerContext::HeaderInfo &RHS) {
309 return LHS.Header == RHS.Header;
311 if (!IsUniqueHeader) {
312 errs() <<
"Expect exactly one unique header.\n";
320 bool IsUniqueQualifiedName = std::equal(
321 HeaderInfos.begin() + 1, HeaderInfos.end(), HeaderInfos.begin(),
322 [](
const IncludeFixerContext::HeaderInfo &LHS,
323 const IncludeFixerContext::HeaderInfo &RHS) {
324 return LHS.QualifiedName == RHS.QualifiedName;
326 auto InsertStyle = format::getStyle(format::DefaultFormatStyle,
327 Context.getFilePath(), Style);
333 Code->getBuffer(), Context, *InsertStyle,
334 IsUniqueQualifiedName);
336 errs() <<
"Failed to create replacements: " 342 tooling::applyAllReplacements(Code->getBuffer(), *Replacements);
347 llvm::outs() << *ChangedCode;
352 std::unique_ptr<include_fixer::SymbolIndexManager> SymbolIndexMgr =
353 createSymbolIndexManager(SourceFilePath);
358 if (!QuerySymbol.empty()) {
359 auto MatchedSymbols = SymbolIndexMgr->search(
360 QuerySymbol,
true, SourceFilePath);
361 for (
auto &Symbol : MatchedSymbols) {
362 std::string HeaderPath = Symbol.getFilePath().str();
363 Symbol.SetFilePath(((HeaderPath[0] ==
'"' || HeaderPath[0] ==
'<')
365 :
"\"" + HeaderPath +
"\""));
371 IncludeFixerContext::QuerySymbolInfo Symbol;
372 Symbol.RawIdentifier = QuerySymbol;
375 writeToJson(llvm::outs(), Context);
380 std::vector<include_fixer::IncludeFixerContext> Contexts;
382 Style, MinimizeIncludePaths);
384 if (tool.run(&Factory) != 0) {
390 llvm::errs() <<
"Fatal compiler error occurred while parsing file!" 391 " (incorrect include paths?)\n";
395 assert(!Contexts.empty());
399 writeToJson(llvm::outs(), Contexts.front());
403 std::vector<tooling::Replacements> FixerReplacements;
404 for (
const auto &Context : Contexts) {
407 format::getStyle(format::DefaultFormatStyle, FilePath, Style);
412 auto Buffer = llvm::MemoryBuffer::getFile(FilePath);
414 errs() <<
"Couldn't open file: " + FilePath.str() +
": " 415 << Buffer.getError().message() +
"\n";
420 Buffer.get()->getBuffer(), Context, *InsertStyle);
422 errs() <<
"Failed to create replacement: " 426 FixerReplacements.push_back(*Replacements);
430 for (
const auto &Context : Contexts) {
432 llvm::errs() <<
"Added #include " 440 assert(FixerReplacements.size() == 1);
441 auto ChangedCode = tooling::applyAllReplacements(Code->getBuffer(),
442 FixerReplacements.front());
447 llvm::outs() << *ChangedCode;
452 IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts(
new DiagnosticOptions);
453 DiagnosticsEngine Diagnostics(
new DiagnosticIDs, &*DiagOpts);
454 TextDiagnosticPrinter DiagnosticPrinter(outs(), &*DiagOpts);
455 SourceManager SM(Diagnostics, tool.getFiles());
456 Diagnostics.setClient(&DiagnosticPrinter,
false);
459 Rewriter Rewrites(SM, LangOptions());
460 for (
const auto &Replacement : FixerReplacements) {
461 if (!tooling::applyAllReplacements(Replacement, Rewrites)) {
462 llvm::errs() <<
"Failed to apply replacements.\n";
466 return Rewrites.overwriteChangedFiles();
471 int main(
int argc,
const char **argv) {
472 return includeFixerMain(argc, argv);
static unsigned getLength(const Expr *E, const MatchFinder::MatchResult &Result)
Some operations such as code completion produce a set of candidates.
static void mapping(IO &IO, IncludeFixerContext &Context)
llvm::Expected< tooling::Replacements > createIncludeFixerReplacements(StringRef Code, const IncludeFixerContext &Context, const clang::format::FormatStyle &Style, bool AddQualifiers)
const std::vector< QuerySymbolInfo > & getQuerySymbolInfos() const
Get information of symbols being querid.
StringRef getFilePath() const
Get the file path to the file being processed.
A context for a file being processed.
const std::vector< HeaderInfo > & getHeaderInfos() const
Get header information.
int main(int argc, const char **argv)
static llvm::StringRef toString(SpecialMemberFunctionsCheck::SpecialMemberFunctionKind K)
static llvm::Expected< std::unique_ptr< FuzzySymbolIndex > > createFromYAML(llvm::StringRef File)
clang::find_all_symbols::SymbolInfo SymbolInfo
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.
static void mapping(IO &io, IncludeFixerContext::QuerySymbolInfo &Info)
llvm::StringRef Directory
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
CharSourceRange Range
SourceRange for the file name.
static llvm::ErrorOr< std::unique_ptr< YamlSymbolIndex > > createFromFile(llvm::StringRef FilePath)
Create a new Yaml db from a file.
static cl::opt< bool > Quiet("quiet", cl::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))