446 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			446 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			C++
		
	
	
	
//===-- IncludeFixer.cpp - Include inserter based on sema callbacks -------===//
 | 
						|
//
 | 
						|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 | 
						|
// See https://llvm.org/LICENSE.txt for license information.
 | 
						|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 | 
						|
//
 | 
						|
//===----------------------------------------------------------------------===//
 | 
						|
 | 
						|
#include "IncludeFixer.h"
 | 
						|
#include "clang/Format/Format.h"
 | 
						|
#include "clang/Frontend/CompilerInstance.h"
 | 
						|
#include "clang/Lex/HeaderSearch.h"
 | 
						|
#include "clang/Lex/Preprocessor.h"
 | 
						|
#include "clang/Parse/ParseAST.h"
 | 
						|
#include "clang/Sema/Sema.h"
 | 
						|
#include "llvm/Support/Debug.h"
 | 
						|
#include "llvm/Support/raw_ostream.h"
 | 
						|
 | 
						|
#define DEBUG_TYPE "clang-include-fixer"
 | 
						|
 | 
						|
using namespace clang;
 | 
						|
 | 
						|
namespace clang {
 | 
						|
namespace include_fixer {
 | 
						|
namespace {
 | 
						|
/// Manages the parse, gathers include suggestions.
 | 
						|
class Action : public clang::ASTFrontendAction {
 | 
						|
public:
 | 
						|
  explicit Action(SymbolIndexManager &SymbolIndexMgr, bool MinimizeIncludePaths)
 | 
						|
      : SemaSource(SymbolIndexMgr, MinimizeIncludePaths,
 | 
						|
                   /*GenerateDiagnostics=*/false) {}
 | 
						|
 | 
						|
  std::unique_ptr<clang::ASTConsumer>
 | 
						|
  CreateASTConsumer(clang::CompilerInstance &Compiler,
 | 
						|
                    StringRef InFile) override {
 | 
						|
    SemaSource.setFilePath(InFile);
 | 
						|
    return std::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);
 | 
						|
    SemaSource.setCompilerInstance(Compiler);
 | 
						|
    Compiler->getSema().addExternalSource(&SemaSource);
 | 
						|
 | 
						|
    clang::ParseAST(Compiler->getSema(), Compiler->getFrontendOpts().ShowStats,
 | 
						|
                    Compiler->getFrontendOpts().SkipFunctionBodies);
 | 
						|
  }
 | 
						|
 | 
						|
  IncludeFixerContext
 | 
						|
  getIncludeFixerContext(const clang::SourceManager &SourceManager,
 | 
						|
                         clang::HeaderSearch &HeaderSearch) const {
 | 
						|
    return SemaSource.getIncludeFixerContext(SourceManager, HeaderSearch,
 | 
						|
                                             SemaSource.getMatchedSymbols());
 | 
						|
  }
 | 
						|
 | 
						|
private:
 | 
						|
  IncludeFixerSemaSource SemaSource;
 | 
						|
};
 | 
						|
 | 
						|
} // namespace
 | 
						|
 | 
						|
IncludeFixerActionFactory::IncludeFixerActionFactory(
 | 
						|
    SymbolIndexManager &SymbolIndexMgr,
 | 
						|
    std::vector<IncludeFixerContext> &Contexts, StringRef StyleName,
 | 
						|
    bool MinimizeIncludePaths)
 | 
						|
    : SymbolIndexMgr(SymbolIndexMgr), Contexts(Contexts),
 | 
						|
      MinimizeIncludePaths(MinimizeIncludePaths) {}
 | 
						|
 | 
						|
IncludeFixerActionFactory::~IncludeFixerActionFactory() = default;
 | 
						|
 | 
						|
bool IncludeFixerActionFactory::runInvocation(
 | 
						|
    std::shared_ptr<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(std::move(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);
 | 
						|
 | 
						|
  // 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);
 | 
						|
 | 
						|
  // Run the parser, gather missing includes.
 | 
						|
  auto ScopedToolAction =
 | 
						|
      std::make_unique<Action>(SymbolIndexMgr, MinimizeIncludePaths);
 | 
						|
  Compiler.ExecuteAction(*ScopedToolAction);
 | 
						|
 | 
						|
  Contexts.push_back(ScopedToolAction->getIncludeFixerContext(
 | 
						|
      Compiler.getSourceManager(),
 | 
						|
      Compiler.getPreprocessor().getHeaderSearchInfo()));
 | 
						|
 | 
						|
  // Technically this should only return true if we're sure that we have a
 | 
						|
  // parseable file. We don't know that though. Only inform users of fatal
 | 
						|
  // errors.
 | 
						|
  return !Compiler.getDiagnostics().hasFatalErrorOccurred();
 | 
						|
}
 | 
						|
 | 
						|
static bool addDiagnosticsForContext(TypoCorrection &Correction,
 | 
						|
                                     const IncludeFixerContext &Context,
 | 
						|
                                     StringRef Code, SourceLocation StartOfFile,
 | 
						|
                                     ASTContext &Ctx) {
 | 
						|
  auto Reps = createIncludeFixerReplacements(
 | 
						|
      Code, Context, format::getLLVMStyle(), /*AddQualifiers=*/false);
 | 
						|
  if (!Reps || Reps->size() != 1)
 | 
						|
    return false;
 | 
						|
 | 
						|
  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.
 | 
						|
  const tooling::Replacement &Placed = *Reps->begin();
 | 
						|
 | 
						|
  auto Begin = StartOfFile.getLocWithOffset(Placed.getOffset());
 | 
						|
  auto End = Begin.getLocWithOffset(std::max(0, (int)Placed.getLength() - 1));
 | 
						|
  PartialDiagnostic PD(DiagID, Ctx.getDiagAllocator());
 | 
						|
  PD << Context.getHeaderInfos().front().Header
 | 
						|
     << FixItHint::CreateReplacement(CharSourceRange::getCharRange(Begin, End),
 | 
						|
                                     Placed.getReplacementText());
 | 
						|
  Correction.addExtraDiagnostic(std::move(PD));
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
/// 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 = QualType(T->getUnqualifiedDesugaredType(), 0)
 | 
						|
                                .getAsString(context.getPrintingPolicy());
 | 
						|
  LLVM_DEBUG(llvm::dbgs() << "Query missing complete type '" << QueryString
 | 
						|
                          << "'");
 | 
						|
  // Pass an empty range here since we don't add qualifier in this case.
 | 
						|
  std::vector<find_all_symbols::SymbolInfo> MatchedSymbols =
 | 
						|
      query(QueryString, "", tooling::Range());
 | 
						|
 | 
						|
  if (!MatchedSymbols.empty() && 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(),
 | 
						|
                               MatchedSymbols),
 | 
						|
        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());
 | 
						|
  }
 | 
						|
 | 
						|
  LLVM_DEBUG(llvm::dbgs() << "TypoScopeQualifiers: " << TypoScopeString
 | 
						|
                          << "\n");
 | 
						|
  std::vector<find_all_symbols::SymbolInfo> MatchedSymbols =
 | 
						|
      query(QueryString, TypoScopeString, SymbolRange);
 | 
						|
 | 
						|
  if (!MatchedSymbols.empty() && GenerateDiagnostics) {
 | 
						|
    TypoCorrection Correction(Typo.getName());
 | 
						|
    Correction.setCorrectionRange(SS, Typo);
 | 
						|
    FileID FID = SM.getFileID(Typo.getLoc());
 | 
						|
    StringRef Code = SM.getBufferData(FID);
 | 
						|
    SourceLocation StartOfFile = SM.getLocForStartOfFile(FID);
 | 
						|
    if (addDiagnosticsForContext(
 | 
						|
            Correction, getIncludeFixerContext(
 | 
						|
                            SM, CI->getPreprocessor().getHeaderSearchInfo(),
 | 
						|
                            MatchedSymbols),
 | 
						|
            Code, StartOfFile, CI->getASTContext()))
 | 
						|
      return Correction;
 | 
						|
  }
 | 
						|
  return TypoCorrection();
 | 
						|
}
 | 
						|
 | 
						|
/// 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 std::string(Include);
 | 
						|
 | 
						|
  // Get the FileEntry for the include.
 | 
						|
  StringRef StrippedInclude = Include.trim("\"<>");
 | 
						|
  auto 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 std::string(Include);
 | 
						|
 | 
						|
  bool IsSystem = false;
 | 
						|
  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,
 | 
						|
    ArrayRef<find_all_symbols::SymbolInfo> MatchedSymbols) 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.getContexts());
 | 
						|
  }
 | 
						|
  return IncludeFixerContext(FilePath, QuerySymbolInfos, SymbolCandidates);
 | 
						|
}
 | 
						|
 | 
						|
std::vector<find_all_symbols::SymbolInfo>
 | 
						|
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 clang-include-fixer avoids false positives,
 | 
						|
  // and always adds missing qualifiers to correct symbols.
 | 
						|
  if (!GenerateDiagnostics && !QuerySymbolInfos.empty()) {
 | 
						|
    if (ScopedQualifiers == QuerySymbolInfos.front().ScopedQualifiers &&
 | 
						|
        Query == QuerySymbolInfos.front().RawIdentifier) {
 | 
						|
      QuerySymbolInfos.push_back(
 | 
						|
          {Query.str(), std::string(ScopedQualifiers), Range});
 | 
						|
    }
 | 
						|
    return {};
 | 
						|
  }
 | 
						|
 | 
						|
  LLVM_DEBUG(llvm::dbgs() << "Looking up '" << Query << "' at ");
 | 
						|
  LLVM_DEBUG(CI->getSourceManager()
 | 
						|
                 .getLocForStartOfFile(CI->getSourceManager().getMainFileID())
 | 
						|
                 .getLocWithOffset(Range.getOffset())
 | 
						|
                 .print(llvm::dbgs(), CI->getSourceManager()));
 | 
						|
  LLVM_DEBUG(llvm::dbgs() << " ...");
 | 
						|
  llvm::StringRef FileName = CI->getSourceManager().getFilename(
 | 
						|
      CI->getSourceManager().getLocForStartOfFile(
 | 
						|
          CI->getSourceManager().getMainFileID()));
 | 
						|
 | 
						|
  QuerySymbolInfos.push_back(
 | 
						|
      {Query.str(), std::string(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.
 | 
						|
  std::vector<find_all_symbols::SymbolInfo> MatchedSymbols =
 | 
						|
      SymbolIndexMgr.search(QueryString, /*IsNestedSearch=*/false, FileName);
 | 
						|
  if (MatchedSymbols.empty())
 | 
						|
    MatchedSymbols =
 | 
						|
        SymbolIndexMgr.search(Query, /*IsNestedSearch=*/true, FileName);
 | 
						|
  LLVM_DEBUG(llvm::dbgs() << "Having found " << MatchedSymbols.size()
 | 
						|
                          << " symbols\n");
 | 
						|
  // We store a copy of MatchedSymbols in a place where it's globally reachable.
 | 
						|
  // This is used by the standalone version of the tool.
 | 
						|
  this->MatchedSymbols = MatchedSymbols;
 | 
						|
  return MatchedSymbols;
 | 
						|
}
 | 
						|
 | 
						|
llvm::Expected<tooling::Replacements> createIncludeFixerReplacements(
 | 
						|
    StringRef Code, const IncludeFixerContext &Context,
 | 
						|
    const clang::format::FormatStyle &Style, bool AddQualifiers) {
 | 
						|
  if (Context.getHeaderInfos().empty())
 | 
						|
    return tooling::Replacements();
 | 
						|
  StringRef FilePath = Context.getFilePath();
 | 
						|
  std::string IncludeName =
 | 
						|
      "#include " + Context.getHeaderInfos().front().Header + "\n";
 | 
						|
  // Create replacements for the new header.
 | 
						|
  clang::tooling::Replacements Insertions;
 | 
						|
  auto Err =
 | 
						|
      Insertions.add(tooling::Replacement(FilePath, UINT_MAX, 0, IncludeName));
 | 
						|
  if (Err)
 | 
						|
    return std::move(Err);
 | 
						|
 | 
						|
  auto CleanReplaces = cleanupAroundReplacements(Code, Insertions, Style);
 | 
						|
  if (!CleanReplaces)
 | 
						|
    return CleanReplaces;
 | 
						|
 | 
						|
  auto Replaces = std::move(*CleanReplaces);
 | 
						|
  if (AddQualifiers) {
 | 
						|
    for (const auto &Info : Context.getQuerySymbolInfos()) {
 | 
						|
      // Ignore the empty range.
 | 
						|
      if (Info.Range.getLength() > 0) {
 | 
						|
        auto R = tooling::Replacement(
 | 
						|
            {FilePath, Info.Range.getOffset(), Info.Range.getLength(),
 | 
						|
             Context.getHeaderInfos().front().QualifiedName});
 | 
						|
        auto Err = Replaces.add(R);
 | 
						|
        if (Err) {
 | 
						|
          llvm::consumeError(std::move(Err));
 | 
						|
          R = tooling::Replacement(
 | 
						|
              R.getFilePath(), Replaces.getShiftedCodePosition(R.getOffset()),
 | 
						|
              R.getLength(), R.getReplacementText());
 | 
						|
          Replaces = Replaces.merge(tooling::Replacements(R));
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
  return formatReplacements(Code, Replaces, Style);
 | 
						|
}
 | 
						|
 | 
						|
} // namespace include_fixer
 | 
						|
} // namespace clang
 |