[clang-tools-extra] r287228 - [include-fixer] Refactor include fixer to be usable as a plugin

Benjamin Kramer via cfe-commits cfe-commits at lists.llvm.org
Thu Nov 17 07:16:06 PST 2016


Author: d0k
Date: Thu Nov 17 09:16:05 2016
New Revision: 287228

URL: http://llvm.org/viewvc/llvm-project?rev=287228&view=rev
Log:
[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

Added:
    clang-tools-extra/trunk/include-fixer/plugin/
    clang-tools-extra/trunk/include-fixer/plugin/CMakeLists.txt
    clang-tools-extra/trunk/include-fixer/plugin/IncludeFixerPlugin.cpp
Modified:
    clang-tools-extra/trunk/include-fixer/CMakeLists.txt
    clang-tools-extra/trunk/include-fixer/IncludeFixer.cpp
    clang-tools-extra/trunk/include-fixer/IncludeFixer.h

Modified: clang-tools-extra/trunk/include-fixer/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/include-fixer/CMakeLists.txt?rev=287228&r1=287227&r2=287228&view=diff
==============================================================================
--- clang-tools-extra/trunk/include-fixer/CMakeLists.txt (original)
+++ clang-tools-extra/trunk/include-fixer/CMakeLists.txt Thu Nov 17 09:16:05 2016
@@ -22,5 +22,6 @@ add_clang_library(clangIncludeFixer
   findAllSymbols
   )
 
+add_subdirectory(plugin)
 add_subdirectory(tool)
 add_subdirectory(find-all-symbols)

Modified: clang-tools-extra/trunk/include-fixer/IncludeFixer.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/include-fixer/IncludeFixer.cpp?rev=287228&r1=287227&r2=287228&view=diff
==============================================================================
--- clang-tools-extra/trunk/include-fixer/IncludeFixer.cpp (original)
+++ clang-tools-extra/trunk/include-fixer/IncludeFixer.cpp Thu Nov 17 09:16:05 2016
@@ -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::runInvoc
   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) {

Modified: clang-tools-extra/trunk/include-fixer/IncludeFixer.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/include-fixer/IncludeFixer.h?rev=287228&r1=287227&r2=287228&view=diff
==============================================================================
--- clang-tools-extra/trunk/include-fixer/IncludeFixer.h (original)
+++ clang-tools-extra/trunk/include-fixer/IncludeFixer.h Thu Nov 17 09:16:05 2016
@@ -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> cr
     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
 

Added: clang-tools-extra/trunk/include-fixer/plugin/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/include-fixer/plugin/CMakeLists.txt?rev=287228&view=auto
==============================================================================
--- clang-tools-extra/trunk/include-fixer/plugin/CMakeLists.txt (added)
+++ clang-tools-extra/trunk/include-fixer/plugin/CMakeLists.txt Thu Nov 17 09:16:05 2016
@@ -0,0 +1,12 @@
+add_clang_library(clangIncludeFixerPlugin
+  IncludeFixerPlugin.cpp
+
+  LINK_LIBS
+  clangAST
+  clangBasic
+  clangFrontend
+  clangIncludeFixer
+  clangParse
+  clangSema
+  clangTooling
+  )

Added: clang-tools-extra/trunk/include-fixer/plugin/IncludeFixerPlugin.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/include-fixer/plugin/IncludeFixerPlugin.cpp?rev=287228&view=auto
==============================================================================
--- clang-tools-extra/trunk/include-fixer/plugin/IncludeFixerPlugin.cpp (added)
+++ clang-tools-extra/trunk/include-fixer/plugin/IncludeFixerPlugin.cpp Thu Nov 17 09:16:05 2016
@@ -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");




More information about the cfe-commits mailing list