mirror of
https://github.com/capstone-engine/llvm-capstone.git
synced 2025-02-09 10:55:03 +00:00
[include-fixer] Refactor include fixer to be usable as a plugin
- Refactor the external sema source into a visible class - Add support for emitting FixIts - Wrap up include fixer as a plugin as I did with clang-tidy Test case will follow as soon as I wire this up in libclang. Differential Revision: https://reviews.llvm.org/D26752 llvm-svn: 287228
This commit is contained in:
parent
957d856e7e
commit
cd2494e93f
@ -22,5 +22,6 @@ add_clang_library(clangIncludeFixer
|
||||
findAllSymbols
|
||||
)
|
||||
|
||||
add_subdirectory(plugin)
|
||||
add_subdirectory(tool)
|
||||
add_subdirectory(find-all-symbols)
|
||||
|
@ -13,7 +13,6 @@
|
||||
#include "clang/Lex/HeaderSearch.h"
|
||||
#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"
|
||||
@ -25,19 +24,17 @@ using namespace clang;
|
||||
namespace clang {
|
||||
namespace include_fixer {
|
||||
namespace {
|
||||
|
||||
/// Manages the parse, gathers include suggestions.
|
||||
class Action : public clang::ASTFrontendAction,
|
||||
public clang::ExternalSemaSource {
|
||||
class Action : public clang::ASTFrontendAction {
|
||||
public:
|
||||
explicit Action(SymbolIndexManager &SymbolIndexMgr, bool MinimizeIncludePaths)
|
||||
: SymbolIndexMgr(SymbolIndexMgr),
|
||||
MinimizeIncludePaths(MinimizeIncludePaths) {}
|
||||
: SemaSource(SymbolIndexMgr, MinimizeIncludePaths,
|
||||
/*GenerateDiagnostics=*/false) {}
|
||||
|
||||
std::unique_ptr<clang::ASTConsumer>
|
||||
CreateASTConsumer(clang::CompilerInstance &Compiler,
|
||||
StringRef InFile) override {
|
||||
FilePath = InFile;
|
||||
SemaSource.setFilePath(InFile);
|
||||
return llvm::make_unique<clang::ASTConsumer>();
|
||||
}
|
||||
|
||||
@ -55,254 +52,21 @@ public:
|
||||
CompletionConsumer = &Compiler->getCodeCompletionConsumer();
|
||||
|
||||
Compiler->createSema(getTranslationUnitKind(), CompletionConsumer);
|
||||
Compiler->getSema().addExternalSource(this);
|
||||
SemaSource.setCompilerInstance(Compiler);
|
||||
Compiler->getSema().addExternalSource(&SemaSource);
|
||||
|
||||
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 {
|
||||
// Ignore spurious callbacks from SFINAE contexts.
|
||||
if (getCompilerInstance().getSema().isSFINAEContext())
|
||||
return false;
|
||||
|
||||
clang::ASTContext &context = getCompilerInstance().getASTContext();
|
||||
std::string QueryString =
|
||||
T.getUnqualifiedType().getAsString(context.getPrintingPolicy());
|
||||
DEBUG(llvm::dbgs() << "Query missing complete type '" << QueryString
|
||||
<< "'");
|
||||
// Pass an empty range here since we don't add qualifier in this case.
|
||||
query(QueryString, "", tooling::Range());
|
||||
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 {
|
||||
// Ignore spurious callbacks from SFINAE contexts.
|
||||
if (getCompilerInstance().getSema().isSFINAEContext())
|
||||
return clang::TypoCorrection();
|
||||
|
||||
// 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();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto ExtendNestedNameSpecifier = [this](CharSourceRange Range) {
|
||||
StringRef Source =
|
||||
Lexer::getSourceText(Range, getCompilerInstance().getSourceManager(),
|
||||
getCompilerInstance().getLangOpts());
|
||||
|
||||
// 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;
|
||||
|
||||
return std::string(Source.begin(), End);
|
||||
};
|
||||
|
||||
/// If we have a scope specification, use that to get more precise results.
|
||||
std::string QueryString;
|
||||
tooling::Range SymbolRange;
|
||||
const auto &SM = getCompilerInstance().getSourceManager();
|
||||
auto CreateToolingRange = [&QueryString, &SM](SourceLocation BeginLoc) {
|
||||
return tooling::Range(SM.getDecomposedLoc(BeginLoc).second,
|
||||
QueryString.size());
|
||||
};
|
||||
if (SS && SS->getRange().isValid()) {
|
||||
auto Range = CharSourceRange::getTokenRange(SS->getRange().getBegin(),
|
||||
Typo.getLoc());
|
||||
|
||||
QueryString = ExtendNestedNameSpecifier(Range);
|
||||
SymbolRange = CreateToolingRange(Range.getBegin());
|
||||
} else if (Typo.getName().isIdentifier() && !Typo.getLoc().isMacroID()) {
|
||||
auto Range =
|
||||
CharSourceRange::getTokenRange(Typo.getBeginLoc(), Typo.getEndLoc());
|
||||
|
||||
QueryString = ExtendNestedNameSpecifier(Range);
|
||||
SymbolRange = CreateToolingRange(Range.getBegin());
|
||||
} else {
|
||||
QueryString = Typo.getAsString();
|
||||
SymbolRange = CreateToolingRange(Typo.getLoc());
|
||||
}
|
||||
|
||||
DEBUG(llvm::dbgs() << "TypoScopeQualifiers: " << TypoScopeString << "\n");
|
||||
query(QueryString, TypoScopeString, SymbolRange);
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
/// Get the minimal include for a given path.
|
||||
std::string minimizeInclude(StringRef Include,
|
||||
const clang::SourceManager &SourceManager,
|
||||
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 + '"';
|
||||
}
|
||||
|
||||
/// Get the include fixer context for the queried symbol.
|
||||
IncludeFixerContext
|
||||
getIncludeFixerContext(const clang::SourceManager &SourceManager,
|
||||
clang::HeaderSearch &HeaderSearch) {
|
||||
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(FilePath, QuerySymbolInfos, SymbolCandidates);
|
||||
clang::HeaderSearch &HeaderSearch) const {
|
||||
return SemaSource.getIncludeFixerContext(SourceManager, HeaderSearch);
|
||||
}
|
||||
|
||||
private:
|
||||
/// Query the database for a given identifier.
|
||||
bool query(StringRef Query, StringRef ScopedQualifiers,
|
||||
tooling::Range Range) {
|
||||
assert(!Query.empty() && "Empty query!");
|
||||
|
||||
// Save all instances of an unidentified symbol.
|
||||
//
|
||||
// We use conservative behavior for detecting the same unidentified symbol
|
||||
// here. The symbols which have the same ScopedQualifier and RawIdentifier
|
||||
// are considered equal. So that include-fixer avoids false positives, and
|
||||
// always adds missing qualifiers to correct symbols.
|
||||
if (!QuerySymbolInfos.empty()) {
|
||||
if (ScopedQualifiers == QuerySymbolInfos.front().ScopedQualifiers &&
|
||||
Query == QuerySymbolInfos.front().RawIdentifier) {
|
||||
QuerySymbolInfos.push_back({Query.str(), ScopedQualifiers, Range});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
DEBUG(llvm::dbgs() << "Looking up '" << Query << "' at ");
|
||||
DEBUG(getCompilerInstance()
|
||||
.getSourceManager()
|
||||
.getLocForStartOfFile(
|
||||
getCompilerInstance().getSourceManager().getMainFileID())
|
||||
.getLocWithOffset(Range.getOffset())
|
||||
.print(llvm::dbgs(), getCompilerInstance().getSourceManager()));
|
||||
DEBUG(llvm::dbgs() << " ...");
|
||||
|
||||
QuerySymbolInfos.push_back({Query.str(), ScopedQualifiers, Range});
|
||||
|
||||
// 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();
|
||||
// It's unsafe to do nested search for the identifier with scoped namespace
|
||||
// context, it might treat the identifier as a nested class of the scoped
|
||||
// namespace.
|
||||
MatchedSymbols = SymbolIndexMgr.search(QueryString, /*IsNestedSearch=*/false);
|
||||
if (MatchedSymbols.empty())
|
||||
MatchedSymbols = SymbolIndexMgr.search(Query);
|
||||
DEBUG(llvm::dbgs() << "Having found " << MatchedSymbols.size()
|
||||
<< " symbols\n");
|
||||
return !MatchedSymbols.empty();
|
||||
}
|
||||
|
||||
/// The client to use to find cross-references.
|
||||
SymbolIndexManager &SymbolIndexMgr;
|
||||
|
||||
/// The information of the symbols being queried.
|
||||
std::vector<IncludeFixerContext::QuerySymbolInfo> QuerySymbolInfos;
|
||||
|
||||
/// 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;
|
||||
|
||||
/// The file path to the file being processed.
|
||||
std::string FilePath;
|
||||
|
||||
/// Whether we should use the smallest possible include path.
|
||||
bool MinimizeIncludePaths = true;
|
||||
IncludeFixerSemaSource SemaSource;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
@ -352,6 +116,273 @@ bool IncludeFixerActionFactory::runInvocation(
|
||||
return !Compiler.getDiagnostics().hasFatalErrorOccurred();
|
||||
}
|
||||
|
||||
static void addDiagnosticsForContext(TypoCorrection &Correction,
|
||||
const IncludeFixerContext &Context,
|
||||
StringRef Code, SourceLocation StartOfFile,
|
||||
ASTContext &Ctx) {
|
||||
auto Reps = createIncludeFixerReplacements(
|
||||
Code, Context, format::getLLVMStyle(), /*AddQualifiers=*/false);
|
||||
if (!Reps)
|
||||
return;
|
||||
|
||||
unsigned DiagID = Ctx.getDiagnostics().getCustomDiagID(
|
||||
DiagnosticsEngine::Note, "Add '#include %0' to provide the missing "
|
||||
"declaration [clang-include-fixer]");
|
||||
|
||||
// FIXME: Currently we only generate a diagnostic for the first header. Give
|
||||
// the user choices.
|
||||
assert(Reps->size() == 1 && "Expected exactly one replacement");
|
||||
const tooling::Replacement &Placed = *Reps->begin();
|
||||
|
||||
auto Begin = StartOfFile.getLocWithOffset(Placed.getOffset());
|
||||
auto End = Begin.getLocWithOffset(Placed.getLength());
|
||||
PartialDiagnostic PD(DiagID, Ctx.getDiagAllocator());
|
||||
PD << Context.getHeaderInfos().front().Header
|
||||
<< FixItHint::CreateReplacement(SourceRange(Begin, End),
|
||||
Placed.getReplacementText());
|
||||
Correction.addExtraDiagnostic(std::move(PD));
|
||||
}
|
||||
|
||||
/// Callback for incomplete types. If we encounter a forward declaration we
|
||||
/// have the fully qualified name ready. Just query that.
|
||||
bool IncludeFixerSemaSource::MaybeDiagnoseMissingCompleteType(
|
||||
clang::SourceLocation Loc, clang::QualType T) {
|
||||
// Ignore spurious callbacks from SFINAE contexts.
|
||||
if (CI->getSema().isSFINAEContext())
|
||||
return false;
|
||||
|
||||
clang::ASTContext &context = CI->getASTContext();
|
||||
std::string QueryString =
|
||||
T.getUnqualifiedType().getAsString(context.getPrintingPolicy());
|
||||
DEBUG(llvm::dbgs() << "Query missing complete type '" << QueryString << "'");
|
||||
// Pass an empty range here since we don't add qualifier in this case.
|
||||
query(QueryString, "", tooling::Range());
|
||||
|
||||
if (GenerateDiagnostics) {
|
||||
TypoCorrection Correction;
|
||||
FileID FID = CI->getSourceManager().getFileID(Loc);
|
||||
StringRef Code = CI->getSourceManager().getBufferData(FID);
|
||||
SourceLocation StartOfFile =
|
||||
CI->getSourceManager().getLocForStartOfFile(FID);
|
||||
addDiagnosticsForContext(
|
||||
Correction,
|
||||
getIncludeFixerContext(CI->getSourceManager(),
|
||||
CI->getPreprocessor().getHeaderSearchInfo()),
|
||||
Code, StartOfFile, CI->getASTContext());
|
||||
for (const PartialDiagnostic &PD : Correction.getExtraDiagnostics())
|
||||
CI->getSema().Diag(Loc, PD);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Callback for unknown identifiers. Try to piece together as much
|
||||
/// qualification as we can get and do a query.
|
||||
clang::TypoCorrection IncludeFixerSemaSource::CorrectTypo(
|
||||
const DeclarationNameInfo &Typo, int LookupKind, Scope *S, CXXScopeSpec *SS,
|
||||
CorrectionCandidateCallback &CCC, DeclContext *MemberContext,
|
||||
bool EnteringContext, const ObjCObjectPointerType *OPT) {
|
||||
// Ignore spurious callbacks from SFINAE contexts.
|
||||
if (CI->getSema().isSFINAEContext())
|
||||
return clang::TypoCorrection();
|
||||
|
||||
// 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 (!CI->getSourceManager().isWrittenInMainFile(Typo.getLoc()))
|
||||
return clang::TypoCorrection();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto ExtendNestedNameSpecifier = [this](CharSourceRange Range) {
|
||||
StringRef Source =
|
||||
Lexer::getSourceText(Range, CI->getSourceManager(), CI->getLangOpts());
|
||||
|
||||
// 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;
|
||||
|
||||
return std::string(Source.begin(), End);
|
||||
};
|
||||
|
||||
/// If we have a scope specification, use that to get more precise results.
|
||||
std::string QueryString;
|
||||
tooling::Range SymbolRange;
|
||||
const auto &SM = CI->getSourceManager();
|
||||
auto CreateToolingRange = [&QueryString, &SM](SourceLocation BeginLoc) {
|
||||
return tooling::Range(SM.getDecomposedLoc(BeginLoc).second,
|
||||
QueryString.size());
|
||||
};
|
||||
if (SS && SS->getRange().isValid()) {
|
||||
auto Range = CharSourceRange::getTokenRange(SS->getRange().getBegin(),
|
||||
Typo.getLoc());
|
||||
|
||||
QueryString = ExtendNestedNameSpecifier(Range);
|
||||
SymbolRange = CreateToolingRange(Range.getBegin());
|
||||
} else if (Typo.getName().isIdentifier() && !Typo.getLoc().isMacroID()) {
|
||||
auto Range =
|
||||
CharSourceRange::getTokenRange(Typo.getBeginLoc(), Typo.getEndLoc());
|
||||
|
||||
QueryString = ExtendNestedNameSpecifier(Range);
|
||||
SymbolRange = CreateToolingRange(Range.getBegin());
|
||||
} else {
|
||||
QueryString = Typo.getAsString();
|
||||
SymbolRange = CreateToolingRange(Typo.getLoc());
|
||||
}
|
||||
|
||||
DEBUG(llvm::dbgs() << "TypoScopeQualifiers: " << TypoScopeString << "\n");
|
||||
query(QueryString, TypoScopeString, SymbolRange);
|
||||
|
||||
clang::TypoCorrection Correction(Typo.getName());
|
||||
Correction.setCorrectionRange(SS, Typo);
|
||||
if (GenerateDiagnostics) {
|
||||
FileID FID = SM.getFileID(Typo.getLoc());
|
||||
StringRef Code = SM.getBufferData(FID);
|
||||
SourceLocation StartOfFile = SM.getLocForStartOfFile(FID);
|
||||
addDiagnosticsForContext(
|
||||
Correction,
|
||||
getIncludeFixerContext(SM, CI->getPreprocessor().getHeaderSearchInfo()),
|
||||
Code, StartOfFile, CI->getASTContext());
|
||||
}
|
||||
return Correction;
|
||||
}
|
||||
|
||||
/// Get the minimal include for a given path.
|
||||
std::string IncludeFixerSemaSource::minimizeInclude(
|
||||
StringRef Include, const clang::SourceManager &SourceManager,
|
||||
clang::HeaderSearch &HeaderSearch) const {
|
||||
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 + '"';
|
||||
}
|
||||
|
||||
/// Get the include fixer context for the queried symbol.
|
||||
IncludeFixerContext IncludeFixerSemaSource::getIncludeFixerContext(
|
||||
const clang::SourceManager &SourceManager,
|
||||
clang::HeaderSearch &HeaderSearch) const {
|
||||
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(FilePath, QuerySymbolInfos, SymbolCandidates);
|
||||
}
|
||||
|
||||
bool IncludeFixerSemaSource::query(StringRef Query, StringRef ScopedQualifiers,
|
||||
tooling::Range Range) {
|
||||
assert(!Query.empty() && "Empty query!");
|
||||
|
||||
// Save all instances of an unidentified symbol.
|
||||
//
|
||||
// We use conservative behavior for detecting the same unidentified symbol
|
||||
// here. The symbols which have the same ScopedQualifier and RawIdentifier
|
||||
// are considered equal. So that include-fixer avoids false positives, and
|
||||
// always adds missing qualifiers to correct symbols.
|
||||
if (!QuerySymbolInfos.empty()) {
|
||||
if (ScopedQualifiers == QuerySymbolInfos.front().ScopedQualifiers &&
|
||||
Query == QuerySymbolInfos.front().RawIdentifier) {
|
||||
QuerySymbolInfos.push_back({Query.str(), ScopedQualifiers, Range});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
DEBUG(llvm::dbgs() << "Looking up '" << Query << "' at ");
|
||||
DEBUG(CI->getSourceManager()
|
||||
.getLocForStartOfFile(CI->getSourceManager().getMainFileID())
|
||||
.getLocWithOffset(Range.getOffset())
|
||||
.print(llvm::dbgs(), CI->getSourceManager()));
|
||||
DEBUG(llvm::dbgs() << " ...");
|
||||
|
||||
QuerySymbolInfos.push_back({Query.str(), ScopedQualifiers, Range});
|
||||
|
||||
// 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();
|
||||
// It's unsafe to do nested search for the identifier with scoped namespace
|
||||
// context, it might treat the identifier as a nested class of the scoped
|
||||
// namespace.
|
||||
MatchedSymbols = SymbolIndexMgr.search(QueryString, /*IsNestedSearch=*/false);
|
||||
if (MatchedSymbols.empty())
|
||||
MatchedSymbols = SymbolIndexMgr.search(Query);
|
||||
DEBUG(llvm::dbgs() << "Having found " << MatchedSymbols.size()
|
||||
<< " symbols\n");
|
||||
return !MatchedSymbols.empty();
|
||||
}
|
||||
|
||||
llvm::Expected<tooling::Replacements> createIncludeFixerReplacements(
|
||||
StringRef Code, const IncludeFixerContext &Context,
|
||||
const clang::format::FormatStyle &Style, bool AddQualifiers) {
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include "IncludeFixerContext.h"
|
||||
#include "SymbolIndexManager.h"
|
||||
#include "clang/Format/Format.h"
|
||||
#include "clang/Sema/ExternalSemaSource.h"
|
||||
#include "clang/Tooling/Core/Replacement.h"
|
||||
#include "clang/Tooling/Tooling.h"
|
||||
#include <memory>
|
||||
@ -80,6 +81,70 @@ llvm::Expected<tooling::Replacements> createIncludeFixerReplacements(
|
||||
const format::FormatStyle &Style = format::getLLVMStyle(),
|
||||
bool AddQualifiers = true);
|
||||
|
||||
/// Handles callbacks from sema, does the include lookup and turns it into an
|
||||
/// IncludeFixerContext.
|
||||
class IncludeFixerSemaSource : public clang::ExternalSemaSource {
|
||||
public:
|
||||
explicit IncludeFixerSemaSource(SymbolIndexManager &SymbolIndexMgr,
|
||||
bool MinimizeIncludePaths,
|
||||
bool GenerateDiagnostics)
|
||||
: SymbolIndexMgr(SymbolIndexMgr),
|
||||
MinimizeIncludePaths(MinimizeIncludePaths),
|
||||
GenerateDiagnostics(GenerateDiagnostics) {}
|
||||
|
||||
void setCompilerInstance(CompilerInstance *CI) { this->CI = CI; }
|
||||
void setFilePath(StringRef FilePath) { this->FilePath = FilePath; }
|
||||
|
||||
/// 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;
|
||||
|
||||
/// 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;
|
||||
|
||||
/// Get the minimal include for a given path.
|
||||
std::string minimizeInclude(StringRef Include,
|
||||
const clang::SourceManager &SourceManager,
|
||||
clang::HeaderSearch &HeaderSearch) const;
|
||||
|
||||
/// Get the include fixer context for the queried symbol.
|
||||
IncludeFixerContext
|
||||
getIncludeFixerContext(const clang::SourceManager &SourceManager,
|
||||
clang::HeaderSearch &HeaderSearch) const;
|
||||
|
||||
private:
|
||||
/// Query the database for a given identifier.
|
||||
bool query(StringRef Query, StringRef ScopedQualifiers, tooling::Range Range);
|
||||
|
||||
CompilerInstance *CI;
|
||||
|
||||
/// The client to use to find cross-references.
|
||||
SymbolIndexManager &SymbolIndexMgr;
|
||||
|
||||
/// The information of the symbols being queried.
|
||||
std::vector<IncludeFixerContext::QuerySymbolInfo> QuerySymbolInfos;
|
||||
|
||||
/// 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;
|
||||
|
||||
/// The file path to the file being processed.
|
||||
std::string FilePath;
|
||||
|
||||
/// Whether we should use the smallest possible include path.
|
||||
bool MinimizeIncludePaths = true;
|
||||
|
||||
/// Whether we should generate diagnostics with fixits for missing symbols.
|
||||
bool GenerateDiagnostics = false;
|
||||
};
|
||||
} // namespace include_fixer
|
||||
} // namespace clang
|
||||
|
||||
|
12
clang-tools-extra/include-fixer/plugin/CMakeLists.txt
Normal file
12
clang-tools-extra/include-fixer/plugin/CMakeLists.txt
Normal file
@ -0,0 +1,12 @@
|
||||
add_clang_library(clangIncludeFixerPlugin
|
||||
IncludeFixerPlugin.cpp
|
||||
|
||||
LINK_LIBS
|
||||
clangAST
|
||||
clangBasic
|
||||
clangFrontend
|
||||
clangIncludeFixer
|
||||
clangParse
|
||||
clangSema
|
||||
clangTooling
|
||||
)
|
@ -0,0 +1,97 @@
|
||||
//===- IncludeFixerPlugin.cpp - clang-include-fixer as a clang plugin -----===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "../IncludeFixer.h"
|
||||
#include "../YamlSymbolIndex.h"
|
||||
#include "clang/Frontend/CompilerInstance.h"
|
||||
#include "clang/Frontend/FrontendPluginRegistry.h"
|
||||
#include "clang/Parse/ParseAST.h"
|
||||
#include "clang/Sema/Sema.h"
|
||||
#include "llvm/Support/Path.h"
|
||||
|
||||
namespace clang {
|
||||
namespace include_fixer {
|
||||
|
||||
/// The core include fixer plugin action. This just provides the AST consumer
|
||||
/// and command line flag parsing for using include fixer as a clang plugin.
|
||||
class ClangIncludeFixerPluginAction : public PluginASTAction {
|
||||
/// ASTConsumer to keep the symbol index alive. We don't really need an
|
||||
/// ASTConsumer for this plugin (everything is funneled on the side through
|
||||
/// Sema) but we have to keep the symbol index alive until sema is done.
|
||||
struct ASTConsumerManagerWrapper : public ASTConsumer {
|
||||
ASTConsumerManagerWrapper(std::shared_ptr<SymbolIndexManager> SIM)
|
||||
: SymbolIndexMgr(std::move(SIM)) {}
|
||||
std::shared_ptr<SymbolIndexManager> SymbolIndexMgr;
|
||||
};
|
||||
|
||||
public:
|
||||
explicit ClangIncludeFixerPluginAction()
|
||||
: SymbolIndexMgr(std::make_shared<SymbolIndexManager>()),
|
||||
SemaSource(new IncludeFixerSemaSource(*SymbolIndexMgr,
|
||||
/*MinimizeIncludePaths=*/true,
|
||||
/*GenerateDiagnostics=*/true)) {}
|
||||
|
||||
std::unique_ptr<clang::ASTConsumer>
|
||||
CreateASTConsumer(clang::CompilerInstance &CI, StringRef InFile) override {
|
||||
CI.setExternalSemaSource(SemaSource);
|
||||
SemaSource->setFilePath(InFile);
|
||||
SemaSource->setCompilerInstance(&CI);
|
||||
return llvm::make_unique<ASTConsumerManagerWrapper>(SymbolIndexMgr);
|
||||
}
|
||||
|
||||
void ExecuteAction() override {} // Do nothing.
|
||||
|
||||
bool ParseArgs(const CompilerInstance &CI,
|
||||
const std::vector<std::string> &Args) override {
|
||||
StringRef DB = "yaml";
|
||||
StringRef Input;
|
||||
|
||||
// Parse the extra command line args.
|
||||
// FIXME: This is very limited at the moment.
|
||||
for (StringRef Arg : Args) {
|
||||
if (Arg.startswith("-db="))
|
||||
DB = Arg.substr(strlen("-db="));
|
||||
else if (Arg.startswith("-input="))
|
||||
Input = Arg.substr(strlen("-input="));
|
||||
}
|
||||
|
||||
llvm::ErrorOr<std::unique_ptr<include_fixer::YamlSymbolIndex>> SymbolIdx(
|
||||
nullptr);
|
||||
if (DB == "yaml") {
|
||||
if (!Input.empty()) {
|
||||
SymbolIdx = include_fixer::YamlSymbolIndex::createFromFile(Input);
|
||||
} else {
|
||||
// If we don't have any input file, look in the directory of the first
|
||||
// file and its parents.
|
||||
const FrontendOptions &FO = CI.getFrontendOpts();
|
||||
SmallString<128> AbsolutePath(
|
||||
tooling::getAbsolutePath(FO.Inputs[0].getFile()));
|
||||
StringRef Directory = llvm::sys::path::parent_path(AbsolutePath);
|
||||
SymbolIdx = include_fixer::YamlSymbolIndex::createFromDirectory(
|
||||
Directory, "find_all_symbols_db.yaml");
|
||||
}
|
||||
}
|
||||
SymbolIndexMgr->addSymbolIndex(std::move(*SymbolIdx));
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<SymbolIndexManager> SymbolIndexMgr;
|
||||
IntrusiveRefCntPtr<IncludeFixerSemaSource> SemaSource;
|
||||
};
|
||||
} // namespace include_fixer
|
||||
} // namespace clang
|
||||
|
||||
// This anchor is used to force the linker to link in the generated object file
|
||||
// and thus register the include fixer plugin.
|
||||
volatile int ClangIncludeFixerPluginAnchorSource = 0;
|
||||
|
||||
static clang::FrontendPluginRegistry::Add<
|
||||
clang::include_fixer::ClangIncludeFixerPluginAction>
|
||||
X("clang-include-fixer", "clang-include-fixer");
|
Loading…
x
Reference in New Issue
Block a user