2016-04-20 20:43:43 +08:00
|
|
|
//===-- IncludeFixer.cpp - Include inserter based on sema callbacks -------===//
|
|
|
|
//
|
|
|
|
// The LLVM Compiler Infrastructure
|
|
|
|
//
|
|
|
|
// This file is distributed under the University of Illinois Open Source
|
|
|
|
// License. See LICENSE.TXT for details.
|
|
|
|
//
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
|
|
|
#include "IncludeFixer.h"
|
2016-05-19 16:21:09 +08:00
|
|
|
#include "clang/Format/Format.h"
|
2016-04-20 20:43:43 +08:00
|
|
|
#include "clang/Frontend/CompilerInstance.h"
|
2016-04-28 19:21:29 +08:00
|
|
|
#include "clang/Lex/HeaderSearch.h"
|
2016-04-20 20:43:43 +08:00
|
|
|
#include "clang/Lex/Preprocessor.h"
|
|
|
|
#include "clang/Parse/ParseAST.h"
|
|
|
|
#include "clang/Sema/ExternalSemaSource.h"
|
|
|
|
#include "clang/Sema/Sema.h"
|
|
|
|
#include "llvm/Support/Debug.h"
|
|
|
|
#include "llvm/Support/raw_ostream.h"
|
|
|
|
|
|
|
|
#define DEBUG_TYPE "include-fixer"
|
|
|
|
|
|
|
|
using namespace clang;
|
|
|
|
|
|
|
|
namespace clang {
|
|
|
|
namespace include_fixer {
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
/// Manages the parse, gathers include suggestions.
|
|
|
|
class Action : public clang::ASTFrontendAction,
|
|
|
|
public clang::ExternalSemaSource {
|
|
|
|
public:
|
2016-05-31 21:23:00 +08:00
|
|
|
explicit Action(SymbolIndexManager &SymbolIndexMgr, bool MinimizeIncludePaths)
|
|
|
|
: SymbolIndexMgr(SymbolIndexMgr),
|
2016-05-13 17:27:54 +08:00
|
|
|
MinimizeIncludePaths(MinimizeIncludePaths) {}
|
2016-04-20 20:43:43 +08:00
|
|
|
|
|
|
|
std::unique_ptr<clang::ASTConsumer>
|
|
|
|
CreateASTConsumer(clang::CompilerInstance &Compiler,
|
|
|
|
StringRef InFile) override {
|
|
|
|
Filename = InFile;
|
|
|
|
return llvm::make_unique<clang::ASTConsumer>();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ExecuteAction() override {
|
|
|
|
clang::CompilerInstance *Compiler = &getCompilerInstance();
|
|
|
|
assert(!Compiler->hasSema() && "CI already has Sema");
|
|
|
|
|
|
|
|
// Set up our hooks into sema and parse the AST.
|
|
|
|
if (hasCodeCompletionSupport() &&
|
|
|
|
!Compiler->getFrontendOpts().CodeCompletionAt.FileName.empty())
|
|
|
|
Compiler->createCodeCompletionConsumer();
|
|
|
|
|
|
|
|
clang::CodeCompleteConsumer *CompletionConsumer = nullptr;
|
|
|
|
if (Compiler->hasCodeCompletionConsumer())
|
|
|
|
CompletionConsumer = &Compiler->getCodeCompletionConsumer();
|
|
|
|
|
|
|
|
Compiler->createSema(getTranslationUnitKind(), CompletionConsumer);
|
|
|
|
Compiler->getSema().addExternalSource(this);
|
|
|
|
|
|
|
|
clang::ParseAST(Compiler->getSema(), Compiler->getFrontendOpts().ShowStats,
|
|
|
|
Compiler->getFrontendOpts().SkipFunctionBodies);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Callback for incomplete types. If we encounter a forward declaration we
|
|
|
|
/// have the fully qualified name ready. Just query that.
|
|
|
|
bool MaybeDiagnoseMissingCompleteType(clang::SourceLocation Loc,
|
|
|
|
clang::QualType T) override {
|
2016-05-11 23:33:30 +08:00
|
|
|
// Ignore spurious callbacks from SFINAE contexts.
|
|
|
|
if (getCompilerInstance().getSema().isSFINAEContext())
|
|
|
|
return false;
|
|
|
|
|
2016-04-20 20:43:43 +08:00
|
|
|
clang::ASTContext &context = getCompilerInstance().getASTContext();
|
2016-07-08 17:10:29 +08:00
|
|
|
std::string QueryString =
|
|
|
|
T.getUnqualifiedType().getAsString(context.getPrintingPolicy());
|
|
|
|
DEBUG(llvm::dbgs() << "Query missing complete type '" << QueryString
|
|
|
|
<< "'");
|
|
|
|
query(QueryString, "", tooling::Range());
|
2016-04-20 20:43:43 +08:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Callback for unknown identifiers. Try to piece together as much
|
|
|
|
/// qualification as we can get and do a query.
|
|
|
|
clang::TypoCorrection CorrectTypo(const DeclarationNameInfo &Typo,
|
|
|
|
int LookupKind, Scope *S, CXXScopeSpec *SS,
|
|
|
|
CorrectionCandidateCallback &CCC,
|
|
|
|
DeclContext *MemberContext,
|
|
|
|
bool EnteringContext,
|
|
|
|
const ObjCObjectPointerType *OPT) override {
|
2016-05-11 23:33:30 +08:00
|
|
|
// Ignore spurious callbacks from SFINAE contexts.
|
|
|
|
if (getCompilerInstance().getSema().isSFINAEContext())
|
|
|
|
return clang::TypoCorrection();
|
|
|
|
|
2016-06-03 19:26:02 +08:00
|
|
|
// We currently ignore the unidentified symbol which is not from the
|
|
|
|
// main file.
|
|
|
|
//
|
|
|
|
// However, this is not always true due to templates in a non-self contained
|
|
|
|
// header, consider the case:
|
|
|
|
//
|
|
|
|
// // header.h
|
|
|
|
// template <typename T>
|
|
|
|
// class Foo {
|
|
|
|
// T t;
|
|
|
|
// };
|
|
|
|
//
|
|
|
|
// // test.cc
|
|
|
|
// // We need to add <bar.h> in test.cc instead of header.h.
|
|
|
|
// class Bar;
|
|
|
|
// Foo<Bar> foo;
|
|
|
|
//
|
|
|
|
// FIXME: Add the missing header to the header file where the symbol comes
|
|
|
|
// from.
|
|
|
|
if (!getCompilerInstance().getSourceManager().isWrittenInMainFile(
|
|
|
|
Typo.getLoc()))
|
|
|
|
return clang::TypoCorrection();
|
|
|
|
|
2016-05-13 23:44:16 +08:00
|
|
|
std::string TypoScopeString;
|
|
|
|
if (S) {
|
|
|
|
// FIXME: Currently we only use namespace contexts. Use other context
|
|
|
|
// types for query.
|
|
|
|
for (const auto *Context = S->getEntity(); Context;
|
|
|
|
Context = Context->getParent()) {
|
|
|
|
if (const auto *ND = dyn_cast<NamespaceDecl>(Context)) {
|
|
|
|
if (!ND->getName().empty())
|
|
|
|
TypoScopeString = ND->getNameAsString() + "::" + TypoScopeString;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-17 20:35:18 +08:00
|
|
|
auto ExtendNestedNameSpecifier = [this](CharSourceRange Range) {
|
2016-05-10 16:25:31 +08:00
|
|
|
StringRef Source =
|
2016-04-20 20:43:43 +08:00
|
|
|
Lexer::getSourceText(Range, getCompilerInstance().getSourceManager(),
|
|
|
|
getCompilerInstance().getLangOpts());
|
2016-05-10 16:25:31 +08:00
|
|
|
|
|
|
|
// Skip forward until we find a character that's neither identifier nor
|
|
|
|
// colon. This is a bit of a hack around the fact that we will only get a
|
|
|
|
// single callback for a long nested name if a part of the beginning is
|
|
|
|
// unknown. For example:
|
|
|
|
//
|
|
|
|
// llvm::sys::path::parent_path(...)
|
|
|
|
// ^~~~ ^~~
|
|
|
|
// known
|
|
|
|
// ^~~~
|
|
|
|
// unknown, last callback
|
|
|
|
// ^~~~~~~~~~~
|
|
|
|
// no callback
|
|
|
|
//
|
|
|
|
// With the extension we get the full nested name specifier including
|
|
|
|
// parent_path.
|
|
|
|
// FIXME: Don't rely on source text.
|
|
|
|
const char *End = Source.end();
|
|
|
|
while (isIdentifierBody(*End) || *End == ':')
|
|
|
|
++End;
|
|
|
|
|
2016-05-17 20:35:18 +08:00
|
|
|
return std::string(Source.begin(), End);
|
|
|
|
};
|
|
|
|
|
|
|
|
/// If we have a scope specification, use that to get more precise results.
|
|
|
|
std::string QueryString;
|
2016-07-08 17:10:29 +08:00
|
|
|
tooling::Range SymbolRange;
|
|
|
|
const auto &SM = getCompilerInstance().getSourceManager();
|
|
|
|
auto CreateToolingRange = [&QueryString, &SM](SourceLocation BeginLoc) {
|
|
|
|
return tooling::Range(SM.getDecomposedLoc(BeginLoc).second,
|
|
|
|
QueryString.size());
|
|
|
|
};
|
2016-05-17 20:35:18 +08:00
|
|
|
if (SS && SS->getRange().isValid()) {
|
|
|
|
auto Range = CharSourceRange::getTokenRange(SS->getRange().getBegin(),
|
|
|
|
Typo.getLoc());
|
|
|
|
|
|
|
|
QueryString = ExtendNestedNameSpecifier(Range);
|
2016-07-08 17:10:29 +08:00
|
|
|
SymbolRange = CreateToolingRange(Range.getBegin());
|
2016-05-17 20:35:18 +08:00
|
|
|
} else if (Typo.getName().isIdentifier() && !Typo.getLoc().isMacroID()) {
|
|
|
|
auto Range =
|
|
|
|
CharSourceRange::getTokenRange(Typo.getBeginLoc(), Typo.getEndLoc());
|
|
|
|
|
|
|
|
QueryString = ExtendNestedNameSpecifier(Range);
|
2016-07-08 17:10:29 +08:00
|
|
|
SymbolRange = CreateToolingRange(Range.getBegin());
|
2016-04-20 20:43:43 +08:00
|
|
|
} else {
|
|
|
|
QueryString = Typo.getAsString();
|
2016-07-08 17:10:29 +08:00
|
|
|
SymbolRange = CreateToolingRange(Typo.getLoc());
|
2016-04-20 20:43:43 +08:00
|
|
|
}
|
|
|
|
|
2016-07-08 17:10:29 +08:00
|
|
|
DEBUG(llvm::dbgs() << "TypoScopeQualifiers: " << TypoScopeString << "\n");
|
|
|
|
query(QueryString, TypoScopeString, SymbolRange);
|
2016-05-13 23:44:16 +08:00
|
|
|
|
|
|
|
// FIXME: We should just return the name we got as input here and prevent
|
|
|
|
// clang from trying to correct the typo by itself. That may change the
|
|
|
|
// identifier to something that's not wanted by the user.
|
|
|
|
return clang::TypoCorrection();
|
2016-04-20 20:43:43 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
StringRef filename() const { return Filename; }
|
|
|
|
|
2016-04-28 19:21:29 +08:00
|
|
|
/// Get the minimal include for a given path.
|
|
|
|
std::string minimizeInclude(StringRef Include,
|
2016-05-31 17:31:51 +08:00
|
|
|
const clang::SourceManager &SourceManager,
|
2016-04-28 19:21:29 +08:00
|
|
|
clang::HeaderSearch &HeaderSearch) {
|
|
|
|
if (!MinimizeIncludePaths)
|
|
|
|
return Include;
|
|
|
|
|
|
|
|
// Get the FileEntry for the include.
|
|
|
|
StringRef StrippedInclude = Include.trim("\"<>");
|
|
|
|
const FileEntry *Entry =
|
|
|
|
SourceManager.getFileManager().getFile(StrippedInclude);
|
|
|
|
|
|
|
|
// If the file doesn't exist return the path from the database.
|
|
|
|
// FIXME: This should never happen.
|
|
|
|
if (!Entry)
|
|
|
|
return Include;
|
|
|
|
|
|
|
|
bool IsSystem;
|
|
|
|
std::string Suggestion =
|
|
|
|
HeaderSearch.suggestPathToFileForDiagnostics(Entry, &IsSystem);
|
|
|
|
|
|
|
|
return IsSystem ? '<' + Suggestion + '>' : '"' + Suggestion + '"';
|
|
|
|
}
|
|
|
|
|
2016-05-31 17:31:51 +08:00
|
|
|
/// Get the include fixer context for the queried symbol.
|
|
|
|
IncludeFixerContext
|
|
|
|
getIncludeFixerContext(const clang::SourceManager &SourceManager,
|
|
|
|
clang::HeaderSearch &HeaderSearch) {
|
2016-07-08 17:10:29 +08:00
|
|
|
std::vector<find_all_symbols::SymbolInfo> SymbolCandidates;
|
|
|
|
for (const auto &Symbol : MatchedSymbols) {
|
|
|
|
std::string FilePath = Symbol.getFilePath().str();
|
|
|
|
std::string MinimizedFilePath = minimizeInclude(
|
|
|
|
((FilePath[0] == '"' || FilePath[0] == '<') ? FilePath
|
|
|
|
: "\"" + FilePath + "\""),
|
|
|
|
SourceManager, HeaderSearch);
|
|
|
|
SymbolCandidates.emplace_back(Symbol.getName(), Symbol.getSymbolKind(),
|
|
|
|
MinimizedFilePath, Symbol.getLineNumber(),
|
|
|
|
Symbol.getContexts(),
|
|
|
|
Symbol.getNumOccurrences());
|
|
|
|
}
|
|
|
|
return IncludeFixerContext(QuerySymbol, SymbolScopedQualifiers,
|
|
|
|
SymbolCandidates, QuerySymbolRange);
|
2016-04-20 20:43:43 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
/// Query the database for a given identifier.
|
2016-07-08 17:10:29 +08:00
|
|
|
bool query(StringRef Query, StringRef ScopedQualifiers, tooling::Range Range) {
|
2016-04-20 20:43:43 +08:00
|
|
|
assert(!Query.empty() && "Empty query!");
|
|
|
|
|
2016-07-08 17:10:29 +08:00
|
|
|
// Skip other identifiers once we have discovered an identfier successfully.
|
|
|
|
if (!MatchedSymbols.empty())
|
2016-05-25 22:06:12 +08:00
|
|
|
return false;
|
2016-04-20 20:43:43 +08:00
|
|
|
|
2016-05-11 23:33:28 +08:00
|
|
|
DEBUG(llvm::dbgs() << "Looking up '" << Query << "' at ");
|
2016-07-08 17:10:29 +08:00
|
|
|
DEBUG(getCompilerInstance()
|
|
|
|
.getSourceManager()
|
|
|
|
.getLocForStartOfFile(
|
|
|
|
getCompilerInstance().getSourceManager().getMainFileID())
|
|
|
|
.getLocWithOffset(Range.getOffset())
|
|
|
|
.print(llvm::dbgs(), getCompilerInstance().getSourceManager()));
|
2016-05-11 23:33:28 +08:00
|
|
|
DEBUG(llvm::dbgs() << " ...");
|
2016-04-20 20:43:43 +08:00
|
|
|
|
2016-05-31 17:31:51 +08:00
|
|
|
QuerySymbol = Query.str();
|
2016-07-08 17:10:29 +08:00
|
|
|
QuerySymbolRange = Range;
|
|
|
|
SymbolScopedQualifiers = ScopedQualifiers;
|
|
|
|
|
|
|
|
// Query the symbol based on C++ name Lookup rules.
|
|
|
|
// Firstly, lookup the identifier with scoped namespace contexts;
|
|
|
|
// If that fails, falls back to look up the identifier directly.
|
|
|
|
//
|
|
|
|
// For example:
|
|
|
|
//
|
|
|
|
// namespace a {
|
|
|
|
// b::foo f;
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// 1. lookup a::b::foo.
|
|
|
|
// 2. lookup b::foo.
|
|
|
|
std::string QueryString = ScopedQualifiers.str() + Query.str();
|
|
|
|
MatchedSymbols = SymbolIndexMgr.search(QueryString);
|
|
|
|
if (MatchedSymbols.empty() && !ScopedQualifiers.empty())
|
|
|
|
MatchedSymbols = SymbolIndexMgr.search(Query);
|
|
|
|
DEBUG(llvm::dbgs() << "Having found " << MatchedSymbols.size()
|
|
|
|
<< " symbols\n");
|
|
|
|
return !MatchedSymbols.empty();
|
2016-04-20 20:43:43 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/// The client to use to find cross-references.
|
2016-05-13 17:27:54 +08:00
|
|
|
SymbolIndexManager &SymbolIndexMgr;
|
2016-04-20 20:43:43 +08:00
|
|
|
|
|
|
|
/// The absolute path to the file being processed.
|
|
|
|
std::string Filename;
|
|
|
|
|
2016-05-31 17:31:51 +08:00
|
|
|
/// The symbol being queried.
|
|
|
|
std::string QuerySymbol;
|
|
|
|
|
2016-07-08 17:10:29 +08:00
|
|
|
/// The scoped qualifiers of QuerySymbol. It is represented as a sequence of
|
|
|
|
/// names and scope resolution operatiors ::, ending with a scope resolution
|
|
|
|
/// operator (e.g. a::b::). Empty if the symbol is not in a specific scope.
|
|
|
|
std::string SymbolScopedQualifiers;
|
|
|
|
|
|
|
|
/// The replacement range of the first discovered QuerySymbol.
|
|
|
|
tooling::Range QuerySymbolRange;
|
|
|
|
|
|
|
|
/// All symbol candidates which match QuerySymbol. We only include the first
|
|
|
|
/// discovered identifier to avoid getting caught in results from error
|
|
|
|
/// recovery.
|
|
|
|
std::vector<find_all_symbols::SymbolInfo> MatchedSymbols;
|
2016-04-28 19:21:29 +08:00
|
|
|
|
|
|
|
/// Whether we should use the smallest possible include path.
|
|
|
|
bool MinimizeIncludePaths = true;
|
2016-04-20 20:43:43 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
IncludeFixerActionFactory::IncludeFixerActionFactory(
|
2016-05-31 17:31:51 +08:00
|
|
|
SymbolIndexManager &SymbolIndexMgr, IncludeFixerContext &Context,
|
|
|
|
StringRef StyleName, bool MinimizeIncludePaths)
|
|
|
|
: SymbolIndexMgr(SymbolIndexMgr), Context(Context),
|
2016-05-31 21:23:00 +08:00
|
|
|
MinimizeIncludePaths(MinimizeIncludePaths) {}
|
2016-04-20 20:43:43 +08:00
|
|
|
|
|
|
|
IncludeFixerActionFactory::~IncludeFixerActionFactory() = default;
|
|
|
|
|
|
|
|
bool IncludeFixerActionFactory::runInvocation(
|
|
|
|
clang::CompilerInvocation *Invocation, clang::FileManager *Files,
|
|
|
|
std::shared_ptr<clang::PCHContainerOperations> PCHContainerOps,
|
|
|
|
clang::DiagnosticConsumer *Diagnostics) {
|
|
|
|
assert(Invocation->getFrontendOpts().Inputs.size() == 1);
|
|
|
|
|
|
|
|
// Set up Clang.
|
|
|
|
clang::CompilerInstance Compiler(PCHContainerOps);
|
|
|
|
Compiler.setInvocation(Invocation);
|
|
|
|
Compiler.setFileManager(Files);
|
|
|
|
|
|
|
|
// Create the compiler's actual diagnostics engine. We want to drop all
|
|
|
|
// diagnostics here.
|
|
|
|
Compiler.createDiagnostics(new clang::IgnoringDiagConsumer,
|
|
|
|
/*ShouldOwnClient=*/true);
|
|
|
|
Compiler.createSourceManager(*Files);
|
|
|
|
|
2016-05-18 21:32:38 +08:00
|
|
|
// We abort on fatal errors so don't let a large number of errors become
|
|
|
|
// fatal. A missing #include can cause thousands of errors.
|
|
|
|
Compiler.getDiagnostics().setErrorLimit(0);
|
|
|
|
|
2016-04-20 20:43:43 +08:00
|
|
|
// Run the parser, gather missing includes.
|
2016-05-31 21:23:00 +08:00
|
|
|
auto ScopedToolAction =
|
|
|
|
llvm::make_unique<Action>(SymbolIndexMgr, MinimizeIncludePaths);
|
2016-04-20 20:43:43 +08:00
|
|
|
Compiler.ExecuteAction(*ScopedToolAction);
|
|
|
|
|
2016-05-31 17:31:51 +08:00
|
|
|
Context = ScopedToolAction->getIncludeFixerContext(
|
|
|
|
Compiler.getSourceManager(),
|
|
|
|
Compiler.getPreprocessor().getHeaderSearchInfo());
|
2016-04-20 20:43:43 +08:00
|
|
|
|
|
|
|
// Technically this should only return true if we're sure that we have a
|
2016-05-18 21:32:38 +08:00
|
|
|
// parseable file. We don't know that though. Only inform users of fatal
|
|
|
|
// errors.
|
|
|
|
return !Compiler.getDiagnostics().hasFatalErrorOccurred();
|
2016-04-20 20:43:43 +08:00
|
|
|
}
|
|
|
|
|
2016-05-31 21:52:59 +08:00
|
|
|
tooling::Replacements
|
2016-05-31 17:31:51 +08:00
|
|
|
createInsertHeaderReplacements(StringRef Code, StringRef FilePath,
|
2016-05-31 22:48:45 +08:00
|
|
|
StringRef Header,
|
2016-05-31 17:31:51 +08:00
|
|
|
const clang::format::FormatStyle &Style) {
|
|
|
|
if (Header.empty())
|
2016-05-31 21:52:59 +08:00
|
|
|
return tooling::Replacements();
|
2016-05-31 22:48:45 +08:00
|
|
|
std::string IncludeName = "#include " + Header.str() + "\n";
|
|
|
|
// Create replacements for the new header.
|
|
|
|
clang::tooling::Replacements Insertions = {
|
|
|
|
tooling::Replacement(FilePath, UINT_MAX, 0, IncludeName)};
|
|
|
|
|
|
|
|
return formatReplacements(
|
|
|
|
Code, cleanupAroundReplacements(Code, Insertions, Style), Style);
|
2016-05-31 17:31:51 +08:00
|
|
|
}
|
|
|
|
|
2016-04-20 20:43:43 +08:00
|
|
|
} // namespace include_fixer
|
|
|
|
} // namespace clang
|