Index: CMakeLists.txt =================================================================== --- CMakeLists.txt +++ CMakeLists.txt @@ -11,10 +11,10 @@ add_subdirectory(clang-change-namespace) add_subdirectory(clang-doc) -add_subdirectory(clang-query) +add_subdirectory(clang-include-fixer) add_subdirectory(clang-move) +add_subdirectory(clang-query) add_subdirectory(clangd) -add_subdirectory(include-fixer) add_subdirectory(pp-trace) add_subdirectory(tool-template) Index: clang-include-fixer/CMakeLists.txt =================================================================== --- clang-include-fixer/CMakeLists.txt +++ clang-include-fixer/CMakeLists.txt @@ -0,0 +1,29 @@ +set(LLVM_LINK_COMPONENTS + support + ) + +add_clang_library(clangIncludeFixer + IncludeFixer.cpp + IncludeFixerContext.cpp + InMemorySymbolIndex.cpp + FuzzySymbolIndex.cpp + SymbolIndexManager.cpp + YamlSymbolIndex.cpp + + LINK_LIBS + clangAST + clangBasic + clangFormat + clangFrontend + clangLex + clangParse + clangSema + clangSerialization + clangTooling + clangToolingCore + findAllSymbols + ) + +add_subdirectory(plugin) +add_subdirectory(tool) +add_subdirectory(find-all-symbols) Index: clang-include-fixer/FuzzySymbolIndex.h =================================================================== --- clang-include-fixer/FuzzySymbolIndex.h +++ clang-include-fixer/FuzzySymbolIndex.h @@ -0,0 +1,54 @@ +//===--- FuzzySymbolIndex.h - Lookup symbols for autocomplete ---*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_FUZZY_SYMBOL_INDEX_H +#define LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_FUZZY_SYMBOL_INDEX_H + +#include "SymbolIndex.h" +#include "find-all-symbols/SymbolInfo.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" +#include +#include + +namespace clang { +namespace include_fixer { + +// A FuzzySymbolIndex retrieves top-level symbols matching a query string. +// +// It refines the contract of SymbolIndex::search to do fuzzy matching: +// - symbol names are tokenized: "unique ptr", "string ref". +// - query must match prefixes of symbol tokens: [upt] +// - if the query has multiple tokens, splits must match: [StR], not [STr]. +// Helpers for tokenization and regex matching are provided. +// +// Implementations may choose to truncate results, refuse short queries, etc. +class FuzzySymbolIndex : public SymbolIndex { +public: + // Loads the specified clang-include-fixer database and returns an index serving it. + static llvm::Expected> + createFromYAML(llvm::StringRef File); + + // Helpers for implementing indexes: + + // Transforms a symbol name or query into a sequence of tokens. + // - URLHandlerCallback --> [url, handler, callback] + // - snake_case11 --> [snake, case, 11] + // - _WTF$ --> [wtf] + static std::vector tokenize(llvm::StringRef Text); + + // Transforms query tokens into an unanchored regexp to match symbol tokens. + // - [fe f] --> /f(\w* )?e\w* f/, matches [fee fie foe]. + static std::string queryRegexp(const std::vector &Tokens); +}; + +} // namespace include_fixer +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_FUZZY_SYMBOL_INDEX_H Index: clang-include-fixer/FuzzySymbolIndex.cpp =================================================================== --- clang-include-fixer/FuzzySymbolIndex.cpp +++ clang-include-fixer/FuzzySymbolIndex.cpp @@ -0,0 +1,142 @@ +//===--- FuzzySymbolIndex.cpp - Lookup symbols for autocomplete -*- C++ -*-===// +// +// 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 "FuzzySymbolIndex.h" +#include "llvm/Support/Regex.h" + +using clang::find_all_symbols::SymbolAndSignals; +using llvm::StringRef; + +namespace clang { +namespace include_fixer { +namespace { + +class MemSymbolIndex : public FuzzySymbolIndex { +public: + MemSymbolIndex(std::vector Symbols) { + for (auto &Symbol : Symbols) { + auto Tokens = tokenize(Symbol.Symbol.getName()); + this->Symbols.emplace_back( + StringRef(llvm::join(Tokens.begin(), Tokens.end(), " ")), + std::move(Symbol)); + } + } + + std::vector search(StringRef Query) override { + auto Tokens = tokenize(Query); + llvm::Regex Pattern("^" + queryRegexp(Tokens)); + std::vector Results; + for (const Entry &E : Symbols) + if (Pattern.match(E.first)) + Results.push_back(E.second); + return Results; + } + +private: + using Entry = std::pair, SymbolAndSignals>; + std::vector Symbols; +}; + +// Helpers for tokenize state machine. +enum TokenizeState { + EMPTY, // No pending characters. + ONE_BIG, // Read one uppercase letter, could be WORD or Word. + BIG_WORD, // Reading an uppercase WORD. + SMALL_WORD, // Reading a lowercase word. + NUMBER // Reading a number. +}; + +enum CharType { UPPER, LOWER, DIGIT, MISC }; +CharType classify(char c) { + if (isupper(c)) + return UPPER; + if (islower(c)) + return LOWER; + if (isdigit(c)) + return DIGIT; + return MISC; +} + +} // namespace + +std::vector FuzzySymbolIndex::tokenize(StringRef Text) { + std::vector Result; + // State describes the treatment of text from Start to I. + // Once text is Flush()ed into Result, we're done with it and advance Start. + TokenizeState State = EMPTY; + size_t Start = 0; + auto Flush = [&](size_t End) { + if (State != EMPTY) { + Result.push_back(Text.substr(Start, End - Start).lower()); + State = EMPTY; + } + Start = End; + }; + for (size_t I = 0; I < Text.size(); ++I) { + CharType Type = classify(Text[I]); + if (Type == MISC) + Flush(I); + else if (Type == LOWER) + switch (State) { + case BIG_WORD: + Flush(I - 1); // FOOBar: first token is FOO, not FOOB. + LLVM_FALLTHROUGH; + case ONE_BIG: + State = SMALL_WORD; + LLVM_FALLTHROUGH; + case SMALL_WORD: + break; + default: + Flush(I); + State = SMALL_WORD; + } + else if (Type == UPPER) + switch (State) { + case ONE_BIG: + State = BIG_WORD; + LLVM_FALLTHROUGH; + case BIG_WORD: + break; + default: + Flush(I); + State = ONE_BIG; + } + else if (Type == DIGIT && State != NUMBER) { + Flush(I); + State = NUMBER; + } + } + Flush(Text.size()); + return Result; +} + +std::string +FuzzySymbolIndex::queryRegexp(const std::vector &Tokens) { + std::string Result; + for (size_t I = 0; I < Tokens.size(); ++I) { + if (I) + Result.append("[[:alnum:]]* "); + for (size_t J = 0; J < Tokens[I].size(); ++J) { + if (J) + Result.append("([[:alnum:]]* )?"); + Result.push_back(Tokens[I][J]); + } + } + return Result; +} + +llvm::Expected> +FuzzySymbolIndex::createFromYAML(StringRef FilePath) { + auto Buffer = llvm::MemoryBuffer::getFile(FilePath); + if (!Buffer) + return llvm::errorCodeToError(Buffer.getError()); + return llvm::make_unique( + find_all_symbols::ReadSymbolInfosFromYAML(Buffer.get()->getBuffer())); +} + +} // namespace include_fixer +} // namespace clang Index: clang-include-fixer/InMemorySymbolIndex.h =================================================================== --- clang-include-fixer/InMemorySymbolIndex.h +++ clang-include-fixer/InMemorySymbolIndex.h @@ -0,0 +1,37 @@ +//===-- InMemorySymbolIndex.h -----------------------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +#ifndef LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_INMEMORYSYMBOLINDEX_H +#define LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_INMEMORYSYMBOLINDEX_H + +#include "SymbolIndex.h" +#include +#include +#include + +namespace clang { +namespace include_fixer { + +/// Xref database with fixed content. +class InMemorySymbolIndex : public SymbolIndex { +public: + InMemorySymbolIndex( + const std::vector &Symbols); + + std::vector + search(llvm::StringRef Identifier) override; + +private: + std::map> + LookupTable; +}; + +} // namespace include_fixer +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_INMEMORYSYMBOLINDEX_H Index: clang-include-fixer/InMemorySymbolIndex.cpp =================================================================== --- clang-include-fixer/InMemorySymbolIndex.cpp +++ clang-include-fixer/InMemorySymbolIndex.cpp @@ -0,0 +1,31 @@ +//===-- InMemorySymbolIndex.cpp--------------------------------------------===// +// +// 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 "InMemorySymbolIndex.h" + +using clang::find_all_symbols::SymbolAndSignals; + +namespace clang { +namespace include_fixer { + +InMemorySymbolIndex::InMemorySymbolIndex( + const std::vector &Symbols) { + for (const auto &Symbol : Symbols) + LookupTable[Symbol.Symbol.getName()].push_back(Symbol); +} + +std::vector +InMemorySymbolIndex::search(llvm::StringRef Identifier) { + auto I = LookupTable.find(Identifier); + if (I != LookupTable.end()) + return I->second; + return {}; +} + +} // namespace include_fixer +} // namespace clang Index: clang-include-fixer/IncludeFixer.h =================================================================== --- clang-include-fixer/IncludeFixer.h +++ clang-include-fixer/IncludeFixer.h @@ -0,0 +1,157 @@ +//===-- IncludeFixer.h - Include inserter -----------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_INCLUDEFIXER_H +#define LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_INCLUDEFIXER_H + +#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 +#include + +namespace clang { + +class CompilerInvocation; +class DiagnosticConsumer; +class FileManager; +class PCHContainerOperations; + +namespace include_fixer { + +class IncludeFixerActionFactory : public clang::tooling::ToolAction { +public: + /// \param SymbolIndexMgr A source for matching symbols to header files. + /// \param Contexts The contexts for the symbols being queried. + /// \param StyleName Fallback style for reformatting. + /// \param MinimizeIncludePaths whether inserted include paths are optimized. + IncludeFixerActionFactory(SymbolIndexManager &SymbolIndexMgr, + std::vector &Contexts, + StringRef StyleName, + bool MinimizeIncludePaths = true); + + ~IncludeFixerActionFactory() override; + + bool + runInvocation(std::shared_ptr Invocation, + clang::FileManager *Files, + std::shared_ptr PCHContainerOps, + clang::DiagnosticConsumer *Diagnostics) override; + +private: + /// The client to use to find cross-references. + SymbolIndexManager &SymbolIndexMgr; + + /// Multiple contexts for files being processed. + std::vector &Contexts; + + /// Whether inserted include paths should be optimized. + bool MinimizeIncludePaths; + + /// The fallback format style for formatting after insertion if no + /// clang-format config file was found. + std::string FallbackStyle; +}; + +/// Create replacements, which are generated by clang-format, for the +/// missing header and mising qualifiers insertions. The function uses the +/// first header for insertion. +/// +/// \param Code The source code. +/// \param Context The context which contains all information for creating +/// clang-include-fixer replacements. +/// \param Style clang-format style being used. +/// \param AddQualifiers Whether we should add qualifiers to all instances of +/// an unidentified symbol. +/// +/// \return Formatted replacements for inserting, sorting headers and adding +/// qualifiers on success; otherwise, an llvm::Error carrying llvm::StringError +/// is returned. +llvm::Expected createIncludeFixerReplacements( + StringRef Code, const IncludeFixerContext &Context, + 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, + ArrayRef MatchedSymbols) const; + + /// Get the global matched symbols. + ArrayRef getMatchedSymbols() const { + return MatchedSymbols; + } + +private: + /// Query the database for a given identifier. + std::vector + 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 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 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 + +#endif // LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_INCLUDEFIXER_H Index: clang-include-fixer/IncludeFixer.cpp =================================================================== --- clang-include-fixer/IncludeFixer.cpp +++ clang-include-fixer/IncludeFixer.cpp @@ -0,0 +1,444 @@ +//===-- 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 + CreateASTConsumer(clang::CompilerInstance &Compiler, + StringRef InFile) override { + SemaSource.setFilePath(InFile); + return llvm::make_unique(); + } + + 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 &Contexts, StringRef StyleName, + bool MinimizeIncludePaths) + : SymbolIndexMgr(SymbolIndexMgr), Contexts(Contexts), + MinimizeIncludePaths(MinimizeIncludePaths) {} + +IncludeFixerActionFactory::~IncludeFixerActionFactory() = default; + +bool IncludeFixerActionFactory::runInvocation( + std::shared_ptr Invocation, + clang::FileManager *Files, + std::shared_ptr 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 = + llvm::make_unique(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 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 + // class Foo { + // T t; + // }; + // + // // test.cc + // // We need to add in test.cc instead of header.h. + // class Bar; + // Foo 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(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 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 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, + ArrayRef MatchedSymbols) const { + std::vector 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 +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(), 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(), 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 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 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 Index: clang-include-fixer/IncludeFixerContext.h =================================================================== --- clang-include-fixer/IncludeFixerContext.h +++ clang-include-fixer/IncludeFixerContext.h @@ -0,0 +1,94 @@ +//===-- IncludeFixerContext.h - Include fixer context -----------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_INCLUDEFIXERCONTEXT_H +#define LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_INCLUDEFIXERCONTEXT_H + +#include "find-all-symbols/SymbolInfo.h" +#include "clang/Tooling/Core/Replacement.h" +#include +#include + +namespace clang { +namespace include_fixer { + +/// \brief A context for a file being processed. It includes all query +/// information, e.g. symbols being queried in database, all header candidates. +class IncludeFixerContext { +public: + struct HeaderInfo { + /// \brief The header where QualifiedName comes from. + std::string Header; + /// \brief A symbol name with completed namespace qualifiers which will + /// replace the original symbol. + std::string QualifiedName; + }; + + struct QuerySymbolInfo { + /// \brief The raw symbol name being queried in database. This name might + /// miss some namespace qualifiers, and will be replaced by a fully + /// qualified one. + std::string RawIdentifier; + + /// \brief The qualifiers of the scope in which SymbolIdentifier lookup + /// occurs. It is represented as a sequence of names and scope resolution + /// operatiors ::, ending with a scope resolution operator (e.g. a::b::). + /// Empty if SymbolIdentifier is not in a specific scope. + std::string ScopedQualifiers; + + /// \brief The replacement range of RawIdentifier. + tooling::Range Range; + }; + + IncludeFixerContext() = default; + IncludeFixerContext(StringRef FilePath, + std::vector QuerySymbols, + std::vector Symbols); + + /// \brief Get symbol name. + llvm::StringRef getSymbolIdentifier() const { + return QuerySymbolInfos.front().RawIdentifier; + } + + /// \brief Get replacement range of the symbol. + tooling::Range getSymbolRange() const { + return QuerySymbolInfos.front().Range; + } + + /// \brief Get the file path to the file being processed. + StringRef getFilePath() const { return FilePath; } + + /// \brief Get header information. + const std::vector &getHeaderInfos() const { return HeaderInfos; } + + /// \brief Get information of symbols being querid. + const std::vector &getQuerySymbolInfos() const { + return QuerySymbolInfos; + } + +private: + friend struct llvm::yaml::MappingTraits; + + /// \brief The file path to the file being processed. + std::string FilePath; + + /// \brief All instances of an unidentified symbol being queried. + std::vector QuerySymbolInfos; + + /// \brief The symbol candidates which match SymbolIdentifier. The symbols are + /// sorted in a descending order based on the popularity info in SymbolInfo. + std::vector MatchedSymbols; + + /// \brief The header information. + std::vector HeaderInfos; +}; + +} // namespace include_fixer +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_INCLUDEFIXERCONTEXT_H Index: clang-include-fixer/IncludeFixerContext.cpp =================================================================== --- clang-include-fixer/IncludeFixerContext.cpp +++ clang-include-fixer/IncludeFixerContext.cpp @@ -0,0 +1,115 @@ +//===-- IncludeFixerContext.cpp - Include fixer context ---------*- C++ -*-===// +// +// 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 "IncludeFixerContext.h" +#include + +namespace clang { +namespace include_fixer { + +namespace { + +// Splits a multiply qualified names (e.g. a::b::c). +llvm::SmallVector +SplitQualifiers(llvm::StringRef StringQualifiers) { + llvm::SmallVector Qualifiers; + StringQualifiers.split(Qualifiers, "::"); + return Qualifiers; +} + +std::string createQualifiedNameForReplacement( + llvm::StringRef RawSymbolName, + llvm::StringRef SymbolScopedQualifiersName, + const find_all_symbols::SymbolInfo &MatchedSymbol) { + // No need to add missing qualifiers if SymbolIndentifer has a global scope + // operator "::". + if (RawSymbolName.startswith("::")) + return RawSymbolName; + + std::string QualifiedName = MatchedSymbol.getQualifiedName(); + + // For nested classes, the qualified name constructed from database misses + // some stripped qualifiers, because when we search a symbol in database, + // we strip qualifiers from the end until we find a result. So append the + // missing stripped qualifiers here. + // + // Get stripped qualifiers. + auto SymbolQualifiers = SplitQualifiers(RawSymbolName); + std::string StrippedQualifiers; + while (!SymbolQualifiers.empty() && + !llvm::StringRef(QualifiedName).endswith(SymbolQualifiers.back())) { + StrippedQualifiers = + "::" + SymbolQualifiers.back().str() + StrippedQualifiers; + SymbolQualifiers.pop_back(); + } + // Append the missing stripped qualifiers. + std::string FullyQualifiedName = QualifiedName + StrippedQualifiers; + + // Try to find and skip the common prefix qualifiers. + auto FullySymbolQualifiers = SplitQualifiers(FullyQualifiedName); + auto ScopedQualifiers = SplitQualifiers(SymbolScopedQualifiersName); + auto FullySymbolQualifiersIter = FullySymbolQualifiers.begin(); + auto SymbolScopedQualifiersIter = ScopedQualifiers.begin(); + while (FullySymbolQualifiersIter != FullySymbolQualifiers.end() && + SymbolScopedQualifiersIter != ScopedQualifiers.end()) { + if (*FullySymbolQualifiersIter != *SymbolScopedQualifiersIter) + break; + ++FullySymbolQualifiersIter; + ++SymbolScopedQualifiersIter; + } + std::string Result; + for (; FullySymbolQualifiersIter != FullySymbolQualifiers.end(); + ++FullySymbolQualifiersIter) { + if (!Result.empty()) + Result += "::"; + Result += *FullySymbolQualifiersIter; + } + return Result; +} + +} // anonymous namespace + +IncludeFixerContext::IncludeFixerContext( + StringRef FilePath, std::vector QuerySymbols, + std::vector Symbols) + : FilePath(FilePath), QuerySymbolInfos(std::move(QuerySymbols)), + MatchedSymbols(std::move(Symbols)) { + // Remove replicated QuerySymbolInfos with the same range. + // + // QuerySymbolInfos may contain replicated elements. Because CorrectTypo + // callback doesn't always work as we expected. In somecases, it will be + // triggered at the same position or unidentified symbol multiple times. + std::sort(QuerySymbolInfos.begin(), QuerySymbolInfos.end(), + [&](const QuerySymbolInfo &A, const QuerySymbolInfo &B) { + return std::make_pair(A.Range.getOffset(), A.Range.getLength()) < + std::make_pair(B.Range.getOffset(), B.Range.getLength()); + }); + QuerySymbolInfos.erase( + std::unique(QuerySymbolInfos.begin(), QuerySymbolInfos.end(), + [](const QuerySymbolInfo &A, const QuerySymbolInfo &B) { + return A.Range == B.Range; + }), + QuerySymbolInfos.end()); + for (const auto &Symbol : MatchedSymbols) { + HeaderInfos.push_back( + {Symbol.getFilePath().str(), + createQualifiedNameForReplacement( + QuerySymbolInfos.front().RawIdentifier, + QuerySymbolInfos.front().ScopedQualifiers, Symbol)}); + } + // Deduplicate header infos. + HeaderInfos.erase(std::unique(HeaderInfos.begin(), HeaderInfos.end(), + [](const HeaderInfo &A, const HeaderInfo &B) { + return A.Header == B.Header && + A.QualifiedName == B.QualifiedName; + }), + HeaderInfos.end()); +} + +} // include_fixer +} // clang Index: clang-include-fixer/SymbolIndex.h =================================================================== --- clang-include-fixer/SymbolIndex.h +++ clang-include-fixer/SymbolIndex.h @@ -0,0 +1,37 @@ +//===-- SymbolIndex.h - Interface for symbol-header matching ----*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_SYMBOLINDEX_H +#define LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_SYMBOLINDEX_H + +#include "find-all-symbols/SymbolInfo.h" +#include "llvm/ADT/StringRef.h" +#include + +namespace clang { +namespace include_fixer { + +/// This class provides an interface for finding all `SymbolInfo`s corresponding +/// to a symbol name from a symbol database. +class SymbolIndex { +public: + virtual ~SymbolIndex() = default; + + /// Search for all `SymbolInfo`s corresponding to an identifier. + /// \param Identifier The unqualified identifier being searched for. + /// \returns A list of `SymbolInfo` candidates. + // FIXME: Expose the type name so we can also insert using declarations (or + // fix the usage) + virtual std::vector + search(llvm::StringRef Identifier) = 0; +}; + +} // namespace include_fixer +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_SYMBOLINDEX_H Index: clang-include-fixer/SymbolIndexManager.h =================================================================== --- clang-include-fixer/SymbolIndexManager.h +++ clang-include-fixer/SymbolIndexManager.h @@ -0,0 +1,65 @@ +//===-- SymbolIndexManager.h - Managing multiple SymbolIndices --*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_SYMBOLINDEXMANAGER_H +#define LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_SYMBOLINDEXMANAGER_H + +#include "SymbolIndex.h" +#include "find-all-symbols/SymbolInfo.h" +#include "llvm/ADT/StringRef.h" + +#ifdef _MSC_VER +// Disable warnings from ppltasks.h transitively included by . +#pragma warning(push) +#pragma warning(disable:4530) +#endif + +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +namespace clang { +namespace include_fixer { + +/// This class provides an interface for finding the header files corresponding +/// to an identifier in the source code from multiple symbol databases. +class SymbolIndexManager { +public: + void addSymbolIndex(std::function()> F) { +#if LLVM_ENABLE_THREADS + auto Strategy = std::launch::async; +#else + auto Strategy = std::launch::deferred; +#endif + SymbolIndices.push_back(std::async(Strategy, F)); + } + + /// Search for header files to be included for an identifier. + /// \param Identifier The identifier being searched for. May or may not be + /// fully qualified. + /// \param IsNestedSearch Whether searching nested classes. If true, the + /// method tries to strip identifier name parts from the end until it + /// finds the corresponding candidates in database (e.g for identifier + /// "b::foo", the method will try to find "b" if it fails to find + /// "b::foo"). + /// + /// \returns A list of symbol candidates. + std::vector + search(llvm::StringRef Identifier, bool IsNestedSearch = true, + llvm::StringRef FileName = "") const; + +private: + std::vector>> SymbolIndices; +}; + +} // namespace include_fixer +} // namespace clang + +#endif Index: clang-include-fixer/SymbolIndexManager.cpp =================================================================== --- clang-include-fixer/SymbolIndexManager.cpp +++ clang-include-fixer/SymbolIndexManager.cpp @@ -0,0 +1,158 @@ +//===-- SymbolIndexManager.cpp - Managing multiple SymbolIndices-*- C++ -*-===// +// +// 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 "SymbolIndexManager.h" +#include "find-all-symbols/SymbolInfo.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/Path.h" + +#define DEBUG_TYPE "clang-include-fixer" + +namespace clang { +namespace include_fixer { + +using find_all_symbols::SymbolInfo; +using find_all_symbols::SymbolAndSignals; + +// Calculate a score based on whether we think the given header is closely +// related to the given source file. +static double similarityScore(llvm::StringRef FileName, + llvm::StringRef Header) { + // Compute the maximum number of common path segements between Header and + // a suffix of FileName. + // We do not do a full longest common substring computation, as Header + // specifies the path we would directly #include, so we assume it is rooted + // relatively to a subproject of the repository. + int MaxSegments = 1; + for (auto FileI = llvm::sys::path::begin(FileName), + FileE = llvm::sys::path::end(FileName); + FileI != FileE; ++FileI) { + int Segments = 0; + for (auto HeaderI = llvm::sys::path::begin(Header), + HeaderE = llvm::sys::path::end(Header), I = FileI; + HeaderI != HeaderE && *I == *HeaderI && I != FileE; ++I, ++HeaderI) { + ++Segments; + } + MaxSegments = std::max(Segments, MaxSegments); + } + return MaxSegments; +} + +static void rank(std::vector &Symbols, + llvm::StringRef FileName) { + llvm::DenseMap Score; + for (const auto &Symbol : Symbols) { + // Calculate a score from the similarity of the header the symbol is in + // with the current file and the popularity of the symbol. + double NewScore = similarityScore(FileName, Symbol.Symbol.getFilePath()) * + (1.0 + std::log2(1 + Symbol.Signals.Seen)); + double &S = Score[Symbol.Symbol.getFilePath()]; + S = std::max(S, NewScore); + } + // Sort by the gathered scores. Use file name as a tie breaker so we can + // deduplicate. + std::sort(Symbols.begin(), Symbols.end(), + [&](const SymbolAndSignals &A, const SymbolAndSignals &B) { + auto AS = Score[A.Symbol.getFilePath()]; + auto BS = Score[B.Symbol.getFilePath()]; + if (AS != BS) + return AS > BS; + return A.Symbol.getFilePath() < B.Symbol.getFilePath(); + }); +} + +std::vector +SymbolIndexManager::search(llvm::StringRef Identifier, + bool IsNestedSearch, + llvm::StringRef FileName) const { + // The identifier may be fully qualified, so split it and get all the context + // names. + llvm::SmallVector Names; + Identifier.split(Names, "::"); + + bool IsFullyQualified = false; + if (Identifier.startswith("::")) { + Names.erase(Names.begin()); // Drop first (empty) element. + IsFullyQualified = true; + } + + // As long as we don't find a result keep stripping name parts from the end. + // This is to support nested classes which aren't recorded in the database. + // Eventually we will either hit a class (namespaces aren't in the database + // either) and can report that result. + bool TookPrefix = false; + std::vector MatchedSymbols; + do { + std::vector Symbols; + for (const auto &DB : SymbolIndices) { + auto Res = DB.get()->search(Names.back()); + Symbols.insert(Symbols.end(), Res.begin(), Res.end()); + } + + LLVM_DEBUG(llvm::dbgs() << "Searching " << Names.back() << "... got " + << Symbols.size() << " results...\n"); + + for (auto &SymAndSig : Symbols) { + const SymbolInfo &Symbol = SymAndSig.Symbol; + // Match the identifier name without qualifier. + bool IsMatched = true; + auto SymbolContext = Symbol.getContexts().begin(); + auto IdentiferContext = Names.rbegin() + 1; // Skip identifier name. + // Match the remaining context names. + while (IdentiferContext != Names.rend() && + SymbolContext != Symbol.getContexts().end()) { + if (SymbolContext->second == *IdentiferContext) { + ++IdentiferContext; + ++SymbolContext; + } else if (SymbolContext->first == + find_all_symbols::SymbolInfo::ContextType::EnumDecl) { + // Skip non-scoped enum context. + ++SymbolContext; + } else { + IsMatched = false; + break; + } + } + + // If the name was qualified we only want to add results if we evaluated + // all contexts. + if (IsFullyQualified) + IsMatched &= (SymbolContext == Symbol.getContexts().end()); + + // FIXME: Support full match. At this point, we only find symbols in + // database which end with the same contexts with the identifier. + if (IsMatched && IdentiferContext == Names.rend()) { + // If we're in a situation where we took a prefix but the thing we + // found couldn't possibly have a nested member ignore it. + if (TookPrefix && + (Symbol.getSymbolKind() == SymbolInfo::SymbolKind::Function || + Symbol.getSymbolKind() == SymbolInfo::SymbolKind::Variable || + Symbol.getSymbolKind() == + SymbolInfo::SymbolKind::EnumConstantDecl || + Symbol.getSymbolKind() == SymbolInfo::SymbolKind::Macro)) + continue; + + MatchedSymbols.push_back(std::move(SymAndSig)); + } + } + Names.pop_back(); + TookPrefix = true; + } while (MatchedSymbols.empty() && !Names.empty() && IsNestedSearch); + + rank(MatchedSymbols, FileName); + // Strip signals, they are no longer needed. + std::vector Res; + for (auto &SymAndSig : MatchedSymbols) + Res.push_back(std::move(SymAndSig.Symbol)); + return Res; +} + +} // namespace include_fixer +} // namespace clang Index: clang-include-fixer/YamlSymbolIndex.h =================================================================== --- clang-include-fixer/YamlSymbolIndex.h +++ clang-include-fixer/YamlSymbolIndex.h @@ -0,0 +1,45 @@ +//===-- YamlSymbolIndex.h ---------------------------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_YAMLSYMBOLINDEX_H +#define LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_YAMLSYMBOLINDEX_H + +#include "SymbolIndex.h" +#include "find-all-symbols/SymbolInfo.h" +#include "llvm/Support/ErrorOr.h" +#include +#include + +namespace clang { +namespace include_fixer { + +/// Yaml format database. +class YamlSymbolIndex : public SymbolIndex { +public: + /// Create a new Yaml db from a file. + static llvm::ErrorOr> + createFromFile(llvm::StringRef FilePath); + /// Look for a file called \c Name in \c Directory and all parent directories. + static llvm::ErrorOr> + createFromDirectory(llvm::StringRef Directory, llvm::StringRef Name); + + std::vector + search(llvm::StringRef Identifier) override; + +private: + explicit YamlSymbolIndex( + std::vector Symbols) + : Symbols(std::move(Symbols)) {} + + std::vector Symbols; +}; + +} // namespace include_fixer +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_YAMLSYMBOLINDEX_H Index: clang-include-fixer/YamlSymbolIndex.cpp =================================================================== --- clang-include-fixer/YamlSymbolIndex.cpp +++ clang-include-fixer/YamlSymbolIndex.cpp @@ -0,0 +1,60 @@ +//===-- YamlSymbolIndex.cpp -----------------------------------------------===// +// +// 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 "YamlSymbolIndex.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Support/Errc.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Path.h" +#include +#include + +using clang::find_all_symbols::SymbolInfo; +using clang::find_all_symbols::SymbolAndSignals; + +namespace clang { +namespace include_fixer { + +llvm::ErrorOr> +YamlSymbolIndex::createFromFile(llvm::StringRef FilePath) { + auto Buffer = llvm::MemoryBuffer::getFile(FilePath); + if (!Buffer) + return Buffer.getError(); + + return std::unique_ptr(new YamlSymbolIndex( + find_all_symbols::ReadSymbolInfosFromYAML(Buffer.get()->getBuffer()))); +} + +llvm::ErrorOr> +YamlSymbolIndex::createFromDirectory(llvm::StringRef Directory, + llvm::StringRef Name) { + // Walk upwards from Directory, looking for files. + for (llvm::SmallString<128> PathStorage = Directory; !Directory.empty(); + Directory = llvm::sys::path::parent_path(Directory)) { + assert(Directory.size() <= PathStorage.size()); + PathStorage.resize(Directory.size()); // Shrink to parent. + llvm::sys::path::append(PathStorage, Name); + if (auto DB = createFromFile(PathStorage)) + return DB; + } + return llvm::make_error_code(llvm::errc::no_such_file_or_directory); +} + +std::vector +YamlSymbolIndex::search(llvm::StringRef Identifier) { + std::vector Results; + for (const auto &Symbol : Symbols) { + if (Symbol.Symbol.getName() == Identifier) + Results.push_back(Symbol); + } + return Results; +} + +} // namespace include_fixer +} // namespace clang Index: clang-include-fixer/find-all-symbols/CMakeLists.txt =================================================================== --- clang-include-fixer/find-all-symbols/CMakeLists.txt +++ clang-include-fixer/find-all-symbols/CMakeLists.txt @@ -0,0 +1,24 @@ +set(LLVM_LINK_COMPONENTS + Support + ) + +add_clang_library(findAllSymbols + FindAllSymbols.cpp + FindAllSymbolsAction.cpp + FindAllMacros.cpp + HeaderMapCollector.cpp + PathConfig.cpp + PragmaCommentHandler.cpp + STLPostfixHeaderMap.cpp + SymbolInfo.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangFrontend + clangLex + clangTooling + ) + +add_subdirectory(tool) Index: clang-include-fixer/find-all-symbols/FindAllMacros.h =================================================================== --- clang-include-fixer/find-all-symbols/FindAllMacros.h +++ clang-include-fixer/find-all-symbols/FindAllMacros.h @@ -0,0 +1,64 @@ +//===-- FindAllMacros.h - find all macros -----------------------*- C++ -*-===// +// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_FIND_ALL_MACROS_H +#define LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_FIND_ALL_MACROS_H + +#include "SymbolInfo.h" +#include "SymbolReporter.h" +#include "clang/Lex/PPCallbacks.h" + +namespace clang { +class MacroInfo; +namespace find_all_symbols { + +class HeaderMapCollector; + +/// \brief A preprocessor that collects all macro symbols. +/// The contexts of a macro will be ignored since they are not available during +/// preprocessing period. +class FindAllMacros : public clang::PPCallbacks { +public: + explicit FindAllMacros(SymbolReporter *Reporter, SourceManager *SM, + HeaderMapCollector *Collector = nullptr) + : Reporter(Reporter), SM(SM), Collector(Collector) {} + + void MacroDefined(const Token &MacroNameTok, + const MacroDirective *MD) override; + + void MacroExpands(const Token &MacroNameTok, const MacroDefinition &MD, + SourceRange Range, const MacroArgs *Args) override; + + void Ifdef(SourceLocation Loc, const Token &MacroNameTok, + const MacroDefinition &MD) override; + + void Ifndef(SourceLocation Loc, const Token &MacroNameTok, + const MacroDefinition &MD) override; + + void EndOfMainFile() override; + +private: + llvm::Optional CreateMacroSymbol(const Token &MacroNameTok, + const MacroInfo *MD); + // Not a callback, just a common path for all usage types. + void MacroUsed(const Token &Name, const MacroDefinition &MD); + + SymbolInfo::SignalMap FileSymbols; + // Reporter for SymbolInfo. + SymbolReporter *const Reporter; + SourceManager *const SM; + // A remapping header file collector allowing clients to include a different + // header. + HeaderMapCollector *const Collector; +}; + +} // namespace find_all_symbols +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_FIND_ALL_MACROS_H Index: clang-include-fixer/find-all-symbols/FindAllMacros.cpp =================================================================== --- clang-include-fixer/find-all-symbols/FindAllMacros.cpp +++ clang-include-fixer/find-all-symbols/FindAllMacros.cpp @@ -0,0 +1,69 @@ +//===-- FindAllMacros.cpp - find all macros ---------------------*- C++ -*-===// +// +// 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 "FindAllMacros.h" +#include "HeaderMapCollector.h" +#include "PathConfig.h" +#include "SymbolInfo.h" +#include "clang/Basic/IdentifierTable.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Lex/MacroInfo.h" +#include "clang/Lex/Token.h" +#include "llvm/Support/Path.h" + +namespace clang { +namespace find_all_symbols { + +llvm::Optional +FindAllMacros::CreateMacroSymbol(const Token &MacroNameTok, + const MacroInfo *info) { + std::string FilePath = + getIncludePath(*SM, info->getDefinitionLoc(), Collector); + if (FilePath.empty()) + return llvm::None; + return SymbolInfo(MacroNameTok.getIdentifierInfo()->getName(), + SymbolInfo::SymbolKind::Macro, FilePath, {}); +} + +void FindAllMacros::MacroDefined(const Token &MacroNameTok, + const MacroDirective *MD) { + if (auto Symbol = CreateMacroSymbol(MacroNameTok, MD->getMacroInfo())) + ++FileSymbols[*Symbol].Seen; +} + +void FindAllMacros::MacroUsed(const Token &Name, const MacroDefinition &MD) { + if (!MD || !SM->isInMainFile(SM->getExpansionLoc(Name.getLocation()))) + return; + if (auto Symbol = CreateMacroSymbol(Name, MD.getMacroInfo())) + ++FileSymbols[*Symbol].Used; +} + +void FindAllMacros::MacroExpands(const Token &MacroNameTok, + const MacroDefinition &MD, SourceRange Range, + const MacroArgs *Args) { + MacroUsed(MacroNameTok, MD); +} + +void FindAllMacros::Ifdef(SourceLocation Loc, const Token &MacroNameTok, + const MacroDefinition &MD) { + MacroUsed(MacroNameTok, MD); +} + +void FindAllMacros::Ifndef(SourceLocation Loc, const Token &MacroNameTok, + const MacroDefinition &MD) { + MacroUsed(MacroNameTok, MD); +} + +void FindAllMacros::EndOfMainFile() { + Reporter->reportSymbols(SM->getFileEntryForID(SM->getMainFileID())->getName(), + FileSymbols); + FileSymbols.clear(); +} + +} // namespace find_all_symbols +} // namespace clang Index: clang-include-fixer/find-all-symbols/FindAllSymbols.h =================================================================== --- clang-include-fixer/find-all-symbols/FindAllSymbols.h +++ clang-include-fixer/find-all-symbols/FindAllSymbols.h @@ -0,0 +1,62 @@ +//===-- FindAllSymbols.h - find all symbols----------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_SYMBOL_MATCHER_H +#define LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_SYMBOL_MATCHER_H + +#include "SymbolInfo.h" +#include "SymbolReporter.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include + +namespace clang { +namespace find_all_symbols { + +class HeaderMapCollector; + +/// \brief FindAllSymbols collects all classes, free standing functions and +/// global variables with some extra information such as the path of the header +/// file, the namespaces they are contained in, the type of variables and the +/// parameter types of functions. +/// +/// NOTE: +/// - Symbols declared in main files are not collected since they can not be +/// included. +/// - Member functions are not collected because accessing them must go +/// through the class. #include fixer only needs the class name to find +/// headers. +/// +class FindAllSymbols : public ast_matchers::MatchFinder::MatchCallback { +public: + explicit FindAllSymbols(SymbolReporter *Reporter, + HeaderMapCollector *Collector = nullptr) + : Reporter(Reporter), Collector(Collector) {} + + void registerMatchers(ast_matchers::MatchFinder *MatchFinder); + + void run(const ast_matchers::MatchFinder::MatchResult &result) override; + +protected: + void onEndOfTranslationUnit() override; + +private: + // Current source file being processed, filled by first symbol found. + std::string Filename; + // Findings for the current source file, flushed on onEndOfTranslationUnit. + SymbolInfo::SignalMap FileSymbols; + // Reporter for SymbolInfo. + SymbolReporter *const Reporter; + // A remapping header file collector allowing clients include a different + // header. + HeaderMapCollector *const Collector; +}; + +} // namespace find_all_symbols +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_SYMBOL_MATCHER_H Index: clang-include-fixer/find-all-symbols/FindAllSymbols.cpp =================================================================== --- clang-include-fixer/find-all-symbols/FindAllSymbols.cpp +++ clang-include-fixer/find-all-symbols/FindAllSymbols.cpp @@ -0,0 +1,268 @@ +//===-- FindAllSymbols.cpp - find all symbols--------------------*- C++ -*-===// +// +// 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 "FindAllSymbols.h" +#include "HeaderMapCollector.h" +#include "PathConfig.h" +#include "SymbolInfo.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/Type.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/ADT/Optional.h" +#include "llvm/Support/FileSystem.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace find_all_symbols { +namespace { + +AST_MATCHER(EnumConstantDecl, isInScopedEnum) { + if (const auto *ED = dyn_cast(Node.getDeclContext())) + return ED->isScoped(); + return false; +} + +AST_POLYMORPHIC_MATCHER(isFullySpecialized, + AST_POLYMORPHIC_SUPPORTED_TYPES(FunctionDecl, VarDecl, + CXXRecordDecl)) { + if (Node.getTemplateSpecializationKind() == TSK_ExplicitSpecialization) { + bool IsPartialSpecialization = + llvm::isa(Node) || + llvm::isa(Node); + return !IsPartialSpecialization; + } + return false; +} + +std::vector GetContexts(const NamedDecl *ND) { + std::vector Contexts; + for (const auto *Context = ND->getDeclContext(); Context; + Context = Context->getParent()) { + if (llvm::isa(Context) || + llvm::isa(Context)) + break; + + assert(llvm::isa(Context) && + "Expect Context to be a NamedDecl"); + if (const auto *NSD = dyn_cast(Context)) { + if (!NSD->isInlineNamespace()) + Contexts.emplace_back(SymbolInfo::ContextType::Namespace, + NSD->getName().str()); + } else if (const auto *ED = dyn_cast(Context)) { + Contexts.emplace_back(SymbolInfo::ContextType::EnumDecl, + ED->getName().str()); + } else { + const auto *RD = cast(Context); + Contexts.emplace_back(SymbolInfo::ContextType::Record, + RD->getName().str()); + } + } + return Contexts; +} + +llvm::Optional +CreateSymbolInfo(const NamedDecl *ND, const SourceManager &SM, + const HeaderMapCollector *Collector) { + SymbolInfo::SymbolKind Type; + if (llvm::isa(ND)) { + Type = SymbolInfo::SymbolKind::Variable; + } else if (llvm::isa(ND)) { + Type = SymbolInfo::SymbolKind::Function; + } else if (llvm::isa(ND)) { + Type = SymbolInfo::SymbolKind::TypedefName; + } else if (llvm::isa(ND)) { + Type = SymbolInfo::SymbolKind::EnumConstantDecl; + } else if (llvm::isa(ND)) { + Type = SymbolInfo::SymbolKind::EnumDecl; + // Ignore anonymous enum declarations. + if (ND->getName().empty()) + return llvm::None; + } else { + assert(llvm::isa(ND) && + "Matched decl must be one of VarDecl, " + "FunctionDecl, TypedefNameDecl, EnumConstantDecl, " + "EnumDecl and RecordDecl!"); + // C-style record decl can have empty name, e.g "struct { ... } var;". + if (ND->getName().empty()) + return llvm::None; + Type = SymbolInfo::SymbolKind::Class; + } + + SourceLocation Loc = SM.getExpansionLoc(ND->getLocation()); + if (!Loc.isValid()) { + llvm::errs() << "Declaration " << ND->getNameAsString() << "(" + << ND->getDeclKindName() + << ") has invalid declaration location."; + return llvm::None; + } + + std::string FilePath = getIncludePath(SM, Loc, Collector); + if (FilePath.empty()) return llvm::None; + + return SymbolInfo(ND->getNameAsString(), Type, FilePath, GetContexts(ND)); +} + +} // namespace + +void FindAllSymbols::registerMatchers(MatchFinder *MatchFinder) { + // FIXME: Handle specialization. + auto IsInSpecialization = hasAncestor( + decl(anyOf(cxxRecordDecl(isExplicitTemplateSpecialization()), + functionDecl(isExplicitTemplateSpecialization())))); + + // Matchers for both C and C++. + // We only match symbols from header files, i.e. not from main files (see + // function's comment for detailed explanation). + auto CommonFilter = + allOf(unless(isImplicit()), unless(isExpansionInMainFile())); + + auto HasNSOrTUCtxMatcher = + hasDeclContext(anyOf(namespaceDecl(), translationUnitDecl())); + + // We need seperate rules for C record types and C++ record types since some + // template related matchers are inapplicable on C record declarations. + // + // Matchers specific to C++ code. + // All declarations should be in namespace or translation unit. + auto CCMatcher = + allOf(HasNSOrTUCtxMatcher, unless(IsInSpecialization), + unless(ast_matchers::isTemplateInstantiation()), + unless(isInstantiated()), unless(isFullySpecialized())); + + // Matchers specific to code in extern "C" {...}. + auto ExternCMatcher = hasDeclContext(linkageSpecDecl()); + + // Matchers for variable declarations. + // + // In most cases, `ParmVarDecl` is filtered out by hasDeclContext(...) + // matcher since the declaration context is usually `MethodDecl`. However, + // this assumption does not hold for parameters of a function pointer + // parameter. + // For example, consider a function declaration: + // void Func(void (*)(float), int); + // The float parameter of the function pointer has an empty name, and its + // declaration context is an anonymous namespace; therefore, it won't be + // filtered out by our matchers above. + auto Vars = varDecl(CommonFilter, anyOf(ExternCMatcher, CCMatcher), + unless(parmVarDecl())); + + // Matchers for C-style record declarations in extern "C" {...}. + auto CRecords = recordDecl(CommonFilter, ExternCMatcher, isDefinition()); + // Matchers for C++ record declarations. + auto CXXRecords = cxxRecordDecl(CommonFilter, CCMatcher, isDefinition()); + + // Matchers for function declarations. + // We want to exclude friend declaration, but the `DeclContext` of a friend + // function declaration is not the class in which it is declared, so we need + // to explicitly check if the parent is a `friendDecl`. + auto Functions = functionDecl(CommonFilter, unless(hasParent(friendDecl())), + anyOf(ExternCMatcher, CCMatcher)); + + // Matcher for typedef and type alias declarations. + // + // typedef and type alias can come from C-style headers and C++ headers. + // For C-style headers, `DeclContxet` can be either `TranslationUnitDecl` + // or `LinkageSpecDecl`. + // For C++ headers, `DeclContext ` can be either `TranslationUnitDecl` + // or `NamespaceDecl`. + // With the following context matcher, we can match `typedefNameDecl` from + // both C-style headers and C++ headers (except for those in classes). + // "cc_matchers" are not included since template-related matchers are not + // applicable on `TypedefNameDecl`. + auto Typedefs = + typedefNameDecl(CommonFilter, anyOf(HasNSOrTUCtxMatcher, + hasDeclContext(linkageSpecDecl()))); + + // Matchers for enum declarations. + auto Enums = enumDecl(CommonFilter, isDefinition(), + anyOf(HasNSOrTUCtxMatcher, ExternCMatcher)); + + // Matchers for enum constant declarations. + // We only match the enum constants in non-scoped enum declarations which are + // inside toplevel translation unit or a namespace. + auto EnumConstants = enumConstantDecl( + CommonFilter, unless(isInScopedEnum()), + anyOf(hasDeclContext(enumDecl(HasNSOrTUCtxMatcher)), ExternCMatcher)); + + // Most of the time we care about all matchable decls, or all types. + auto Types = namedDecl(anyOf(CRecords, CXXRecords, Enums)); + auto Decls = namedDecl(anyOf(CRecords, CXXRecords, Enums, Typedefs, Vars, + EnumConstants, Functions)); + + // We want eligible decls bound to "decl"... + MatchFinder->addMatcher(Decls.bind("decl"), this); + + // ... and all uses of them bound to "use". These have many cases: + // Uses of values/functions: these generate a declRefExpr. + MatchFinder->addMatcher( + declRefExpr(isExpansionInMainFile(), to(Decls.bind("use"))), this); + // Uses of function templates: + MatchFinder->addMatcher( + declRefExpr(isExpansionInMainFile(), + to(functionDecl(hasParent( + functionTemplateDecl(has(Functions.bind("use"))))))), + this); + + // Uses of most types: just look at what the typeLoc refers to. + MatchFinder->addMatcher( + typeLoc(isExpansionInMainFile(), + loc(qualType(hasDeclaration(Types.bind("use"))))), + this); + // Uses of typedefs: these are often transparent to hasDeclaration, so we need + // to handle them explicitly. + MatchFinder->addMatcher( + typeLoc(isExpansionInMainFile(), + loc(typedefType(hasDeclaration(Typedefs.bind("use"))))), + this); + // Uses of class templates: + // The typeLoc names the templateSpecializationType. Its declaration is the + // ClassTemplateDecl, which contains the CXXRecordDecl we want. + MatchFinder->addMatcher( + typeLoc(isExpansionInMainFile(), + loc(templateSpecializationType(hasDeclaration( + classTemplateSpecializationDecl(hasSpecializedTemplate( + classTemplateDecl(has(CXXRecords.bind("use"))))))))), + this); +} + +void FindAllSymbols::run(const MatchFinder::MatchResult &Result) { + // Ignore Results in failing TUs. + if (Result.Context->getDiagnostics().hasErrorOccurred()) { + return; + } + + SymbolInfo::Signals Signals; + const NamedDecl *ND; + if ((ND = Result.Nodes.getNodeAs("use"))) + Signals.Used = 1; + else if ((ND = Result.Nodes.getNodeAs("decl"))) + Signals.Seen = 1; + else + assert(false && "Must match a NamedDecl!"); + + const SourceManager *SM = Result.SourceManager; + if (auto Symbol = CreateSymbolInfo(ND, *SM, Collector)) { + Filename = SM->getFileEntryForID(SM->getMainFileID())->getName(); + FileSymbols[*Symbol] += Signals; + } +} + +void FindAllSymbols::onEndOfTranslationUnit() { + if (Filename != "") { + Reporter->reportSymbols(Filename, FileSymbols); + FileSymbols.clear(); + Filename = ""; + } +} + +} // namespace find_all_symbols +} // namespace clang Index: clang-include-fixer/find-all-symbols/FindAllSymbolsAction.h =================================================================== --- clang-include-fixer/find-all-symbols/FindAllSymbolsAction.h +++ clang-include-fixer/find-all-symbols/FindAllSymbolsAction.h @@ -0,0 +1,62 @@ +//===-- FindAllSymbolsAction.h - find all symbols action --------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_FIND_ALL_SYMBOLS_ACTION_H +#define LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_FIND_ALL_SYMBOLS_ACTION_H + +#include "FindAllSymbols.h" +#include "HeaderMapCollector.h" +#include "PragmaCommentHandler.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendAction.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/ADT/StringRef.h" +#include + +namespace clang { +namespace find_all_symbols { + +class FindAllSymbolsAction : public clang::ASTFrontendAction { +public: + explicit FindAllSymbolsAction( + SymbolReporter *Reporter, + const HeaderMapCollector::RegexHeaderMap *RegexHeaderMap = nullptr); + + std::unique_ptr + CreateASTConsumer(clang::CompilerInstance &Compiler, + StringRef InFile) override; + +private: + SymbolReporter *const Reporter; + clang::ast_matchers::MatchFinder MatchFinder; + HeaderMapCollector Collector; + PragmaCommentHandler Handler; + FindAllSymbols Matcher; +}; + +class FindAllSymbolsActionFactory : public tooling::FrontendActionFactory { +public: + FindAllSymbolsActionFactory( + SymbolReporter *Reporter, + const HeaderMapCollector::RegexHeaderMap *RegexHeaderMap = nullptr) + : Reporter(Reporter), RegexHeaderMap(RegexHeaderMap) {} + + clang::FrontendAction *create() override { + return new FindAllSymbolsAction(Reporter, RegexHeaderMap); + } + +private: + SymbolReporter *const Reporter; + const HeaderMapCollector::RegexHeaderMap *const RegexHeaderMap; +}; + +} // namespace find_all_symbols +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_FIND_ALL_SYMBOLS_ACTION_H Index: clang-include-fixer/find-all-symbols/FindAllSymbolsAction.cpp =================================================================== --- clang-include-fixer/find-all-symbols/FindAllSymbolsAction.cpp +++ clang-include-fixer/find-all-symbols/FindAllSymbolsAction.cpp @@ -0,0 +1,36 @@ +//===-- FindAllSymbolsAction.cpp - find all symbols action --------*- C++ -*-===// +// +// 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 "FindAllSymbolsAction.h" +#include "FindAllMacros.h" +#include "clang/Lex/PPCallbacks.h" +#include "clang/Lex/Preprocessor.h" +#include "llvm/ADT/STLExtras.h" + +namespace clang { +namespace find_all_symbols { + +FindAllSymbolsAction::FindAllSymbolsAction( + SymbolReporter *Reporter, + const HeaderMapCollector::RegexHeaderMap *RegexHeaderMap) + : Reporter(Reporter), Collector(RegexHeaderMap), Handler(&Collector), + Matcher(Reporter, &Collector) { + Matcher.registerMatchers(&MatchFinder); +} + +std::unique_ptr +FindAllSymbolsAction::CreateASTConsumer(CompilerInstance &Compiler, + StringRef InFile) { + Compiler.getPreprocessor().addCommentHandler(&Handler); + Compiler.getPreprocessor().addPPCallbacks(llvm::make_unique( + Reporter, &Compiler.getSourceManager(), &Collector)); + return MatchFinder.newASTConsumer(); +} + +} // namespace find_all_symbols +} // namespace clang Index: clang-include-fixer/find-all-symbols/HeaderMapCollector.h =================================================================== --- clang-include-fixer/find-all-symbols/HeaderMapCollector.h +++ clang-include-fixer/find-all-symbols/HeaderMapCollector.h @@ -0,0 +1,56 @@ +//===-- HeaderMapCoolector.h - find all symbols------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_HEADER_MAP_COLLECTOR_H +#define LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_HEADER_MAP_COLLECTOR_H + +#include "llvm/ADT/StringMap.h" +#include "llvm/Support/Regex.h" +#include +#include + +namespace clang { +namespace find_all_symbols { + +/// \brief HeaderMappCollector collects all remapping header files. This maps +/// complete header names or header name regex patterns to header names. +class HeaderMapCollector { +public: + typedef llvm::StringMap HeaderMap; + typedef std::vector> RegexHeaderMap; + + HeaderMapCollector() = default; + explicit HeaderMapCollector(const RegexHeaderMap *RegexHeaderMappingTable); + + void addHeaderMapping(llvm::StringRef OrignalHeaderPath, + llvm::StringRef MappingHeaderPath) { + HeaderMappingTable[OrignalHeaderPath] = MappingHeaderPath; + }; + + /// Check if there is a mapping from \p Header or a regex pattern that matches + /// it to another header name. + /// \param Header A header name. + /// \return \p Header itself if there is no mapping for it; otherwise, return + /// a mapped header name. + llvm::StringRef getMappedHeader(llvm::StringRef Header) const; + +private: + /// A string-to-string map saving the mapping relationship. + HeaderMap HeaderMappingTable; + + // A map from header patterns to header names. + // The header names are not owned. This is only threadsafe because the regexes + // never fail. + mutable std::vector> + RegexHeaderMappingTable; +}; + +} // namespace find_all_symbols +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_HEADER_MAP_COLLECTOR_H Index: clang-include-fixer/find-all-symbols/HeaderMapCollector.cpp =================================================================== --- clang-include-fixer/find-all-symbols/HeaderMapCollector.cpp +++ clang-include-fixer/find-all-symbols/HeaderMapCollector.cpp @@ -0,0 +1,44 @@ +//===-- HeaderMapCoolector.h - find all symbols------------------*- C++ -*-===// +// +// 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 "HeaderMapCollector.h" +#include "llvm/Support/Regex.h" + +namespace clang { +namespace find_all_symbols { + +HeaderMapCollector::HeaderMapCollector( + const RegexHeaderMap *RegexHeaderMappingTable) { + assert(RegexHeaderMappingTable); + this->RegexHeaderMappingTable.reserve(RegexHeaderMappingTable->size()); + for (const auto &Entry : *RegexHeaderMappingTable) { + this->RegexHeaderMappingTable.emplace_back(llvm::Regex(Entry.first), + Entry.second); + } +} + +llvm::StringRef +HeaderMapCollector::getMappedHeader(llvm::StringRef Header) const { + auto Iter = HeaderMappingTable.find(Header); + if (Iter != HeaderMappingTable.end()) + return Iter->second; + // If there is no complete header name mapping for this header, check the + // regex header mapping. + for (auto &Entry : RegexHeaderMappingTable) { +#ifndef NDEBUG + std::string Dummy; + assert(Entry.first.isValid(Dummy) && "Regex should never be invalid!"); +#endif + if (Entry.first.match(Header)) + return Entry.second; + } + return Header; +} + +} // namespace find_all_symbols +} // namespace clang Index: clang-include-fixer/find-all-symbols/PathConfig.h =================================================================== --- clang-include-fixer/find-all-symbols/PathConfig.h +++ clang-include-fixer/find-all-symbols/PathConfig.h @@ -0,0 +1,36 @@ +//===-- PathConfig.h - Process paths of symbols -----------------*- C++ -*-===// +// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_PATH_CONFIG_H +#define LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_PATH_CONFIG_H + +#include "HeaderMapCollector.h" +#include "clang/Basic/SourceManager.h" +#include + +namespace clang { +namespace find_all_symbols { + +/// \brief This calculates the include path for \p Loc. +/// +/// \param SM SourceManager. +/// \param Loc A SourceLocation. +/// \param Collector An optional header mapping collector. +/// +/// \return The file path (or mapped file path if Collector is provided) of the +/// header that includes \p Loc. If \p Loc comes from .inc header file, \p Loc +/// is set to the location from which the .inc header file is included. If \p +/// Loc is invalid or comes from a main file, this returns an empty string. +std::string getIncludePath(const SourceManager &SM, SourceLocation Loc, + const HeaderMapCollector *Collector = nullptr); + +} // namespace find_all_symbols +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_PATH_CONFIG_H Index: clang-include-fixer/find-all-symbols/PathConfig.cpp =================================================================== --- clang-include-fixer/find-all-symbols/PathConfig.cpp +++ clang-include-fixer/find-all-symbols/PathConfig.cpp @@ -0,0 +1,41 @@ +//===-- PathConfig.cpp - Process paths of symbols ---------------*- C++ -*-===// +// +// +// 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 "PathConfig.h" +#include "llvm/Support/Path.h" + +namespace clang { +namespace find_all_symbols { + +std::string getIncludePath(const SourceManager &SM, SourceLocation Loc, + const HeaderMapCollector *Collector) { + llvm::StringRef FilePath; + // Walk up the include stack to skip .inc files. + while (true) { + if (!Loc.isValid() || SM.isInMainFile(Loc)) + return ""; + FilePath = SM.getFilename(Loc); + if (FilePath.empty()) + return ""; + if (!FilePath.endswith(".inc")) + break; + FileID ID = SM.getFileID(Loc); + Loc = SM.getIncludeLoc(ID); + } + + if (Collector) + FilePath = Collector->getMappedHeader(FilePath); + SmallString<256> CleanedFilePath = FilePath; + llvm::sys::path::remove_dots(CleanedFilePath, /*remove_dot_dot=*/false); + + return CleanedFilePath.str(); +} + +} // namespace find_all_symbols +} // namespace clang Index: clang-include-fixer/find-all-symbols/PragmaCommentHandler.h =================================================================== --- clang-include-fixer/find-all-symbols/PragmaCommentHandler.h +++ clang-include-fixer/find-all-symbols/PragmaCommentHandler.h @@ -0,0 +1,40 @@ +//===-- PragmaCommentHandler.h - find all symbols----------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_PRAGMA_COMMENT_HANDLER_H +#define LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_PRAGMA_COMMENT_HANDLER_H + +#include "clang/Basic/SourceLocation.h" +#include "clang/Lex/Preprocessor.h" +#include + +namespace clang { +namespace find_all_symbols { + +class HeaderMapCollector; + +/// \brief PragmaCommentHandler parses pragma comment on include files to +/// determine when we should include a different header from the header that +/// directly defines a symbol. +/// +/// Currently it only supports IWYU private pragma: +/// https://github.com/include-what-you-use/include-what-you-use/blob/master/docs/IWYUPragmas.md#iwyu-pragma-private +class PragmaCommentHandler : public clang::CommentHandler { +public: + PragmaCommentHandler(HeaderMapCollector *Collector) : Collector(Collector) {} + + bool HandleComment(Preprocessor &PP, SourceRange Range) override; + +private: + HeaderMapCollector *const Collector; +}; + +} // namespace find_all_symbols +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_PRAGMA_COMMENT_HANDLER_H Index: clang-include-fixer/find-all-symbols/PragmaCommentHandler.cpp =================================================================== --- clang-include-fixer/find-all-symbols/PragmaCommentHandler.cpp +++ clang-include-fixer/find-all-symbols/PragmaCommentHandler.cpp @@ -0,0 +1,36 @@ +//===-- PragmaCommentHandler.cpp - find all symbols -----------------------===// +// +// 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 "PragmaCommentHandler.h" +#include "FindAllSymbols.h" +#include "HeaderMapCollector.h" +#include "clang/Lex/Preprocessor.h" +#include "llvm/Support/Regex.h" + +namespace clang { +namespace find_all_symbols { +namespace { +const char IWYUPragma[] = "// IWYU pragma: private, include "; +} // namespace + +bool PragmaCommentHandler::HandleComment(Preprocessor &PP, SourceRange Range) { + StringRef Text = + Lexer::getSourceText(CharSourceRange::getCharRange(Range), + PP.getSourceManager(), PP.getLangOpts()); + size_t Pos = Text.find(IWYUPragma); + if (Pos == StringRef::npos) + return false; + StringRef RemappingFilePath = Text.substr(Pos + std::strlen(IWYUPragma)); + Collector->addHeaderMapping( + PP.getSourceManager().getFilename(Range.getBegin()), + RemappingFilePath.trim("\"<>")); + return false; +} + +} // namespace find_all_symbols +} // namespace clang Index: clang-include-fixer/find-all-symbols/STLPostfixHeaderMap.h =================================================================== --- clang-include-fixer/find-all-symbols/STLPostfixHeaderMap.h +++ clang-include-fixer/find-all-symbols/STLPostfixHeaderMap.h @@ -0,0 +1,22 @@ +//===-- STLPostfixHeaderMap.h - hardcoded header map for STL ----*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_TOOL_STL_POSTFIX_HEADER_MAP_H +#define LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_TOOL_STL_POSTFIX_HEADER_MAP_H + +#include "HeaderMapCollector.h" + +namespace clang { +namespace find_all_symbols { + +const HeaderMapCollector::RegexHeaderMap *getSTLPostfixHeaderMap(); + +} // namespace find_all_symbols +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_TOOL_STL_POSTFIX_HEADER_MAP_H Index: clang-include-fixer/find-all-symbols/STLPostfixHeaderMap.cpp =================================================================== --- clang-include-fixer/find-all-symbols/STLPostfixHeaderMap.cpp +++ clang-include-fixer/find-all-symbols/STLPostfixHeaderMap.cpp @@ -0,0 +1,653 @@ +//===-- STLPostfixHeaderMap.h - hardcoded STL header map --------*- C++ -*-===// +// +// 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 "STLPostfixHeaderMap.h" + +namespace clang { +namespace find_all_symbols { + +const HeaderMapCollector::RegexHeaderMap *getSTLPostfixHeaderMap() { + static const HeaderMapCollector::RegexHeaderMap STLPostfixHeaderMap = { + {"include/__stddef_max_align_t.h$", ""}, + {"include/__wmmintrin_aes.h$", ""}, + {"include/__wmmintrin_pclmul.h$", ""}, + {"include/adxintrin.h$", ""}, + {"include/ammintrin.h$", ""}, + {"include/avx2intrin.h$", ""}, + {"include/avx512bwintrin.h$", ""}, + {"include/avx512cdintrin.h$", ""}, + {"include/avx512dqintrin.h$", ""}, + {"include/avx512erintrin.h$", ""}, + {"include/avx512fintrin.h$", ""}, + {"include/avx512ifmaintrin.h$", ""}, + {"include/avx512ifmavlintrin.h$", ""}, + {"include/avx512pfintrin.h$", ""}, + {"include/avx512vbmiintrin.h$", ""}, + {"include/avx512vbmivlintrin.h$", ""}, + {"include/avx512vlbwintrin.h$", ""}, + {"include/avx512vlcdintrin.h$", ""}, + {"include/avx512vldqintrin.h$", ""}, + {"include/avx512vlintrin.h$", ""}, + {"include/avxintrin.h$", ""}, + {"include/bmi2intrin.h$", ""}, + {"include/bmiintrin.h$", ""}, + {"include/emmintrin.h$", ""}, + {"include/f16cintrin.h$", ""}, + {"include/float.h$", ""}, + {"include/fma4intrin.h$", ""}, + {"include/fmaintrin.h$", ""}, + {"include/fxsrintrin.h$", ""}, + {"include/ia32intrin.h$", ""}, + {"include/immintrin.h$", ""}, + {"include/inttypes.h$", ""}, + {"include/limits.h$", ""}, + {"include/lzcntintrin.h$", ""}, + {"include/mm3dnow.h$", ""}, + {"include/mm_malloc.h$", ""}, + {"include/mmintrin.h$", ""}, + {"include/mwaitxintrin.h$", ""}, + {"include/pkuintrin.h$", ""}, + {"include/pmmintrin.h$", ""}, + {"include/popcntintrin.h$", ""}, + {"include/prfchwintrin.h$", ""}, + {"include/rdseedintrin.h$", ""}, + {"include/rtmintrin.h$", ""}, + {"include/shaintrin.h$", ""}, + {"include/smmintrin.h$", ""}, + {"include/stdalign.h$", ""}, + {"include/stdarg.h$", ""}, + {"include/stdbool.h$", ""}, + {"include/stddef.h$", ""}, + {"include/stdint.h$", ""}, + {"include/tbmintrin.h$", ""}, + {"include/tmmintrin.h$", ""}, + {"include/wmmintrin.h$", ""}, + {"include/x86intrin.h$", ""}, + {"include/xmmintrin.h$", ""}, + {"include/xopintrin.h$", ""}, + {"include/xsavecintrin.h$", ""}, + {"include/xsaveintrin.h$", ""}, + {"include/xsaveoptintrin.h$", ""}, + {"include/xsavesintrin.h$", ""}, + {"include/xtestintrin.h$", ""}, + {"include/_G_config.h$", ""}, + {"include/assert.h$", ""}, + {"algorithm$", ""}, + {"array$", ""}, + {"atomic$", ""}, + {"backward/auto_ptr.h$", ""}, + {"backward/binders.h$", ""}, + {"bits/algorithmfwd.h$", ""}, + {"bits/alloc_traits.h$", ""}, + {"bits/allocator.h$", ""}, + {"bits/atomic_base.h$", ""}, + {"bits/atomic_lockfree_defines.h$", ""}, + {"bits/basic_ios.h$", ""}, + {"bits/basic_ios.tcc$", ""}, + {"bits/basic_string.h$", ""}, + {"bits/basic_string.tcc$", ""}, + {"bits/char_traits.h$", ""}, + {"bits/codecvt.h$", ""}, + {"bits/concept_check.h$", ""}, + {"bits/cpp_type_traits.h$", ""}, + {"bits/cxxabi_forced.h$", ""}, + {"bits/deque.tcc$", ""}, + {"bits/exception_defines.h$", ""}, + {"bits/exception_ptr.h$", ""}, + {"bits/forward_list.h$", ""}, + {"bits/forward_list.tcc$", ""}, + {"bits/fstream.tcc$", ""}, + {"bits/functexcept.h$", ""}, + {"bits/functional_hash.h$", ""}, + {"bits/gslice.h$", ""}, + {"bits/gslice_array.h$", ""}, + {"bits/hash_bytes.h$", ""}, + {"bits/hashtable.h$", ""}, + {"bits/hashtable_policy.h$", ""}, + {"bits/indirect_array.h$", ""}, + {"bits/ios_base.h$", ""}, + {"bits/istream.tcc$", ""}, + {"bits/list.tcc$", ""}, + {"bits/locale_classes.h$", ""}, + {"bits/locale_classes.tcc$", ""}, + {"bits/locale_facets.h$", ""}, + {"bits/locale_facets.tcc$", ""}, + {"bits/locale_facets_nonio.h$", ""}, + {"bits/locale_facets_nonio.tcc$", ""}, + {"bits/localefwd.h$", ""}, + {"bits/mask_array.h$", ""}, + {"bits/memoryfwd.h$", ""}, + {"bits/move.h$", ""}, + {"bits/nested_exception.h$", ""}, + {"bits/ostream.tcc$", ""}, + {"bits/ostream_insert.h$", ""}, + {"bits/postypes.h$", ""}, + {"bits/ptr_traits.h$", ""}, + {"bits/random.h$", ""}, + {"bits/random.tcc$", ""}, + {"bits/range_access.h$", ""}, + {"bits/regex.h$", ""}, + {"bits/regex_compiler.h$", ""}, + {"bits/regex_constants.h$", ""}, + {"bits/regex_cursor.h$", ""}, + {"bits/regex_error.h$", ""}, + {"bits/regex_grep_matcher.h$", ""}, + {"bits/regex_grep_matcher.tcc$", ""}, + {"bits/regex_nfa.h$", ""}, + {"bits/shared_ptr.h$", ""}, + {"bits/shared_ptr_base.h$", ""}, + {"bits/slice_array.h$", ""}, + {"bits/sstream.tcc$", ""}, + {"bits/stl_algo.h$", ""}, + {"bits/stl_algobase.h$", ""}, + {"bits/stl_bvector.h$", ""}, + {"bits/stl_construct.h$", ""}, + {"bits/stl_deque.h$", ""}, + {"bits/stl_function.h$", ""}, + {"bits/stl_heap.h$", ""}, + {"bits/stl_iterator.h$", ""}, + {"bits/stl_iterator_base_funcs.h$", ""}, + {"bits/stl_iterator_base_types.h$", ""}, + {"bits/stl_list.h$", ""}, + {"bits/stl_map.h$", ""}, + {"bits/stl_multimap.h$", ""}, + {"bits/stl_multiset.h$", ""}, + {"bits/stl_numeric.h$", ""}, + {"bits/stl_pair.h$", ""}, + {"bits/stl_queue.h$", ""}, + {"bits/stl_raw_storage_iter.h$", ""}, + {"bits/stl_relops.h$", ""}, + {"bits/stl_set.h$", ""}, + {"bits/stl_stack.h$", ""}, + {"bits/stl_tempbuf.h$", ""}, + {"bits/stl_tree.h$", ""}, + {"bits/stl_uninitialized.h$", ""}, + {"bits/stl_vector.h$", ""}, + {"bits/stream_iterator.h$", ""}, + {"bits/streambuf.tcc$", ""}, + {"bits/streambuf_iterator.h$", ""}, + {"bits/stringfwd.h$", ""}, + {"bits/unique_ptr.h$", ""}, + {"bits/unordered_map.h$", ""}, + {"bits/unordered_set.h$", ""}, + {"bits/uses_allocator.h$", ""}, + {"bits/valarray_after.h$", ""}, + {"bits/valarray_array.h$", ""}, + {"bits/valarray_array.tcc$", ""}, + {"bits/valarray_before.h$", ""}, + {"bits/vector.tcc$", ""}, + {"bitset$", ""}, + {"ccomplex$", ""}, + {"cctype$", ""}, + {"cerrno$", ""}, + {"cfenv$", ""}, + {"cfloat$", ""}, + {"chrono$", ""}, + {"cinttypes$", ""}, + {"climits$", ""}, + {"clocale$", ""}, + {"cmath$", ""}, + {"complex$", ""}, + {"complex.h$", ""}, + {"condition_variable$", ""}, + {"csetjmp$", ""}, + {"csignal$", ""}, + {"cstdalign$", ""}, + {"cstdarg$", ""}, + {"cstdbool$", ""}, + {"cstdint$", ""}, + {"cstdio$", ""}, + {"cstdlib$", ""}, + {"cstring$", ""}, + {"ctgmath$", ""}, + {"ctime$", ""}, + {"cwchar$", ""}, + {"cwctype$", ""}, + {"cxxabi.h$", ""}, + {"debug/debug.h$", ""}, + {"debug/map.h$", ""}, + {"debug/multimap.h$", ""}, + {"debug/multiset.h$", ""}, + {"debug/set.h$", ""}, + {"deque$", ""}, + {"exception$", ""}, + {"ext/alloc_traits.h$", ""}, + {"ext/atomicity.h$", ""}, + {"ext/concurrence.h$", ""}, + {"ext/new_allocator.h$", ""}, + {"ext/numeric_traits.h$", ""}, + {"ext/string_conversions.h$", ""}, + {"ext/type_traits.h$", ""}, + {"fenv.h$", ""}, + {"forward_list$", ""}, + {"fstream$", ""}, + {"functional$", ""}, + {"future$", ""}, + {"initializer_list$", ""}, + {"iomanip$", ""}, + {"ios$", ""}, + {"iosfwd$", ""}, + {"iostream$", ""}, + {"istream$", ""}, + {"iterator$", ""}, + {"limits$", ""}, + {"list$", ""}, + {"locale$", ""}, + {"map$", ""}, + {"memory$", ""}, + {"mutex$", ""}, + {"new$", ""}, + {"numeric$", ""}, + {"ostream$", ""}, + {"queue$", ""}, + {"random$", ""}, + {"ratio$", ""}, + {"regex$", ""}, + {"scoped_allocator$", ""}, + {"set$", ""}, + {"sstream$", ""}, + {"stack$", ""}, + {"stdexcept$", ""}, + {"streambuf$", ""}, + {"string$", ""}, + {"system_error$", ""}, + {"tgmath.h$", ""}, + {"thread$", ""}, + {"tuple$", ""}, + {"type_traits$", ""}, + {"typeindex$", ""}, + {"typeinfo$", ""}, + {"unordered_map$", ""}, + {"unordered_set$", ""}, + {"utility$", ""}, + {"valarray$", ""}, + {"vector$", ""}, + {"include/complex.h$", ""}, + {"include/ctype.h$", ""}, + {"include/errno.h$", ""}, + {"include/fenv.h$", ""}, + {"include/inttypes.h$", ""}, + {"include/libio.h$", ""}, + {"include/limits.h$", ""}, + {"include/locale.h$", ""}, + {"include/math.h$", ""}, + {"include/setjmp.h$", ""}, + {"include/signal.h$", ""}, + {"include/stdint.h$", ""}, + {"include/stdio.h$", ""}, + {"include/stdlib.h$", ""}, + {"include/string.h$", ""}, + {"include/time.h$", ""}, + {"include/wchar.h$", ""}, + {"include/wctype.h$", ""}, + {"bits/cmathcalls.h$", ""}, + {"bits/errno.h$", ""}, + {"bits/fenv.h$", ""}, + {"bits/huge_val.h$", ""}, + {"bits/huge_valf.h$", ""}, + {"bits/huge_vall.h$", ""}, + {"bits/inf.h$", ""}, + {"bits/local_lim.h$", ""}, + {"bits/locale.h$", ""}, + {"bits/mathcalls.h$", ""}, + {"bits/mathdef.h$", ""}, + {"bits/nan.h$", ""}, + {"bits/posix1_lim.h$", ""}, + {"bits/posix2_lim.h$", ""}, + {"bits/setjmp.h$", ""}, + {"bits/sigaction.h$", ""}, + {"bits/sigcontext.h$", ""}, + {"bits/siginfo.h$", ""}, + {"bits/signum.h$", ""}, + {"bits/sigset.h$", ""}, + {"bits/sigstack.h$", ""}, + {"bits/stdio_lim.h$", ""}, + {"bits/sys_errlist.h$", ""}, + {"bits/time.h$", ""}, + {"bits/timex.h$", ""}, + {"bits/typesizes.h$", ""}, + {"bits/wchar.h$", ""}, + {"bits/wordsize.h$", ""}, + {"bits/xopen_lim.h$", ""}, + {"include/xlocale.h$", ""}, + {"bits/atomic_word.h$", ""}, + {"bits/basic_file.h$", ""}, + {"bits/c\\+\\+allocator.h$", ""}, + {"bits/c\\+\\+config.h$", ""}, + {"bits/c\\+\\+io.h$", ""}, + {"bits/c\\+\\+locale.h$", ""}, + {"bits/cpu_defines.h$", ""}, + {"bits/ctype_base.h$", ""}, + {"bits/cxxabi_tweaks.h$", ""}, + {"bits/error_constants.h$", ""}, + {"bits/gthr-default.h$", ""}, + {"bits/gthr.h$", ""}, + {"bits/opt_random.h$", ""}, + {"bits/os_defines.h$", ""}, + // GNU C headers + {"include/aio.h$", ""}, + {"include/aliases.h$", ""}, + {"include/alloca.h$", ""}, + {"include/ar.h$", ""}, + {"include/argp.h$", ""}, + {"include/argz.h$", ""}, + {"include/arpa/nameser.h$", ""}, + {"include/arpa/nameser_compat.h$", ""}, + {"include/byteswap.h$", ""}, + {"include/cpio.h$", ""}, + {"include/crypt.h$", ""}, + {"include/dirent.h$", ""}, + {"include/dlfcn.h$", ""}, + {"include/elf.h$", ""}, + {"include/endian.h$", ""}, + {"include/envz.h$", ""}, + {"include/err.h$", ""}, + {"include/error.h$", ""}, + {"include/execinfo.h$", ""}, + {"include/fcntl.h$", ""}, + {"include/features.h$", ""}, + {"include/fenv.h$", ""}, + {"include/fmtmsg.h$", ""}, + {"include/fnmatch.h$", ""}, + {"include/fstab.h$", ""}, + {"include/fts.h$", ""}, + {"include/ftw.h$", ""}, + {"include/gconv.h$", ""}, + {"include/getopt.h$", ""}, + {"include/glob.h$", ""}, + {"include/grp.h$", ""}, + {"include/gshadow.h$", ""}, + {"include/iconv.h$", ""}, + {"include/ifaddrs.h$", ""}, + {"include/kdb.h$", ""}, + {"include/langinfo.h$", ""}, + {"include/libgen.h$", ""}, + {"include/libintl.h$", ""}, + {"include/link.h$", ""}, + {"include/malloc.h$", ""}, + {"include/mcheck.h$", ""}, + {"include/memory.h$", ""}, + {"include/mntent.h$", ""}, + {"include/monetary.h$", ""}, + {"include/mqueue.h$", ""}, + {"include/netdb.h$", ""}, + {"include/netinet/in.h$", ""}, + {"include/nl_types.h$", ""}, + {"include/nss.h$", ""}, + {"include/obstack.h$", ""}, + {"include/panel.h$", ""}, + {"include/paths.h$", ""}, + {"include/printf.h$", ""}, + {"include/profile.h$", ""}, + {"include/pthread.h$", ""}, + {"include/pty.h$", ""}, + {"include/pwd.h$", ""}, + {"include/re_comp.h$", ""}, + {"include/regex.h$", ""}, + {"include/regexp.h$", ""}, + {"include/resolv.h$", ""}, + {"include/rpc/netdb.h$", ""}, + {"include/sched.h$", ""}, + {"include/search.h$", ""}, + {"include/semaphore.h$", ""}, + {"include/sgtty.h$", ""}, + {"include/shadow.h$", ""}, + {"include/spawn.h$", ""}, + {"include/stab.h$", ""}, + {"include/stdc-predef.h$", ""}, + {"include/stdio_ext.h$", ""}, + {"include/strings.h$", ""}, + {"include/stropts.h$", ""}, + {"include/sudo_plugin.h$", ""}, + {"include/sysexits.h$", ""}, + {"include/tar.h$", ""}, + {"include/tcpd.h$", ""}, + {"include/term.h$", ""}, + {"include/term_entry.h$", ""}, + {"include/termcap.h$", ""}, + {"include/termios.h$", ""}, + {"include/thread_db.h$", ""}, + {"include/tic.h$", ""}, + {"include/ttyent.h$", ""}, + {"include/uchar.h$", ""}, + {"include/ucontext.h$", ""}, + {"include/ulimit.h$", ""}, + {"include/unctrl.h$", ""}, + {"include/unistd.h$", ""}, + {"include/utime.h$", ""}, + {"include/utmp.h$", ""}, + {"include/utmpx.h$", ""}, + {"include/values.h$", ""}, + {"include/wordexp.h$", ""}, + {"fpu_control.h$", ""}, + {"ieee754.h$", ""}, + {"include/xlocale.h$", ""}, + {"gnu/lib-names.h$", ""}, + {"gnu/libc-version.h$", ""}, + {"gnu/option-groups.h$", ""}, + {"gnu/stubs-32.h$", ""}, + {"gnu/stubs-64.h$", ""}, + {"gnu/stubs-x32.h$", ""}, + {"include/rpc/auth_des.h$", ""}, + {"include/rpc/rpc_msg.h$", ""}, + {"include/rpc/pmap_clnt.h$", ""}, + {"include/rpc/rpc.h$", ""}, + {"include/rpc/types.h$", ""}, + {"include/rpc/auth_unix.h$", ""}, + {"include/rpc/key_prot.h$", ""}, + {"include/rpc/pmap_prot.h$", ""}, + {"include/rpc/auth.h$", ""}, + {"include/rpc/svc_auth.h$", ""}, + {"include/rpc/xdr.h$", ""}, + {"include/rpc/pmap_rmt.h$", ""}, + {"include/rpc/des_crypt.h$", ""}, + {"include/rpc/svc.h$", ""}, + {"include/rpc/rpc_des.h$", ""}, + {"include/rpc/clnt.h$", ""}, + {"include/scsi/scsi.h$", ""}, + {"include/scsi/sg.h$", ""}, + {"include/scsi/scsi_ioctl.h$", ""}, + {"include/netrose/rose.h$", ""}, + {"include/nfs/nfs.h$", ""}, + {"include/netatalk/at.h$", ""}, + {"include/netinet/ether.h$", ""}, + {"include/netinet/icmp6.h$", ""}, + {"include/netinet/if_ether.h$", ""}, + {"include/netinet/if_fddi.h$", ""}, + {"include/netinet/if_tr.h$", ""}, + {"include/netinet/igmp.h$", ""}, + {"include/netinet/in.h$", ""}, + {"include/netinet/in_systm.h$", ""}, + {"include/netinet/ip.h$", ""}, + {"include/netinet/ip6.h$", ""}, + {"include/netinet/ip_icmp.h$", ""}, + {"include/netinet/tcp.h$", ""}, + {"include/netinet/udp.h$", ""}, + {"include/netrom/netrom.h$", ""}, + {"include/protocols/routed.h$", ""}, + {"include/protocols/rwhod.h$", ""}, + {"include/protocols/talkd.h$", ""}, + {"include/protocols/timed.h$", ""}, + {"include/rpcsvc/klm_prot.x$", ""}, + {"include/rpcsvc/rstat.h$", ""}, + {"include/rpcsvc/spray.x$", ""}, + {"include/rpcsvc/nlm_prot.x$", ""}, + {"include/rpcsvc/nis_callback.x$", ""}, + {"include/rpcsvc/yp.h$", ""}, + {"include/rpcsvc/yp.x$", ""}, + {"include/rpcsvc/nfs_prot.h$", ""}, + {"include/rpcsvc/rex.h$", ""}, + {"include/rpcsvc/yppasswd.h$", ""}, + {"include/rpcsvc/rex.x$", ""}, + {"include/rpcsvc/nis_tags.h$", ""}, + {"include/rpcsvc/nis_callback.h$", ""}, + {"include/rpcsvc/nfs_prot.x$", ""}, + {"include/rpcsvc/bootparam_prot.x$", ""}, + {"include/rpcsvc/rusers.x$", ""}, + {"include/rpcsvc/rquota.x$", ""}, + {"include/rpcsvc/nis.h$", ""}, + {"include/rpcsvc/nislib.h$", ""}, + {"include/rpcsvc/ypupd.h$", ""}, + {"include/rpcsvc/bootparam.h$", ""}, + {"include/rpcsvc/spray.h$", ""}, + {"include/rpcsvc/key_prot.h$", ""}, + {"include/rpcsvc/klm_prot.h$", ""}, + {"include/rpcsvc/sm_inter.h$", ""}, + {"include/rpcsvc/nlm_prot.h$", ""}, + {"include/rpcsvc/yp_prot.h$", ""}, + {"include/rpcsvc/ypclnt.h$", ""}, + {"include/rpcsvc/rstat.x$", ""}, + {"include/rpcsvc/rusers.h$", ""}, + {"include/rpcsvc/key_prot.x$", ""}, + {"include/rpcsvc/sm_inter.x$", ""}, + {"include/rpcsvc/rquota.h$", ""}, + {"include/rpcsvc/nis.x$", ""}, + {"include/rpcsvc/bootparam_prot.h$", ""}, + {"include/rpcsvc/mount.h$", ""}, + {"include/rpcsvc/mount.x$", ""}, + {"include/rpcsvc/nis_object.x$", ""}, + {"include/rpcsvc/yppasswd.x$", ""}, + {"sys/acct.h$", ""}, + {"sys/auxv.h$", ""}, + {"sys/cdefs.h$", ""}, + {"sys/debugreg.h$", ""}, + {"sys/dir.h$", ""}, + {"sys/elf.h$", ""}, + {"sys/epoll.h$", ""}, + {"sys/eventfd.h$", ""}, + {"sys/fanotify.h$", ""}, + {"sys/file.h$", ""}, + {"sys/fsuid.h$", ""}, + {"sys/gmon.h$", ""}, + {"sys/gmon_out.h$", ""}, + {"sys/inotify.h$", ""}, + {"sys/io.h$", ""}, + {"sys/ioctl.h$", ""}, + {"sys/ipc.h$", ""}, + {"sys/kd.h$", ""}, + {"sys/kdaemon.h$", ""}, + {"sys/klog.h$", ""}, + {"sys/mman.h$", ""}, + {"sys/mount.h$", ""}, + {"sys/msg.h$", ""}, + {"sys/mtio.h$", ""}, + {"sys/param.h$", ""}, + {"sys/pci.h$", ""}, + {"sys/perm.h$", ""}, + {"sys/personality.h$", ""}, + {"sys/poll.h$", ""}, + {"sys/prctl.h$", ""}, + {"sys/procfs.h$", ""}, + {"sys/profil.h$", ""}, + {"sys/ptrace.h$", ""}, + {"sys/queue.h$", ""}, + {"sys/quota.h$", ""}, + {"sys/raw.h$", ""}, + {"sys/reboot.h$", ""}, + {"sys/reg.h$", ""}, + {"sys/resource.h$", ""}, + {"sys/select.h$", ""}, + {"sys/sem.h$", ""}, + {"sys/sendfile.h$", ""}, + {"sys/shm.h$", ""}, + {"sys/signalfd.h$", ""}, + {"sys/socket.h$", ""}, + {"sys/stat.h$", ""}, + {"sys/statfs.h$", ""}, + {"sys/statvfs.h$", ""}, + {"sys/swap.h$", ""}, + {"sys/syscall.h$", ""}, + {"sys/sysctl.h$", ""}, + {"sys/sysinfo.h$", ""}, + {"sys/syslog.h$", ""}, + {"sys/sysmacros.h$", ""}, + {"sys/termios.h$", ""}, + {"sys/time.h$", ""}, + {"sys/timeb.h$", ""}, + {"sys/timerfd.h$", ""}, + {"sys/times.h$", ""}, + {"sys/timex.h$", ""}, + {"sys/ttychars.h$", ""}, + {"sys/ttydefaults.h$", ""}, + {"sys/types.h$", ""}, + {"sys/ucontext.h$", ""}, + {"sys/uio.h$", ""}, + {"sys/un.h$", ""}, + {"sys/user.h$", ""}, + {"sys/ustat.h$", ""}, + {"sys/utsname.h$", ""}, + {"sys/vlimit.h$", ""}, + {"sys/vm86.h$", ""}, + {"sys/vtimes.h$", ""}, + {"sys/wait.h$", ""}, + {"sys/xattr.h$", ""}, + {"bits/epoll.h$", ""}, + {"bits/eventfd.h$", ""}, + {"bits/inotify.h$", ""}, + {"bits/ipc.h$", ""}, + {"bits/ipctypes.h$", ""}, + {"bits/mman-linux.h$", ""}, + {"bits/mman.h$", ""}, + {"bits/msq.h$", ""}, + {"bits/resource.h$", ""}, + {"bits/sem.h$", ""}, + {"bits/shm.h$", ""}, + {"bits/signalfd.h$", ""}, + {"bits/statfs.h$", ""}, + {"bits/statvfs.h$", ""}, + {"bits/timerfd.h$", ""}, + {"bits/utsname.h$", ""}, + {"bits/auxv.h$", ""}, + {"bits/byteswap-16.h$", ""}, + {"bits/byteswap.h$", ""}, + {"bits/confname.h$", ""}, + {"bits/dirent.h$", ""}, + {"bits/dlfcn.h$", ""}, + {"bits/elfclass.h$", ""}, + {"bits/endian.h$", ""}, + {"bits/environments.h$", ""}, + {"bits/fcntl-linux.h$", ""}, + {"bits/fcntl.h$", ""}, + {"bits/in.h$", ""}, + {"bits/ioctl-types.h$", ""}, + {"bits/ioctls.h$", ""}, + {"bits/link.h$", ""}, + {"bits/mqueue.h$", ""}, + {"bits/netdb.h$", ""}, + {"bits/param.h$", ""}, + {"bits/poll.h$", ""}, + {"bits/posix_opt.h$", ""}, + {"bits/pthreadtypes.h$", ""}, + {"bits/sched.h$", ""}, + {"bits/select.h$", ""}, + {"bits/semaphore.h$", ""}, + {"bits/sigthread.h$", ""}, + {"bits/sockaddr.h$", ""}, + {"bits/socket.h$", ""}, + {"bits/socket_type.h$", ""}, + {"bits/stab.def$", ""}, + {"bits/stat.h$", ""}, + {"bits/stropts.h$", ""}, + {"bits/syscall.h$", ""}, + {"bits/syslog-path.h$", ""}, + {"bits/termios.h$", ""}, + {"bits/types.h$", ""}, + {"bits/typesizes.h$", ""}, + {"bits/uio.h$", ""}, + {"bits/ustat.h$", ""}, + {"bits/utmp.h$", ""}, + {"bits/utmpx.h$", ""}, + {"bits/waitflags.h$", ""}, + {"bits/waitstatus.h$", ""}, + {"bits/xtitypes.h$", ""}, + }; + return &STLPostfixHeaderMap; +} + +} // namespace find_all_symbols +} // namespace clang Index: clang-include-fixer/find-all-symbols/SymbolInfo.h =================================================================== --- clang-include-fixer/find-all-symbols/SymbolInfo.h +++ clang-include-fixer/find-all-symbols/SymbolInfo.h @@ -0,0 +1,142 @@ +//===-- SymbolInfo.h - Symbol Info ------------------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_FIND_ALL_SYMBOLS_SYMBOLINFO_H +#define LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_FIND_ALL_SYMBOLS_SYMBOLINFO_H + +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/YAMLTraits.h" +#include "llvm/Support/raw_ostream.h" +#include +#include +#include + +namespace clang { +namespace find_all_symbols { +/// \brief Describes a named symbol from a header. +/// Symbols with the same qualified name and type (e.g. function overloads) +/// that appear in the same header are represented by a single SymbolInfo. +/// +/// TODO: keep track of instances, e.g. overload locations and signatures. +class SymbolInfo { +public: + /// \brief The SymbolInfo Type. + enum class SymbolKind { + Function, + Class, + Variable, + TypedefName, + EnumDecl, + EnumConstantDecl, + Macro, + Unknown, + }; + + /// \brief The Context Type. + enum class ContextType { + Namespace, // Symbols declared in a namespace. + Record, // Symbols declared in a class. + EnumDecl, // Enum constants declared in a enum declaration. + }; + + /// \brief A pair of . + typedef std::pair Context; + + // \brief Signals are signals gathered by observing how a symbol is used. + // These are used to rank results. + struct Signals { + Signals() {} + Signals(unsigned Seen, unsigned Used) : Seen(Seen), Used(Used) {} + + // Number of times this symbol was visible to a TU. + unsigned Seen = 0; + + // Number of times this symbol was referenced a TU's main file. + unsigned Used = 0; + + Signals &operator+=(const Signals &RHS); + Signals operator+(const Signals &RHS) const; + bool operator==(const Signals &RHS) const; + }; + + using SignalMap = std::map; + + // The default constructor is required by YAML traits in + // LLVM_YAML_IS_DOCUMENT_LIST_VECTOR. + SymbolInfo() : Type(SymbolKind::Unknown) {} + + SymbolInfo(llvm::StringRef Name, SymbolKind Type, llvm::StringRef FilePath, + const std::vector &Contexts); + + void SetFilePath(llvm::StringRef Path) { FilePath = Path; } + + /// \brief Get symbol name. + llvm::StringRef getName() const { return Name; } + + /// \brief Get the fully-qualified symbol name. + std::string getQualifiedName() const; + + /// \brief Get symbol type. + SymbolKind getSymbolKind() const { return Type; } + + /// \brief Get a relative file path where symbol comes from. + llvm::StringRef getFilePath() const { return FilePath; } + + /// \brief Get symbol contexts. + const std::vector &getContexts() const { + return Contexts; + } + + bool operator<(const SymbolInfo &Symbol) const; + + bool operator==(const SymbolInfo &Symbol) const; + +private: + friend struct llvm::yaml::MappingTraits; + + /// \brief Identifier name. + std::string Name; + + /// \brief Symbol type. + SymbolKind Type; + + /// \brief The file path where the symbol comes from. It's a relative file + /// path based on the build directory. + std::string FilePath; + + /// \brief Contains information about symbol contexts. Context information is + /// stored from the inner-most level to outer-most level. + /// + /// For example, if a symbol 'x' is declared as: + /// namespace na { namespace nb { class A { int x; } } } + /// The contexts would be { {RECORD, "A"}, {NAMESPACE, "nb"}, {NAMESPACE, + /// "na"} }. + /// The name of an anonymous namespace is "". + /// + /// If the symbol is declared in `TranslationUnitDecl`, it has no context. + std::vector Contexts; +}; + +struct SymbolAndSignals { + SymbolInfo Symbol; + SymbolInfo::Signals Signals; + bool operator==(const SymbolAndSignals& RHS) const; +}; + +/// \brief Write SymbolInfos to a stream (YAML format). +bool WriteSymbolInfosToStream(llvm::raw_ostream &OS, + const SymbolInfo::SignalMap &Symbols); + +/// \brief Read SymbolInfos from a YAML document. +std::vector ReadSymbolInfosFromYAML(llvm::StringRef Yaml); + +} // namespace find_all_symbols +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_FIND_ALL_SYMBOLS_SYMBOLINFO_H Index: clang-include-fixer/find-all-symbols/SymbolInfo.cpp =================================================================== --- clang-include-fixer/find-all-symbols/SymbolInfo.cpp +++ clang-include-fixer/find-all-symbols/SymbolInfo.cpp @@ -0,0 +1,136 @@ +//===-- SymbolInfo.cpp - Symbol Info ----------------------------*- C++ -*-===// +// +// 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 "SymbolInfo.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/YAMLTraits.h" +#include "llvm/Support/raw_ostream.h" + +using llvm::yaml::MappingTraits; +using llvm::yaml::IO; +using llvm::yaml::Input; +using ContextType = clang::find_all_symbols::SymbolInfo::ContextType; +using clang::find_all_symbols::SymbolInfo; +using clang::find_all_symbols::SymbolAndSignals; +using SymbolKind = clang::find_all_symbols::SymbolInfo::SymbolKind; + +LLVM_YAML_IS_DOCUMENT_LIST_VECTOR(SymbolAndSignals) +LLVM_YAML_IS_SEQUENCE_VECTOR(SymbolInfo::Context) + +namespace llvm { +namespace yaml { +template <> struct MappingTraits { + static void mapping(IO &io, SymbolAndSignals &Symbol) { + io.mapRequired("Name", Symbol.Symbol.Name); + io.mapRequired("Contexts", Symbol.Symbol.Contexts); + io.mapRequired("FilePath", Symbol.Symbol.FilePath); + io.mapRequired("Type", Symbol.Symbol.Type); + io.mapRequired("Seen", Symbol.Signals.Seen); + io.mapRequired("Used", Symbol.Signals.Used); + } +}; + +template <> struct ScalarEnumerationTraits { + static void enumeration(IO &io, ContextType &value) { + io.enumCase(value, "Record", ContextType::Record); + io.enumCase(value, "Namespace", ContextType::Namespace); + io.enumCase(value, "EnumDecl", ContextType::EnumDecl); + } +}; + +template <> struct ScalarEnumerationTraits { + static void enumeration(IO &io, SymbolKind &value) { + io.enumCase(value, "Variable", SymbolKind::Variable); + io.enumCase(value, "Function", SymbolKind::Function); + io.enumCase(value, "Class", SymbolKind::Class); + io.enumCase(value, "TypedefName", SymbolKind::TypedefName); + io.enumCase(value, "EnumDecl", SymbolKind::EnumDecl); + io.enumCase(value, "EnumConstantDecl", SymbolKind::EnumConstantDecl); + io.enumCase(value, "Macro", SymbolKind::Macro); + io.enumCase(value, "Unknown", SymbolKind::Unknown); + } +}; + +template <> struct MappingTraits { + static void mapping(IO &io, SymbolInfo::Context &Context) { + io.mapRequired("ContextType", Context.first); + io.mapRequired("ContextName", Context.second); + } +}; + +} // namespace yaml +} // namespace llvm + +namespace clang { +namespace find_all_symbols { + +SymbolInfo::SymbolInfo(llvm::StringRef Name, SymbolKind Type, + llvm::StringRef FilePath, + const std::vector &Contexts) + : Name(Name), Type(Type), FilePath(FilePath), Contexts(Contexts) {} + +bool SymbolInfo::operator==(const SymbolInfo &Symbol) const { + return std::tie(Name, Type, FilePath, Contexts) == + std::tie(Symbol.Name, Symbol.Type, Symbol.FilePath, Symbol.Contexts); +} + +bool SymbolInfo::operator<(const SymbolInfo &Symbol) const { + return std::tie(Name, Type, FilePath, Contexts) < + std::tie(Symbol.Name, Symbol.Type, Symbol.FilePath, Symbol.Contexts); +} + +std::string SymbolInfo::getQualifiedName() const { + std::string QualifiedName = Name; + for (const auto &Context : Contexts) { + if (Context.first == ContextType::EnumDecl) + continue; + QualifiedName = Context.second + "::" + QualifiedName; + } + return QualifiedName; +} + +SymbolInfo::Signals &SymbolInfo::Signals::operator+=(const Signals &RHS) { + Seen += RHS.Seen; + Used += RHS.Used; + return *this; +} + +SymbolInfo::Signals SymbolInfo::Signals::operator+(const Signals &RHS) const { + Signals Result = *this; + Result += RHS; + return Result; +} + +bool SymbolInfo::Signals::operator==(const Signals &RHS) const { + return std::tie(Seen, Used) == std::tie(RHS.Seen, RHS.Used); +} + +bool SymbolAndSignals::operator==(const SymbolAndSignals& RHS) const { + return std::tie(Symbol, Signals) == std::tie(RHS.Symbol, RHS.Signals); +} + +bool WriteSymbolInfosToStream(llvm::raw_ostream &OS, + const SymbolInfo::SignalMap &Symbols) { + llvm::yaml::Output yout(OS); + for (const auto &Symbol : Symbols) { + SymbolAndSignals S{Symbol.first, Symbol.second}; + yout << S; + } + return true; +} + +std::vector ReadSymbolInfosFromYAML(llvm::StringRef Yaml) { + std::vector Symbols; + llvm::yaml::Input yin(Yaml); + yin >> Symbols; + return Symbols; +} + +} // namespace find_all_symbols +} // namespace clang Index: clang-include-fixer/find-all-symbols/SymbolReporter.h =================================================================== --- clang-include-fixer/find-all-symbols/SymbolReporter.h +++ clang-include-fixer/find-all-symbols/SymbolReporter.h @@ -0,0 +1,29 @@ +//===--- SymbolReporter.h - Symbol Reporter ---------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_SYMBOL_REPORTER_H +#define LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_SYMBOL_REPORTER_H + +#include "SymbolInfo.h" + +namespace clang { +namespace find_all_symbols { + +/// \brief An interface for classes that collect symbols. +class SymbolReporter { +public: + virtual ~SymbolReporter() = default; + + virtual void reportSymbols(llvm::StringRef FileName, + const SymbolInfo::SignalMap &Symbols) = 0; +}; + +} // namespace find_all_symbols +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_SYMBOL_REPORTER_H Index: clang-include-fixer/find-all-symbols/tool/CMakeLists.txt =================================================================== --- clang-include-fixer/find-all-symbols/tool/CMakeLists.txt +++ clang-include-fixer/find-all-symbols/tool/CMakeLists.txt @@ -0,0 +1,24 @@ +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) + +add_clang_executable(find-all-symbols + FindAllSymbolsMain.cpp + ) + +target_link_libraries(find-all-symbols + PRIVATE + clangAST + clangASTMatchers + clangBasic + clangFrontend + clangLex + clangSerialization + clangTooling + findAllSymbols + ) + +install(TARGETS find-all-symbols + RUNTIME DESTINATION bin) + +install(PROGRAMS run-find-all-symbols.py + DESTINATION share/clang + COMPONENT find-all-symbols) Index: clang-include-fixer/find-all-symbols/tool/FindAllSymbolsMain.cpp =================================================================== --- clang-include-fixer/find-all-symbols/tool/FindAllSymbolsMain.cpp +++ clang-include-fixer/find-all-symbols/tool/FindAllSymbolsMain.cpp @@ -0,0 +1,151 @@ +//===-- FindAllSymbolsMain.cpp - find all symbols tool ----------*- C++ -*-===// +// +// 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 "FindAllSymbolsAction.h" +#include "STLPostfixHeaderMap.h" +#include "SymbolInfo.h" +#include "SymbolReporter.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendActions.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/ThreadPool.h" +#include "llvm/Support/raw_ostream.h" +#include +#include +#include +#include +#include +#include + +using namespace clang::tooling; +using namespace llvm; +using SymbolInfo = clang::find_all_symbols::SymbolInfo; + +// Apply a custom category to all command-line options so that they are the +// only ones displayed. +static cl::OptionCategory FindAllSymbolsCategory("find_all_symbols options"); + +// CommonOptionsParser declares HelpMessage with a description of the common +// command-line options related to the compilation database and input files. +// It's nice to have this help message in all tools. +static cl::extrahelp CommonHelp(CommonOptionsParser::HelpMessage); + +// A help message for this specific tool can be added afterwards. +static cl::extrahelp MoreHelp("\nMore help text..."); + +static cl::opt OutputDir("output-dir", cl::desc(R"( +The output directory for saving the results.)"), + cl::init("."), + cl::cat(FindAllSymbolsCategory)); + +static cl::opt MergeDir("merge-dir", cl::desc(R"( +The directory for merging symbols.)"), + cl::init(""), + cl::cat(FindAllSymbolsCategory)); +namespace clang { +namespace find_all_symbols { + +class YamlReporter : public SymbolReporter { +public: + void reportSymbols(StringRef FileName, + const SymbolInfo::SignalMap &Symbols) override { + int FD; + SmallString<128> ResultPath; + llvm::sys::fs::createUniqueFile( + OutputDir + "/" + llvm::sys::path::filename(FileName) + "-%%%%%%.yaml", + FD, ResultPath); + llvm::raw_fd_ostream OS(FD, /*shouldClose=*/true); + WriteSymbolInfosToStream(OS, Symbols); + } +}; + +bool Merge(llvm::StringRef MergeDir, llvm::StringRef OutputFile) { + std::error_code EC; + SymbolInfo::SignalMap Symbols; + std::mutex SymbolMutex; + auto AddSymbols = [&](ArrayRef NewSymbols) { + // Synchronize set accesses. + std::unique_lock LockGuard(SymbolMutex); + for (const auto &Symbol : NewSymbols) { + Symbols[Symbol.Symbol] += Symbol.Signals; + } + }; + + // Load all symbol files in MergeDir. + { + llvm::ThreadPool Pool; + for (llvm::sys::fs::directory_iterator Dir(MergeDir, EC), DirEnd; + Dir != DirEnd && !EC; Dir.increment(EC)) { + // Parse YAML files in parallel. + Pool.async( + [&AddSymbols](std::string Path) { + auto Buffer = llvm::MemoryBuffer::getFile(Path); + if (!Buffer) { + llvm::errs() << "Can't open " << Path << "\n"; + return; + } + std::vector Symbols = + ReadSymbolInfosFromYAML(Buffer.get()->getBuffer()); + for (auto &Symbol : Symbols) { + // Only count one occurrence per file, to avoid spam. + Symbol.Signals.Seen = std::min(Symbol.Signals.Seen, 1u); + Symbol.Signals.Used = std::min(Symbol.Signals.Used, 1u); + } + // FIXME: Merge without creating such a heavy contention point. + AddSymbols(Symbols); + }, + Dir->path()); + } + } + + llvm::raw_fd_ostream OS(OutputFile, EC, llvm::sys::fs::F_None); + if (EC) { + llvm::errs() << "Can't open '" << OutputFile << "': " << EC.message() + << '\n'; + return false; + } + WriteSymbolInfosToStream(OS, Symbols); + return true; +} + +} // namespace clang +} // namespace find_all_symbols + +int main(int argc, const char **argv) { + CommonOptionsParser OptionsParser(argc, argv, FindAllSymbolsCategory); + ClangTool Tool(OptionsParser.getCompilations(), + OptionsParser.getSourcePathList()); + + std::vector sources = OptionsParser.getSourcePathList(); + if (sources.empty()) { + llvm::errs() << "Must specify at least one one source file.\n"; + return 1; + } + if (!MergeDir.empty()) { + clang::find_all_symbols::Merge(MergeDir, sources[0]); + return 0; + } + + clang::find_all_symbols::YamlReporter Reporter; + + auto Factory = + llvm::make_unique( + &Reporter, clang::find_all_symbols::getSTLPostfixHeaderMap()); + return Tool.run(Factory.get()); +} Index: clang-include-fixer/find-all-symbols/tool/run-find-all-symbols.py =================================================================== --- clang-include-fixer/find-all-symbols/tool/run-find-all-symbols.py +++ clang-include-fixer/find-all-symbols/tool/run-find-all-symbols.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python +# +#=- run-find-all-symbols.py - Parallel find-all-symbols runner -*- python -*-=# +# +# 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 +# +#===------------------------------------------------------------------------===# + +""" +Parallel find-all-symbols runner +================================ + +Runs find-all-symbols over all files in a compilation database. + +Example invocations. +- Run find-all-symbols on all files in the current working directory. + run-find-all-symbols.py + +Compilation database setup: +http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html +""" + +import argparse +import json +import multiprocessing +import os +import Queue +import shutil +import subprocess +import sys +import tempfile +import threading + + +def find_compilation_database(path): + """Adjusts the directory until a compilation database is found.""" + result = './' + while not os.path.isfile(os.path.join(result, path)): + if os.path.realpath(result) == '/': + print 'Error: could not find compilation database.' + sys.exit(1) + result += '../' + return os.path.realpath(result) + + +def MergeSymbols(directory, args): + """Merge all symbol files (yaml) in a given directaory into a single file.""" + invocation = [args.binary, '-merge-dir='+directory, args.saving_path] + subprocess.call(invocation) + print 'Merge is finished. Saving results in ' + args.saving_path + + +def run_find_all_symbols(args, tmpdir, build_path, queue): + """Takes filenames out of queue and runs find-all-symbols on them.""" + while True: + name = queue.get() + invocation = [args.binary, name, '-output-dir='+tmpdir, '-p='+build_path] + sys.stdout.write(' '.join(invocation) + '\n') + subprocess.call(invocation) + queue.task_done() + + +def main(): + parser = argparse.ArgumentParser(description='Runs find-all-symbols over all' + 'files in a compilation database.') + parser.add_argument('-binary', metavar='PATH', + default='./bin/find-all-symbols', + help='path to find-all-symbols binary') + parser.add_argument('-j', type=int, default=0, + help='number of instances to be run in parallel.') + parser.add_argument('-p', dest='build_path', + help='path used to read a compilation database.') + parser.add_argument('-saving-path', default='./find_all_symbols_db.yaml', + help='result saving path') + args = parser.parse_args() + + db_path = 'compile_commands.json' + + if args.build_path is not None: + build_path = args.build_path + else: + build_path = find_compilation_database(db_path) + + tmpdir = tempfile.mkdtemp() + + # Load the database and extract all files. + database = json.load(open(os.path.join(build_path, db_path))) + files = [entry['file'] for entry in database] + + max_task = args.j + if max_task == 0: + max_task = multiprocessing.cpu_count() + + try: + # Spin up a bunch of tidy-launching threads. + queue = Queue.Queue(max_task) + for _ in range(max_task): + t = threading.Thread(target=run_find_all_symbols, + args=(args, tmpdir, build_path, queue)) + t.daemon = True + t.start() + + # Fill the queue with files. + for name in files: + queue.put(name) + + # Wait for all threads to be done. + queue.join() + + MergeSymbols(tmpdir, args) + + + except KeyboardInterrupt: + # This is a sad hack. Unfortunately subprocess goes + # bonkers with ctrl-c and we start forking merrily. + print '\nCtrl-C detected, goodbye.' + os.kill(0, 9) + + +if __name__ == '__main__': + main() Index: clang-include-fixer/plugin/CMakeLists.txt =================================================================== --- clang-include-fixer/plugin/CMakeLists.txt +++ clang-include-fixer/plugin/CMakeLists.txt @@ -0,0 +1,13 @@ +add_clang_library(clangIncludeFixerPlugin + IncludeFixerPlugin.cpp + + LINK_LIBS + clangAST + clangBasic + clangFrontend + clangIncludeFixer + clangParse + clangSema + clangTooling + ${LLVM_PTHREAD_LIB} + ) Index: clang-include-fixer/plugin/IncludeFixerPlugin.cpp =================================================================== --- clang-include-fixer/plugin/IncludeFixerPlugin.cpp +++ clang-include-fixer/plugin/IncludeFixerPlugin.cpp @@ -0,0 +1,99 @@ +//===- IncludeFixerPlugin.cpp - clang-include-fixer as a clang plugin -----===// +// +// 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 "../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 SIM) + : SymbolIndexMgr(std::move(SIM)) {} + std::shared_ptr SymbolIndexMgr; + }; + +public: + explicit ClangIncludeFixerPluginAction() + : SymbolIndexMgr(std::make_shared()), + SemaSource(new IncludeFixerSemaSource(*SymbolIndexMgr, + /*MinimizeIncludePaths=*/true, + /*GenerateDiagnostics=*/true)) {} + + std::unique_ptr + CreateASTConsumer(clang::CompilerInstance &CI, StringRef InFile) override { + CI.setExternalSemaSource(SemaSource); + SemaSource->setFilePath(InFile); + SemaSource->setCompilerInstance(&CI); + return llvm::make_unique(SymbolIndexMgr); + } + + void ExecuteAction() override {} // Do nothing. + + bool ParseArgs(const CompilerInstance &CI, + const std::vector &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=")); + } + + std::string InputFile = CI.getFrontendOpts().Inputs[0].getFile(); + auto CreateYamlIdx = [=]() -> std::unique_ptr { + llvm::ErrorOr> 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. + SmallString<128> AbsolutePath(tooling::getAbsolutePath(InputFile)); + StringRef Directory = llvm::sys::path::parent_path(AbsolutePath); + SymbolIdx = include_fixer::YamlSymbolIndex::createFromDirectory( + Directory, "find_all_symbols_db.yaml"); + } + } + return std::move(*SymbolIdx); + }; + + SymbolIndexMgr->addSymbolIndex(std::move(CreateYamlIdx)); + return true; + } + +private: + std::shared_ptr SymbolIndexMgr; + IntrusiveRefCntPtr 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"); Index: clang-include-fixer/tool/CMakeLists.txt =================================================================== --- clang-include-fixer/tool/CMakeLists.txt +++ clang-include-fixer/tool/CMakeLists.txt @@ -0,0 +1,28 @@ +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) + +add_clang_tool(clang-include-fixer + ClangIncludeFixer.cpp + ) + +target_link_libraries(clang-include-fixer + PRIVATE + clangBasic + clangFormat + clangFrontend + clangIncludeFixer + clangRewrite + clangSerialization + clangTooling + clangToolingCore + findAllSymbols + ) + +install(TARGETS clang-include-fixer + RUNTIME DESTINATION bin) + +install(PROGRAMS clang-include-fixer.el + DESTINATION share/clang + COMPONENT clang-include-fixer) +install(PROGRAMS clang-include-fixer.py + DESTINATION share/clang + COMPONENT clang-include-fixer) Index: clang-include-fixer/tool/ClangIncludeFixer.cpp =================================================================== --- clang-include-fixer/tool/ClangIncludeFixer.cpp +++ clang-include-fixer/tool/ClangIncludeFixer.cpp @@ -0,0 +1,473 @@ +//===-- ClangIncludeFixer.cpp - Standalone include fixer ------------------===// +// +// 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 "FuzzySymbolIndex.h" +#include "InMemorySymbolIndex.h" +#include "IncludeFixer.h" +#include "IncludeFixerContext.h" +#include "SymbolIndexManager.h" +#include "YamlSymbolIndex.h" +#include "clang/Format/Format.h" +#include "clang/Frontend/TextDiagnosticPrinter.h" +#include "clang/Rewrite/Core/Rewriter.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Core/Replacement.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/YAMLTraits.h" + +using namespace clang; +using namespace llvm; +using clang::include_fixer::IncludeFixerContext; + +LLVM_YAML_IS_DOCUMENT_LIST_VECTOR(IncludeFixerContext) +LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(IncludeFixerContext::HeaderInfo) +LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(IncludeFixerContext::QuerySymbolInfo) + +namespace llvm { +namespace yaml { + +template <> struct MappingTraits { + struct NormalizedRange { + NormalizedRange(const IO &) : Offset(0), Length(0) {} + + NormalizedRange(const IO &, const tooling::Range &R) + : Offset(R.getOffset()), Length(R.getLength()) {} + + tooling::Range denormalize(const IO &) { + return tooling::Range(Offset, Length); + } + + unsigned Offset; + unsigned Length; + }; + static void mapping(IO &IO, tooling::Range &Info) { + MappingNormalization Keys(IO, Info); + IO.mapRequired("Offset", Keys->Offset); + IO.mapRequired("Length", Keys->Length); + } +}; + +template <> struct MappingTraits { + static void mapping(IO &io, IncludeFixerContext::HeaderInfo &Info) { + io.mapRequired("Header", Info.Header); + io.mapRequired("QualifiedName", Info.QualifiedName); + } +}; + +template <> struct MappingTraits { + static void mapping(IO &io, IncludeFixerContext::QuerySymbolInfo &Info) { + io.mapRequired("RawIdentifier", Info.RawIdentifier); + io.mapRequired("Range", Info.Range); + } +}; + +template <> struct MappingTraits { + static void mapping(IO &IO, IncludeFixerContext &Context) { + IO.mapRequired("QuerySymbolInfos", Context.QuerySymbolInfos); + IO.mapRequired("HeaderInfos", Context.HeaderInfos); + IO.mapRequired("FilePath", Context.FilePath); + } +}; +} // namespace yaml +} // namespace llvm + +namespace { +cl::OptionCategory IncludeFixerCategory("Tool options"); + +enum DatabaseFormatTy { + fixed, ///< Hard-coded mapping. + yaml, ///< Yaml database created by find-all-symbols. + fuzzyYaml, ///< Yaml database with fuzzy-matched identifiers. +}; + +cl::opt DatabaseFormat( + "db", cl::desc("Specify input format"), + cl::values(clEnumVal(fixed, "Hard-coded mapping"), + clEnumVal(yaml, "Yaml database created by find-all-symbols"), + clEnumVal(fuzzyYaml, "Yaml database, with fuzzy-matched names")), + cl::init(yaml), cl::cat(IncludeFixerCategory)); + +cl::opt Input("input", + cl::desc("String to initialize the database"), + cl::cat(IncludeFixerCategory)); + +cl::opt + QuerySymbol("query-symbol", + cl::desc("Query a given symbol (e.g. \"a::b::foo\") in\n" + "database directly without parsing the file."), + cl::cat(IncludeFixerCategory)); + +cl::opt + MinimizeIncludePaths("minimize-paths", + cl::desc("Whether to minimize added include paths"), + cl::init(true), cl::cat(IncludeFixerCategory)); + +cl::opt Quiet("q", cl::desc("Reduce terminal output"), cl::init(false), + cl::cat(IncludeFixerCategory)); + +cl::opt + STDINMode("stdin", + cl::desc("Override source file's content (in the overlaying\n" + "virtual file system) with input from and run\n" + "the tool on the new content with the compilation\n" + "options of the source file. This mode is currently\n" + "used for editor integration."), + cl::init(false), cl::cat(IncludeFixerCategory)); + +cl::opt OutputHeaders( + "output-headers", + cl::desc("Print the symbol being queried and all its relevant headers in\n" + "JSON format to stdout:\n" + " {\n" + " \"FilePath\": \"/path/to/foo.cc\",\n" + " \"QuerySymbolInfos\": [\n" + " {\"RawIdentifier\": \"foo\",\n" + " \"Range\": {\"Offset\": 0, \"Length\": 3}}\n" + " ],\n" + " \"HeaderInfos\": [ {\"Header\": \"\\\"foo_a.h\\\"\",\n" + " \"QualifiedName\": \"a::foo\"} ]\n" + " }"), + cl::init(false), cl::cat(IncludeFixerCategory)); + +cl::opt InsertHeader( + "insert-header", + cl::desc("Insert a specific header. This should run with STDIN mode.\n" + "The result is written to stdout. It is currently used for\n" + "editor integration. Support YAML/JSON format:\n" + " -insert-header=\"{\n" + " FilePath: \"/path/to/foo.cc\",\n" + " QuerySymbolInfos: [\n" + " {RawIdentifier: foo,\n" + " Range: {Offset: 0, Length: 3}}\n" + " ],\n" + " HeaderInfos: [ {Headers: \"\\\"foo_a.h\\\"\",\n" + " QualifiedName: \"a::foo\"} ]}\""), + cl::init(""), cl::cat(IncludeFixerCategory)); + +cl::opt + Style("style", + cl::desc("Fallback style for reformatting after inserting new\n" + "headers if there is no clang-format config file found."), + cl::init("llvm"), cl::cat(IncludeFixerCategory)); + +std::unique_ptr +createSymbolIndexManager(StringRef FilePath) { + using find_all_symbols::SymbolInfo; + + auto SymbolIndexMgr = llvm::make_unique(); + switch (DatabaseFormat) { + case fixed: { + // Parse input and fill the database with it. + // =
<, header...> + // Multiple symbols can be given, separated by semicolons. + std::map> SymbolsMap; + SmallVector SemicolonSplits; + StringRef(Input).split(SemicolonSplits, ";"); + std::vector Symbols; + for (StringRef Pair : SemicolonSplits) { + auto Split = Pair.split('='); + std::vector Headers; + SmallVector CommaSplits; + Split.second.split(CommaSplits, ","); + for (size_t I = 0, E = CommaSplits.size(); I != E; ++I) + Symbols.push_back( + {SymbolInfo(Split.first.trim(), SymbolInfo::SymbolKind::Unknown, + CommaSplits[I].trim(), {}), + // Use fake "seen" signal for tests, so first header wins. + SymbolInfo::Signals(/*Seen=*/static_cast(E - I), + /*Used=*/0)}); + } + SymbolIndexMgr->addSymbolIndex([=]() { + return llvm::make_unique(Symbols); + }); + break; + } + case yaml: { + auto CreateYamlIdx = [=]() -> std::unique_ptr { + llvm::ErrorOr> DB( + nullptr); + if (!Input.empty()) { + DB = 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. + SmallString<128> AbsolutePath(tooling::getAbsolutePath(FilePath)); + StringRef Directory = llvm::sys::path::parent_path(AbsolutePath); + DB = include_fixer::YamlSymbolIndex::createFromDirectory( + Directory, "find_all_symbols_db.yaml"); + } + + if (!DB) { + llvm::errs() << "Couldn't find YAML db: " << DB.getError().message() + << '\n'; + return nullptr; + } + return std::move(*DB); + }; + + SymbolIndexMgr->addSymbolIndex(std::move(CreateYamlIdx)); + break; + } + case fuzzyYaml: { + // This mode is not very useful, because we don't correct the identifier. + // It's main purpose is to expose FuzzySymbolIndex to tests. + SymbolIndexMgr->addSymbolIndex( + []() -> std::unique_ptr { + auto DB = include_fixer::FuzzySymbolIndex::createFromYAML(Input); + if (!DB) { + llvm::errs() << "Couldn't load fuzzy YAML db: " + << llvm::toString(DB.takeError()) << '\n'; + return nullptr; + } + return std::move(*DB); + }); + break; + } + } + return SymbolIndexMgr; +} + +void writeToJson(llvm::raw_ostream &OS, const IncludeFixerContext& Context) { + OS << "{\n" + << " \"FilePath\": \"" + << llvm::yaml::escape(Context.getFilePath()) << "\",\n" + << " \"QuerySymbolInfos\": [\n"; + for (const auto &Info : Context.getQuerySymbolInfos()) { + OS << " {\"RawIdentifier\": \"" << Info.RawIdentifier << "\",\n"; + OS << " \"Range\":{"; + OS << "\"Offset\":" << Info.Range.getOffset() << ","; + OS << "\"Length\":" << Info.Range.getLength() << "}}"; + if (&Info != &Context.getQuerySymbolInfos().back()) + OS << ",\n"; + } + OS << "\n ],\n"; + OS << " \"HeaderInfos\": [\n"; + const auto &HeaderInfos = Context.getHeaderInfos(); + for (const auto &Info : HeaderInfos) { + OS << " {\"Header\": \"" << llvm::yaml::escape(Info.Header) << "\",\n" + << " \"QualifiedName\": \"" << Info.QualifiedName << "\"}"; + if (&Info != &HeaderInfos.back()) + OS << ",\n"; + } + OS << "\n"; + OS << " ]\n"; + OS << "}\n"; +} + +int includeFixerMain(int argc, const char **argv) { + tooling::CommonOptionsParser options(argc, argv, IncludeFixerCategory); + tooling::ClangTool tool(options.getCompilations(), + options.getSourcePathList()); + + llvm::StringRef SourceFilePath = options.getSourcePathList().front(); + // In STDINMode, we override the file content with the input. + // Since `tool.mapVirtualFile` takes `StringRef`, we define `Code` outside of + // the if-block so that `Code` is not released after the if-block. + std::unique_ptr Code; + if (STDINMode) { + assert(options.getSourcePathList().size() == 1 && + "Expect exactly one file path in STDINMode."); + llvm::ErrorOr> CodeOrErr = + MemoryBuffer::getSTDIN(); + if (std::error_code EC = CodeOrErr.getError()) { + errs() << EC.message() << "\n"; + return 1; + } + Code = std::move(CodeOrErr.get()); + if (Code->getBufferSize() == 0) + return 0; // Skip empty files. + + tool.mapVirtualFile(SourceFilePath, Code->getBuffer()); + } + + if (!InsertHeader.empty()) { + if (!STDINMode) { + errs() << "Should be running in STDIN mode\n"; + return 1; + } + + llvm::yaml::Input yin(InsertHeader); + IncludeFixerContext Context; + yin >> Context; + + const auto &HeaderInfos = Context.getHeaderInfos(); + assert(!HeaderInfos.empty()); + // We only accept one unique header. + // Check all elements in HeaderInfos have the same header. + bool IsUniqueHeader = std::equal( + HeaderInfos.begin()+1, HeaderInfos.end(), HeaderInfos.begin(), + [](const IncludeFixerContext::HeaderInfo &LHS, + const IncludeFixerContext::HeaderInfo &RHS) { + return LHS.Header == RHS.Header; + }); + if (!IsUniqueHeader) { + errs() << "Expect exactly one unique header.\n"; + return 1; + } + + // If a header has multiple symbols, we won't add the missing namespace + // qualifiers because we don't know which one is exactly used. + // + // Check whether all elements in HeaderInfos have the same qualified name. + bool IsUniqueQualifiedName = std::equal( + HeaderInfos.begin() + 1, HeaderInfos.end(), HeaderInfos.begin(), + [](const IncludeFixerContext::HeaderInfo &LHS, + const IncludeFixerContext::HeaderInfo &RHS) { + return LHS.QualifiedName == RHS.QualifiedName; + }); + auto InsertStyle = format::getStyle(format::DefaultFormatStyle, + Context.getFilePath(), Style); + if (!InsertStyle) { + llvm::errs() << llvm::toString(InsertStyle.takeError()) << "\n"; + return 1; + } + auto Replacements = clang::include_fixer::createIncludeFixerReplacements( + Code->getBuffer(), Context, *InsertStyle, + /*AddQualifiers=*/IsUniqueQualifiedName); + if (!Replacements) { + errs() << "Failed to create replacements: " + << llvm::toString(Replacements.takeError()) << "\n"; + return 1; + } + + auto ChangedCode = + tooling::applyAllReplacements(Code->getBuffer(), *Replacements); + if (!ChangedCode) { + llvm::errs() << llvm::toString(ChangedCode.takeError()) << "\n"; + return 1; + } + llvm::outs() << *ChangedCode; + return 0; + } + + // Set up data source. + std::unique_ptr SymbolIndexMgr = + createSymbolIndexManager(SourceFilePath); + if (!SymbolIndexMgr) + return 1; + + // Query symbol mode. + if (!QuerySymbol.empty()) { + auto MatchedSymbols = SymbolIndexMgr->search( + QuerySymbol, /*IsNestedSearch=*/true, SourceFilePath); + for (auto &Symbol : MatchedSymbols) { + std::string HeaderPath = Symbol.getFilePath().str(); + Symbol.SetFilePath(((HeaderPath[0] == '"' || HeaderPath[0] == '<') + ? HeaderPath + : "\"" + HeaderPath + "\"")); + } + + // We leave an empty symbol range as we don't know the range of the symbol + // being queried in this mode. clang-include-fixer won't add namespace + // qualifiers if the symbol range is empty, which also fits this case. + IncludeFixerContext::QuerySymbolInfo Symbol; + Symbol.RawIdentifier = QuerySymbol; + auto Context = + IncludeFixerContext(SourceFilePath, {Symbol}, MatchedSymbols); + writeToJson(llvm::outs(), Context); + return 0; + } + + // Now run our tool. + std::vector Contexts; + include_fixer::IncludeFixerActionFactory Factory(*SymbolIndexMgr, Contexts, + Style, MinimizeIncludePaths); + + if (tool.run(&Factory) != 0) { + // We suppress all Clang diagnostics (because they would be wrong, + // clang-include-fixer does custom recovery) but still want to give some + // feedback in case there was a compiler error we couldn't recover from. + // The most common case for this is a #include in the file that couldn't be + // found. + llvm::errs() << "Fatal compiler error occurred while parsing file!" + " (incorrect include paths?)\n"; + return 1; + } + + assert(!Contexts.empty()); + + if (OutputHeaders) { + // FIXME: Print contexts of all processing files instead of the first one. + writeToJson(llvm::outs(), Contexts.front()); + return 0; + } + + std::vector FixerReplacements; + for (const auto &Context : Contexts) { + StringRef FilePath = Context.getFilePath(); + auto InsertStyle = + format::getStyle(format::DefaultFormatStyle, FilePath, Style); + if (!InsertStyle) { + llvm::errs() << llvm::toString(InsertStyle.takeError()) << "\n"; + return 1; + } + auto Buffer = llvm::MemoryBuffer::getFile(FilePath); + if (!Buffer) { + errs() << "Couldn't open file: " + FilePath.str() + ": " + << Buffer.getError().message() + "\n"; + return 1; + } + + auto Replacements = clang::include_fixer::createIncludeFixerReplacements( + Buffer.get()->getBuffer(), Context, *InsertStyle); + if (!Replacements) { + errs() << "Failed to create replacement: " + << llvm::toString(Replacements.takeError()) << "\n"; + return 1; + } + FixerReplacements.push_back(*Replacements); + } + + if (!Quiet) { + for (const auto &Context : Contexts) { + if (!Context.getHeaderInfos().empty()) { + llvm::errs() << "Added #include " + << Context.getHeaderInfos().front().Header << " for " + << Context.getFilePath() << "\n"; + } + } + } + + if (STDINMode) { + assert(FixerReplacements.size() == 1); + auto ChangedCode = tooling::applyAllReplacements(Code->getBuffer(), + FixerReplacements.front()); + if (!ChangedCode) { + llvm::errs() << llvm::toString(ChangedCode.takeError()) << "\n"; + return 1; + } + llvm::outs() << *ChangedCode; + return 0; + } + + // Set up a new source manager for applying the resulting replacements. + IntrusiveRefCntPtr DiagOpts(new DiagnosticOptions); + DiagnosticsEngine Diagnostics(new DiagnosticIDs, &*DiagOpts); + TextDiagnosticPrinter DiagnosticPrinter(outs(), &*DiagOpts); + SourceManager SM(Diagnostics, tool.getFiles()); + Diagnostics.setClient(&DiagnosticPrinter, false); + + // Write replacements to disk. + Rewriter Rewrites(SM, LangOptions()); + for (const auto &Replacement : FixerReplacements) { + if (!tooling::applyAllReplacements(Replacement, Rewrites)) { + llvm::errs() << "Failed to apply replacements.\n"; + return 1; + } + } + return Rewrites.overwriteChangedFiles(); +} + +} // namespace + +int main(int argc, const char **argv) { + return includeFixerMain(argc, argv); +} Index: clang-include-fixer/tool/clang-include-fixer-test.el =================================================================== --- clang-include-fixer/tool/clang-include-fixer-test.el +++ clang-include-fixer/tool/clang-include-fixer-test.el @@ -0,0 +1,65 @@ +;;; clang-include-fixer-test.el --- unit tests for clang-include-fixer.el -*- lexical-binding: t; -*- + +;;; Commentary: + +;; Unit tests for clang-include-fixer.el. + +;;; Code: + +(require 'clang-include-fixer) + +(require 'cc-mode) +(require 'ert) + +(ert-deftest clang-include-fixer--insert-line () + "Unit test for `clang-include-fixer--insert-line'." + (with-temp-buffer + (insert "aa\nab\nac\nad\n") + (let ((from (current-buffer))) + (with-temp-buffer + (insert "aa\nac\nad\n") + (let ((to (current-buffer))) + (should (clang-include-fixer--insert-line from to)) + (should (equal (buffer-string) "aa\nab\nac\nad\n"))))) + (should (equal (buffer-string) "aa\nab\nac\nad\n")))) + +(ert-deftest clang-include-fixer--insert-line-diff-on-empty-line () + "Unit test for `clang-include-fixer--insert-line'." + (with-temp-buffer + (insert "aa\nab\n\nac\nad\n") + (let ((from (current-buffer))) + (with-temp-buffer + (insert "aa\n\nac\nad\n") + (let ((to (current-buffer))) + (should (clang-include-fixer--insert-line from to)) + (should (equal (buffer-string) "aa\nab\n\nac\nad\n"))))) + (should (equal (buffer-string) "aa\nab\n\nac\nad\n")))) + +(ert-deftest clang-include-fixer--symbol-at-point () + "Unit test for `clang-include-fixer--symbol-at-point'." + (with-temp-buffer + (insert "a+bbb::cc") + (c++-mode) + (goto-char (point-min)) + (should (equal (clang-include-fixer--symbol-at-point) "a")) + (forward-char) + ;; Emacs treats the character immediately following a symbol as part of the + ;; symbol. + (should (equal (clang-include-fixer--symbol-at-point) "a")) + (forward-char) + (should (equal (clang-include-fixer--symbol-at-point) "bbb::cc")) + (goto-char (point-max)) + (should (equal (clang-include-fixer--symbol-at-point) "bbb::cc")))) + +(ert-deftest clang-include-fixer--highlight () + (with-temp-buffer + (insert "util::Status foo;\n") + (setq buffer-file-coding-system 'utf-8-unix) + (should (equal nil (clang-include-fixer--highlight + '((Range . ((Offset . 0) (Length . 0))))))) + (let ((overlay (clang-include-fixer--highlight + '((Range . ((Offset . 1) (Length . 12))))))) + (should (equal 2 (overlay-start overlay))) + (should (equal 14 (overlay-end overlay)))))) + +;;; clang-include-fixer-test.el ends here Index: clang-include-fixer/tool/clang-include-fixer.el =================================================================== --- clang-include-fixer/tool/clang-include-fixer.el +++ clang-include-fixer/tool/clang-include-fixer.el @@ -0,0 +1,460 @@ +;;; clang-include-fixer.el --- Emacs integration of the clang include fixer -*- lexical-binding: t; -*- + +;; Keywords: tools, c +;; Package-Requires: ((cl-lib "0.5") (json "1.2") (let-alist "1.0.4")) + +;;; Commentary: + +;; This package allows Emacs users to invoke the 'clang-include-fixer' within +;; Emacs. 'clang-include-fixer' provides an automated way of adding #include +;; directives for missing symbols in one translation unit, see +;; . + +;;; Code: + +(require 'cl-lib) +(require 'json) +(require 'let-alist) + +(defgroup clang-include-fixer nil + "Clang-based include fixer." + :group 'tools) + +(defvar clang-include-fixer-add-include-hook nil + "A hook that will be called for every added include. +The first argument is the filename of the include, the second argument is +non-nil if the include is a system-header.") + +(defcustom clang-include-fixer-executable + "clang-include-fixer" + "Location of the clang-include-fixer executable. + +A string containing the name or the full path of the executable." + :group 'clang-include-fixer + :type '(file :must-match t) + :risky t) + +(defcustom clang-include-fixer-input-format + 'yaml + "Input format for clang-include-fixer. +This string is passed as -db argument to +`clang-include-fixer-executable'." + :group 'clang-include-fixer + :type '(radio + (const :tag "Hard-coded mapping" :fixed) + (const :tag "YAML" yaml) + (symbol :tag "Other")) + :risky t) + +(defcustom clang-include-fixer-init-string + "" + "Database initialization string for clang-include-fixer. +This string is passed as -input argument to +`clang-include-fixer-executable'." + :group 'clang-include-fixer + :type 'string + :risky t) + +(defface clang-include-fixer-highlight '((t :background "green")) + "Used for highlighting the symbol for which a header file is being added.") + +;;;###autoload +(defun clang-include-fixer () + "Invoke the Include Fixer to insert missing C++ headers." + (interactive) + (message (concat "Calling the include fixer. " + "This might take some seconds. Please wait.")) + (clang-include-fixer--start #'clang-include-fixer--add-header + "-output-headers")) + +;;;###autoload +(defun clang-include-fixer-at-point () + "Invoke the Clang include fixer for the symbol at point." + (interactive) + (let ((symbol (clang-include-fixer--symbol-at-point))) + (unless symbol + (user-error "No symbol at current location")) + (clang-include-fixer-from-symbol symbol))) + +;;;###autoload +(defun clang-include-fixer-from-symbol (symbol) + "Invoke the Clang include fixer for the SYMBOL. +When called interactively, prompts the user for a symbol." + (interactive + (list (read-string "Symbol: " (clang-include-fixer--symbol-at-point)))) + (clang-include-fixer--start #'clang-include-fixer--add-header + (format "-query-symbol=%s" symbol))) + +(defun clang-include-fixer--start (callback &rest args) + "Asynchronously start clang-include-fixer with parameters ARGS. +The current file name is passed after ARGS as last argument. If +the call was successful the returned result is stored in a +temporary buffer, and CALLBACK is called with the temporary +buffer as only argument." + (unless buffer-file-name + (user-error "clang-include-fixer works only in buffers that visit a file")) + (let ((process (if (and (fboundp 'make-process) + ;; ‘make-process’ doesn’t support remote files + ;; (https://debbugs.gnu.org/cgi/bugreport.cgi?bug=28691). + (not (find-file-name-handler default-directory + 'start-file-process))) + ;; Prefer using ‘make-process’ if possible, because + ;; ‘start-process’ doesn’t allow us to separate the + ;; standard error from the output. + (clang-include-fixer--make-process callback args) + (clang-include-fixer--start-process callback args)))) + (save-restriction + (widen) + (process-send-region process (point-min) (point-max))) + (process-send-eof process)) + nil) + +(defun clang-include-fixer--make-process (callback args) + "Start a new clang-incude-fixer process using `make-process'. +CALLBACK is called after the process finishes successfully; it is +called with a single argument, the buffer where standard output +has been inserted. ARGS is a list of additional command line +arguments. Return the new process object." + (let ((stdin (current-buffer)) + (stdout (generate-new-buffer "*clang-include-fixer output*")) + (stderr (generate-new-buffer "*clang-include-fixer errors*"))) + (make-process :name "clang-include-fixer" + :buffer stdout + :command (clang-include-fixer--command args) + :coding 'utf-8-unix + :noquery t + :connection-type 'pipe + :sentinel (clang-include-fixer--sentinel stdin stdout stderr + callback) + :stderr stderr))) + +(defun clang-include-fixer--start-process (callback args) + "Start a new clang-incude-fixer process using `start-file-process'. +CALLBACK is called after the process finishes successfully; it is +called with a single argument, the buffer where standard output +has been inserted. ARGS is a list of additional command line +arguments. Return the new process object." + (let* ((stdin (current-buffer)) + (stdout (generate-new-buffer "*clang-include-fixer output*")) + (process-connection-type nil) + (process (apply #'start-file-process "clang-include-fixer" stdout + (clang-include-fixer--command args)))) + (set-process-coding-system process 'utf-8-unix 'utf-8-unix) + (set-process-query-on-exit-flag process nil) + (set-process-sentinel process + (clang-include-fixer--sentinel stdin stdout nil + callback)) + process)) + +(defun clang-include-fixer--command (args) + "Return the clang-include-fixer command line. +Returns a list; the first element is the binary to +execute (`clang-include-fixer-executable'), and the remaining +elements are the command line arguments. Adds proper arguments +for `clang-include-fixer-input-format' and +`clang-include-fixer-init-string'. Appends the current buffer's +file name; prepends ARGS directly in front of it." + (cl-check-type args list) + `(,clang-include-fixer-executable + ,(format "-db=%s" clang-include-fixer-input-format) + ,(format "-input=%s" clang-include-fixer-init-string) + "-stdin" + ,@args + ,(clang-include-fixer--file-local-name buffer-file-name))) + +(defun clang-include-fixer--sentinel (stdin stdout stderr callback) + "Return a process sentinel for clang-include-fixer processes. +STDIN, STDOUT, and STDERR are buffers for the standard streams; +only STDERR may be nil. CALLBACK is called in the case of +success; it is called with a single argument, STDOUT. On +failure, a buffer containing the error output is displayed." + (cl-check-type stdin buffer-live) + (cl-check-type stdout buffer-live) + (cl-check-type stderr (or null buffer-live)) + (cl-check-type callback function) + (lambda (process event) + (cl-check-type process process) + (cl-check-type event string) + (unwind-protect + (if (string-equal event "finished\n") + (progn + (when stderr (kill-buffer stderr)) + (with-current-buffer stdin + (funcall callback stdout)) + (kill-buffer stdout)) + (when stderr (kill-buffer stdout)) + (message "clang-include-fixer failed") + (with-current-buffer (or stderr stdout) + (insert "\nProcess " (process-name process) + ?\s event)) + (display-buffer (or stderr stdout)))) + nil)) + +(defun clang-include-fixer--replace-buffer (stdout) + "Replace current buffer by content of STDOUT." + (cl-check-type stdout buffer-live) + (barf-if-buffer-read-only) + (cond ((fboundp 'replace-buffer-contents) (replace-buffer-contents stdout)) + ((clang-include-fixer--insert-line stdout (current-buffer))) + (t (erase-buffer) (insert-buffer-substring stdout))) + (message "Fix applied") + nil) + +(defun clang-include-fixer--insert-line (from to) + "Insert a single missing line from the buffer FROM into TO. +FROM and TO must be buffers. If the contents of FROM and TO are +equal, do nothing and return non-nil. If FROM contains a single +line missing from TO, insert that line into TO so that the buffer +contents are equal and return non-nil. Otherwise, do nothing and +return nil. Buffer restrictions are ignored." + (cl-check-type from buffer-live) + (cl-check-type to buffer-live) + (with-current-buffer from + (save-excursion + (save-restriction + (widen) + (with-current-buffer to + (save-excursion + (save-restriction + (widen) + ;; Search for the first buffer difference. + (let ((chars (abs (compare-buffer-substrings to nil nil from nil nil)))) + (if (zerop chars) + ;; Buffer contents are equal, nothing to do. + t + (goto-char chars) + ;; We might have ended up in the middle of a line if the + ;; current line partially matches. In this case we would + ;; have to insert more than a line. Move to the beginning of + ;; the line to avoid this situation. + (beginning-of-line) + (with-current-buffer from + (goto-char chars) + (beginning-of-line) + (let ((from-begin (point)) + (from-end (progn (forward-line) (point))) + (to-point (with-current-buffer to (point)))) + ;; Search for another buffer difference after the line in + ;; question. If there is none, we can proceed. + (when (zerop (compare-buffer-substrings from from-end nil + to to-point nil)) + (with-current-buffer to + (insert-buffer-substring from from-begin from-end)) + t)))))))))))) + +(defun clang-include-fixer--add-header (stdout) + "Analyse the result of clang-include-fixer stored in STDOUT. +Add a missing header if there is any. If there are multiple +possible headers the user can select one of them to be included. +Temporarily highlight the affected symbols. Asynchronously call +clang-include-fixer to insert the selected header." + (cl-check-type stdout buffer-live) + (let ((context (clang-include-fixer--parse-json stdout))) + (let-alist context + (cond + ((null .QuerySymbolInfos) + (message "The file is fine, no need to add a header.")) + ((null .HeaderInfos) + (message "Couldn't find header for '%s'" + (let-alist (car .QuerySymbolInfos) .RawIdentifier))) + (t + ;; Users may C-g in prompts, make sure the process sentinel + ;; behaves correctly. + (with-local-quit + ;; Replace the HeaderInfos list by a single header selected by + ;; the user. + (clang-include-fixer--select-header context) + ;; Call clang-include-fixer again to insert the selected header. + (clang-include-fixer--start + (let ((old-tick (buffer-chars-modified-tick))) + (lambda (stdout) + (when (/= old-tick (buffer-chars-modified-tick)) + ;; Replacing the buffer now would undo the user’s changes. + (user-error (concat "The buffer has been changed " + "before the header could be inserted"))) + (clang-include-fixer--replace-buffer stdout) + (let-alist context + (let-alist (car .HeaderInfos) + (with-local-quit + (run-hook-with-args 'clang-include-fixer-add-include-hook + (substring .Header 1 -1) + (string= (substring .Header 0 1) "<"))))))) + (format "-insert-header=%s" + (clang-include-fixer--encode-json context)))))))) + nil) + +(defun clang-include-fixer--select-header (context) + "Prompt the user for a header if necessary. +CONTEXT must be a clang-include-fixer context object in +association list format. If it contains more than one HeaderInfo +element, prompt the user to select one of the headers. CONTEXT +is modified to include only the selected element." + (cl-check-type context cons) + (let-alist context + (if (cdr .HeaderInfos) + (clang-include-fixer--prompt-for-header context) + (message "Only one include is missing: %s" + (let-alist (car .HeaderInfos) .Header)))) + nil) + +(defvar clang-include-fixer--history nil + "History for `clang-include-fixer--prompt-for-header'.") + +(defun clang-include-fixer--prompt-for-header (context) + "Prompt the user for a single header. +The choices are taken from the HeaderInfo elements in CONTEXT. +They are replaced by the single element selected by the user." + (let-alist context + (let ((symbol (clang-include-fixer--symbol-name .QuerySymbolInfos)) + ;; Add temporary highlighting so that the user knows which + ;; symbols the current session is about. + (overlays (remove nil + (mapcar #'clang-include-fixer--highlight .QuerySymbolInfos)))) + (unwind-protect + (save-excursion + ;; While prompting, go to the closest overlay so that the user sees + ;; some context. + (when overlays + (goto-char (clang-include-fixer--closest-overlay overlays))) + (cl-flet ((header (info) (let-alist info .Header))) + ;; The header-infos is already sorted by clang-include-fixer. + (let* ((headers (mapcar #'header .HeaderInfos)) + (header (completing-read + (clang-include-fixer--format-message + "Select include for '%s': " symbol) + headers nil :require-match nil + 'clang-include-fixer--history + ;; Specify a default to prevent the behavior + ;; described in + ;; https://github.com/DarwinAwardWinner/ido-completing-read-plus#why-does-ret-sometimes-not-select-the-first-completion-on-the-list--why-is-there-an-empty-entry-at-the-beginning-of-the-completion-list--what-happened-to-old-style-default-selection. + (car headers))) + (info (cl-find header .HeaderInfos :key #'header :test #'string=))) + (unless info (user-error "No header selected")) + (setcar .HeaderInfos info) + (setcdr .HeaderInfos nil)))) + (mapc #'delete-overlay overlays))))) + +(defun clang-include-fixer--symbol-name (symbol-infos) + "Return the unique symbol name in SYMBOL-INFOS. +Raise a signal if the symbol name is not unique." + (let ((symbols (delete-dups (mapcar (lambda (info) + (let-alist info .RawIdentifier)) + symbol-infos)))) + (when (cdr symbols) + (error "Multiple symbols %s returned" symbols)) + (car symbols))) + +(defun clang-include-fixer--highlight (symbol-info) + "Add an overlay to highlight SYMBOL-INFO, if it points to a non-empty range. +Return the overlay object, or nil." + (let-alist symbol-info + (unless (zerop .Range.Length) + (let ((overlay (make-overlay + (clang-include-fixer--filepos-to-bufferpos + .Range.Offset 'approximate) + (clang-include-fixer--filepos-to-bufferpos + (+ .Range.Offset .Range.Length) 'approximate)))) + (overlay-put overlay 'face 'clang-include-fixer-highlight) + overlay)))) + +(defun clang-include-fixer--closest-overlay (overlays) + "Return the start of the overlay in OVERLAYS that is closest to point." + (cl-check-type overlays cons) + (let ((point (point)) + acc) + (dolist (overlay overlays acc) + (let ((start (overlay-start overlay))) + (when (or (null acc) (< (abs (- point start)) (abs (- point acc)))) + (setq acc start)))))) + +(defun clang-include-fixer--parse-json (buffer) + "Parse a JSON response from clang-include-fixer in BUFFER. +Return the JSON object as an association list." + (with-current-buffer buffer + (save-excursion + (goto-char (point-min)) + (let ((json-object-type 'alist) + (json-array-type 'list) + (json-key-type 'symbol) + (json-false :json-false) + (json-null nil) + (json-pre-element-read-function nil) + (json-post-element-read-function nil)) + (json-read))))) + +(defun clang-include-fixer--encode-json (object) + "Return the JSON representation of OBJECT as a string." + (let ((json-encoding-separator ",") + (json-encoding-default-indentation " ") + (json-encoding-pretty-print nil) + (json-encoding-lisp-style-closings nil) + (json-encoding-object-sort-predicate nil)) + (json-encode object))) + +(defun clang-include-fixer--symbol-at-point () + "Return the qualified symbol at point. +If there is no symbol at point, return nil." + ;; Let ‘bounds-of-thing-at-point’ to do the hard work and deal with edge + ;; cases. + (let ((bounds (bounds-of-thing-at-point 'symbol))) + (when bounds + (let ((beg (car bounds)) + (end (cdr bounds))) + (save-excursion + ;; Extend the symbol range to the left. Skip over namespace + ;; delimiters and parent namespace names. + (goto-char beg) + (while (and (clang-include-fixer--skip-double-colon-backward) + (skip-syntax-backward "w_"))) + ;; Skip over one more namespace delimiter, for absolute names. + (clang-include-fixer--skip-double-colon-backward) + (setq beg (point)) + ;; Extend the symbol range to the right. Skip over namespace + ;; delimiters and child namespace names. + (goto-char end) + (while (and (clang-include-fixer--skip-double-colon-forward) + (skip-syntax-forward "w_"))) + (setq end (point))) + (buffer-substring-no-properties beg end))))) + +(defun clang-include-fixer--skip-double-colon-forward () + "Skip a double colon. +When the next two characters are '::', skip them and return +non-nil. Otherwise return nil." + (let ((end (+ (point) 2))) + (when (and (<= end (point-max)) + (string-equal (buffer-substring-no-properties (point) end) "::")) + (goto-char end) + t))) + +(defun clang-include-fixer--skip-double-colon-backward () + "Skip a double colon. +When the previous two characters are '::', skip them and return +non-nil. Otherwise return nil." + (let ((beg (- (point) 2))) + (when (and (>= beg (point-min)) + (string-equal (buffer-substring-no-properties beg (point)) "::")) + (goto-char beg) + t))) + +;; ‘filepos-to-bufferpos’ is new in Emacs 25.1. Provide a fallback for older +;; versions. +(defalias 'clang-include-fixer--filepos-to-bufferpos + (if (fboundp 'filepos-to-bufferpos) + 'filepos-to-bufferpos + (lambda (byte &optional _quality _coding-system) + (byte-to-position (1+ byte))))) + +;; ‘format-message’ is new in Emacs 25.1. Provide a fallback for older +;; versions. +(defalias 'clang-include-fixer--format-message + (if (fboundp 'format-message) 'format-message 'format)) + +;; ‘file-local-name’ is new in Emacs 26.1. Provide a fallback for older +;; versions. +(defalias 'clang-include-fixer--file-local-name + (if (fboundp 'file-local-name) #'file-local-name + (lambda (file) (or (file-remote-p file 'localname) file)))) + +(provide 'clang-include-fixer) +;;; clang-include-fixer.el ends here Index: clang-include-fixer/tool/clang-include-fixer.py =================================================================== --- clang-include-fixer/tool/clang-include-fixer.py +++ clang-include-fixer/tool/clang-include-fixer.py @@ -0,0 +1,210 @@ +# This file is a minimal clang-include-fixer vim-integration. To install: +# - Change 'binary' if clang-include-fixer is not on the path (see below). +# - Add to your .vimrc: +# +# noremap cf :pyf path/to/llvm/source/tools/clang/tools/extra/clang-include-fixer/tool/clang-include-fixer.py +# +# This enables clang-include-fixer for NORMAL and VISUAL mode. Change +# "cf" to another binding if you need clang-include-fixer on a +# different key. +# +# To set up clang-include-fixer, see +# http://clang.llvm.org/extra/clang-include-fixer.html +# +# With this integration you can press the bound key and clang-include-fixer will +# be run on the current buffer. +# +# It operates on the current, potentially unsaved buffer and does not create +# or save any files. To revert a fix, just undo. + +import argparse +import difflib +import json +import re +import subprocess +import vim + +# set g:clang_include_fixer_path to the path to clang-include-fixer if it is not +# on the path. +# Change this to the full path if clang-include-fixer is not on the path. +binary = 'clang-include-fixer' +if vim.eval('exists("g:clang_include_fixer_path")') == "1": + binary = vim.eval('g:clang_include_fixer_path') + +maximum_suggested_headers = 3 +if vim.eval('exists("g:clang_include_fixer_maximum_suggested_headers")') == "1": + maximum_suggested_headers = max( + 1, + vim.eval('g:clang_include_fixer_maximum_suggested_headers')) + +increment_num = 5 +if vim.eval('exists("g:clang_include_fixer_increment_num")') == "1": + increment_num = max( + 1, + vim.eval('g:clang_include_fixer_increment_num')) + +jump_to_include = False +if vim.eval('exists("g:clang_include_fixer_jump_to_include")') == "1": + jump_to_include = vim.eval('g:clang_include_fixer_jump_to_include') != "0" + +query_mode = False +if vim.eval('exists("g:clang_include_fixer_query_mode")') == "1": + query_mode = vim.eval('g:clang_include_fixer_query_mode') != "0" + + +def GetUserSelection(message, headers, maximum_suggested_headers): + eval_message = message + '\n' + for idx, header in enumerate(headers[0:maximum_suggested_headers]): + eval_message += "({0}). {1}\n".format(idx + 1, header) + eval_message += "Enter (q) to quit;" + if maximum_suggested_headers < len(headers): + eval_message += " (m) to show {0} more candidates.".format( + min(increment_num, len(headers) - maximum_suggested_headers)) + + eval_message += "\nSelect (default 1): " + res = vim.eval("input('{0}')".format(eval_message)) + if res == '': + # choose the top ranked header by default + idx = 1 + elif res == 'q': + raise Exception(' Insertion cancelled...') + elif res == 'm': + return GetUserSelection(message, + headers, maximum_suggested_headers + increment_num) + else: + try: + idx = int(res) + if idx <= 0 or idx > len(headers): + raise Exception() + except Exception: + # Show a new prompt on invalid option instead of aborting so that users + # don't need to wait for another clang-include-fixer run. + print >> sys.stderr, "Invalid option:", res + return GetUserSelection(message, headers, maximum_suggested_headers) + return headers[idx - 1] + + +def execute(command, text): + p = subprocess.Popen(command, + stdout=subprocess.PIPE, stderr=subprocess.PIPE, + stdin=subprocess.PIPE) + return p.communicate(input=text) + + +def InsertHeaderToVimBuffer(header, text): + command = [binary, "-stdin", "-insert-header=" + json.dumps(header), + vim.current.buffer.name] + stdout, stderr = execute(command, text) + if stderr: + raise Exception(stderr) + if stdout: + lines = stdout.splitlines() + sequence = difflib.SequenceMatcher(None, vim.current.buffer, lines) + line_num = None + for op in reversed(sequence.get_opcodes()): + if op[0] != 'equal': + vim.current.buffer[op[1]:op[2]] = lines[op[3]:op[4]] + if op[0] == 'insert': + # line_num in vim is 1-based. + line_num = op[1] + 1 + + if jump_to_include and line_num: + vim.current.window.cursor = (line_num, 0) + + +# The vim internal implementation (expand("cword"/"cWORD")) doesn't support +# our use case very well, we re-implement our own one. +def get_symbol_under_cursor(): + line = vim.eval("line(\".\")") + # column number in vim is 1-based. + col = int(vim.eval("col(\".\")")) - 1 + line_text = vim.eval("getline({0})".format(line)) + if len(line_text) == 0: return "" + symbol_pos_begin = col + p = re.compile('[a-zA-Z0-9:_]') + while symbol_pos_begin >= 0 and p.match(line_text[symbol_pos_begin]): + symbol_pos_begin -= 1 + + symbol_pos_end = col + while symbol_pos_end < len(line_text) and p.match(line_text[symbol_pos_end]): + symbol_pos_end += 1 + return line_text[symbol_pos_begin+1:symbol_pos_end] + + +def main(): + parser = argparse.ArgumentParser( + description='Vim integration for clang-include-fixer') + parser.add_argument('-db', default='yaml', + help='clang-include-fixer input format.') + parser.add_argument('-input', default='', + help='String to initialize the database.') + # Don't throw exception when parsing unknown arguements to make the script + # work in neovim. + # Neovim (at least v0.2.1) somehow mangles the sys.argv in a weird way: it + # will pass additional arguments (e.g. "-c script_host.py") to sys.argv, + # which makes the script fail. + args, _ = parser.parse_known_args() + + # Get the current text. + buf = vim.current.buffer + text = '\n'.join(buf) + + if query_mode: + symbol = get_symbol_under_cursor() + if len(symbol) == 0: + print "Skip querying empty symbol." + return + command = [binary, "-stdin", "-query-symbol="+get_symbol_under_cursor(), + "-db=" + args.db, "-input=" + args.input, + vim.current.buffer.name] + else: + # Run command to get all headers. + command = [binary, "-stdin", "-output-headers", "-db=" + args.db, + "-input=" + args.input, vim.current.buffer.name] + stdout, stderr = execute(command, text) + if stderr: + print >> sys.stderr, "Error while running clang-include-fixer: " + stderr + return + + include_fixer_context = json.loads(stdout) + query_symbol_infos = include_fixer_context["QuerySymbolInfos"] + if not query_symbol_infos: + print "The file is fine, no need to add a header." + return + symbol = query_symbol_infos[0]["RawIdentifier"] + # The header_infos is already sorted by clang-include-fixer. + header_infos = include_fixer_context["HeaderInfos"] + # Deduplicate headers while keeping the order, so that the same header would + # not be suggested twice. + unique_headers = [] + seen = set() + for header_info in header_infos: + header = header_info["Header"] + if header not in seen: + seen.add(header) + unique_headers.append(header) + + if not unique_headers: + print "Couldn't find a header for {0}.".format(symbol) + return + + try: + selected = unique_headers[0] + inserted_header_infos = header_infos + if len(unique_headers) > 1: + selected = GetUserSelection( + "choose a header file for {0}.".format(symbol), + unique_headers, maximum_suggested_headers) + inserted_header_infos = [ + header for header in header_infos if header["Header"] == selected] + include_fixer_context["HeaderInfos"] = inserted_header_infos + + InsertHeaderToVimBuffer(include_fixer_context, text) + print "Added #include {0} for {1}.".format(selected, symbol) + except Exception as error: + print >> sys.stderr, error.message + return + + +if __name__ == '__main__': + main() Index: clang-move/Move.cpp =================================================================== --- clang-move/Move.cpp +++ clang-move/Move.cpp @@ -765,7 +765,7 @@ if (Context->Spec.OldDependOnNew && MakeAbsolutePath(SM, FilePath) == makeAbsolutePath(Context->Spec.OldHeader)) { - // FIXME: Minimize the include path like include-fixer. + // FIXME: Minimize the include path like clang-include-fixer. std::string IncludeNewH = "#include \"" + Context->Spec.NewHeader + "\"\n"; // This replacment for inserting header will be cleaned up at the end. Index: clang-tidy/add_new_check.py =================================================================== --- clang-tidy/add_new_check.py +++ clang-tidy/add_new_check.py @@ -198,7 +198,7 @@ lines = f.readlines() lineMatcher = re.compile('Improvements to clang-tidy') - nextSectionMatcher = re.compile('Improvements to include-fixer') + nextSectionMatcher = re.compile('Improvements to clang-include-fixer') checkerMatcher = re.compile('- New :doc:`(.*)') print('Updating %s...' % filename) Index: docs/ReleaseNotes.rst =================================================================== --- docs/ReleaseNotes.rst +++ docs/ReleaseNotes.rst @@ -143,7 +143,7 @@ but either don't specify it or the clause is specified but with the kind other than ``none``, and suggests to use the ``default(none)`` clause. -Improvements to include-fixer +Improvements to clang-include-fixer ----------------------------- The improvements are... Index: docs/clang-include-fixer.rst =================================================================== --- docs/clang-include-fixer.rst +++ docs/clang-include-fixer.rst @@ -0,0 +1,155 @@ +=================== +Clang-Include-Fixer +=================== + +.. contents:: + +One of the major nuisances of C++ compared to other languages is the manual +management of ``#include`` directives in any file. +:program:`clang-include-fixer` addresses one aspect of this problem by providing +an automated way of adding ``#include`` directives for missing symbols in one +translation unit. + +While inserting missing ``#include``, :program:`clang-include-fixer` adds +missing namespace qualifiers to all instances of an unidentified symbol if +the symbol is missing some prefix namespace qualifiers. + +Setup +===== + +To use :program:`clang-include-fixer` two databases are required. Both can be +generated with existing tools. + +- Compilation database. Contains the compiler commands for any given file in a + project and can be generated by CMake, see `How To Setup Tooling For LLVM`_. +- Symbol index. Contains all symbol information in a project to match a given + identifier to a header file. + +Ideally both databases (``compile_commands.json`` and +``find_all_symbols_db.yaml``) are linked into the root of the source tree they +correspond to. Then the :program:`clang-include-fixer` can automatically pick +them up if called with a source file from that tree. Note that by default +``compile_commands.json`` as generated by CMake does not include header files, +so only implementation files can be handled by tools. + +.. _How To Setup Tooling For LLVM: https://clang.llvm.org/docs/HowToSetupToolingForLLVM.html + +Creating a Symbol Index From a Compilation Database +--------------------------------------------------- + +The include fixer contains :program:`find-all-symbols`, a tool to create a +symbol database in YAML format from a compilation database by parsing all +source files listed in it. The following list of commands shows how to set up a +database for LLVM, any project built by CMake should follow similar steps. + +.. code-block:: console + + $ cd path/to/llvm-build + $ ninja find-all-symbols // build find-all-symbols tool. + $ ninja clang-include-fixer // build clang-include-fixer tool. + $ ls compile_commands.json # Make sure compile_commands.json exists. + compile_commands.json + $ path/to/llvm/source/tools/clang/tools/extra/clang-include-fixer/find-all-symbols/tool/run-find-all-symbols.py + ... wait as clang indexes the code base ... + $ ln -s $PWD/find_all_symbols_db.yaml path/to/llvm/source/ # Link database into the source tree. + $ ln -s $PWD/compile_commands.json path/to/llvm/source/ # Also link compilation database if it's not there already. + $ cd path/to/llvm/source + $ /path/to/clang-include-fixer -db=yaml path/to/file/with/missing/include.cpp + Added #include "foo.h" + +Integrate with Vim +------------------ +To run `clang-include-fixer` on a potentially unsaved buffer in Vim. Add the +following key binding to your ``.vimrc``: + +.. code-block:: console + + noremap cf :pyf path/to/llvm/source/tools/clang/tools/extra/clang-include-fixer/tool/clang-include-fixer.py + +This enables `clang-include-fixer` for NORMAL and VISUAL mode. Change +`cf` to another binding if you need clang-include-fixer on a different +key. The ` key +`_ +is a reference to a specific key defined by the mapleader variable and is bound +to backslash by default. + +Make sure vim can find :program:`clang-include-fixer`: + +- Add the path to :program:`clang-include-fixer` to the PATH environment variable. +- Or set ``g:clang_include_fixer_path`` in vimrc: ``let g:clang_include_fixer_path=path/to/clang-include-fixer`` + +You can customize the number of headers being shown by setting +``let g:clang_include_fixer_maximum_suggested_headers=5`` + +Customized settings in `.vimrc`: + +- ``let g:clang_include_fixer_path = "clang-include-fixer"`` + + Set clang-include-fixer binary file path. + +- ``let g:clang_include_fixer_maximum_suggested_headers = 3`` + + Set the maximum number of ``#includes`` to show. Default is 3. + +- ``let g:clang_include_fixer_increment_num = 5`` + + Set the increment number of #includes to show every time when pressing ``m``. + Default is 5. + +- ``let g:clang_include_fixer_jump_to_include = 0`` + + Set to 1 if you want to jump to the new inserted ``#include`` line. Default is + 0. + +- ``let g:clang_include_fixer_query_mode = 0`` + + Set to 1 if you want to insert ``#include`` for the symbol under the cursor. + Default is 0. Compared to normal mode, this mode won't parse the source file + and only search the sysmbol from database, which is faster than normal mode. + +See ``clang-include-fixer.py`` for more details. + +Integrate with Emacs +-------------------- +To run `clang-include-fixer` on a potentially unsaved buffer in Emacs. +Ensure that Emacs finds ``clang-include-fixer.el`` by adding the directory +containing the file to the ``load-path`` and requiring the `clang-include-fixer` +in your ``.emacs``: + +.. code-block:: console + + (add-to-list 'load-path "path/to/llvm/source/tools/clang/tools/extra/clang-include-fixer/tool/" + (require 'clang-include-fixer) + +Within Emacs the tool can be invoked with the command +``M-x clang-include-fixer``. This will insert the header that defines the +first undefined symbol; if there is more than one header that would define the +symbol, the user is prompted to select one. + +To include the header that defines the symbol at point, run +``M-x clang-include-fixer-at-point``. + +Make sure Emacs can find :program:`clang-include-fixer`: + +- Either add the parent directory of :program:`clang-include-fixer` to the PATH + environment variable, or customize the Emacs user option + ``clang-include-fixer-executable`` to point to the file name of the program. + +How it Works +============ + +To get the most information out of Clang at parse time, +:program:`clang-include-fixer` runs in tandem with the parse and receives +callbacks from Clang's semantic analysis. In particular it reuses the existing +support for typo corrections. Whenever Clang tries to correct a potential typo +it emits a callback to the include fixer which then looks for a corresponding +file. At this point rich lookup information is still available, which is not +available in the AST at a later stage. + +The identifier that should be typo corrected is then sent to the database, if a +header file is returned it is added as an include directive at the top of the +file. + +Currently :program:`clang-include-fixer` only inserts a single include at a +time to avoid getting caught in follow-up errors. If multiple `#include` +additions are desired the program can be rerun until a fix-point is reached. Index: docs/doxygen.cfg.in =================================================================== --- docs/doxygen.cfg.in +++ docs/doxygen.cfg.in @@ -752,7 +752,7 @@ @abs_srcdir@/../clang-reorder-fields \ @abs_srcdir@/../clang-tidy \ @abs_srcdir@/../clangd \ - @abs_srcdir@/../include-fixer \ + @abs_srcdir@/../clang-include-fixer \ @abs_srcdir@/../modularize \ @abs_srcdir@/../pp-trace \ @abs_srcdir@/../tool-template \ Index: docs/include-fixer.rst =================================================================== --- docs/include-fixer.rst +++ docs/include-fixer.rst @@ -1,155 +0,0 @@ -=================== -Clang-Include-Fixer -=================== - -.. contents:: - -One of the major nuisances of C++ compared to other languages is the manual -management of ``#include`` directives in any file. -:program:`clang-include-fixer` addresses one aspect of this problem by providing -an automated way of adding ``#include`` directives for missing symbols in one -translation unit. - -While inserting missing ``#include``, :program:`clang-include-fixer` adds -missing namespace qualifiers to all instances of an unidentified symbol if -the symbol is missing some prefix namespace qualifiers. - -Setup -===== - -To use :program:`clang-include-fixer` two databases are required. Both can be -generated with existing tools. - -- Compilation database. Contains the compiler commands for any given file in a - project and can be generated by CMake, see `How To Setup Tooling For LLVM`_. -- Symbol index. Contains all symbol information in a project to match a given - identifier to a header file. - -Ideally both databases (``compile_commands.json`` and -``find_all_symbols_db.yaml``) are linked into the root of the source tree they -correspond to. Then the :program:`clang-include-fixer` can automatically pick -them up if called with a source file from that tree. Note that by default -``compile_commands.json`` as generated by CMake does not include header files, -so only implementation files can be handled by tools. - -.. _How To Setup Tooling For LLVM: https://clang.llvm.org/docs/HowToSetupToolingForLLVM.html - -Creating a Symbol Index From a Compilation Database ---------------------------------------------------- - -The include fixer contains :program:`find-all-symbols`, a tool to create a -symbol database in YAML format from a compilation database by parsing all -source files listed in it. The following list of commands shows how to set up a -database for LLVM, any project built by CMake should follow similar steps. - -.. code-block:: console - - $ cd path/to/llvm-build - $ ninja find-all-symbols // build find-all-symbols tool. - $ ninja clang-include-fixer // build clang-include-fixer tool. - $ ls compile_commands.json # Make sure compile_commands.json exists. - compile_commands.json - $ path/to/llvm/source/tools/clang/tools/extra/include-fixer/find-all-symbols/tool/run-find-all-symbols.py - ... wait as clang indexes the code base ... - $ ln -s $PWD/find_all_symbols_db.yaml path/to/llvm/source/ # Link database into the source tree. - $ ln -s $PWD/compile_commands.json path/to/llvm/source/ # Also link compilation database if it's not there already. - $ cd path/to/llvm/source - $ /path/to/clang-include-fixer -db=yaml path/to/file/with/missing/include.cpp - Added #include "foo.h" - -Integrate with Vim ------------------- -To run `clang-include-fixer` on a potentially unsaved buffer in Vim. Add the -following key binding to your ``.vimrc``: - -.. code-block:: console - - noremap cf :pyf path/to/llvm/source/tools/clang/tools/extra/include-fixer/tool/clang-include-fixer.py - -This enables `clang-include-fixer` for NORMAL and VISUAL mode. Change -`cf` to another binding if you need clang-include-fixer on a different -key. The ` key -`_ -is a reference to a specific key defined by the mapleader variable and is bound -to backslash by default. - -Make sure vim can find :program:`clang-include-fixer`: - -- Add the path to :program:`clang-include-fixer` to the PATH environment variable. -- Or set ``g:clang_include_fixer_path`` in vimrc: ``let g:clang_include_fixer_path=path/to/clang-include-fixer`` - -You can customize the number of headers being shown by setting -``let g:clang_include_fixer_maximum_suggested_headers=5`` - -Customized settings in `.vimrc`: - -- ``let g:clang_include_fixer_path = "clang-include-fixer"`` - - Set clang-include-fixer binary file path. - -- ``let g:clang_include_fixer_maximum_suggested_headers = 3`` - - Set the maximum number of ``#includes`` to show. Default is 3. - -- ``let g:clang_include_fixer_increment_num = 5`` - - Set the increment number of #includes to show every time when pressing ``m``. - Default is 5. - -- ``let g:clang_include_fixer_jump_to_include = 0`` - - Set to 1 if you want to jump to the new inserted ``#include`` line. Default is - 0. - -- ``let g:clang_include_fixer_query_mode = 0`` - - Set to 1 if you want to insert ``#include`` for the symbol under the cursor. - Default is 0. Compared to normal mode, this mode won't parse the source file - and only search the sysmbol from database, which is faster than normal mode. - -See ``clang-include-fixer.py`` for more details. - -Integrate with Emacs --------------------- -To run `clang-include-fixer` on a potentially unsaved buffer in Emacs. -Ensure that Emacs finds ``clang-include-fixer.el`` by adding the directory -containing the file to the ``load-path`` and requiring the `clang-include-fixer` -in your ``.emacs``: - -.. code-block:: console - - (add-to-list 'load-path "path/to/llvm/source/tools/clang/tools/extra/include-fixer/tool/" - (require 'clang-include-fixer) - -Within Emacs the tool can be invoked with the command -``M-x clang-include-fixer``. This will insert the header that defines the -first undefined symbol; if there is more than one header that would define the -symbol, the user is prompted to select one. - -To include the header that defines the symbol at point, run -``M-x clang-include-fixer-at-point``. - -Make sure Emacs can find :program:`clang-include-fixer`: - -- Either add the parent directory of :program:`clang-include-fixer` to the PATH - environment variable, or customize the Emacs user option - ``clang-include-fixer-executable`` to point to the file name of the program. - -How it Works -============ - -To get the most information out of Clang at parse time, -:program:`clang-include-fixer` runs in tandem with the parse and receives -callbacks from Clang's semantic analysis. In particular it reuses the existing -support for typo corrections. Whenever Clang tries to correct a potential typo -it emits a callback to the include fixer which then looks for a corresponding -file. At this point rich lookup information is still available, which is not -available in the AST at a later stage. - -The identifier that should be typo corrected is then sent to the database, if a -header file is returned it is added as an include directive at the top of the -file. - -Currently :program:`clang-include-fixer` only inserts a single include at a -time to avoid getting caught in follow-up errors. If multiple `#include` -additions are desired the program can be rerun until a fix-point is reached. Index: docs/index.rst =================================================================== --- docs/index.rst +++ docs/index.rst @@ -16,7 +16,7 @@ :maxdepth: 2 clang-tidy/index - include-fixer + clang-include-fixer modularize pp-trace clang-rename Index: include-fixer/CMakeLists.txt =================================================================== --- include-fixer/CMakeLists.txt +++ include-fixer/CMakeLists.txt @@ -1,29 +0,0 @@ -set(LLVM_LINK_COMPONENTS - support - ) - -add_clang_library(clangIncludeFixer - IncludeFixer.cpp - IncludeFixerContext.cpp - InMemorySymbolIndex.cpp - FuzzySymbolIndex.cpp - SymbolIndexManager.cpp - YamlSymbolIndex.cpp - - LINK_LIBS - clangAST - clangBasic - clangFormat - clangFrontend - clangLex - clangParse - clangSema - clangSerialization - clangTooling - clangToolingCore - findAllSymbols - ) - -add_subdirectory(plugin) -add_subdirectory(tool) -add_subdirectory(find-all-symbols) Index: include-fixer/FuzzySymbolIndex.h =================================================================== --- include-fixer/FuzzySymbolIndex.h +++ include-fixer/FuzzySymbolIndex.h @@ -1,54 +0,0 @@ -//===--- FuzzySymbolIndex.h - Lookup symbols for autocomplete ---*- C++ -*-===// -// -// 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 -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_FUZZY_SYMBOL_INDEX_H -#define LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_FUZZY_SYMBOL_INDEX_H - -#include "SymbolIndex.h" -#include "find-all-symbols/SymbolInfo.h" -#include "llvm/ADT/SmallString.h" -#include "llvm/ADT/StringRef.h" -#include "llvm/Support/Error.h" -#include -#include - -namespace clang { -namespace include_fixer { - -// A FuzzySymbolIndex retrieves top-level symbols matching a query string. -// -// It refines the contract of SymbolIndex::search to do fuzzy matching: -// - symbol names are tokenized: "unique ptr", "string ref". -// - query must match prefixes of symbol tokens: [upt] -// - if the query has multiple tokens, splits must match: [StR], not [STr]. -// Helpers for tokenization and regex matching are provided. -// -// Implementations may choose to truncate results, refuse short queries, etc. -class FuzzySymbolIndex : public SymbolIndex { -public: - // Loads the specified include-fixer database and returns an index serving it. - static llvm::Expected> - createFromYAML(llvm::StringRef File); - - // Helpers for implementing indexes: - - // Transforms a symbol name or query into a sequence of tokens. - // - URLHandlerCallback --> [url, handler, callback] - // - snake_case11 --> [snake, case, 11] - // - _WTF$ --> [wtf] - static std::vector tokenize(llvm::StringRef Text); - - // Transforms query tokens into an unanchored regexp to match symbol tokens. - // - [fe f] --> /f(\w* )?e\w* f/, matches [fee fie foe]. - static std::string queryRegexp(const std::vector &Tokens); -}; - -} // namespace include_fixer -} // namespace clang - -#endif // LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_FUZZY_SYMBOL_INDEX_H Index: include-fixer/FuzzySymbolIndex.cpp =================================================================== --- include-fixer/FuzzySymbolIndex.cpp +++ include-fixer/FuzzySymbolIndex.cpp @@ -1,142 +0,0 @@ -//===--- FuzzySymbolIndex.cpp - Lookup symbols for autocomplete -*- C++ -*-===// -// -// 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 "FuzzySymbolIndex.h" -#include "llvm/Support/Regex.h" - -using clang::find_all_symbols::SymbolAndSignals; -using llvm::StringRef; - -namespace clang { -namespace include_fixer { -namespace { - -class MemSymbolIndex : public FuzzySymbolIndex { -public: - MemSymbolIndex(std::vector Symbols) { - for (auto &Symbol : Symbols) { - auto Tokens = tokenize(Symbol.Symbol.getName()); - this->Symbols.emplace_back( - StringRef(llvm::join(Tokens.begin(), Tokens.end(), " ")), - std::move(Symbol)); - } - } - - std::vector search(StringRef Query) override { - auto Tokens = tokenize(Query); - llvm::Regex Pattern("^" + queryRegexp(Tokens)); - std::vector Results; - for (const Entry &E : Symbols) - if (Pattern.match(E.first)) - Results.push_back(E.second); - return Results; - } - -private: - using Entry = std::pair, SymbolAndSignals>; - std::vector Symbols; -}; - -// Helpers for tokenize state machine. -enum TokenizeState { - EMPTY, // No pending characters. - ONE_BIG, // Read one uppercase letter, could be WORD or Word. - BIG_WORD, // Reading an uppercase WORD. - SMALL_WORD, // Reading a lowercase word. - NUMBER // Reading a number. -}; - -enum CharType { UPPER, LOWER, DIGIT, MISC }; -CharType classify(char c) { - if (isupper(c)) - return UPPER; - if (islower(c)) - return LOWER; - if (isdigit(c)) - return DIGIT; - return MISC; -} - -} // namespace - -std::vector FuzzySymbolIndex::tokenize(StringRef Text) { - std::vector Result; - // State describes the treatment of text from Start to I. - // Once text is Flush()ed into Result, we're done with it and advance Start. - TokenizeState State = EMPTY; - size_t Start = 0; - auto Flush = [&](size_t End) { - if (State != EMPTY) { - Result.push_back(Text.substr(Start, End - Start).lower()); - State = EMPTY; - } - Start = End; - }; - for (size_t I = 0; I < Text.size(); ++I) { - CharType Type = classify(Text[I]); - if (Type == MISC) - Flush(I); - else if (Type == LOWER) - switch (State) { - case BIG_WORD: - Flush(I - 1); // FOOBar: first token is FOO, not FOOB. - LLVM_FALLTHROUGH; - case ONE_BIG: - State = SMALL_WORD; - LLVM_FALLTHROUGH; - case SMALL_WORD: - break; - default: - Flush(I); - State = SMALL_WORD; - } - else if (Type == UPPER) - switch (State) { - case ONE_BIG: - State = BIG_WORD; - LLVM_FALLTHROUGH; - case BIG_WORD: - break; - default: - Flush(I); - State = ONE_BIG; - } - else if (Type == DIGIT && State != NUMBER) { - Flush(I); - State = NUMBER; - } - } - Flush(Text.size()); - return Result; -} - -std::string -FuzzySymbolIndex::queryRegexp(const std::vector &Tokens) { - std::string Result; - for (size_t I = 0; I < Tokens.size(); ++I) { - if (I) - Result.append("[[:alnum:]]* "); - for (size_t J = 0; J < Tokens[I].size(); ++J) { - if (J) - Result.append("([[:alnum:]]* )?"); - Result.push_back(Tokens[I][J]); - } - } - return Result; -} - -llvm::Expected> -FuzzySymbolIndex::createFromYAML(StringRef FilePath) { - auto Buffer = llvm::MemoryBuffer::getFile(FilePath); - if (!Buffer) - return llvm::errorCodeToError(Buffer.getError()); - return llvm::make_unique( - find_all_symbols::ReadSymbolInfosFromYAML(Buffer.get()->getBuffer())); -} - -} // namespace include_fixer -} // namespace clang Index: include-fixer/InMemorySymbolIndex.h =================================================================== --- include-fixer/InMemorySymbolIndex.h +++ include-fixer/InMemorySymbolIndex.h @@ -1,37 +0,0 @@ -//===-- InMemorySymbolIndex.h -----------------------------------*- C++ -*-===// -// -// 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 -// -//===----------------------------------------------------------------------===// -// -#ifndef LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_INMEMORYSYMBOLINDEX_H -#define LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_INMEMORYSYMBOLINDEX_H - -#include "SymbolIndex.h" -#include -#include -#include - -namespace clang { -namespace include_fixer { - -/// Xref database with fixed content. -class InMemorySymbolIndex : public SymbolIndex { -public: - InMemorySymbolIndex( - const std::vector &Symbols); - - std::vector - search(llvm::StringRef Identifier) override; - -private: - std::map> - LookupTable; -}; - -} // namespace include_fixer -} // namespace clang - -#endif // LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_INMEMORYSYMBOLINDEX_H Index: include-fixer/InMemorySymbolIndex.cpp =================================================================== --- include-fixer/InMemorySymbolIndex.cpp +++ include-fixer/InMemorySymbolIndex.cpp @@ -1,31 +0,0 @@ -//===-- InMemorySymbolIndex.cpp--------------------------------------------===// -// -// 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 "InMemorySymbolIndex.h" - -using clang::find_all_symbols::SymbolAndSignals; - -namespace clang { -namespace include_fixer { - -InMemorySymbolIndex::InMemorySymbolIndex( - const std::vector &Symbols) { - for (const auto &Symbol : Symbols) - LookupTable[Symbol.Symbol.getName()].push_back(Symbol); -} - -std::vector -InMemorySymbolIndex::search(llvm::StringRef Identifier) { - auto I = LookupTable.find(Identifier); - if (I != LookupTable.end()) - return I->second; - return {}; -} - -} // namespace include_fixer -} // namespace clang Index: include-fixer/IncludeFixer.h =================================================================== --- include-fixer/IncludeFixer.h +++ include-fixer/IncludeFixer.h @@ -1,157 +0,0 @@ -//===-- IncludeFixer.h - Include inserter -----------------------*- C++ -*-===// -// -// 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 -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_INCLUDEFIXER_H -#define LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_INCLUDEFIXER_H - -#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 -#include - -namespace clang { - -class CompilerInvocation; -class DiagnosticConsumer; -class FileManager; -class PCHContainerOperations; - -namespace include_fixer { - -class IncludeFixerActionFactory : public clang::tooling::ToolAction { -public: - /// \param SymbolIndexMgr A source for matching symbols to header files. - /// \param Contexts The contexts for the symbols being queried. - /// \param StyleName Fallback style for reformatting. - /// \param MinimizeIncludePaths whether inserted include paths are optimized. - IncludeFixerActionFactory(SymbolIndexManager &SymbolIndexMgr, - std::vector &Contexts, - StringRef StyleName, - bool MinimizeIncludePaths = true); - - ~IncludeFixerActionFactory() override; - - bool - runInvocation(std::shared_ptr Invocation, - clang::FileManager *Files, - std::shared_ptr PCHContainerOps, - clang::DiagnosticConsumer *Diagnostics) override; - -private: - /// The client to use to find cross-references. - SymbolIndexManager &SymbolIndexMgr; - - /// Multiple contexts for files being processed. - std::vector &Contexts; - - /// Whether inserted include paths should be optimized. - bool MinimizeIncludePaths; - - /// The fallback format style for formatting after insertion if no - /// clang-format config file was found. - std::string FallbackStyle; -}; - -/// Create replacements, which are generated by clang-format, for the -/// missing header and mising qualifiers insertions. The function uses the -/// first header for insertion. -/// -/// \param Code The source code. -/// \param Context The context which contains all information for creating -/// include-fixer replacements. -/// \param Style clang-format style being used. -/// \param AddQualifiers Whether we should add qualifiers to all instances of -/// an unidentified symbol. -/// -/// \return Formatted replacements for inserting, sorting headers and adding -/// qualifiers on success; otherwise, an llvm::Error carrying llvm::StringError -/// is returned. -llvm::Expected createIncludeFixerReplacements( - StringRef Code, const IncludeFixerContext &Context, - 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, - ArrayRef MatchedSymbols) const; - - /// Get the global matched symbols. - ArrayRef getMatchedSymbols() const { - return MatchedSymbols; - } - -private: - /// Query the database for a given identifier. - std::vector - 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 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 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 - -#endif // LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_INCLUDEFIXER_H Index: include-fixer/IncludeFixer.cpp =================================================================== --- include-fixer/IncludeFixer.cpp +++ include-fixer/IncludeFixer.cpp @@ -1,444 +0,0 @@ -//===-- 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 "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 - CreateASTConsumer(clang::CompilerInstance &Compiler, - StringRef InFile) override { - SemaSource.setFilePath(InFile); - return llvm::make_unique(); - } - - 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 &Contexts, StringRef StyleName, - bool MinimizeIncludePaths) - : SymbolIndexMgr(SymbolIndexMgr), Contexts(Contexts), - MinimizeIncludePaths(MinimizeIncludePaths) {} - -IncludeFixerActionFactory::~IncludeFixerActionFactory() = default; - -bool IncludeFixerActionFactory::runInvocation( - std::shared_ptr Invocation, - clang::FileManager *Files, - std::shared_ptr 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 = - llvm::make_unique(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 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 - // class Foo { - // T t; - // }; - // - // // test.cc - // // We need to add in test.cc instead of header.h. - // class Bar; - // Foo 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(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 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 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, - ArrayRef MatchedSymbols) const { - std::vector 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 -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 (!GenerateDiagnostics && !QuerySymbolInfos.empty()) { - if (ScopedQualifiers == QuerySymbolInfos.front().ScopedQualifiers && - Query == QuerySymbolInfos.front().RawIdentifier) { - QuerySymbolInfos.push_back({Query.str(), 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(), 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 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 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 Index: include-fixer/IncludeFixerContext.h =================================================================== --- include-fixer/IncludeFixerContext.h +++ include-fixer/IncludeFixerContext.h @@ -1,94 +0,0 @@ -//===-- IncludeFixerContext.h - Include fixer context -----------*- C++ -*-===// -// -// 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 -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_INCLUDEFIXERCONTEXT_H -#define LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_INCLUDEFIXERCONTEXT_H - -#include "find-all-symbols/SymbolInfo.h" -#include "clang/Tooling/Core/Replacement.h" -#include -#include - -namespace clang { -namespace include_fixer { - -/// \brief A context for a file being processed. It includes all query -/// information, e.g. symbols being queried in database, all header candidates. -class IncludeFixerContext { -public: - struct HeaderInfo { - /// \brief The header where QualifiedName comes from. - std::string Header; - /// \brief A symbol name with completed namespace qualifiers which will - /// replace the original symbol. - std::string QualifiedName; - }; - - struct QuerySymbolInfo { - /// \brief The raw symbol name being queried in database. This name might - /// miss some namespace qualifiers, and will be replaced by a fully - /// qualified one. - std::string RawIdentifier; - - /// \brief The qualifiers of the scope in which SymbolIdentifier lookup - /// occurs. It is represented as a sequence of names and scope resolution - /// operatiors ::, ending with a scope resolution operator (e.g. a::b::). - /// Empty if SymbolIdentifier is not in a specific scope. - std::string ScopedQualifiers; - - /// \brief The replacement range of RawIdentifier. - tooling::Range Range; - }; - - IncludeFixerContext() = default; - IncludeFixerContext(StringRef FilePath, - std::vector QuerySymbols, - std::vector Symbols); - - /// \brief Get symbol name. - llvm::StringRef getSymbolIdentifier() const { - return QuerySymbolInfos.front().RawIdentifier; - } - - /// \brief Get replacement range of the symbol. - tooling::Range getSymbolRange() const { - return QuerySymbolInfos.front().Range; - } - - /// \brief Get the file path to the file being processed. - StringRef getFilePath() const { return FilePath; } - - /// \brief Get header information. - const std::vector &getHeaderInfos() const { return HeaderInfos; } - - /// \brief Get information of symbols being querid. - const std::vector &getQuerySymbolInfos() const { - return QuerySymbolInfos; - } - -private: - friend struct llvm::yaml::MappingTraits; - - /// \brief The file path to the file being processed. - std::string FilePath; - - /// \brief All instances of an unidentified symbol being queried. - std::vector QuerySymbolInfos; - - /// \brief The symbol candidates which match SymbolIdentifier. The symbols are - /// sorted in a descending order based on the popularity info in SymbolInfo. - std::vector MatchedSymbols; - - /// \brief The header information. - std::vector HeaderInfos; -}; - -} // namespace include_fixer -} // namespace clang - -#endif // LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_INCLUDEFIXERCONTEXT_H Index: include-fixer/IncludeFixerContext.cpp =================================================================== --- include-fixer/IncludeFixerContext.cpp +++ include-fixer/IncludeFixerContext.cpp @@ -1,115 +0,0 @@ -//===-- IncludeFixerContext.cpp - Include fixer context ---------*- C++ -*-===// -// -// 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 "IncludeFixerContext.h" -#include - -namespace clang { -namespace include_fixer { - -namespace { - -// Splits a multiply qualified names (e.g. a::b::c). -llvm::SmallVector -SplitQualifiers(llvm::StringRef StringQualifiers) { - llvm::SmallVector Qualifiers; - StringQualifiers.split(Qualifiers, "::"); - return Qualifiers; -} - -std::string createQualifiedNameForReplacement( - llvm::StringRef RawSymbolName, - llvm::StringRef SymbolScopedQualifiersName, - const find_all_symbols::SymbolInfo &MatchedSymbol) { - // No need to add missing qualifiers if SymbolIndentifer has a global scope - // operator "::". - if (RawSymbolName.startswith("::")) - return RawSymbolName; - - std::string QualifiedName = MatchedSymbol.getQualifiedName(); - - // For nested classes, the qualified name constructed from database misses - // some stripped qualifiers, because when we search a symbol in database, - // we strip qualifiers from the end until we find a result. So append the - // missing stripped qualifiers here. - // - // Get stripped qualifiers. - auto SymbolQualifiers = SplitQualifiers(RawSymbolName); - std::string StrippedQualifiers; - while (!SymbolQualifiers.empty() && - !llvm::StringRef(QualifiedName).endswith(SymbolQualifiers.back())) { - StrippedQualifiers = - "::" + SymbolQualifiers.back().str() + StrippedQualifiers; - SymbolQualifiers.pop_back(); - } - // Append the missing stripped qualifiers. - std::string FullyQualifiedName = QualifiedName + StrippedQualifiers; - - // Try to find and skip the common prefix qualifiers. - auto FullySymbolQualifiers = SplitQualifiers(FullyQualifiedName); - auto ScopedQualifiers = SplitQualifiers(SymbolScopedQualifiersName); - auto FullySymbolQualifiersIter = FullySymbolQualifiers.begin(); - auto SymbolScopedQualifiersIter = ScopedQualifiers.begin(); - while (FullySymbolQualifiersIter != FullySymbolQualifiers.end() && - SymbolScopedQualifiersIter != ScopedQualifiers.end()) { - if (*FullySymbolQualifiersIter != *SymbolScopedQualifiersIter) - break; - ++FullySymbolQualifiersIter; - ++SymbolScopedQualifiersIter; - } - std::string Result; - for (; FullySymbolQualifiersIter != FullySymbolQualifiers.end(); - ++FullySymbolQualifiersIter) { - if (!Result.empty()) - Result += "::"; - Result += *FullySymbolQualifiersIter; - } - return Result; -} - -} // anonymous namespace - -IncludeFixerContext::IncludeFixerContext( - StringRef FilePath, std::vector QuerySymbols, - std::vector Symbols) - : FilePath(FilePath), QuerySymbolInfos(std::move(QuerySymbols)), - MatchedSymbols(std::move(Symbols)) { - // Remove replicated QuerySymbolInfos with the same range. - // - // QuerySymbolInfos may contain replicated elements. Because CorrectTypo - // callback doesn't always work as we expected. In somecases, it will be - // triggered at the same position or unidentified symbol multiple times. - std::sort(QuerySymbolInfos.begin(), QuerySymbolInfos.end(), - [&](const QuerySymbolInfo &A, const QuerySymbolInfo &B) { - return std::make_pair(A.Range.getOffset(), A.Range.getLength()) < - std::make_pair(B.Range.getOffset(), B.Range.getLength()); - }); - QuerySymbolInfos.erase( - std::unique(QuerySymbolInfos.begin(), QuerySymbolInfos.end(), - [](const QuerySymbolInfo &A, const QuerySymbolInfo &B) { - return A.Range == B.Range; - }), - QuerySymbolInfos.end()); - for (const auto &Symbol : MatchedSymbols) { - HeaderInfos.push_back( - {Symbol.getFilePath().str(), - createQualifiedNameForReplacement( - QuerySymbolInfos.front().RawIdentifier, - QuerySymbolInfos.front().ScopedQualifiers, Symbol)}); - } - // Deduplicate header infos. - HeaderInfos.erase(std::unique(HeaderInfos.begin(), HeaderInfos.end(), - [](const HeaderInfo &A, const HeaderInfo &B) { - return A.Header == B.Header && - A.QualifiedName == B.QualifiedName; - }), - HeaderInfos.end()); -} - -} // include_fixer -} // clang Index: include-fixer/SymbolIndex.h =================================================================== --- include-fixer/SymbolIndex.h +++ include-fixer/SymbolIndex.h @@ -1,37 +0,0 @@ -//===-- SymbolIndex.h - Interface for symbol-header matching ----*- C++ -*-===// -// -// 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 -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_SYMBOLINDEX_H -#define LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_SYMBOLINDEX_H - -#include "find-all-symbols/SymbolInfo.h" -#include "llvm/ADT/StringRef.h" -#include - -namespace clang { -namespace include_fixer { - -/// This class provides an interface for finding all `SymbolInfo`s corresponding -/// to a symbol name from a symbol database. -class SymbolIndex { -public: - virtual ~SymbolIndex() = default; - - /// Search for all `SymbolInfo`s corresponding to an identifier. - /// \param Identifier The unqualified identifier being searched for. - /// \returns A list of `SymbolInfo` candidates. - // FIXME: Expose the type name so we can also insert using declarations (or - // fix the usage) - virtual std::vector - search(llvm::StringRef Identifier) = 0; -}; - -} // namespace include_fixer -} // namespace clang - -#endif // LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_SYMBOLINDEX_H Index: include-fixer/SymbolIndexManager.h =================================================================== --- include-fixer/SymbolIndexManager.h +++ include-fixer/SymbolIndexManager.h @@ -1,65 +0,0 @@ -//===-- SymbolIndexManager.h - Managing multiple SymbolIndices --*- C++ -*-===// -// -// 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 -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_SYMBOLINDEXMANAGER_H -#define LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_SYMBOLINDEXMANAGER_H - -#include "SymbolIndex.h" -#include "find-all-symbols/SymbolInfo.h" -#include "llvm/ADT/StringRef.h" - -#ifdef _MSC_VER -// Disable warnings from ppltasks.h transitively included by . -#pragma warning(push) -#pragma warning(disable:4530) -#endif - -#include - -#ifdef _MSC_VER -#pragma warning(pop) -#endif - -namespace clang { -namespace include_fixer { - -/// This class provides an interface for finding the header files corresponding -/// to an identifier in the source code from multiple symbol databases. -class SymbolIndexManager { -public: - void addSymbolIndex(std::function()> F) { -#if LLVM_ENABLE_THREADS - auto Strategy = std::launch::async; -#else - auto Strategy = std::launch::deferred; -#endif - SymbolIndices.push_back(std::async(Strategy, F)); - } - - /// Search for header files to be included for an identifier. - /// \param Identifier The identifier being searched for. May or may not be - /// fully qualified. - /// \param IsNestedSearch Whether searching nested classes. If true, the - /// method tries to strip identifier name parts from the end until it - /// finds the corresponding candidates in database (e.g for identifier - /// "b::foo", the method will try to find "b" if it fails to find - /// "b::foo"). - /// - /// \returns A list of symbol candidates. - std::vector - search(llvm::StringRef Identifier, bool IsNestedSearch = true, - llvm::StringRef FileName = "") const; - -private: - std::vector>> SymbolIndices; -}; - -} // namespace include_fixer -} // namespace clang - -#endif Index: include-fixer/SymbolIndexManager.cpp =================================================================== --- include-fixer/SymbolIndexManager.cpp +++ include-fixer/SymbolIndexManager.cpp @@ -1,158 +0,0 @@ -//===-- SymbolIndexManager.cpp - Managing multiple SymbolIndices-*- C++ -*-===// -// -// 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 "SymbolIndexManager.h" -#include "find-all-symbols/SymbolInfo.h" -#include "llvm/ADT/DenseMap.h" -#include "llvm/ADT/SmallVector.h" -#include "llvm/Support/Debug.h" -#include "llvm/Support/Path.h" - -#define DEBUG_TYPE "include-fixer" - -namespace clang { -namespace include_fixer { - -using find_all_symbols::SymbolInfo; -using find_all_symbols::SymbolAndSignals; - -// Calculate a score based on whether we think the given header is closely -// related to the given source file. -static double similarityScore(llvm::StringRef FileName, - llvm::StringRef Header) { - // Compute the maximum number of common path segements between Header and - // a suffix of FileName. - // We do not do a full longest common substring computation, as Header - // specifies the path we would directly #include, so we assume it is rooted - // relatively to a subproject of the repository. - int MaxSegments = 1; - for (auto FileI = llvm::sys::path::begin(FileName), - FileE = llvm::sys::path::end(FileName); - FileI != FileE; ++FileI) { - int Segments = 0; - for (auto HeaderI = llvm::sys::path::begin(Header), - HeaderE = llvm::sys::path::end(Header), I = FileI; - HeaderI != HeaderE && *I == *HeaderI && I != FileE; ++I, ++HeaderI) { - ++Segments; - } - MaxSegments = std::max(Segments, MaxSegments); - } - return MaxSegments; -} - -static void rank(std::vector &Symbols, - llvm::StringRef FileName) { - llvm::DenseMap Score; - for (const auto &Symbol : Symbols) { - // Calculate a score from the similarity of the header the symbol is in - // with the current file and the popularity of the symbol. - double NewScore = similarityScore(FileName, Symbol.Symbol.getFilePath()) * - (1.0 + std::log2(1 + Symbol.Signals.Seen)); - double &S = Score[Symbol.Symbol.getFilePath()]; - S = std::max(S, NewScore); - } - // Sort by the gathered scores. Use file name as a tie breaker so we can - // deduplicate. - std::sort(Symbols.begin(), Symbols.end(), - [&](const SymbolAndSignals &A, const SymbolAndSignals &B) { - auto AS = Score[A.Symbol.getFilePath()]; - auto BS = Score[B.Symbol.getFilePath()]; - if (AS != BS) - return AS > BS; - return A.Symbol.getFilePath() < B.Symbol.getFilePath(); - }); -} - -std::vector -SymbolIndexManager::search(llvm::StringRef Identifier, - bool IsNestedSearch, - llvm::StringRef FileName) const { - // The identifier may be fully qualified, so split it and get all the context - // names. - llvm::SmallVector Names; - Identifier.split(Names, "::"); - - bool IsFullyQualified = false; - if (Identifier.startswith("::")) { - Names.erase(Names.begin()); // Drop first (empty) element. - IsFullyQualified = true; - } - - // As long as we don't find a result keep stripping name parts from the end. - // This is to support nested classes which aren't recorded in the database. - // Eventually we will either hit a class (namespaces aren't in the database - // either) and can report that result. - bool TookPrefix = false; - std::vector MatchedSymbols; - do { - std::vector Symbols; - for (const auto &DB : SymbolIndices) { - auto Res = DB.get()->search(Names.back()); - Symbols.insert(Symbols.end(), Res.begin(), Res.end()); - } - - LLVM_DEBUG(llvm::dbgs() << "Searching " << Names.back() << "... got " - << Symbols.size() << " results...\n"); - - for (auto &SymAndSig : Symbols) { - const SymbolInfo &Symbol = SymAndSig.Symbol; - // Match the identifier name without qualifier. - bool IsMatched = true; - auto SymbolContext = Symbol.getContexts().begin(); - auto IdentiferContext = Names.rbegin() + 1; // Skip identifier name. - // Match the remaining context names. - while (IdentiferContext != Names.rend() && - SymbolContext != Symbol.getContexts().end()) { - if (SymbolContext->second == *IdentiferContext) { - ++IdentiferContext; - ++SymbolContext; - } else if (SymbolContext->first == - find_all_symbols::SymbolInfo::ContextType::EnumDecl) { - // Skip non-scoped enum context. - ++SymbolContext; - } else { - IsMatched = false; - break; - } - } - - // If the name was qualified we only want to add results if we evaluated - // all contexts. - if (IsFullyQualified) - IsMatched &= (SymbolContext == Symbol.getContexts().end()); - - // FIXME: Support full match. At this point, we only find symbols in - // database which end with the same contexts with the identifier. - if (IsMatched && IdentiferContext == Names.rend()) { - // If we're in a situation where we took a prefix but the thing we - // found couldn't possibly have a nested member ignore it. - if (TookPrefix && - (Symbol.getSymbolKind() == SymbolInfo::SymbolKind::Function || - Symbol.getSymbolKind() == SymbolInfo::SymbolKind::Variable || - Symbol.getSymbolKind() == - SymbolInfo::SymbolKind::EnumConstantDecl || - Symbol.getSymbolKind() == SymbolInfo::SymbolKind::Macro)) - continue; - - MatchedSymbols.push_back(std::move(SymAndSig)); - } - } - Names.pop_back(); - TookPrefix = true; - } while (MatchedSymbols.empty() && !Names.empty() && IsNestedSearch); - - rank(MatchedSymbols, FileName); - // Strip signals, they are no longer needed. - std::vector Res; - for (auto &SymAndSig : MatchedSymbols) - Res.push_back(std::move(SymAndSig.Symbol)); - return Res; -} - -} // namespace include_fixer -} // namespace clang Index: include-fixer/YamlSymbolIndex.h =================================================================== --- include-fixer/YamlSymbolIndex.h +++ include-fixer/YamlSymbolIndex.h @@ -1,45 +0,0 @@ -//===-- YamlSymbolIndex.h ---------------------------------------*- C++ -*-===// -// -// 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 -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_YAMLSYMBOLINDEX_H -#define LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_YAMLSYMBOLINDEX_H - -#include "SymbolIndex.h" -#include "find-all-symbols/SymbolInfo.h" -#include "llvm/Support/ErrorOr.h" -#include -#include - -namespace clang { -namespace include_fixer { - -/// Yaml format database. -class YamlSymbolIndex : public SymbolIndex { -public: - /// Create a new Yaml db from a file. - static llvm::ErrorOr> - createFromFile(llvm::StringRef FilePath); - /// Look for a file called \c Name in \c Directory and all parent directories. - static llvm::ErrorOr> - createFromDirectory(llvm::StringRef Directory, llvm::StringRef Name); - - std::vector - search(llvm::StringRef Identifier) override; - -private: - explicit YamlSymbolIndex( - std::vector Symbols) - : Symbols(std::move(Symbols)) {} - - std::vector Symbols; -}; - -} // namespace include_fixer -} // namespace clang - -#endif // LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_YAMLSYMBOLINDEX_H Index: include-fixer/YamlSymbolIndex.cpp =================================================================== --- include-fixer/YamlSymbolIndex.cpp +++ include-fixer/YamlSymbolIndex.cpp @@ -1,60 +0,0 @@ -//===-- YamlSymbolIndex.cpp -----------------------------------------------===// -// -// 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 "YamlSymbolIndex.h" -#include "llvm/ADT/SmallVector.h" -#include "llvm/Support/Errc.h" -#include "llvm/Support/FileSystem.h" -#include "llvm/Support/MemoryBuffer.h" -#include "llvm/Support/Path.h" -#include -#include - -using clang::find_all_symbols::SymbolInfo; -using clang::find_all_symbols::SymbolAndSignals; - -namespace clang { -namespace include_fixer { - -llvm::ErrorOr> -YamlSymbolIndex::createFromFile(llvm::StringRef FilePath) { - auto Buffer = llvm::MemoryBuffer::getFile(FilePath); - if (!Buffer) - return Buffer.getError(); - - return std::unique_ptr(new YamlSymbolIndex( - find_all_symbols::ReadSymbolInfosFromYAML(Buffer.get()->getBuffer()))); -} - -llvm::ErrorOr> -YamlSymbolIndex::createFromDirectory(llvm::StringRef Directory, - llvm::StringRef Name) { - // Walk upwards from Directory, looking for files. - for (llvm::SmallString<128> PathStorage = Directory; !Directory.empty(); - Directory = llvm::sys::path::parent_path(Directory)) { - assert(Directory.size() <= PathStorage.size()); - PathStorage.resize(Directory.size()); // Shrink to parent. - llvm::sys::path::append(PathStorage, Name); - if (auto DB = createFromFile(PathStorage)) - return DB; - } - return llvm::make_error_code(llvm::errc::no_such_file_or_directory); -} - -std::vector -YamlSymbolIndex::search(llvm::StringRef Identifier) { - std::vector Results; - for (const auto &Symbol : Symbols) { - if (Symbol.Symbol.getName() == Identifier) - Results.push_back(Symbol); - } - return Results; -} - -} // namespace include_fixer -} // namespace clang Index: include-fixer/find-all-symbols/CMakeLists.txt =================================================================== --- include-fixer/find-all-symbols/CMakeLists.txt +++ include-fixer/find-all-symbols/CMakeLists.txt @@ -1,24 +0,0 @@ -set(LLVM_LINK_COMPONENTS - Support - ) - -add_clang_library(findAllSymbols - FindAllSymbols.cpp - FindAllSymbolsAction.cpp - FindAllMacros.cpp - HeaderMapCollector.cpp - PathConfig.cpp - PragmaCommentHandler.cpp - STLPostfixHeaderMap.cpp - SymbolInfo.cpp - - LINK_LIBS - clangAST - clangASTMatchers - clangBasic - clangFrontend - clangLex - clangTooling - ) - -add_subdirectory(tool) Index: include-fixer/find-all-symbols/FindAllMacros.h =================================================================== --- include-fixer/find-all-symbols/FindAllMacros.h +++ include-fixer/find-all-symbols/FindAllMacros.h @@ -1,64 +0,0 @@ -//===-- FindAllMacros.h - find all macros -----------------------*- C++ -*-===// -// -// -// 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 -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_FIND_ALL_MACROS_H -#define LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_FIND_ALL_MACROS_H - -#include "SymbolInfo.h" -#include "SymbolReporter.h" -#include "clang/Lex/PPCallbacks.h" - -namespace clang { -class MacroInfo; -namespace find_all_symbols { - -class HeaderMapCollector; - -/// \brief A preprocessor that collects all macro symbols. -/// The contexts of a macro will be ignored since they are not available during -/// preprocessing period. -class FindAllMacros : public clang::PPCallbacks { -public: - explicit FindAllMacros(SymbolReporter *Reporter, SourceManager *SM, - HeaderMapCollector *Collector = nullptr) - : Reporter(Reporter), SM(SM), Collector(Collector) {} - - void MacroDefined(const Token &MacroNameTok, - const MacroDirective *MD) override; - - void MacroExpands(const Token &MacroNameTok, const MacroDefinition &MD, - SourceRange Range, const MacroArgs *Args) override; - - void Ifdef(SourceLocation Loc, const Token &MacroNameTok, - const MacroDefinition &MD) override; - - void Ifndef(SourceLocation Loc, const Token &MacroNameTok, - const MacroDefinition &MD) override; - - void EndOfMainFile() override; - -private: - llvm::Optional CreateMacroSymbol(const Token &MacroNameTok, - const MacroInfo *MD); - // Not a callback, just a common path for all usage types. - void MacroUsed(const Token &Name, const MacroDefinition &MD); - - SymbolInfo::SignalMap FileSymbols; - // Reporter for SymbolInfo. - SymbolReporter *const Reporter; - SourceManager *const SM; - // A remapping header file collector allowing clients to include a different - // header. - HeaderMapCollector *const Collector; -}; - -} // namespace find_all_symbols -} // namespace clang - -#endif // LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_FIND_ALL_MACROS_H Index: include-fixer/find-all-symbols/FindAllMacros.cpp =================================================================== --- include-fixer/find-all-symbols/FindAllMacros.cpp +++ include-fixer/find-all-symbols/FindAllMacros.cpp @@ -1,69 +0,0 @@ -//===-- FindAllMacros.cpp - find all macros ---------------------*- C++ -*-===// -// -// 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 "FindAllMacros.h" -#include "HeaderMapCollector.h" -#include "PathConfig.h" -#include "SymbolInfo.h" -#include "clang/Basic/IdentifierTable.h" -#include "clang/Basic/SourceManager.h" -#include "clang/Lex/MacroInfo.h" -#include "clang/Lex/Token.h" -#include "llvm/Support/Path.h" - -namespace clang { -namespace find_all_symbols { - -llvm::Optional -FindAllMacros::CreateMacroSymbol(const Token &MacroNameTok, - const MacroInfo *info) { - std::string FilePath = - getIncludePath(*SM, info->getDefinitionLoc(), Collector); - if (FilePath.empty()) - return llvm::None; - return SymbolInfo(MacroNameTok.getIdentifierInfo()->getName(), - SymbolInfo::SymbolKind::Macro, FilePath, {}); -} - -void FindAllMacros::MacroDefined(const Token &MacroNameTok, - const MacroDirective *MD) { - if (auto Symbol = CreateMacroSymbol(MacroNameTok, MD->getMacroInfo())) - ++FileSymbols[*Symbol].Seen; -} - -void FindAllMacros::MacroUsed(const Token &Name, const MacroDefinition &MD) { - if (!MD || !SM->isInMainFile(SM->getExpansionLoc(Name.getLocation()))) - return; - if (auto Symbol = CreateMacroSymbol(Name, MD.getMacroInfo())) - ++FileSymbols[*Symbol].Used; -} - -void FindAllMacros::MacroExpands(const Token &MacroNameTok, - const MacroDefinition &MD, SourceRange Range, - const MacroArgs *Args) { - MacroUsed(MacroNameTok, MD); -} - -void FindAllMacros::Ifdef(SourceLocation Loc, const Token &MacroNameTok, - const MacroDefinition &MD) { - MacroUsed(MacroNameTok, MD); -} - -void FindAllMacros::Ifndef(SourceLocation Loc, const Token &MacroNameTok, - const MacroDefinition &MD) { - MacroUsed(MacroNameTok, MD); -} - -void FindAllMacros::EndOfMainFile() { - Reporter->reportSymbols(SM->getFileEntryForID(SM->getMainFileID())->getName(), - FileSymbols); - FileSymbols.clear(); -} - -} // namespace find_all_symbols -} // namespace clang Index: include-fixer/find-all-symbols/FindAllSymbols.h =================================================================== --- include-fixer/find-all-symbols/FindAllSymbols.h +++ include-fixer/find-all-symbols/FindAllSymbols.h @@ -1,62 +0,0 @@ -//===-- FindAllSymbols.h - find all symbols----------------------*- C++ -*-===// -// -// 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 -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_SYMBOL_MATCHER_H -#define LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_SYMBOL_MATCHER_H - -#include "SymbolInfo.h" -#include "SymbolReporter.h" -#include "clang/ASTMatchers/ASTMatchFinder.h" -#include - -namespace clang { -namespace find_all_symbols { - -class HeaderMapCollector; - -/// \brief FindAllSymbols collects all classes, free standing functions and -/// global variables with some extra information such as the path of the header -/// file, the namespaces they are contained in, the type of variables and the -/// parameter types of functions. -/// -/// NOTE: -/// - Symbols declared in main files are not collected since they can not be -/// included. -/// - Member functions are not collected because accessing them must go -/// through the class. #include fixer only needs the class name to find -/// headers. -/// -class FindAllSymbols : public ast_matchers::MatchFinder::MatchCallback { -public: - explicit FindAllSymbols(SymbolReporter *Reporter, - HeaderMapCollector *Collector = nullptr) - : Reporter(Reporter), Collector(Collector) {} - - void registerMatchers(ast_matchers::MatchFinder *MatchFinder); - - void run(const ast_matchers::MatchFinder::MatchResult &result) override; - -protected: - void onEndOfTranslationUnit() override; - -private: - // Current source file being processed, filled by first symbol found. - std::string Filename; - // Findings for the current source file, flushed on onEndOfTranslationUnit. - SymbolInfo::SignalMap FileSymbols; - // Reporter for SymbolInfo. - SymbolReporter *const Reporter; - // A remapping header file collector allowing clients include a different - // header. - HeaderMapCollector *const Collector; -}; - -} // namespace find_all_symbols -} // namespace clang - -#endif // LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_SYMBOL_MATCHER_H Index: include-fixer/find-all-symbols/FindAllSymbols.cpp =================================================================== --- include-fixer/find-all-symbols/FindAllSymbols.cpp +++ include-fixer/find-all-symbols/FindAllSymbols.cpp @@ -1,268 +0,0 @@ -//===-- FindAllSymbols.cpp - find all symbols--------------------*- C++ -*-===// -// -// 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 "FindAllSymbols.h" -#include "HeaderMapCollector.h" -#include "PathConfig.h" -#include "SymbolInfo.h" -#include "clang/AST/Decl.h" -#include "clang/AST/DeclCXX.h" -#include "clang/AST/Type.h" -#include "clang/ASTMatchers/ASTMatchFinder.h" -#include "clang/ASTMatchers/ASTMatchers.h" -#include "clang/Tooling/Tooling.h" -#include "llvm/ADT/Optional.h" -#include "llvm/Support/FileSystem.h" - -using namespace clang::ast_matchers; - -namespace clang { -namespace find_all_symbols { -namespace { - -AST_MATCHER(EnumConstantDecl, isInScopedEnum) { - if (const auto *ED = dyn_cast(Node.getDeclContext())) - return ED->isScoped(); - return false; -} - -AST_POLYMORPHIC_MATCHER(isFullySpecialized, - AST_POLYMORPHIC_SUPPORTED_TYPES(FunctionDecl, VarDecl, - CXXRecordDecl)) { - if (Node.getTemplateSpecializationKind() == TSK_ExplicitSpecialization) { - bool IsPartialSpecialization = - llvm::isa(Node) || - llvm::isa(Node); - return !IsPartialSpecialization; - } - return false; -} - -std::vector GetContexts(const NamedDecl *ND) { - std::vector Contexts; - for (const auto *Context = ND->getDeclContext(); Context; - Context = Context->getParent()) { - if (llvm::isa(Context) || - llvm::isa(Context)) - break; - - assert(llvm::isa(Context) && - "Expect Context to be a NamedDecl"); - if (const auto *NSD = dyn_cast(Context)) { - if (!NSD->isInlineNamespace()) - Contexts.emplace_back(SymbolInfo::ContextType::Namespace, - NSD->getName().str()); - } else if (const auto *ED = dyn_cast(Context)) { - Contexts.emplace_back(SymbolInfo::ContextType::EnumDecl, - ED->getName().str()); - } else { - const auto *RD = cast(Context); - Contexts.emplace_back(SymbolInfo::ContextType::Record, - RD->getName().str()); - } - } - return Contexts; -} - -llvm::Optional -CreateSymbolInfo(const NamedDecl *ND, const SourceManager &SM, - const HeaderMapCollector *Collector) { - SymbolInfo::SymbolKind Type; - if (llvm::isa(ND)) { - Type = SymbolInfo::SymbolKind::Variable; - } else if (llvm::isa(ND)) { - Type = SymbolInfo::SymbolKind::Function; - } else if (llvm::isa(ND)) { - Type = SymbolInfo::SymbolKind::TypedefName; - } else if (llvm::isa(ND)) { - Type = SymbolInfo::SymbolKind::EnumConstantDecl; - } else if (llvm::isa(ND)) { - Type = SymbolInfo::SymbolKind::EnumDecl; - // Ignore anonymous enum declarations. - if (ND->getName().empty()) - return llvm::None; - } else { - assert(llvm::isa(ND) && - "Matched decl must be one of VarDecl, " - "FunctionDecl, TypedefNameDecl, EnumConstantDecl, " - "EnumDecl and RecordDecl!"); - // C-style record decl can have empty name, e.g "struct { ... } var;". - if (ND->getName().empty()) - return llvm::None; - Type = SymbolInfo::SymbolKind::Class; - } - - SourceLocation Loc = SM.getExpansionLoc(ND->getLocation()); - if (!Loc.isValid()) { - llvm::errs() << "Declaration " << ND->getNameAsString() << "(" - << ND->getDeclKindName() - << ") has invalid declaration location."; - return llvm::None; - } - - std::string FilePath = getIncludePath(SM, Loc, Collector); - if (FilePath.empty()) return llvm::None; - - return SymbolInfo(ND->getNameAsString(), Type, FilePath, GetContexts(ND)); -} - -} // namespace - -void FindAllSymbols::registerMatchers(MatchFinder *MatchFinder) { - // FIXME: Handle specialization. - auto IsInSpecialization = hasAncestor( - decl(anyOf(cxxRecordDecl(isExplicitTemplateSpecialization()), - functionDecl(isExplicitTemplateSpecialization())))); - - // Matchers for both C and C++. - // We only match symbols from header files, i.e. not from main files (see - // function's comment for detailed explanation). - auto CommonFilter = - allOf(unless(isImplicit()), unless(isExpansionInMainFile())); - - auto HasNSOrTUCtxMatcher = - hasDeclContext(anyOf(namespaceDecl(), translationUnitDecl())); - - // We need seperate rules for C record types and C++ record types since some - // template related matchers are inapplicable on C record declarations. - // - // Matchers specific to C++ code. - // All declarations should be in namespace or translation unit. - auto CCMatcher = - allOf(HasNSOrTUCtxMatcher, unless(IsInSpecialization), - unless(ast_matchers::isTemplateInstantiation()), - unless(isInstantiated()), unless(isFullySpecialized())); - - // Matchers specific to code in extern "C" {...}. - auto ExternCMatcher = hasDeclContext(linkageSpecDecl()); - - // Matchers for variable declarations. - // - // In most cases, `ParmVarDecl` is filtered out by hasDeclContext(...) - // matcher since the declaration context is usually `MethodDecl`. However, - // this assumption does not hold for parameters of a function pointer - // parameter. - // For example, consider a function declaration: - // void Func(void (*)(float), int); - // The float parameter of the function pointer has an empty name, and its - // declaration context is an anonymous namespace; therefore, it won't be - // filtered out by our matchers above. - auto Vars = varDecl(CommonFilter, anyOf(ExternCMatcher, CCMatcher), - unless(parmVarDecl())); - - // Matchers for C-style record declarations in extern "C" {...}. - auto CRecords = recordDecl(CommonFilter, ExternCMatcher, isDefinition()); - // Matchers for C++ record declarations. - auto CXXRecords = cxxRecordDecl(CommonFilter, CCMatcher, isDefinition()); - - // Matchers for function declarations. - // We want to exclude friend declaration, but the `DeclContext` of a friend - // function declaration is not the class in which it is declared, so we need - // to explicitly check if the parent is a `friendDecl`. - auto Functions = functionDecl(CommonFilter, unless(hasParent(friendDecl())), - anyOf(ExternCMatcher, CCMatcher)); - - // Matcher for typedef and type alias declarations. - // - // typedef and type alias can come from C-style headers and C++ headers. - // For C-style headers, `DeclContxet` can be either `TranslationUnitDecl` - // or `LinkageSpecDecl`. - // For C++ headers, `DeclContext ` can be either `TranslationUnitDecl` - // or `NamespaceDecl`. - // With the following context matcher, we can match `typedefNameDecl` from - // both C-style headers and C++ headers (except for those in classes). - // "cc_matchers" are not included since template-related matchers are not - // applicable on `TypedefNameDecl`. - auto Typedefs = - typedefNameDecl(CommonFilter, anyOf(HasNSOrTUCtxMatcher, - hasDeclContext(linkageSpecDecl()))); - - // Matchers for enum declarations. - auto Enums = enumDecl(CommonFilter, isDefinition(), - anyOf(HasNSOrTUCtxMatcher, ExternCMatcher)); - - // Matchers for enum constant declarations. - // We only match the enum constants in non-scoped enum declarations which are - // inside toplevel translation unit or a namespace. - auto EnumConstants = enumConstantDecl( - CommonFilter, unless(isInScopedEnum()), - anyOf(hasDeclContext(enumDecl(HasNSOrTUCtxMatcher)), ExternCMatcher)); - - // Most of the time we care about all matchable decls, or all types. - auto Types = namedDecl(anyOf(CRecords, CXXRecords, Enums)); - auto Decls = namedDecl(anyOf(CRecords, CXXRecords, Enums, Typedefs, Vars, - EnumConstants, Functions)); - - // We want eligible decls bound to "decl"... - MatchFinder->addMatcher(Decls.bind("decl"), this); - - // ... and all uses of them bound to "use". These have many cases: - // Uses of values/functions: these generate a declRefExpr. - MatchFinder->addMatcher( - declRefExpr(isExpansionInMainFile(), to(Decls.bind("use"))), this); - // Uses of function templates: - MatchFinder->addMatcher( - declRefExpr(isExpansionInMainFile(), - to(functionDecl(hasParent( - functionTemplateDecl(has(Functions.bind("use"))))))), - this); - - // Uses of most types: just look at what the typeLoc refers to. - MatchFinder->addMatcher( - typeLoc(isExpansionInMainFile(), - loc(qualType(hasDeclaration(Types.bind("use"))))), - this); - // Uses of typedefs: these are often transparent to hasDeclaration, so we need - // to handle them explicitly. - MatchFinder->addMatcher( - typeLoc(isExpansionInMainFile(), - loc(typedefType(hasDeclaration(Typedefs.bind("use"))))), - this); - // Uses of class templates: - // The typeLoc names the templateSpecializationType. Its declaration is the - // ClassTemplateDecl, which contains the CXXRecordDecl we want. - MatchFinder->addMatcher( - typeLoc(isExpansionInMainFile(), - loc(templateSpecializationType(hasDeclaration( - classTemplateSpecializationDecl(hasSpecializedTemplate( - classTemplateDecl(has(CXXRecords.bind("use"))))))))), - this); -} - -void FindAllSymbols::run(const MatchFinder::MatchResult &Result) { - // Ignore Results in failing TUs. - if (Result.Context->getDiagnostics().hasErrorOccurred()) { - return; - } - - SymbolInfo::Signals Signals; - const NamedDecl *ND; - if ((ND = Result.Nodes.getNodeAs("use"))) - Signals.Used = 1; - else if ((ND = Result.Nodes.getNodeAs("decl"))) - Signals.Seen = 1; - else - assert(false && "Must match a NamedDecl!"); - - const SourceManager *SM = Result.SourceManager; - if (auto Symbol = CreateSymbolInfo(ND, *SM, Collector)) { - Filename = SM->getFileEntryForID(SM->getMainFileID())->getName(); - FileSymbols[*Symbol] += Signals; - } -} - -void FindAllSymbols::onEndOfTranslationUnit() { - if (Filename != "") { - Reporter->reportSymbols(Filename, FileSymbols); - FileSymbols.clear(); - Filename = ""; - } -} - -} // namespace find_all_symbols -} // namespace clang Index: include-fixer/find-all-symbols/FindAllSymbolsAction.h =================================================================== --- include-fixer/find-all-symbols/FindAllSymbolsAction.h +++ include-fixer/find-all-symbols/FindAllSymbolsAction.h @@ -1,62 +0,0 @@ -//===-- FindAllSymbolsAction.h - find all symbols action --------*- C++ -*-===// -// -// 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 -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_FIND_ALL_SYMBOLS_ACTION_H -#define LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_FIND_ALL_SYMBOLS_ACTION_H - -#include "FindAllSymbols.h" -#include "HeaderMapCollector.h" -#include "PragmaCommentHandler.h" -#include "clang/ASTMatchers/ASTMatchFinder.h" -#include "clang/Frontend/CompilerInstance.h" -#include "clang/Frontend/FrontendAction.h" -#include "clang/Tooling/Tooling.h" -#include "llvm/ADT/StringRef.h" -#include - -namespace clang { -namespace find_all_symbols { - -class FindAllSymbolsAction : public clang::ASTFrontendAction { -public: - explicit FindAllSymbolsAction( - SymbolReporter *Reporter, - const HeaderMapCollector::RegexHeaderMap *RegexHeaderMap = nullptr); - - std::unique_ptr - CreateASTConsumer(clang::CompilerInstance &Compiler, - StringRef InFile) override; - -private: - SymbolReporter *const Reporter; - clang::ast_matchers::MatchFinder MatchFinder; - HeaderMapCollector Collector; - PragmaCommentHandler Handler; - FindAllSymbols Matcher; -}; - -class FindAllSymbolsActionFactory : public tooling::FrontendActionFactory { -public: - FindAllSymbolsActionFactory( - SymbolReporter *Reporter, - const HeaderMapCollector::RegexHeaderMap *RegexHeaderMap = nullptr) - : Reporter(Reporter), RegexHeaderMap(RegexHeaderMap) {} - - clang::FrontendAction *create() override { - return new FindAllSymbolsAction(Reporter, RegexHeaderMap); - } - -private: - SymbolReporter *const Reporter; - const HeaderMapCollector::RegexHeaderMap *const RegexHeaderMap; -}; - -} // namespace find_all_symbols -} // namespace clang - -#endif // LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_FIND_ALL_SYMBOLS_ACTION_H Index: include-fixer/find-all-symbols/FindAllSymbolsAction.cpp =================================================================== --- include-fixer/find-all-symbols/FindAllSymbolsAction.cpp +++ include-fixer/find-all-symbols/FindAllSymbolsAction.cpp @@ -1,36 +0,0 @@ -//===-- FindAllSymbolsAction.cpp - find all symbols action --------*- C++ -*-===// -// -// 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 "FindAllSymbolsAction.h" -#include "FindAllMacros.h" -#include "clang/Lex/PPCallbacks.h" -#include "clang/Lex/Preprocessor.h" -#include "llvm/ADT/STLExtras.h" - -namespace clang { -namespace find_all_symbols { - -FindAllSymbolsAction::FindAllSymbolsAction( - SymbolReporter *Reporter, - const HeaderMapCollector::RegexHeaderMap *RegexHeaderMap) - : Reporter(Reporter), Collector(RegexHeaderMap), Handler(&Collector), - Matcher(Reporter, &Collector) { - Matcher.registerMatchers(&MatchFinder); -} - -std::unique_ptr -FindAllSymbolsAction::CreateASTConsumer(CompilerInstance &Compiler, - StringRef InFile) { - Compiler.getPreprocessor().addCommentHandler(&Handler); - Compiler.getPreprocessor().addPPCallbacks(llvm::make_unique( - Reporter, &Compiler.getSourceManager(), &Collector)); - return MatchFinder.newASTConsumer(); -} - -} // namespace find_all_symbols -} // namespace clang Index: include-fixer/find-all-symbols/HeaderMapCollector.h =================================================================== --- include-fixer/find-all-symbols/HeaderMapCollector.h +++ include-fixer/find-all-symbols/HeaderMapCollector.h @@ -1,56 +0,0 @@ -//===-- HeaderMapCoolector.h - find all symbols------------------*- C++ -*-===// -// -// 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 -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_HEADER_MAP_COLLECTOR_H -#define LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_HEADER_MAP_COLLECTOR_H - -#include "llvm/ADT/StringMap.h" -#include "llvm/Support/Regex.h" -#include -#include - -namespace clang { -namespace find_all_symbols { - -/// \brief HeaderMappCollector collects all remapping header files. This maps -/// complete header names or header name regex patterns to header names. -class HeaderMapCollector { -public: - typedef llvm::StringMap HeaderMap; - typedef std::vector> RegexHeaderMap; - - HeaderMapCollector() = default; - explicit HeaderMapCollector(const RegexHeaderMap *RegexHeaderMappingTable); - - void addHeaderMapping(llvm::StringRef OrignalHeaderPath, - llvm::StringRef MappingHeaderPath) { - HeaderMappingTable[OrignalHeaderPath] = MappingHeaderPath; - }; - - /// Check if there is a mapping from \p Header or a regex pattern that matches - /// it to another header name. - /// \param Header A header name. - /// \return \p Header itself if there is no mapping for it; otherwise, return - /// a mapped header name. - llvm::StringRef getMappedHeader(llvm::StringRef Header) const; - -private: - /// A string-to-string map saving the mapping relationship. - HeaderMap HeaderMappingTable; - - // A map from header patterns to header names. - // The header names are not owned. This is only threadsafe because the regexes - // never fail. - mutable std::vector> - RegexHeaderMappingTable; -}; - -} // namespace find_all_symbols -} // namespace clang - -#endif // LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_HEADER_MAP_COLLECTOR_H Index: include-fixer/find-all-symbols/HeaderMapCollector.cpp =================================================================== --- include-fixer/find-all-symbols/HeaderMapCollector.cpp +++ include-fixer/find-all-symbols/HeaderMapCollector.cpp @@ -1,44 +0,0 @@ -//===-- HeaderMapCoolector.h - find all symbols------------------*- C++ -*-===// -// -// 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 "HeaderMapCollector.h" -#include "llvm/Support/Regex.h" - -namespace clang { -namespace find_all_symbols { - -HeaderMapCollector::HeaderMapCollector( - const RegexHeaderMap *RegexHeaderMappingTable) { - assert(RegexHeaderMappingTable); - this->RegexHeaderMappingTable.reserve(RegexHeaderMappingTable->size()); - for (const auto &Entry : *RegexHeaderMappingTable) { - this->RegexHeaderMappingTable.emplace_back(llvm::Regex(Entry.first), - Entry.second); - } -} - -llvm::StringRef -HeaderMapCollector::getMappedHeader(llvm::StringRef Header) const { - auto Iter = HeaderMappingTable.find(Header); - if (Iter != HeaderMappingTable.end()) - return Iter->second; - // If there is no complete header name mapping for this header, check the - // regex header mapping. - for (auto &Entry : RegexHeaderMappingTable) { -#ifndef NDEBUG - std::string Dummy; - assert(Entry.first.isValid(Dummy) && "Regex should never be invalid!"); -#endif - if (Entry.first.match(Header)) - return Entry.second; - } - return Header; -} - -} // namespace find_all_symbols -} // namespace clang Index: include-fixer/find-all-symbols/PathConfig.h =================================================================== --- include-fixer/find-all-symbols/PathConfig.h +++ include-fixer/find-all-symbols/PathConfig.h @@ -1,36 +0,0 @@ -//===-- PathConfig.h - Process paths of symbols -----------------*- C++ -*-===// -// -// -// 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 -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_PATH_CONFIG_H -#define LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_PATH_CONFIG_H - -#include "HeaderMapCollector.h" -#include "clang/Basic/SourceManager.h" -#include - -namespace clang { -namespace find_all_symbols { - -/// \brief This calculates the include path for \p Loc. -/// -/// \param SM SourceManager. -/// \param Loc A SourceLocation. -/// \param Collector An optional header mapping collector. -/// -/// \return The file path (or mapped file path if Collector is provided) of the -/// header that includes \p Loc. If \p Loc comes from .inc header file, \p Loc -/// is set to the location from which the .inc header file is included. If \p -/// Loc is invalid or comes from a main file, this returns an empty string. -std::string getIncludePath(const SourceManager &SM, SourceLocation Loc, - const HeaderMapCollector *Collector = nullptr); - -} // namespace find_all_symbols -} // namespace clang - -#endif // LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_PATH_CONFIG_H Index: include-fixer/find-all-symbols/PathConfig.cpp =================================================================== --- include-fixer/find-all-symbols/PathConfig.cpp +++ include-fixer/find-all-symbols/PathConfig.cpp @@ -1,41 +0,0 @@ -//===-- PathConfig.cpp - Process paths of symbols ---------------*- C++ -*-===// -// -// -// 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 "PathConfig.h" -#include "llvm/Support/Path.h" - -namespace clang { -namespace find_all_symbols { - -std::string getIncludePath(const SourceManager &SM, SourceLocation Loc, - const HeaderMapCollector *Collector) { - llvm::StringRef FilePath; - // Walk up the include stack to skip .inc files. - while (true) { - if (!Loc.isValid() || SM.isInMainFile(Loc)) - return ""; - FilePath = SM.getFilename(Loc); - if (FilePath.empty()) - return ""; - if (!FilePath.endswith(".inc")) - break; - FileID ID = SM.getFileID(Loc); - Loc = SM.getIncludeLoc(ID); - } - - if (Collector) - FilePath = Collector->getMappedHeader(FilePath); - SmallString<256> CleanedFilePath = FilePath; - llvm::sys::path::remove_dots(CleanedFilePath, /*remove_dot_dot=*/false); - - return CleanedFilePath.str(); -} - -} // namespace find_all_symbols -} // namespace clang Index: include-fixer/find-all-symbols/PragmaCommentHandler.h =================================================================== --- include-fixer/find-all-symbols/PragmaCommentHandler.h +++ include-fixer/find-all-symbols/PragmaCommentHandler.h @@ -1,40 +0,0 @@ -//===-- PragmaCommentHandler.h - find all symbols----------------*- C++ -*-===// -// -// 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 -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_PRAGMA_COMMENT_HANDLER_H -#define LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_PRAGMA_COMMENT_HANDLER_H - -#include "clang/Basic/SourceLocation.h" -#include "clang/Lex/Preprocessor.h" -#include - -namespace clang { -namespace find_all_symbols { - -class HeaderMapCollector; - -/// \brief PragmaCommentHandler parses pragma comment on include files to -/// determine when we should include a different header from the header that -/// directly defines a symbol. -/// -/// Currently it only supports IWYU private pragma: -/// https://github.com/include-what-you-use/include-what-you-use/blob/master/docs/IWYUPragmas.md#iwyu-pragma-private -class PragmaCommentHandler : public clang::CommentHandler { -public: - PragmaCommentHandler(HeaderMapCollector *Collector) : Collector(Collector) {} - - bool HandleComment(Preprocessor &PP, SourceRange Range) override; - -private: - HeaderMapCollector *const Collector; -}; - -} // namespace find_all_symbols -} // namespace clang - -#endif // LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_PRAGMA_COMMENT_HANDLER_H Index: include-fixer/find-all-symbols/PragmaCommentHandler.cpp =================================================================== --- include-fixer/find-all-symbols/PragmaCommentHandler.cpp +++ include-fixer/find-all-symbols/PragmaCommentHandler.cpp @@ -1,36 +0,0 @@ -//===-- PragmaCommentHandler.cpp - find all symbols -----------------------===// -// -// 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 "PragmaCommentHandler.h" -#include "FindAllSymbols.h" -#include "HeaderMapCollector.h" -#include "clang/Lex/Preprocessor.h" -#include "llvm/Support/Regex.h" - -namespace clang { -namespace find_all_symbols { -namespace { -const char IWYUPragma[] = "// IWYU pragma: private, include "; -} // namespace - -bool PragmaCommentHandler::HandleComment(Preprocessor &PP, SourceRange Range) { - StringRef Text = - Lexer::getSourceText(CharSourceRange::getCharRange(Range), - PP.getSourceManager(), PP.getLangOpts()); - size_t Pos = Text.find(IWYUPragma); - if (Pos == StringRef::npos) - return false; - StringRef RemappingFilePath = Text.substr(Pos + std::strlen(IWYUPragma)); - Collector->addHeaderMapping( - PP.getSourceManager().getFilename(Range.getBegin()), - RemappingFilePath.trim("\"<>")); - return false; -} - -} // namespace find_all_symbols -} // namespace clang Index: include-fixer/find-all-symbols/STLPostfixHeaderMap.h =================================================================== --- include-fixer/find-all-symbols/STLPostfixHeaderMap.h +++ include-fixer/find-all-symbols/STLPostfixHeaderMap.h @@ -1,22 +0,0 @@ -//===-- STLPostfixHeaderMap.h - hardcoded header map for STL ----*- C++ -*-===// -// -// 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 -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_TOOL_STL_POSTFIX_HEADER_MAP_H -#define LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_TOOL_STL_POSTFIX_HEADER_MAP_H - -#include "HeaderMapCollector.h" - -namespace clang { -namespace find_all_symbols { - -const HeaderMapCollector::RegexHeaderMap *getSTLPostfixHeaderMap(); - -} // namespace find_all_symbols -} // namespace clang - -#endif // LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_TOOL_STL_POSTFIX_HEADER_MAP_H Index: include-fixer/find-all-symbols/STLPostfixHeaderMap.cpp =================================================================== --- include-fixer/find-all-symbols/STLPostfixHeaderMap.cpp +++ include-fixer/find-all-symbols/STLPostfixHeaderMap.cpp @@ -1,653 +0,0 @@ -//===-- STLPostfixHeaderMap.h - hardcoded STL header map --------*- C++ -*-===// -// -// 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 "STLPostfixHeaderMap.h" - -namespace clang { -namespace find_all_symbols { - -const HeaderMapCollector::RegexHeaderMap *getSTLPostfixHeaderMap() { - static const HeaderMapCollector::RegexHeaderMap STLPostfixHeaderMap = { - {"include/__stddef_max_align_t.h$", ""}, - {"include/__wmmintrin_aes.h$", ""}, - {"include/__wmmintrin_pclmul.h$", ""}, - {"include/adxintrin.h$", ""}, - {"include/ammintrin.h$", ""}, - {"include/avx2intrin.h$", ""}, - {"include/avx512bwintrin.h$", ""}, - {"include/avx512cdintrin.h$", ""}, - {"include/avx512dqintrin.h$", ""}, - {"include/avx512erintrin.h$", ""}, - {"include/avx512fintrin.h$", ""}, - {"include/avx512ifmaintrin.h$", ""}, - {"include/avx512ifmavlintrin.h$", ""}, - {"include/avx512pfintrin.h$", ""}, - {"include/avx512vbmiintrin.h$", ""}, - {"include/avx512vbmivlintrin.h$", ""}, - {"include/avx512vlbwintrin.h$", ""}, - {"include/avx512vlcdintrin.h$", ""}, - {"include/avx512vldqintrin.h$", ""}, - {"include/avx512vlintrin.h$", ""}, - {"include/avxintrin.h$", ""}, - {"include/bmi2intrin.h$", ""}, - {"include/bmiintrin.h$", ""}, - {"include/emmintrin.h$", ""}, - {"include/f16cintrin.h$", ""}, - {"include/float.h$", ""}, - {"include/fma4intrin.h$", ""}, - {"include/fmaintrin.h$", ""}, - {"include/fxsrintrin.h$", ""}, - {"include/ia32intrin.h$", ""}, - {"include/immintrin.h$", ""}, - {"include/inttypes.h$", ""}, - {"include/limits.h$", ""}, - {"include/lzcntintrin.h$", ""}, - {"include/mm3dnow.h$", ""}, - {"include/mm_malloc.h$", ""}, - {"include/mmintrin.h$", ""}, - {"include/mwaitxintrin.h$", ""}, - {"include/pkuintrin.h$", ""}, - {"include/pmmintrin.h$", ""}, - {"include/popcntintrin.h$", ""}, - {"include/prfchwintrin.h$", ""}, - {"include/rdseedintrin.h$", ""}, - {"include/rtmintrin.h$", ""}, - {"include/shaintrin.h$", ""}, - {"include/smmintrin.h$", ""}, - {"include/stdalign.h$", ""}, - {"include/stdarg.h$", ""}, - {"include/stdbool.h$", ""}, - {"include/stddef.h$", ""}, - {"include/stdint.h$", ""}, - {"include/tbmintrin.h$", ""}, - {"include/tmmintrin.h$", ""}, - {"include/wmmintrin.h$", ""}, - {"include/x86intrin.h$", ""}, - {"include/xmmintrin.h$", ""}, - {"include/xopintrin.h$", ""}, - {"include/xsavecintrin.h$", ""}, - {"include/xsaveintrin.h$", ""}, - {"include/xsaveoptintrin.h$", ""}, - {"include/xsavesintrin.h$", ""}, - {"include/xtestintrin.h$", ""}, - {"include/_G_config.h$", ""}, - {"include/assert.h$", ""}, - {"algorithm$", ""}, - {"array$", ""}, - {"atomic$", ""}, - {"backward/auto_ptr.h$", ""}, - {"backward/binders.h$", ""}, - {"bits/algorithmfwd.h$", ""}, - {"bits/alloc_traits.h$", ""}, - {"bits/allocator.h$", ""}, - {"bits/atomic_base.h$", ""}, - {"bits/atomic_lockfree_defines.h$", ""}, - {"bits/basic_ios.h$", ""}, - {"bits/basic_ios.tcc$", ""}, - {"bits/basic_string.h$", ""}, - {"bits/basic_string.tcc$", ""}, - {"bits/char_traits.h$", ""}, - {"bits/codecvt.h$", ""}, - {"bits/concept_check.h$", ""}, - {"bits/cpp_type_traits.h$", ""}, - {"bits/cxxabi_forced.h$", ""}, - {"bits/deque.tcc$", ""}, - {"bits/exception_defines.h$", ""}, - {"bits/exception_ptr.h$", ""}, - {"bits/forward_list.h$", ""}, - {"bits/forward_list.tcc$", ""}, - {"bits/fstream.tcc$", ""}, - {"bits/functexcept.h$", ""}, - {"bits/functional_hash.h$", ""}, - {"bits/gslice.h$", ""}, - {"bits/gslice_array.h$", ""}, - {"bits/hash_bytes.h$", ""}, - {"bits/hashtable.h$", ""}, - {"bits/hashtable_policy.h$", ""}, - {"bits/indirect_array.h$", ""}, - {"bits/ios_base.h$", ""}, - {"bits/istream.tcc$", ""}, - {"bits/list.tcc$", ""}, - {"bits/locale_classes.h$", ""}, - {"bits/locale_classes.tcc$", ""}, - {"bits/locale_facets.h$", ""}, - {"bits/locale_facets.tcc$", ""}, - {"bits/locale_facets_nonio.h$", ""}, - {"bits/locale_facets_nonio.tcc$", ""}, - {"bits/localefwd.h$", ""}, - {"bits/mask_array.h$", ""}, - {"bits/memoryfwd.h$", ""}, - {"bits/move.h$", ""}, - {"bits/nested_exception.h$", ""}, - {"bits/ostream.tcc$", ""}, - {"bits/ostream_insert.h$", ""}, - {"bits/postypes.h$", ""}, - {"bits/ptr_traits.h$", ""}, - {"bits/random.h$", ""}, - {"bits/random.tcc$", ""}, - {"bits/range_access.h$", ""}, - {"bits/regex.h$", ""}, - {"bits/regex_compiler.h$", ""}, - {"bits/regex_constants.h$", ""}, - {"bits/regex_cursor.h$", ""}, - {"bits/regex_error.h$", ""}, - {"bits/regex_grep_matcher.h$", ""}, - {"bits/regex_grep_matcher.tcc$", ""}, - {"bits/regex_nfa.h$", ""}, - {"bits/shared_ptr.h$", ""}, - {"bits/shared_ptr_base.h$", ""}, - {"bits/slice_array.h$", ""}, - {"bits/sstream.tcc$", ""}, - {"bits/stl_algo.h$", ""}, - {"bits/stl_algobase.h$", ""}, - {"bits/stl_bvector.h$", ""}, - {"bits/stl_construct.h$", ""}, - {"bits/stl_deque.h$", ""}, - {"bits/stl_function.h$", ""}, - {"bits/stl_heap.h$", ""}, - {"bits/stl_iterator.h$", ""}, - {"bits/stl_iterator_base_funcs.h$", ""}, - {"bits/stl_iterator_base_types.h$", ""}, - {"bits/stl_list.h$", ""}, - {"bits/stl_map.h$", ""}, - {"bits/stl_multimap.h$", ""}, - {"bits/stl_multiset.h$", ""}, - {"bits/stl_numeric.h$", ""}, - {"bits/stl_pair.h$", ""}, - {"bits/stl_queue.h$", ""}, - {"bits/stl_raw_storage_iter.h$", ""}, - {"bits/stl_relops.h$", ""}, - {"bits/stl_set.h$", ""}, - {"bits/stl_stack.h$", ""}, - {"bits/stl_tempbuf.h$", ""}, - {"bits/stl_tree.h$", ""}, - {"bits/stl_uninitialized.h$", ""}, - {"bits/stl_vector.h$", ""}, - {"bits/stream_iterator.h$", ""}, - {"bits/streambuf.tcc$", ""}, - {"bits/streambuf_iterator.h$", ""}, - {"bits/stringfwd.h$", ""}, - {"bits/unique_ptr.h$", ""}, - {"bits/unordered_map.h$", ""}, - {"bits/unordered_set.h$", ""}, - {"bits/uses_allocator.h$", ""}, - {"bits/valarray_after.h$", ""}, - {"bits/valarray_array.h$", ""}, - {"bits/valarray_array.tcc$", ""}, - {"bits/valarray_before.h$", ""}, - {"bits/vector.tcc$", ""}, - {"bitset$", ""}, - {"ccomplex$", ""}, - {"cctype$", ""}, - {"cerrno$", ""}, - {"cfenv$", ""}, - {"cfloat$", ""}, - {"chrono$", ""}, - {"cinttypes$", ""}, - {"climits$", ""}, - {"clocale$", ""}, - {"cmath$", ""}, - {"complex$", ""}, - {"complex.h$", ""}, - {"condition_variable$", ""}, - {"csetjmp$", ""}, - {"csignal$", ""}, - {"cstdalign$", ""}, - {"cstdarg$", ""}, - {"cstdbool$", ""}, - {"cstdint$", ""}, - {"cstdio$", ""}, - {"cstdlib$", ""}, - {"cstring$", ""}, - {"ctgmath$", ""}, - {"ctime$", ""}, - {"cwchar$", ""}, - {"cwctype$", ""}, - {"cxxabi.h$", ""}, - {"debug/debug.h$", ""}, - {"debug/map.h$", ""}, - {"debug/multimap.h$", ""}, - {"debug/multiset.h$", ""}, - {"debug/set.h$", ""}, - {"deque$", ""}, - {"exception$", ""}, - {"ext/alloc_traits.h$", ""}, - {"ext/atomicity.h$", ""}, - {"ext/concurrence.h$", ""}, - {"ext/new_allocator.h$", ""}, - {"ext/numeric_traits.h$", ""}, - {"ext/string_conversions.h$", ""}, - {"ext/type_traits.h$", ""}, - {"fenv.h$", ""}, - {"forward_list$", ""}, - {"fstream$", ""}, - {"functional$", ""}, - {"future$", ""}, - {"initializer_list$", ""}, - {"iomanip$", ""}, - {"ios$", ""}, - {"iosfwd$", ""}, - {"iostream$", ""}, - {"istream$", ""}, - {"iterator$", ""}, - {"limits$", ""}, - {"list$", ""}, - {"locale$", ""}, - {"map$", ""}, - {"memory$", ""}, - {"mutex$", ""}, - {"new$", ""}, - {"numeric$", ""}, - {"ostream$", ""}, - {"queue$", ""}, - {"random$", ""}, - {"ratio$", ""}, - {"regex$", ""}, - {"scoped_allocator$", ""}, - {"set$", ""}, - {"sstream$", ""}, - {"stack$", ""}, - {"stdexcept$", ""}, - {"streambuf$", ""}, - {"string$", ""}, - {"system_error$", ""}, - {"tgmath.h$", ""}, - {"thread$", ""}, - {"tuple$", ""}, - {"type_traits$", ""}, - {"typeindex$", ""}, - {"typeinfo$", ""}, - {"unordered_map$", ""}, - {"unordered_set$", ""}, - {"utility$", ""}, - {"valarray$", ""}, - {"vector$", ""}, - {"include/complex.h$", ""}, - {"include/ctype.h$", ""}, - {"include/errno.h$", ""}, - {"include/fenv.h$", ""}, - {"include/inttypes.h$", ""}, - {"include/libio.h$", ""}, - {"include/limits.h$", ""}, - {"include/locale.h$", ""}, - {"include/math.h$", ""}, - {"include/setjmp.h$", ""}, - {"include/signal.h$", ""}, - {"include/stdint.h$", ""}, - {"include/stdio.h$", ""}, - {"include/stdlib.h$", ""}, - {"include/string.h$", ""}, - {"include/time.h$", ""}, - {"include/wchar.h$", ""}, - {"include/wctype.h$", ""}, - {"bits/cmathcalls.h$", ""}, - {"bits/errno.h$", ""}, - {"bits/fenv.h$", ""}, - {"bits/huge_val.h$", ""}, - {"bits/huge_valf.h$", ""}, - {"bits/huge_vall.h$", ""}, - {"bits/inf.h$", ""}, - {"bits/local_lim.h$", ""}, - {"bits/locale.h$", ""}, - {"bits/mathcalls.h$", ""}, - {"bits/mathdef.h$", ""}, - {"bits/nan.h$", ""}, - {"bits/posix1_lim.h$", ""}, - {"bits/posix2_lim.h$", ""}, - {"bits/setjmp.h$", ""}, - {"bits/sigaction.h$", ""}, - {"bits/sigcontext.h$", ""}, - {"bits/siginfo.h$", ""}, - {"bits/signum.h$", ""}, - {"bits/sigset.h$", ""}, - {"bits/sigstack.h$", ""}, - {"bits/stdio_lim.h$", ""}, - {"bits/sys_errlist.h$", ""}, - {"bits/time.h$", ""}, - {"bits/timex.h$", ""}, - {"bits/typesizes.h$", ""}, - {"bits/wchar.h$", ""}, - {"bits/wordsize.h$", ""}, - {"bits/xopen_lim.h$", ""}, - {"include/xlocale.h$", ""}, - {"bits/atomic_word.h$", ""}, - {"bits/basic_file.h$", ""}, - {"bits/c\\+\\+allocator.h$", ""}, - {"bits/c\\+\\+config.h$", ""}, - {"bits/c\\+\\+io.h$", ""}, - {"bits/c\\+\\+locale.h$", ""}, - {"bits/cpu_defines.h$", ""}, - {"bits/ctype_base.h$", ""}, - {"bits/cxxabi_tweaks.h$", ""}, - {"bits/error_constants.h$", ""}, - {"bits/gthr-default.h$", ""}, - {"bits/gthr.h$", ""}, - {"bits/opt_random.h$", ""}, - {"bits/os_defines.h$", ""}, - // GNU C headers - {"include/aio.h$", ""}, - {"include/aliases.h$", ""}, - {"include/alloca.h$", ""}, - {"include/ar.h$", ""}, - {"include/argp.h$", ""}, - {"include/argz.h$", ""}, - {"include/arpa/nameser.h$", ""}, - {"include/arpa/nameser_compat.h$", ""}, - {"include/byteswap.h$", ""}, - {"include/cpio.h$", ""}, - {"include/crypt.h$", ""}, - {"include/dirent.h$", ""}, - {"include/dlfcn.h$", ""}, - {"include/elf.h$", ""}, - {"include/endian.h$", ""}, - {"include/envz.h$", ""}, - {"include/err.h$", ""}, - {"include/error.h$", ""}, - {"include/execinfo.h$", ""}, - {"include/fcntl.h$", ""}, - {"include/features.h$", ""}, - {"include/fenv.h$", ""}, - {"include/fmtmsg.h$", ""}, - {"include/fnmatch.h$", ""}, - {"include/fstab.h$", ""}, - {"include/fts.h$", ""}, - {"include/ftw.h$", ""}, - {"include/gconv.h$", ""}, - {"include/getopt.h$", ""}, - {"include/glob.h$", ""}, - {"include/grp.h$", ""}, - {"include/gshadow.h$", ""}, - {"include/iconv.h$", ""}, - {"include/ifaddrs.h$", ""}, - {"include/kdb.h$", ""}, - {"include/langinfo.h$", ""}, - {"include/libgen.h$", ""}, - {"include/libintl.h$", ""}, - {"include/link.h$", ""}, - {"include/malloc.h$", ""}, - {"include/mcheck.h$", ""}, - {"include/memory.h$", ""}, - {"include/mntent.h$", ""}, - {"include/monetary.h$", ""}, - {"include/mqueue.h$", ""}, - {"include/netdb.h$", ""}, - {"include/netinet/in.h$", ""}, - {"include/nl_types.h$", ""}, - {"include/nss.h$", ""}, - {"include/obstack.h$", ""}, - {"include/panel.h$", ""}, - {"include/paths.h$", ""}, - {"include/printf.h$", ""}, - {"include/profile.h$", ""}, - {"include/pthread.h$", ""}, - {"include/pty.h$", ""}, - {"include/pwd.h$", ""}, - {"include/re_comp.h$", ""}, - {"include/regex.h$", ""}, - {"include/regexp.h$", ""}, - {"include/resolv.h$", ""}, - {"include/rpc/netdb.h$", ""}, - {"include/sched.h$", ""}, - {"include/search.h$", ""}, - {"include/semaphore.h$", ""}, - {"include/sgtty.h$", ""}, - {"include/shadow.h$", ""}, - {"include/spawn.h$", ""}, - {"include/stab.h$", ""}, - {"include/stdc-predef.h$", ""}, - {"include/stdio_ext.h$", ""}, - {"include/strings.h$", ""}, - {"include/stropts.h$", ""}, - {"include/sudo_plugin.h$", ""}, - {"include/sysexits.h$", ""}, - {"include/tar.h$", ""}, - {"include/tcpd.h$", ""}, - {"include/term.h$", ""}, - {"include/term_entry.h$", ""}, - {"include/termcap.h$", ""}, - {"include/termios.h$", ""}, - {"include/thread_db.h$", ""}, - {"include/tic.h$", ""}, - {"include/ttyent.h$", ""}, - {"include/uchar.h$", ""}, - {"include/ucontext.h$", ""}, - {"include/ulimit.h$", ""}, - {"include/unctrl.h$", ""}, - {"include/unistd.h$", ""}, - {"include/utime.h$", ""}, - {"include/utmp.h$", ""}, - {"include/utmpx.h$", ""}, - {"include/values.h$", ""}, - {"include/wordexp.h$", ""}, - {"fpu_control.h$", ""}, - {"ieee754.h$", ""}, - {"include/xlocale.h$", ""}, - {"gnu/lib-names.h$", ""}, - {"gnu/libc-version.h$", ""}, - {"gnu/option-groups.h$", ""}, - {"gnu/stubs-32.h$", ""}, - {"gnu/stubs-64.h$", ""}, - {"gnu/stubs-x32.h$", ""}, - {"include/rpc/auth_des.h$", ""}, - {"include/rpc/rpc_msg.h$", ""}, - {"include/rpc/pmap_clnt.h$", ""}, - {"include/rpc/rpc.h$", ""}, - {"include/rpc/types.h$", ""}, - {"include/rpc/auth_unix.h$", ""}, - {"include/rpc/key_prot.h$", ""}, - {"include/rpc/pmap_prot.h$", ""}, - {"include/rpc/auth.h$", ""}, - {"include/rpc/svc_auth.h$", ""}, - {"include/rpc/xdr.h$", ""}, - {"include/rpc/pmap_rmt.h$", ""}, - {"include/rpc/des_crypt.h$", ""}, - {"include/rpc/svc.h$", ""}, - {"include/rpc/rpc_des.h$", ""}, - {"include/rpc/clnt.h$", ""}, - {"include/scsi/scsi.h$", ""}, - {"include/scsi/sg.h$", ""}, - {"include/scsi/scsi_ioctl.h$", ""}, - {"include/netrose/rose.h$", ""}, - {"include/nfs/nfs.h$", ""}, - {"include/netatalk/at.h$", ""}, - {"include/netinet/ether.h$", ""}, - {"include/netinet/icmp6.h$", ""}, - {"include/netinet/if_ether.h$", ""}, - {"include/netinet/if_fddi.h$", ""}, - {"include/netinet/if_tr.h$", ""}, - {"include/netinet/igmp.h$", ""}, - {"include/netinet/in.h$", ""}, - {"include/netinet/in_systm.h$", ""}, - {"include/netinet/ip.h$", ""}, - {"include/netinet/ip6.h$", ""}, - {"include/netinet/ip_icmp.h$", ""}, - {"include/netinet/tcp.h$", ""}, - {"include/netinet/udp.h$", ""}, - {"include/netrom/netrom.h$", ""}, - {"include/protocols/routed.h$", ""}, - {"include/protocols/rwhod.h$", ""}, - {"include/protocols/talkd.h$", ""}, - {"include/protocols/timed.h$", ""}, - {"include/rpcsvc/klm_prot.x$", ""}, - {"include/rpcsvc/rstat.h$", ""}, - {"include/rpcsvc/spray.x$", ""}, - {"include/rpcsvc/nlm_prot.x$", ""}, - {"include/rpcsvc/nis_callback.x$", ""}, - {"include/rpcsvc/yp.h$", ""}, - {"include/rpcsvc/yp.x$", ""}, - {"include/rpcsvc/nfs_prot.h$", ""}, - {"include/rpcsvc/rex.h$", ""}, - {"include/rpcsvc/yppasswd.h$", ""}, - {"include/rpcsvc/rex.x$", ""}, - {"include/rpcsvc/nis_tags.h$", ""}, - {"include/rpcsvc/nis_callback.h$", ""}, - {"include/rpcsvc/nfs_prot.x$", ""}, - {"include/rpcsvc/bootparam_prot.x$", ""}, - {"include/rpcsvc/rusers.x$", ""}, - {"include/rpcsvc/rquota.x$", ""}, - {"include/rpcsvc/nis.h$", ""}, - {"include/rpcsvc/nislib.h$", ""}, - {"include/rpcsvc/ypupd.h$", ""}, - {"include/rpcsvc/bootparam.h$", ""}, - {"include/rpcsvc/spray.h$", ""}, - {"include/rpcsvc/key_prot.h$", ""}, - {"include/rpcsvc/klm_prot.h$", ""}, - {"include/rpcsvc/sm_inter.h$", ""}, - {"include/rpcsvc/nlm_prot.h$", ""}, - {"include/rpcsvc/yp_prot.h$", ""}, - {"include/rpcsvc/ypclnt.h$", ""}, - {"include/rpcsvc/rstat.x$", ""}, - {"include/rpcsvc/rusers.h$", ""}, - {"include/rpcsvc/key_prot.x$", ""}, - {"include/rpcsvc/sm_inter.x$", ""}, - {"include/rpcsvc/rquota.h$", ""}, - {"include/rpcsvc/nis.x$", ""}, - {"include/rpcsvc/bootparam_prot.h$", ""}, - {"include/rpcsvc/mount.h$", ""}, - {"include/rpcsvc/mount.x$", ""}, - {"include/rpcsvc/nis_object.x$", ""}, - {"include/rpcsvc/yppasswd.x$", ""}, - {"sys/acct.h$", ""}, - {"sys/auxv.h$", ""}, - {"sys/cdefs.h$", ""}, - {"sys/debugreg.h$", ""}, - {"sys/dir.h$", ""}, - {"sys/elf.h$", ""}, - {"sys/epoll.h$", ""}, - {"sys/eventfd.h$", ""}, - {"sys/fanotify.h$", ""}, - {"sys/file.h$", ""}, - {"sys/fsuid.h$", ""}, - {"sys/gmon.h$", ""}, - {"sys/gmon_out.h$", ""}, - {"sys/inotify.h$", ""}, - {"sys/io.h$", ""}, - {"sys/ioctl.h$", ""}, - {"sys/ipc.h$", ""}, - {"sys/kd.h$", ""}, - {"sys/kdaemon.h$", ""}, - {"sys/klog.h$", ""}, - {"sys/mman.h$", ""}, - {"sys/mount.h$", ""}, - {"sys/msg.h$", ""}, - {"sys/mtio.h$", ""}, - {"sys/param.h$", ""}, - {"sys/pci.h$", ""}, - {"sys/perm.h$", ""}, - {"sys/personality.h$", ""}, - {"sys/poll.h$", ""}, - {"sys/prctl.h$", ""}, - {"sys/procfs.h$", ""}, - {"sys/profil.h$", ""}, - {"sys/ptrace.h$", ""}, - {"sys/queue.h$", ""}, - {"sys/quota.h$", ""}, - {"sys/raw.h$", ""}, - {"sys/reboot.h$", ""}, - {"sys/reg.h$", ""}, - {"sys/resource.h$", ""}, - {"sys/select.h$", ""}, - {"sys/sem.h$", ""}, - {"sys/sendfile.h$", ""}, - {"sys/shm.h$", ""}, - {"sys/signalfd.h$", ""}, - {"sys/socket.h$", ""}, - {"sys/stat.h$", ""}, - {"sys/statfs.h$", ""}, - {"sys/statvfs.h$", ""}, - {"sys/swap.h$", ""}, - {"sys/syscall.h$", ""}, - {"sys/sysctl.h$", ""}, - {"sys/sysinfo.h$", ""}, - {"sys/syslog.h$", ""}, - {"sys/sysmacros.h$", ""}, - {"sys/termios.h$", ""}, - {"sys/time.h$", ""}, - {"sys/timeb.h$", ""}, - {"sys/timerfd.h$", ""}, - {"sys/times.h$", ""}, - {"sys/timex.h$", ""}, - {"sys/ttychars.h$", ""}, - {"sys/ttydefaults.h$", ""}, - {"sys/types.h$", ""}, - {"sys/ucontext.h$", ""}, - {"sys/uio.h$", ""}, - {"sys/un.h$", ""}, - {"sys/user.h$", ""}, - {"sys/ustat.h$", ""}, - {"sys/utsname.h$", ""}, - {"sys/vlimit.h$", ""}, - {"sys/vm86.h$", ""}, - {"sys/vtimes.h$", ""}, - {"sys/wait.h$", ""}, - {"sys/xattr.h$", ""}, - {"bits/epoll.h$", ""}, - {"bits/eventfd.h$", ""}, - {"bits/inotify.h$", ""}, - {"bits/ipc.h$", ""}, - {"bits/ipctypes.h$", ""}, - {"bits/mman-linux.h$", ""}, - {"bits/mman.h$", ""}, - {"bits/msq.h$", ""}, - {"bits/resource.h$", ""}, - {"bits/sem.h$", ""}, - {"bits/shm.h$", ""}, - {"bits/signalfd.h$", ""}, - {"bits/statfs.h$", ""}, - {"bits/statvfs.h$", ""}, - {"bits/timerfd.h$", ""}, - {"bits/utsname.h$", ""}, - {"bits/auxv.h$", ""}, - {"bits/byteswap-16.h$", ""}, - {"bits/byteswap.h$", ""}, - {"bits/confname.h$", ""}, - {"bits/dirent.h$", ""}, - {"bits/dlfcn.h$", ""}, - {"bits/elfclass.h$", ""}, - {"bits/endian.h$", ""}, - {"bits/environments.h$", ""}, - {"bits/fcntl-linux.h$", ""}, - {"bits/fcntl.h$", ""}, - {"bits/in.h$", ""}, - {"bits/ioctl-types.h$", ""}, - {"bits/ioctls.h$", ""}, - {"bits/link.h$", ""}, - {"bits/mqueue.h$", ""}, - {"bits/netdb.h$", ""}, - {"bits/param.h$", ""}, - {"bits/poll.h$", ""}, - {"bits/posix_opt.h$", ""}, - {"bits/pthreadtypes.h$", ""}, - {"bits/sched.h$", ""}, - {"bits/select.h$", ""}, - {"bits/semaphore.h$", ""}, - {"bits/sigthread.h$", ""}, - {"bits/sockaddr.h$", ""}, - {"bits/socket.h$", ""}, - {"bits/socket_type.h$", ""}, - {"bits/stab.def$", ""}, - {"bits/stat.h$", ""}, - {"bits/stropts.h$", ""}, - {"bits/syscall.h$", ""}, - {"bits/syslog-path.h$", ""}, - {"bits/termios.h$", ""}, - {"bits/types.h$", ""}, - {"bits/typesizes.h$", ""}, - {"bits/uio.h$", ""}, - {"bits/ustat.h$", ""}, - {"bits/utmp.h$", ""}, - {"bits/utmpx.h$", ""}, - {"bits/waitflags.h$", ""}, - {"bits/waitstatus.h$", ""}, - {"bits/xtitypes.h$", ""}, - }; - return &STLPostfixHeaderMap; -} - -} // namespace find_all_symbols -} // namespace clang Index: include-fixer/find-all-symbols/SymbolInfo.h =================================================================== --- include-fixer/find-all-symbols/SymbolInfo.h +++ include-fixer/find-all-symbols/SymbolInfo.h @@ -1,142 +0,0 @@ -//===-- SymbolInfo.h - Symbol Info ------------------------------*- C++ -*-===// -// -// 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 -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_FIND_ALL_SYMBOLS_SYMBOLINFO_H -#define LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_FIND_ALL_SYMBOLS_SYMBOLINFO_H - -#include "llvm/ADT/Optional.h" -#include "llvm/ADT/StringRef.h" -#include "llvm/Support/YAMLTraits.h" -#include "llvm/Support/raw_ostream.h" -#include -#include -#include - -namespace clang { -namespace find_all_symbols { -/// \brief Describes a named symbol from a header. -/// Symbols with the same qualified name and type (e.g. function overloads) -/// that appear in the same header are represented by a single SymbolInfo. -/// -/// TODO: keep track of instances, e.g. overload locations and signatures. -class SymbolInfo { -public: - /// \brief The SymbolInfo Type. - enum class SymbolKind { - Function, - Class, - Variable, - TypedefName, - EnumDecl, - EnumConstantDecl, - Macro, - Unknown, - }; - - /// \brief The Context Type. - enum class ContextType { - Namespace, // Symbols declared in a namespace. - Record, // Symbols declared in a class. - EnumDecl, // Enum constants declared in a enum declaration. - }; - - /// \brief A pair of . - typedef std::pair Context; - - // \brief Signals are signals gathered by observing how a symbol is used. - // These are used to rank results. - struct Signals { - Signals() {} - Signals(unsigned Seen, unsigned Used) : Seen(Seen), Used(Used) {} - - // Number of times this symbol was visible to a TU. - unsigned Seen = 0; - - // Number of times this symbol was referenced a TU's main file. - unsigned Used = 0; - - Signals &operator+=(const Signals &RHS); - Signals operator+(const Signals &RHS) const; - bool operator==(const Signals &RHS) const; - }; - - using SignalMap = std::map; - - // The default constructor is required by YAML traits in - // LLVM_YAML_IS_DOCUMENT_LIST_VECTOR. - SymbolInfo() : Type(SymbolKind::Unknown) {} - - SymbolInfo(llvm::StringRef Name, SymbolKind Type, llvm::StringRef FilePath, - const std::vector &Contexts); - - void SetFilePath(llvm::StringRef Path) { FilePath = Path; } - - /// \brief Get symbol name. - llvm::StringRef getName() const { return Name; } - - /// \brief Get the fully-qualified symbol name. - std::string getQualifiedName() const; - - /// \brief Get symbol type. - SymbolKind getSymbolKind() const { return Type; } - - /// \brief Get a relative file path where symbol comes from. - llvm::StringRef getFilePath() const { return FilePath; } - - /// \brief Get symbol contexts. - const std::vector &getContexts() const { - return Contexts; - } - - bool operator<(const SymbolInfo &Symbol) const; - - bool operator==(const SymbolInfo &Symbol) const; - -private: - friend struct llvm::yaml::MappingTraits; - - /// \brief Identifier name. - std::string Name; - - /// \brief Symbol type. - SymbolKind Type; - - /// \brief The file path where the symbol comes from. It's a relative file - /// path based on the build directory. - std::string FilePath; - - /// \brief Contains information about symbol contexts. Context information is - /// stored from the inner-most level to outer-most level. - /// - /// For example, if a symbol 'x' is declared as: - /// namespace na { namespace nb { class A { int x; } } } - /// The contexts would be { {RECORD, "A"}, {NAMESPACE, "nb"}, {NAMESPACE, - /// "na"} }. - /// The name of an anonymous namespace is "". - /// - /// If the symbol is declared in `TranslationUnitDecl`, it has no context. - std::vector Contexts; -}; - -struct SymbolAndSignals { - SymbolInfo Symbol; - SymbolInfo::Signals Signals; - bool operator==(const SymbolAndSignals& RHS) const; -}; - -/// \brief Write SymbolInfos to a stream (YAML format). -bool WriteSymbolInfosToStream(llvm::raw_ostream &OS, - const SymbolInfo::SignalMap &Symbols); - -/// \brief Read SymbolInfos from a YAML document. -std::vector ReadSymbolInfosFromYAML(llvm::StringRef Yaml); - -} // namespace find_all_symbols -} // namespace clang - -#endif // LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_FIND_ALL_SYMBOLS_SYMBOLINFO_H Index: include-fixer/find-all-symbols/SymbolInfo.cpp =================================================================== --- include-fixer/find-all-symbols/SymbolInfo.cpp +++ include-fixer/find-all-symbols/SymbolInfo.cpp @@ -1,136 +0,0 @@ -//===-- SymbolInfo.cpp - Symbol Info ----------------------------*- C++ -*-===// -// -// 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 "SymbolInfo.h" -#include "llvm/Support/CommandLine.h" -#include "llvm/Support/FileSystem.h" -#include "llvm/Support/YAMLTraits.h" -#include "llvm/Support/raw_ostream.h" - -using llvm::yaml::MappingTraits; -using llvm::yaml::IO; -using llvm::yaml::Input; -using ContextType = clang::find_all_symbols::SymbolInfo::ContextType; -using clang::find_all_symbols::SymbolInfo; -using clang::find_all_symbols::SymbolAndSignals; -using SymbolKind = clang::find_all_symbols::SymbolInfo::SymbolKind; - -LLVM_YAML_IS_DOCUMENT_LIST_VECTOR(SymbolAndSignals) -LLVM_YAML_IS_SEQUENCE_VECTOR(SymbolInfo::Context) - -namespace llvm { -namespace yaml { -template <> struct MappingTraits { - static void mapping(IO &io, SymbolAndSignals &Symbol) { - io.mapRequired("Name", Symbol.Symbol.Name); - io.mapRequired("Contexts", Symbol.Symbol.Contexts); - io.mapRequired("FilePath", Symbol.Symbol.FilePath); - io.mapRequired("Type", Symbol.Symbol.Type); - io.mapRequired("Seen", Symbol.Signals.Seen); - io.mapRequired("Used", Symbol.Signals.Used); - } -}; - -template <> struct ScalarEnumerationTraits { - static void enumeration(IO &io, ContextType &value) { - io.enumCase(value, "Record", ContextType::Record); - io.enumCase(value, "Namespace", ContextType::Namespace); - io.enumCase(value, "EnumDecl", ContextType::EnumDecl); - } -}; - -template <> struct ScalarEnumerationTraits { - static void enumeration(IO &io, SymbolKind &value) { - io.enumCase(value, "Variable", SymbolKind::Variable); - io.enumCase(value, "Function", SymbolKind::Function); - io.enumCase(value, "Class", SymbolKind::Class); - io.enumCase(value, "TypedefName", SymbolKind::TypedefName); - io.enumCase(value, "EnumDecl", SymbolKind::EnumDecl); - io.enumCase(value, "EnumConstantDecl", SymbolKind::EnumConstantDecl); - io.enumCase(value, "Macro", SymbolKind::Macro); - io.enumCase(value, "Unknown", SymbolKind::Unknown); - } -}; - -template <> struct MappingTraits { - static void mapping(IO &io, SymbolInfo::Context &Context) { - io.mapRequired("ContextType", Context.first); - io.mapRequired("ContextName", Context.second); - } -}; - -} // namespace yaml -} // namespace llvm - -namespace clang { -namespace find_all_symbols { - -SymbolInfo::SymbolInfo(llvm::StringRef Name, SymbolKind Type, - llvm::StringRef FilePath, - const std::vector &Contexts) - : Name(Name), Type(Type), FilePath(FilePath), Contexts(Contexts) {} - -bool SymbolInfo::operator==(const SymbolInfo &Symbol) const { - return std::tie(Name, Type, FilePath, Contexts) == - std::tie(Symbol.Name, Symbol.Type, Symbol.FilePath, Symbol.Contexts); -} - -bool SymbolInfo::operator<(const SymbolInfo &Symbol) const { - return std::tie(Name, Type, FilePath, Contexts) < - std::tie(Symbol.Name, Symbol.Type, Symbol.FilePath, Symbol.Contexts); -} - -std::string SymbolInfo::getQualifiedName() const { - std::string QualifiedName = Name; - for (const auto &Context : Contexts) { - if (Context.first == ContextType::EnumDecl) - continue; - QualifiedName = Context.second + "::" + QualifiedName; - } - return QualifiedName; -} - -SymbolInfo::Signals &SymbolInfo::Signals::operator+=(const Signals &RHS) { - Seen += RHS.Seen; - Used += RHS.Used; - return *this; -} - -SymbolInfo::Signals SymbolInfo::Signals::operator+(const Signals &RHS) const { - Signals Result = *this; - Result += RHS; - return Result; -} - -bool SymbolInfo::Signals::operator==(const Signals &RHS) const { - return std::tie(Seen, Used) == std::tie(RHS.Seen, RHS.Used); -} - -bool SymbolAndSignals::operator==(const SymbolAndSignals& RHS) const { - return std::tie(Symbol, Signals) == std::tie(RHS.Symbol, RHS.Signals); -} - -bool WriteSymbolInfosToStream(llvm::raw_ostream &OS, - const SymbolInfo::SignalMap &Symbols) { - llvm::yaml::Output yout(OS); - for (const auto &Symbol : Symbols) { - SymbolAndSignals S{Symbol.first, Symbol.second}; - yout << S; - } - return true; -} - -std::vector ReadSymbolInfosFromYAML(llvm::StringRef Yaml) { - std::vector Symbols; - llvm::yaml::Input yin(Yaml); - yin >> Symbols; - return Symbols; -} - -} // namespace find_all_symbols -} // namespace clang Index: include-fixer/find-all-symbols/SymbolReporter.h =================================================================== --- include-fixer/find-all-symbols/SymbolReporter.h +++ include-fixer/find-all-symbols/SymbolReporter.h @@ -1,29 +0,0 @@ -//===--- SymbolReporter.h - Symbol Reporter ---------------------*- C++ -*-===// -// -// 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 -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_SYMBOL_REPORTER_H -#define LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_SYMBOL_REPORTER_H - -#include "SymbolInfo.h" - -namespace clang { -namespace find_all_symbols { - -/// \brief An interface for classes that collect symbols. -class SymbolReporter { -public: - virtual ~SymbolReporter() = default; - - virtual void reportSymbols(llvm::StringRef FileName, - const SymbolInfo::SignalMap &Symbols) = 0; -}; - -} // namespace find_all_symbols -} // namespace clang - -#endif // LLVM_CLANG_TOOLS_EXTRA_FIND_ALL_SYMBOLS_SYMBOL_REPORTER_H Index: include-fixer/find-all-symbols/tool/CMakeLists.txt =================================================================== --- include-fixer/find-all-symbols/tool/CMakeLists.txt +++ include-fixer/find-all-symbols/tool/CMakeLists.txt @@ -1,24 +0,0 @@ -include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) - -add_clang_executable(find-all-symbols - FindAllSymbolsMain.cpp - ) - -target_link_libraries(find-all-symbols - PRIVATE - clangAST - clangASTMatchers - clangBasic - clangFrontend - clangLex - clangSerialization - clangTooling - findAllSymbols - ) - -install(TARGETS find-all-symbols - RUNTIME DESTINATION bin) - -install(PROGRAMS run-find-all-symbols.py - DESTINATION share/clang - COMPONENT find-all-symbols) Index: include-fixer/find-all-symbols/tool/FindAllSymbolsMain.cpp =================================================================== --- include-fixer/find-all-symbols/tool/FindAllSymbolsMain.cpp +++ include-fixer/find-all-symbols/tool/FindAllSymbolsMain.cpp @@ -1,151 +0,0 @@ -//===-- FindAllSymbolsMain.cpp - find all symbols tool ----------*- C++ -*-===// -// -// 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 "FindAllSymbolsAction.h" -#include "STLPostfixHeaderMap.h" -#include "SymbolInfo.h" -#include "SymbolReporter.h" -#include "clang/ASTMatchers/ASTMatchFinder.h" -#include "clang/ASTMatchers/ASTMatchers.h" -#include "clang/Frontend/CompilerInstance.h" -#include "clang/Frontend/FrontendActions.h" -#include "clang/Lex/Preprocessor.h" -#include "clang/Tooling/CommonOptionsParser.h" -#include "clang/Tooling/Tooling.h" -#include "llvm/ADT/ArrayRef.h" -#include "llvm/ADT/SmallString.h" -#include "llvm/ADT/StringRef.h" -#include "llvm/Support/CommandLine.h" -#include "llvm/Support/FileSystem.h" -#include "llvm/Support/MemoryBuffer.h" -#include "llvm/Support/Path.h" -#include "llvm/Support/ThreadPool.h" -#include "llvm/Support/raw_ostream.h" -#include -#include -#include -#include -#include -#include - -using namespace clang::tooling; -using namespace llvm; -using SymbolInfo = clang::find_all_symbols::SymbolInfo; - -// Apply a custom category to all command-line options so that they are the -// only ones displayed. -static cl::OptionCategory FindAllSymbolsCategory("find_all_symbols options"); - -// CommonOptionsParser declares HelpMessage with a description of the common -// command-line options related to the compilation database and input files. -// It's nice to have this help message in all tools. -static cl::extrahelp CommonHelp(CommonOptionsParser::HelpMessage); - -// A help message for this specific tool can be added afterwards. -static cl::extrahelp MoreHelp("\nMore help text..."); - -static cl::opt OutputDir("output-dir", cl::desc(R"( -The output directory for saving the results.)"), - cl::init("."), - cl::cat(FindAllSymbolsCategory)); - -static cl::opt MergeDir("merge-dir", cl::desc(R"( -The directory for merging symbols.)"), - cl::init(""), - cl::cat(FindAllSymbolsCategory)); -namespace clang { -namespace find_all_symbols { - -class YamlReporter : public SymbolReporter { -public: - void reportSymbols(StringRef FileName, - const SymbolInfo::SignalMap &Symbols) override { - int FD; - SmallString<128> ResultPath; - llvm::sys::fs::createUniqueFile( - OutputDir + "/" + llvm::sys::path::filename(FileName) + "-%%%%%%.yaml", - FD, ResultPath); - llvm::raw_fd_ostream OS(FD, /*shouldClose=*/true); - WriteSymbolInfosToStream(OS, Symbols); - } -}; - -bool Merge(llvm::StringRef MergeDir, llvm::StringRef OutputFile) { - std::error_code EC; - SymbolInfo::SignalMap Symbols; - std::mutex SymbolMutex; - auto AddSymbols = [&](ArrayRef NewSymbols) { - // Synchronize set accesses. - std::unique_lock LockGuard(SymbolMutex); - for (const auto &Symbol : NewSymbols) { - Symbols[Symbol.Symbol] += Symbol.Signals; - } - }; - - // Load all symbol files in MergeDir. - { - llvm::ThreadPool Pool; - for (llvm::sys::fs::directory_iterator Dir(MergeDir, EC), DirEnd; - Dir != DirEnd && !EC; Dir.increment(EC)) { - // Parse YAML files in parallel. - Pool.async( - [&AddSymbols](std::string Path) { - auto Buffer = llvm::MemoryBuffer::getFile(Path); - if (!Buffer) { - llvm::errs() << "Can't open " << Path << "\n"; - return; - } - std::vector Symbols = - ReadSymbolInfosFromYAML(Buffer.get()->getBuffer()); - for (auto &Symbol : Symbols) { - // Only count one occurrence per file, to avoid spam. - Symbol.Signals.Seen = std::min(Symbol.Signals.Seen, 1u); - Symbol.Signals.Used = std::min(Symbol.Signals.Used, 1u); - } - // FIXME: Merge without creating such a heavy contention point. - AddSymbols(Symbols); - }, - Dir->path()); - } - } - - llvm::raw_fd_ostream OS(OutputFile, EC, llvm::sys::fs::F_None); - if (EC) { - llvm::errs() << "Can't open '" << OutputFile << "': " << EC.message() - << '\n'; - return false; - } - WriteSymbolInfosToStream(OS, Symbols); - return true; -} - -} // namespace clang -} // namespace find_all_symbols - -int main(int argc, const char **argv) { - CommonOptionsParser OptionsParser(argc, argv, FindAllSymbolsCategory); - ClangTool Tool(OptionsParser.getCompilations(), - OptionsParser.getSourcePathList()); - - std::vector sources = OptionsParser.getSourcePathList(); - if (sources.empty()) { - llvm::errs() << "Must specify at least one one source file.\n"; - return 1; - } - if (!MergeDir.empty()) { - clang::find_all_symbols::Merge(MergeDir, sources[0]); - return 0; - } - - clang::find_all_symbols::YamlReporter Reporter; - - auto Factory = - llvm::make_unique( - &Reporter, clang::find_all_symbols::getSTLPostfixHeaderMap()); - return Tool.run(Factory.get()); -} Index: include-fixer/find-all-symbols/tool/run-find-all-symbols.py =================================================================== --- include-fixer/find-all-symbols/tool/run-find-all-symbols.py +++ include-fixer/find-all-symbols/tool/run-find-all-symbols.py @@ -1,123 +0,0 @@ -#!/usr/bin/env python -# -#=- run-find-all-symbols.py - Parallel find-all-symbols runner -*- python -*-=# -# -# 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 -# -#===------------------------------------------------------------------------===# - -""" -Parallel find-all-symbols runner -================================ - -Runs find-all-symbols over all files in a compilation database. - -Example invocations. -- Run find-all-symbols on all files in the current working directory. - run-find-all-symbols.py - -Compilation database setup: -http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html -""" - -import argparse -import json -import multiprocessing -import os -import Queue -import shutil -import subprocess -import sys -import tempfile -import threading - - -def find_compilation_database(path): - """Adjusts the directory until a compilation database is found.""" - result = './' - while not os.path.isfile(os.path.join(result, path)): - if os.path.realpath(result) == '/': - print 'Error: could not find compilation database.' - sys.exit(1) - result += '../' - return os.path.realpath(result) - - -def MergeSymbols(directory, args): - """Merge all symbol files (yaml) in a given directaory into a single file.""" - invocation = [args.binary, '-merge-dir='+directory, args.saving_path] - subprocess.call(invocation) - print 'Merge is finished. Saving results in ' + args.saving_path - - -def run_find_all_symbols(args, tmpdir, build_path, queue): - """Takes filenames out of queue and runs find-all-symbols on them.""" - while True: - name = queue.get() - invocation = [args.binary, name, '-output-dir='+tmpdir, '-p='+build_path] - sys.stdout.write(' '.join(invocation) + '\n') - subprocess.call(invocation) - queue.task_done() - - -def main(): - parser = argparse.ArgumentParser(description='Runs find-all-symbols over all' - 'files in a compilation database.') - parser.add_argument('-binary', metavar='PATH', - default='./bin/find-all-symbols', - help='path to find-all-symbols binary') - parser.add_argument('-j', type=int, default=0, - help='number of instances to be run in parallel.') - parser.add_argument('-p', dest='build_path', - help='path used to read a compilation database.') - parser.add_argument('-saving-path', default='./find_all_symbols_db.yaml', - help='result saving path') - args = parser.parse_args() - - db_path = 'compile_commands.json' - - if args.build_path is not None: - build_path = args.build_path - else: - build_path = find_compilation_database(db_path) - - tmpdir = tempfile.mkdtemp() - - # Load the database and extract all files. - database = json.load(open(os.path.join(build_path, db_path))) - files = [entry['file'] for entry in database] - - max_task = args.j - if max_task == 0: - max_task = multiprocessing.cpu_count() - - try: - # Spin up a bunch of tidy-launching threads. - queue = Queue.Queue(max_task) - for _ in range(max_task): - t = threading.Thread(target=run_find_all_symbols, - args=(args, tmpdir, build_path, queue)) - t.daemon = True - t.start() - - # Fill the queue with files. - for name in files: - queue.put(name) - - # Wait for all threads to be done. - queue.join() - - MergeSymbols(tmpdir, args) - - - except KeyboardInterrupt: - # This is a sad hack. Unfortunately subprocess goes - # bonkers with ctrl-c and we start forking merrily. - print '\nCtrl-C detected, goodbye.' - os.kill(0, 9) - - -if __name__ == '__main__': - main() Index: include-fixer/plugin/CMakeLists.txt =================================================================== --- include-fixer/plugin/CMakeLists.txt +++ include-fixer/plugin/CMakeLists.txt @@ -1,13 +0,0 @@ -add_clang_library(clangIncludeFixerPlugin - IncludeFixerPlugin.cpp - - LINK_LIBS - clangAST - clangBasic - clangFrontend - clangIncludeFixer - clangParse - clangSema - clangTooling - ${LLVM_PTHREAD_LIB} - ) Index: include-fixer/plugin/IncludeFixerPlugin.cpp =================================================================== --- include-fixer/plugin/IncludeFixerPlugin.cpp +++ include-fixer/plugin/IncludeFixerPlugin.cpp @@ -1,99 +0,0 @@ -//===- IncludeFixerPlugin.cpp - clang-include-fixer as a clang plugin -----===// -// -// 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 "../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 SIM) - : SymbolIndexMgr(std::move(SIM)) {} - std::shared_ptr SymbolIndexMgr; - }; - -public: - explicit ClangIncludeFixerPluginAction() - : SymbolIndexMgr(std::make_shared()), - SemaSource(new IncludeFixerSemaSource(*SymbolIndexMgr, - /*MinimizeIncludePaths=*/true, - /*GenerateDiagnostics=*/true)) {} - - std::unique_ptr - CreateASTConsumer(clang::CompilerInstance &CI, StringRef InFile) override { - CI.setExternalSemaSource(SemaSource); - SemaSource->setFilePath(InFile); - SemaSource->setCompilerInstance(&CI); - return llvm::make_unique(SymbolIndexMgr); - } - - void ExecuteAction() override {} // Do nothing. - - bool ParseArgs(const CompilerInstance &CI, - const std::vector &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=")); - } - - std::string InputFile = CI.getFrontendOpts().Inputs[0].getFile(); - auto CreateYamlIdx = [=]() -> std::unique_ptr { - llvm::ErrorOr> 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. - SmallString<128> AbsolutePath(tooling::getAbsolutePath(InputFile)); - StringRef Directory = llvm::sys::path::parent_path(AbsolutePath); - SymbolIdx = include_fixer::YamlSymbolIndex::createFromDirectory( - Directory, "find_all_symbols_db.yaml"); - } - } - return std::move(*SymbolIdx); - }; - - SymbolIndexMgr->addSymbolIndex(std::move(CreateYamlIdx)); - return true; - } - -private: - std::shared_ptr SymbolIndexMgr; - IntrusiveRefCntPtr 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"); Index: include-fixer/tool/CMakeLists.txt =================================================================== --- include-fixer/tool/CMakeLists.txt +++ include-fixer/tool/CMakeLists.txt @@ -1,28 +0,0 @@ -include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) - -add_clang_tool(clang-include-fixer - ClangIncludeFixer.cpp - ) - -target_link_libraries(clang-include-fixer - PRIVATE - clangBasic - clangFormat - clangFrontend - clangIncludeFixer - clangRewrite - clangSerialization - clangTooling - clangToolingCore - findAllSymbols - ) - -install(TARGETS clang-include-fixer - RUNTIME DESTINATION bin) - -install(PROGRAMS clang-include-fixer.el - DESTINATION share/clang - COMPONENT clang-include-fixer) -install(PROGRAMS clang-include-fixer.py - DESTINATION share/clang - COMPONENT clang-include-fixer) Index: include-fixer/tool/ClangIncludeFixer.cpp =================================================================== --- include-fixer/tool/ClangIncludeFixer.cpp +++ include-fixer/tool/ClangIncludeFixer.cpp @@ -1,472 +0,0 @@ -//===-- ClangIncludeFixer.cpp - Standalone include fixer ------------------===// -// -// 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 "FuzzySymbolIndex.h" -#include "InMemorySymbolIndex.h" -#include "IncludeFixer.h" -#include "IncludeFixerContext.h" -#include "SymbolIndexManager.h" -#include "YamlSymbolIndex.h" -#include "clang/Format/Format.h" -#include "clang/Frontend/TextDiagnosticPrinter.h" -#include "clang/Rewrite/Core/Rewriter.h" -#include "clang/Tooling/CommonOptionsParser.h" -#include "clang/Tooling/Core/Replacement.h" -#include "clang/Tooling/Tooling.h" -#include "llvm/Support/CommandLine.h" -#include "llvm/Support/Path.h" -#include "llvm/Support/YAMLTraits.h" - -using namespace clang; -using namespace llvm; -using clang::include_fixer::IncludeFixerContext; - -LLVM_YAML_IS_DOCUMENT_LIST_VECTOR(IncludeFixerContext) -LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(IncludeFixerContext::HeaderInfo) -LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(IncludeFixerContext::QuerySymbolInfo) - -namespace llvm { -namespace yaml { - -template <> struct MappingTraits { - struct NormalizedRange { - NormalizedRange(const IO &) : Offset(0), Length(0) {} - - NormalizedRange(const IO &, const tooling::Range &R) - : Offset(R.getOffset()), Length(R.getLength()) {} - - tooling::Range denormalize(const IO &) { - return tooling::Range(Offset, Length); - } - - unsigned Offset; - unsigned Length; - }; - static void mapping(IO &IO, tooling::Range &Info) { - MappingNormalization Keys(IO, Info); - IO.mapRequired("Offset", Keys->Offset); - IO.mapRequired("Length", Keys->Length); - } -}; - -template <> struct MappingTraits { - static void mapping(IO &io, IncludeFixerContext::HeaderInfo &Info) { - io.mapRequired("Header", Info.Header); - io.mapRequired("QualifiedName", Info.QualifiedName); - } -}; - -template <> struct MappingTraits { - static void mapping(IO &io, IncludeFixerContext::QuerySymbolInfo &Info) { - io.mapRequired("RawIdentifier", Info.RawIdentifier); - io.mapRequired("Range", Info.Range); - } -}; - -template <> struct MappingTraits { - static void mapping(IO &IO, IncludeFixerContext &Context) { - IO.mapRequired("QuerySymbolInfos", Context.QuerySymbolInfos); - IO.mapRequired("HeaderInfos", Context.HeaderInfos); - IO.mapRequired("FilePath", Context.FilePath); - } -}; -} // namespace yaml -} // namespace llvm - -namespace { -cl::OptionCategory IncludeFixerCategory("Tool options"); - -enum DatabaseFormatTy { - fixed, ///< Hard-coded mapping. - yaml, ///< Yaml database created by find-all-symbols. - fuzzyYaml, ///< Yaml database with fuzzy-matched identifiers. -}; - -cl::opt DatabaseFormat( - "db", cl::desc("Specify input format"), - cl::values(clEnumVal(fixed, "Hard-coded mapping"), - clEnumVal(yaml, "Yaml database created by find-all-symbols"), - clEnumVal(fuzzyYaml, "Yaml database, with fuzzy-matched names")), - cl::init(yaml), cl::cat(IncludeFixerCategory)); - -cl::opt Input("input", - cl::desc("String to initialize the database"), - cl::cat(IncludeFixerCategory)); - -cl::opt - QuerySymbol("query-symbol", - cl::desc("Query a given symbol (e.g. \"a::b::foo\") in\n" - "database directly without parsing the file."), - cl::cat(IncludeFixerCategory)); - -cl::opt - MinimizeIncludePaths("minimize-paths", - cl::desc("Whether to minimize added include paths"), - cl::init(true), cl::cat(IncludeFixerCategory)); - -cl::opt Quiet("q", cl::desc("Reduce terminal output"), cl::init(false), - cl::cat(IncludeFixerCategory)); - -cl::opt - STDINMode("stdin", - cl::desc("Override source file's content (in the overlaying\n" - "virtual file system) with input from and run\n" - "the tool on the new content with the compilation\n" - "options of the source file. This mode is currently\n" - "used for editor integration."), - cl::init(false), cl::cat(IncludeFixerCategory)); - -cl::opt OutputHeaders( - "output-headers", - cl::desc("Print the symbol being queried and all its relevant headers in\n" - "JSON format to stdout:\n" - " {\n" - " \"FilePath\": \"/path/to/foo.cc\",\n" - " \"QuerySymbolInfos\": [\n" - " {\"RawIdentifier\": \"foo\",\n" - " \"Range\": {\"Offset\": 0, \"Length\": 3}}\n" - " ],\n" - " \"HeaderInfos\": [ {\"Header\": \"\\\"foo_a.h\\\"\",\n" - " \"QualifiedName\": \"a::foo\"} ]\n" - " }"), - cl::init(false), cl::cat(IncludeFixerCategory)); - -cl::opt InsertHeader( - "insert-header", - cl::desc("Insert a specific header. This should run with STDIN mode.\n" - "The result is written to stdout. It is currently used for\n" - "editor integration. Support YAML/JSON format:\n" - " -insert-header=\"{\n" - " FilePath: \"/path/to/foo.cc\",\n" - " QuerySymbolInfos: [\n" - " {RawIdentifier: foo,\n" - " Range: {Offset: 0, Length: 3}}\n" - " ],\n" - " HeaderInfos: [ {Headers: \"\\\"foo_a.h\\\"\",\n" - " QualifiedName: \"a::foo\"} ]}\""), - cl::init(""), cl::cat(IncludeFixerCategory)); - -cl::opt - Style("style", - cl::desc("Fallback style for reformatting after inserting new\n" - "headers if there is no clang-format config file found."), - cl::init("llvm"), cl::cat(IncludeFixerCategory)); - -std::unique_ptr -createSymbolIndexManager(StringRef FilePath) { - using find_all_symbols::SymbolInfo; - - auto SymbolIndexMgr = llvm::make_unique(); - switch (DatabaseFormat) { - case fixed: { - // Parse input and fill the database with it. - // =
<, header...> - // Multiple symbols can be given, separated by semicolons. - std::map> SymbolsMap; - SmallVector SemicolonSplits; - StringRef(Input).split(SemicolonSplits, ";"); - std::vector Symbols; - for (StringRef Pair : SemicolonSplits) { - auto Split = Pair.split('='); - std::vector Headers; - SmallVector CommaSplits; - Split.second.split(CommaSplits, ","); - for (size_t I = 0, E = CommaSplits.size(); I != E; ++I) - Symbols.push_back( - {SymbolInfo(Split.first.trim(), SymbolInfo::SymbolKind::Unknown, - CommaSplits[I].trim(), {}), - // Use fake "seen" signal for tests, so first header wins. - SymbolInfo::Signals(/*Seen=*/static_cast(E - I), - /*Used=*/0)}); - } - SymbolIndexMgr->addSymbolIndex([=]() { - return llvm::make_unique(Symbols); - }); - break; - } - case yaml: { - auto CreateYamlIdx = [=]() -> std::unique_ptr { - llvm::ErrorOr> DB( - nullptr); - if (!Input.empty()) { - DB = 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. - SmallString<128> AbsolutePath(tooling::getAbsolutePath(FilePath)); - StringRef Directory = llvm::sys::path::parent_path(AbsolutePath); - DB = include_fixer::YamlSymbolIndex::createFromDirectory( - Directory, "find_all_symbols_db.yaml"); - } - - if (!DB) { - llvm::errs() << "Couldn't find YAML db: " << DB.getError().message() - << '\n'; - return nullptr; - } - return std::move(*DB); - }; - - SymbolIndexMgr->addSymbolIndex(std::move(CreateYamlIdx)); - break; - } - case fuzzyYaml: { - // This mode is not very useful, because we don't correct the identifier. - // It's main purpose is to expose FuzzySymbolIndex to tests. - SymbolIndexMgr->addSymbolIndex( - []() -> std::unique_ptr { - auto DB = include_fixer::FuzzySymbolIndex::createFromYAML(Input); - if (!DB) { - llvm::errs() << "Couldn't load fuzzy YAML db: " - << llvm::toString(DB.takeError()) << '\n'; - return nullptr; - } - return std::move(*DB); - }); - break; - } - } - return SymbolIndexMgr; -} - -void writeToJson(llvm::raw_ostream &OS, const IncludeFixerContext& Context) { - OS << "{\n" - << " \"FilePath\": \"" - << llvm::yaml::escape(Context.getFilePath()) << "\",\n" - << " \"QuerySymbolInfos\": [\n"; - for (const auto &Info : Context.getQuerySymbolInfos()) { - OS << " {\"RawIdentifier\": \"" << Info.RawIdentifier << "\",\n"; - OS << " \"Range\":{"; - OS << "\"Offset\":" << Info.Range.getOffset() << ","; - OS << "\"Length\":" << Info.Range.getLength() << "}}"; - if (&Info != &Context.getQuerySymbolInfos().back()) - OS << ",\n"; - } - OS << "\n ],\n"; - OS << " \"HeaderInfos\": [\n"; - const auto &HeaderInfos = Context.getHeaderInfos(); - for (const auto &Info : HeaderInfos) { - OS << " {\"Header\": \"" << llvm::yaml::escape(Info.Header) << "\",\n" - << " \"QualifiedName\": \"" << Info.QualifiedName << "\"}"; - if (&Info != &HeaderInfos.back()) - OS << ",\n"; - } - OS << "\n"; - OS << " ]\n"; - OS << "}\n"; -} - -int includeFixerMain(int argc, const char **argv) { - tooling::CommonOptionsParser options(argc, argv, IncludeFixerCategory); - tooling::ClangTool tool(options.getCompilations(), - options.getSourcePathList()); - - llvm::StringRef SourceFilePath = options.getSourcePathList().front(); - // In STDINMode, we override the file content with the input. - // Since `tool.mapVirtualFile` takes `StringRef`, we define `Code` outside of - // the if-block so that `Code` is not released after the if-block. - std::unique_ptr Code; - if (STDINMode) { - assert(options.getSourcePathList().size() == 1 && - "Expect exactly one file path in STDINMode."); - llvm::ErrorOr> CodeOrErr = - MemoryBuffer::getSTDIN(); - if (std::error_code EC = CodeOrErr.getError()) { - errs() << EC.message() << "\n"; - return 1; - } - Code = std::move(CodeOrErr.get()); - if (Code->getBufferSize() == 0) - return 0; // Skip empty files. - - tool.mapVirtualFile(SourceFilePath, Code->getBuffer()); - } - - if (!InsertHeader.empty()) { - if (!STDINMode) { - errs() << "Should be running in STDIN mode\n"; - return 1; - } - - llvm::yaml::Input yin(InsertHeader); - IncludeFixerContext Context; - yin >> Context; - - const auto &HeaderInfos = Context.getHeaderInfos(); - assert(!HeaderInfos.empty()); - // We only accept one unique header. - // Check all elements in HeaderInfos have the same header. - bool IsUniqueHeader = std::equal( - HeaderInfos.begin()+1, HeaderInfos.end(), HeaderInfos.begin(), - [](const IncludeFixerContext::HeaderInfo &LHS, - const IncludeFixerContext::HeaderInfo &RHS) { - return LHS.Header == RHS.Header; - }); - if (!IsUniqueHeader) { - errs() << "Expect exactly one unique header.\n"; - return 1; - } - - // If a header has multiple symbols, we won't add the missing namespace - // qualifiers because we don't know which one is exactly used. - // - // Check whether all elements in HeaderInfos have the same qualified name. - bool IsUniqueQualifiedName = std::equal( - HeaderInfos.begin() + 1, HeaderInfos.end(), HeaderInfos.begin(), - [](const IncludeFixerContext::HeaderInfo &LHS, - const IncludeFixerContext::HeaderInfo &RHS) { - return LHS.QualifiedName == RHS.QualifiedName; - }); - auto InsertStyle = format::getStyle(format::DefaultFormatStyle, - Context.getFilePath(), Style); - if (!InsertStyle) { - llvm::errs() << llvm::toString(InsertStyle.takeError()) << "\n"; - return 1; - } - auto Replacements = clang::include_fixer::createIncludeFixerReplacements( - Code->getBuffer(), Context, *InsertStyle, - /*AddQualifiers=*/IsUniqueQualifiedName); - if (!Replacements) { - errs() << "Failed to create replacements: " - << llvm::toString(Replacements.takeError()) << "\n"; - return 1; - } - - auto ChangedCode = - tooling::applyAllReplacements(Code->getBuffer(), *Replacements); - if (!ChangedCode) { - llvm::errs() << llvm::toString(ChangedCode.takeError()) << "\n"; - return 1; - } - llvm::outs() << *ChangedCode; - return 0; - } - - // Set up data source. - std::unique_ptr SymbolIndexMgr = - createSymbolIndexManager(SourceFilePath); - if (!SymbolIndexMgr) - return 1; - - // Query symbol mode. - if (!QuerySymbol.empty()) { - auto MatchedSymbols = SymbolIndexMgr->search( - QuerySymbol, /*IsNestedSearch=*/true, SourceFilePath); - for (auto &Symbol : MatchedSymbols) { - std::string HeaderPath = Symbol.getFilePath().str(); - Symbol.SetFilePath(((HeaderPath[0] == '"' || HeaderPath[0] == '<') - ? HeaderPath - : "\"" + HeaderPath + "\"")); - } - - // We leave an empty symbol range as we don't know the range of the symbol - // being queried in this mode. include-fixer won't add namespace qualifiers - // if the symbol range is empty, which also fits this case. - IncludeFixerContext::QuerySymbolInfo Symbol; - Symbol.RawIdentifier = QuerySymbol; - auto Context = - IncludeFixerContext(SourceFilePath, {Symbol}, MatchedSymbols); - writeToJson(llvm::outs(), Context); - return 0; - } - - // Now run our tool. - std::vector Contexts; - include_fixer::IncludeFixerActionFactory Factory(*SymbolIndexMgr, Contexts, - Style, MinimizeIncludePaths); - - if (tool.run(&Factory) != 0) { - // We suppress all Clang diagnostics (because they would be wrong, - // include-fixer does custom recovery) but still want to give some feedback - // in case there was a compiler error we couldn't recover from. The most - // common case for this is a #include in the file that couldn't be found. - llvm::errs() << "Fatal compiler error occurred while parsing file!" - " (incorrect include paths?)\n"; - return 1; - } - - assert(!Contexts.empty()); - - if (OutputHeaders) { - // FIXME: Print contexts of all processing files instead of the first one. - writeToJson(llvm::outs(), Contexts.front()); - return 0; - } - - std::vector FixerReplacements; - for (const auto &Context : Contexts) { - StringRef FilePath = Context.getFilePath(); - auto InsertStyle = - format::getStyle(format::DefaultFormatStyle, FilePath, Style); - if (!InsertStyle) { - llvm::errs() << llvm::toString(InsertStyle.takeError()) << "\n"; - return 1; - } - auto Buffer = llvm::MemoryBuffer::getFile(FilePath); - if (!Buffer) { - errs() << "Couldn't open file: " + FilePath.str() + ": " - << Buffer.getError().message() + "\n"; - return 1; - } - - auto Replacements = clang::include_fixer::createIncludeFixerReplacements( - Buffer.get()->getBuffer(), Context, *InsertStyle); - if (!Replacements) { - errs() << "Failed to create replacement: " - << llvm::toString(Replacements.takeError()) << "\n"; - return 1; - } - FixerReplacements.push_back(*Replacements); - } - - if (!Quiet) { - for (const auto &Context : Contexts) { - if (!Context.getHeaderInfos().empty()) { - llvm::errs() << "Added #include " - << Context.getHeaderInfos().front().Header << " for " - << Context.getFilePath() << "\n"; - } - } - } - - if (STDINMode) { - assert(FixerReplacements.size() == 1); - auto ChangedCode = tooling::applyAllReplacements(Code->getBuffer(), - FixerReplacements.front()); - if (!ChangedCode) { - llvm::errs() << llvm::toString(ChangedCode.takeError()) << "\n"; - return 1; - } - llvm::outs() << *ChangedCode; - return 0; - } - - // Set up a new source manager for applying the resulting replacements. - IntrusiveRefCntPtr DiagOpts(new DiagnosticOptions); - DiagnosticsEngine Diagnostics(new DiagnosticIDs, &*DiagOpts); - TextDiagnosticPrinter DiagnosticPrinter(outs(), &*DiagOpts); - SourceManager SM(Diagnostics, tool.getFiles()); - Diagnostics.setClient(&DiagnosticPrinter, false); - - // Write replacements to disk. - Rewriter Rewrites(SM, LangOptions()); - for (const auto &Replacement : FixerReplacements) { - if (!tooling::applyAllReplacements(Replacement, Rewrites)) { - llvm::errs() << "Failed to apply replacements.\n"; - return 1; - } - } - return Rewrites.overwriteChangedFiles(); -} - -} // namespace - -int main(int argc, const char **argv) { - return includeFixerMain(argc, argv); -} Index: include-fixer/tool/clang-include-fixer-test.el =================================================================== --- include-fixer/tool/clang-include-fixer-test.el +++ include-fixer/tool/clang-include-fixer-test.el @@ -1,65 +0,0 @@ -;;; clang-include-fixer-test.el --- unit tests for clang-include-fixer.el -*- lexical-binding: t; -*- - -;;; Commentary: - -;; Unit tests for clang-include-fixer.el. - -;;; Code: - -(require 'clang-include-fixer) - -(require 'cc-mode) -(require 'ert) - -(ert-deftest clang-include-fixer--insert-line () - "Unit test for `clang-include-fixer--insert-line'." - (with-temp-buffer - (insert "aa\nab\nac\nad\n") - (let ((from (current-buffer))) - (with-temp-buffer - (insert "aa\nac\nad\n") - (let ((to (current-buffer))) - (should (clang-include-fixer--insert-line from to)) - (should (equal (buffer-string) "aa\nab\nac\nad\n"))))) - (should (equal (buffer-string) "aa\nab\nac\nad\n")))) - -(ert-deftest clang-include-fixer--insert-line-diff-on-empty-line () - "Unit test for `clang-include-fixer--insert-line'." - (with-temp-buffer - (insert "aa\nab\n\nac\nad\n") - (let ((from (current-buffer))) - (with-temp-buffer - (insert "aa\n\nac\nad\n") - (let ((to (current-buffer))) - (should (clang-include-fixer--insert-line from to)) - (should (equal (buffer-string) "aa\nab\n\nac\nad\n"))))) - (should (equal (buffer-string) "aa\nab\n\nac\nad\n")))) - -(ert-deftest clang-include-fixer--symbol-at-point () - "Unit test for `clang-include-fixer--symbol-at-point'." - (with-temp-buffer - (insert "a+bbb::cc") - (c++-mode) - (goto-char (point-min)) - (should (equal (clang-include-fixer--symbol-at-point) "a")) - (forward-char) - ;; Emacs treats the character immediately following a symbol as part of the - ;; symbol. - (should (equal (clang-include-fixer--symbol-at-point) "a")) - (forward-char) - (should (equal (clang-include-fixer--symbol-at-point) "bbb::cc")) - (goto-char (point-max)) - (should (equal (clang-include-fixer--symbol-at-point) "bbb::cc")))) - -(ert-deftest clang-include-fixer--highlight () - (with-temp-buffer - (insert "util::Status foo;\n") - (setq buffer-file-coding-system 'utf-8-unix) - (should (equal nil (clang-include-fixer--highlight - '((Range . ((Offset . 0) (Length . 0))))))) - (let ((overlay (clang-include-fixer--highlight - '((Range . ((Offset . 1) (Length . 12))))))) - (should (equal 2 (overlay-start overlay))) - (should (equal 14 (overlay-end overlay)))))) - -;;; clang-include-fixer-test.el ends here Index: include-fixer/tool/clang-include-fixer.el =================================================================== --- include-fixer/tool/clang-include-fixer.el +++ include-fixer/tool/clang-include-fixer.el @@ -1,460 +0,0 @@ -;;; clang-include-fixer.el --- Emacs integration of the clang include fixer -*- lexical-binding: t; -*- - -;; Keywords: tools, c -;; Package-Requires: ((cl-lib "0.5") (json "1.2") (let-alist "1.0.4")) - -;;; Commentary: - -;; This package allows Emacs users to invoke the 'clang-include-fixer' within -;; Emacs. 'clang-include-fixer' provides an automated way of adding #include -;; directives for missing symbols in one translation unit, see -;; . - -;;; Code: - -(require 'cl-lib) -(require 'json) -(require 'let-alist) - -(defgroup clang-include-fixer nil - "Clang-based include fixer." - :group 'tools) - -(defvar clang-include-fixer-add-include-hook nil - "A hook that will be called for every added include. -The first argument is the filename of the include, the second argument is -non-nil if the include is a system-header.") - -(defcustom clang-include-fixer-executable - "clang-include-fixer" - "Location of the clang-include-fixer executable. - -A string containing the name or the full path of the executable." - :group 'clang-include-fixer - :type '(file :must-match t) - :risky t) - -(defcustom clang-include-fixer-input-format - 'yaml - "Input format for clang-include-fixer. -This string is passed as -db argument to -`clang-include-fixer-executable'." - :group 'clang-include-fixer - :type '(radio - (const :tag "Hard-coded mapping" :fixed) - (const :tag "YAML" yaml) - (symbol :tag "Other")) - :risky t) - -(defcustom clang-include-fixer-init-string - "" - "Database initialization string for clang-include-fixer. -This string is passed as -input argument to -`clang-include-fixer-executable'." - :group 'clang-include-fixer - :type 'string - :risky t) - -(defface clang-include-fixer-highlight '((t :background "green")) - "Used for highlighting the symbol for which a header file is being added.") - -;;;###autoload -(defun clang-include-fixer () - "Invoke the Include Fixer to insert missing C++ headers." - (interactive) - (message (concat "Calling the include fixer. " - "This might take some seconds. Please wait.")) - (clang-include-fixer--start #'clang-include-fixer--add-header - "-output-headers")) - -;;;###autoload -(defun clang-include-fixer-at-point () - "Invoke the Clang include fixer for the symbol at point." - (interactive) - (let ((symbol (clang-include-fixer--symbol-at-point))) - (unless symbol - (user-error "No symbol at current location")) - (clang-include-fixer-from-symbol symbol))) - -;;;###autoload -(defun clang-include-fixer-from-symbol (symbol) - "Invoke the Clang include fixer for the SYMBOL. -When called interactively, prompts the user for a symbol." - (interactive - (list (read-string "Symbol: " (clang-include-fixer--symbol-at-point)))) - (clang-include-fixer--start #'clang-include-fixer--add-header - (format "-query-symbol=%s" symbol))) - -(defun clang-include-fixer--start (callback &rest args) - "Asynchronously start clang-include-fixer with parameters ARGS. -The current file name is passed after ARGS as last argument. If -the call was successful the returned result is stored in a -temporary buffer, and CALLBACK is called with the temporary -buffer as only argument." - (unless buffer-file-name - (user-error "clang-include-fixer works only in buffers that visit a file")) - (let ((process (if (and (fboundp 'make-process) - ;; ‘make-process’ doesn’t support remote files - ;; (https://debbugs.gnu.org/cgi/bugreport.cgi?bug=28691). - (not (find-file-name-handler default-directory - 'start-file-process))) - ;; Prefer using ‘make-process’ if possible, because - ;; ‘start-process’ doesn’t allow us to separate the - ;; standard error from the output. - (clang-include-fixer--make-process callback args) - (clang-include-fixer--start-process callback args)))) - (save-restriction - (widen) - (process-send-region process (point-min) (point-max))) - (process-send-eof process)) - nil) - -(defun clang-include-fixer--make-process (callback args) - "Start a new clang-incude-fixer process using `make-process'. -CALLBACK is called after the process finishes successfully; it is -called with a single argument, the buffer where standard output -has been inserted. ARGS is a list of additional command line -arguments. Return the new process object." - (let ((stdin (current-buffer)) - (stdout (generate-new-buffer "*clang-include-fixer output*")) - (stderr (generate-new-buffer "*clang-include-fixer errors*"))) - (make-process :name "clang-include-fixer" - :buffer stdout - :command (clang-include-fixer--command args) - :coding 'utf-8-unix - :noquery t - :connection-type 'pipe - :sentinel (clang-include-fixer--sentinel stdin stdout stderr - callback) - :stderr stderr))) - -(defun clang-include-fixer--start-process (callback args) - "Start a new clang-incude-fixer process using `start-file-process'. -CALLBACK is called after the process finishes successfully; it is -called with a single argument, the buffer where standard output -has been inserted. ARGS is a list of additional command line -arguments. Return the new process object." - (let* ((stdin (current-buffer)) - (stdout (generate-new-buffer "*clang-include-fixer output*")) - (process-connection-type nil) - (process (apply #'start-file-process "clang-include-fixer" stdout - (clang-include-fixer--command args)))) - (set-process-coding-system process 'utf-8-unix 'utf-8-unix) - (set-process-query-on-exit-flag process nil) - (set-process-sentinel process - (clang-include-fixer--sentinel stdin stdout nil - callback)) - process)) - -(defun clang-include-fixer--command (args) - "Return the clang-include-fixer command line. -Returns a list; the first element is the binary to -execute (`clang-include-fixer-executable'), and the remaining -elements are the command line arguments. Adds proper arguments -for `clang-include-fixer-input-format' and -`clang-include-fixer-init-string'. Appends the current buffer's -file name; prepends ARGS directly in front of it." - (cl-check-type args list) - `(,clang-include-fixer-executable - ,(format "-db=%s" clang-include-fixer-input-format) - ,(format "-input=%s" clang-include-fixer-init-string) - "-stdin" - ,@args - ,(clang-include-fixer--file-local-name buffer-file-name))) - -(defun clang-include-fixer--sentinel (stdin stdout stderr callback) - "Return a process sentinel for clang-include-fixer processes. -STDIN, STDOUT, and STDERR are buffers for the standard streams; -only STDERR may be nil. CALLBACK is called in the case of -success; it is called with a single argument, STDOUT. On -failure, a buffer containing the error output is displayed." - (cl-check-type stdin buffer-live) - (cl-check-type stdout buffer-live) - (cl-check-type stderr (or null buffer-live)) - (cl-check-type callback function) - (lambda (process event) - (cl-check-type process process) - (cl-check-type event string) - (unwind-protect - (if (string-equal event "finished\n") - (progn - (when stderr (kill-buffer stderr)) - (with-current-buffer stdin - (funcall callback stdout)) - (kill-buffer stdout)) - (when stderr (kill-buffer stdout)) - (message "clang-include-fixer failed") - (with-current-buffer (or stderr stdout) - (insert "\nProcess " (process-name process) - ?\s event)) - (display-buffer (or stderr stdout)))) - nil)) - -(defun clang-include-fixer--replace-buffer (stdout) - "Replace current buffer by content of STDOUT." - (cl-check-type stdout buffer-live) - (barf-if-buffer-read-only) - (cond ((fboundp 'replace-buffer-contents) (replace-buffer-contents stdout)) - ((clang-include-fixer--insert-line stdout (current-buffer))) - (t (erase-buffer) (insert-buffer-substring stdout))) - (message "Fix applied") - nil) - -(defun clang-include-fixer--insert-line (from to) - "Insert a single missing line from the buffer FROM into TO. -FROM and TO must be buffers. If the contents of FROM and TO are -equal, do nothing and return non-nil. If FROM contains a single -line missing from TO, insert that line into TO so that the buffer -contents are equal and return non-nil. Otherwise, do nothing and -return nil. Buffer restrictions are ignored." - (cl-check-type from buffer-live) - (cl-check-type to buffer-live) - (with-current-buffer from - (save-excursion - (save-restriction - (widen) - (with-current-buffer to - (save-excursion - (save-restriction - (widen) - ;; Search for the first buffer difference. - (let ((chars (abs (compare-buffer-substrings to nil nil from nil nil)))) - (if (zerop chars) - ;; Buffer contents are equal, nothing to do. - t - (goto-char chars) - ;; We might have ended up in the middle of a line if the - ;; current line partially matches. In this case we would - ;; have to insert more than a line. Move to the beginning of - ;; the line to avoid this situation. - (beginning-of-line) - (with-current-buffer from - (goto-char chars) - (beginning-of-line) - (let ((from-begin (point)) - (from-end (progn (forward-line) (point))) - (to-point (with-current-buffer to (point)))) - ;; Search for another buffer difference after the line in - ;; question. If there is none, we can proceed. - (when (zerop (compare-buffer-substrings from from-end nil - to to-point nil)) - (with-current-buffer to - (insert-buffer-substring from from-begin from-end)) - t)))))))))))) - -(defun clang-include-fixer--add-header (stdout) - "Analyse the result of include-fixer stored in STDOUT. -Add a missing header if there is any. If there are multiple -possible headers the user can select one of them to be included. -Temporarily highlight the affected symbols. Asynchronously call -clang-include-fixer to insert the selected header." - (cl-check-type stdout buffer-live) - (let ((context (clang-include-fixer--parse-json stdout))) - (let-alist context - (cond - ((null .QuerySymbolInfos) - (message "The file is fine, no need to add a header.")) - ((null .HeaderInfos) - (message "Couldn't find header for '%s'" - (let-alist (car .QuerySymbolInfos) .RawIdentifier))) - (t - ;; Users may C-g in prompts, make sure the process sentinel - ;; behaves correctly. - (with-local-quit - ;; Replace the HeaderInfos list by a single header selected by - ;; the user. - (clang-include-fixer--select-header context) - ;; Call clang-include-fixer again to insert the selected header. - (clang-include-fixer--start - (let ((old-tick (buffer-chars-modified-tick))) - (lambda (stdout) - (when (/= old-tick (buffer-chars-modified-tick)) - ;; Replacing the buffer now would undo the user’s changes. - (user-error (concat "The buffer has been changed " - "before the header could be inserted"))) - (clang-include-fixer--replace-buffer stdout) - (let-alist context - (let-alist (car .HeaderInfos) - (with-local-quit - (run-hook-with-args 'clang-include-fixer-add-include-hook - (substring .Header 1 -1) - (string= (substring .Header 0 1) "<"))))))) - (format "-insert-header=%s" - (clang-include-fixer--encode-json context)))))))) - nil) - -(defun clang-include-fixer--select-header (context) - "Prompt the user for a header if necessary. -CONTEXT must be a clang-include-fixer context object in -association list format. If it contains more than one HeaderInfo -element, prompt the user to select one of the headers. CONTEXT -is modified to include only the selected element." - (cl-check-type context cons) - (let-alist context - (if (cdr .HeaderInfos) - (clang-include-fixer--prompt-for-header context) - (message "Only one include is missing: %s" - (let-alist (car .HeaderInfos) .Header)))) - nil) - -(defvar clang-include-fixer--history nil - "History for `clang-include-fixer--prompt-for-header'.") - -(defun clang-include-fixer--prompt-for-header (context) - "Prompt the user for a single header. -The choices are taken from the HeaderInfo elements in CONTEXT. -They are replaced by the single element selected by the user." - (let-alist context - (let ((symbol (clang-include-fixer--symbol-name .QuerySymbolInfos)) - ;; Add temporary highlighting so that the user knows which - ;; symbols the current session is about. - (overlays (remove nil - (mapcar #'clang-include-fixer--highlight .QuerySymbolInfos)))) - (unwind-protect - (save-excursion - ;; While prompting, go to the closest overlay so that the user sees - ;; some context. - (when overlays - (goto-char (clang-include-fixer--closest-overlay overlays))) - (cl-flet ((header (info) (let-alist info .Header))) - ;; The header-infos is already sorted by include-fixer. - (let* ((headers (mapcar #'header .HeaderInfos)) - (header (completing-read - (clang-include-fixer--format-message - "Select include for '%s': " symbol) - headers nil :require-match nil - 'clang-include-fixer--history - ;; Specify a default to prevent the behavior - ;; described in - ;; https://github.com/DarwinAwardWinner/ido-completing-read-plus#why-does-ret-sometimes-not-select-the-first-completion-on-the-list--why-is-there-an-empty-entry-at-the-beginning-of-the-completion-list--what-happened-to-old-style-default-selection. - (car headers))) - (info (cl-find header .HeaderInfos :key #'header :test #'string=))) - (unless info (user-error "No header selected")) - (setcar .HeaderInfos info) - (setcdr .HeaderInfos nil)))) - (mapc #'delete-overlay overlays))))) - -(defun clang-include-fixer--symbol-name (symbol-infos) - "Return the unique symbol name in SYMBOL-INFOS. -Raise a signal if the symbol name is not unique." - (let ((symbols (delete-dups (mapcar (lambda (info) - (let-alist info .RawIdentifier)) - symbol-infos)))) - (when (cdr symbols) - (error "Multiple symbols %s returned" symbols)) - (car symbols))) - -(defun clang-include-fixer--highlight (symbol-info) - "Add an overlay to highlight SYMBOL-INFO, if it points to a non-empty range. -Return the overlay object, or nil." - (let-alist symbol-info - (unless (zerop .Range.Length) - (let ((overlay (make-overlay - (clang-include-fixer--filepos-to-bufferpos - .Range.Offset 'approximate) - (clang-include-fixer--filepos-to-bufferpos - (+ .Range.Offset .Range.Length) 'approximate)))) - (overlay-put overlay 'face 'clang-include-fixer-highlight) - overlay)))) - -(defun clang-include-fixer--closest-overlay (overlays) - "Return the start of the overlay in OVERLAYS that is closest to point." - (cl-check-type overlays cons) - (let ((point (point)) - acc) - (dolist (overlay overlays acc) - (let ((start (overlay-start overlay))) - (when (or (null acc) (< (abs (- point start)) (abs (- point acc)))) - (setq acc start)))))) - -(defun clang-include-fixer--parse-json (buffer) - "Parse a JSON response from clang-include-fixer in BUFFER. -Return the JSON object as an association list." - (with-current-buffer buffer - (save-excursion - (goto-char (point-min)) - (let ((json-object-type 'alist) - (json-array-type 'list) - (json-key-type 'symbol) - (json-false :json-false) - (json-null nil) - (json-pre-element-read-function nil) - (json-post-element-read-function nil)) - (json-read))))) - -(defun clang-include-fixer--encode-json (object) - "Return the JSON representation of OBJECT as a string." - (let ((json-encoding-separator ",") - (json-encoding-default-indentation " ") - (json-encoding-pretty-print nil) - (json-encoding-lisp-style-closings nil) - (json-encoding-object-sort-predicate nil)) - (json-encode object))) - -(defun clang-include-fixer--symbol-at-point () - "Return the qualified symbol at point. -If there is no symbol at point, return nil." - ;; Let ‘bounds-of-thing-at-point’ to do the hard work and deal with edge - ;; cases. - (let ((bounds (bounds-of-thing-at-point 'symbol))) - (when bounds - (let ((beg (car bounds)) - (end (cdr bounds))) - (save-excursion - ;; Extend the symbol range to the left. Skip over namespace - ;; delimiters and parent namespace names. - (goto-char beg) - (while (and (clang-include-fixer--skip-double-colon-backward) - (skip-syntax-backward "w_"))) - ;; Skip over one more namespace delimiter, for absolute names. - (clang-include-fixer--skip-double-colon-backward) - (setq beg (point)) - ;; Extend the symbol range to the right. Skip over namespace - ;; delimiters and child namespace names. - (goto-char end) - (while (and (clang-include-fixer--skip-double-colon-forward) - (skip-syntax-forward "w_"))) - (setq end (point))) - (buffer-substring-no-properties beg end))))) - -(defun clang-include-fixer--skip-double-colon-forward () - "Skip a double colon. -When the next two characters are '::', skip them and return -non-nil. Otherwise return nil." - (let ((end (+ (point) 2))) - (when (and (<= end (point-max)) - (string-equal (buffer-substring-no-properties (point) end) "::")) - (goto-char end) - t))) - -(defun clang-include-fixer--skip-double-colon-backward () - "Skip a double colon. -When the previous two characters are '::', skip them and return -non-nil. Otherwise return nil." - (let ((beg (- (point) 2))) - (when (and (>= beg (point-min)) - (string-equal (buffer-substring-no-properties beg (point)) "::")) - (goto-char beg) - t))) - -;; ‘filepos-to-bufferpos’ is new in Emacs 25.1. Provide a fallback for older -;; versions. -(defalias 'clang-include-fixer--filepos-to-bufferpos - (if (fboundp 'filepos-to-bufferpos) - 'filepos-to-bufferpos - (lambda (byte &optional _quality _coding-system) - (byte-to-position (1+ byte))))) - -;; ‘format-message’ is new in Emacs 25.1. Provide a fallback for older -;; versions. -(defalias 'clang-include-fixer--format-message - (if (fboundp 'format-message) 'format-message 'format)) - -;; ‘file-local-name’ is new in Emacs 26.1. Provide a fallback for older -;; versions. -(defalias 'clang-include-fixer--file-local-name - (if (fboundp 'file-local-name) #'file-local-name - (lambda (file) (or (file-remote-p file 'localname) file)))) - -(provide 'clang-include-fixer) -;;; clang-include-fixer.el ends here Index: include-fixer/tool/clang-include-fixer.py =================================================================== --- include-fixer/tool/clang-include-fixer.py +++ include-fixer/tool/clang-include-fixer.py @@ -1,208 +0,0 @@ -# This file is a minimal clang-include-fixer vim-integration. To install: -# - Change 'binary' if clang-include-fixer is not on the path (see below). -# - Add to your .vimrc: -# -# noremap cf :pyf path/to/llvm/source/tools/clang/tools/extra/include-fixer/tool/clang-include-fixer.py -# -# This enables clang-include-fixer for NORMAL and VISUAL mode. Change "cf" -# to another binding if you need clang-include-fixer on a different key. -# -# To set up clang-include-fixer, see http://clang.llvm.org/extra/include-fixer.html -# -# With this integration you can press the bound key and clang-include-fixer will -# be run on the current buffer. -# -# It operates on the current, potentially unsaved buffer and does not create -# or save any files. To revert a fix, just undo. - -import argparse -import difflib -import json -import re -import subprocess -import vim - -# set g:clang_include_fixer_path to the path to clang-include-fixer if it is not -# on the path. -# Change this to the full path if clang-include-fixer is not on the path. -binary = 'clang-include-fixer' -if vim.eval('exists("g:clang_include_fixer_path")') == "1": - binary = vim.eval('g:clang_include_fixer_path') - -maximum_suggested_headers = 3 -if vim.eval('exists("g:clang_include_fixer_maximum_suggested_headers")') == "1": - maximum_suggested_headers = max( - 1, - vim.eval('g:clang_include_fixer_maximum_suggested_headers')) - -increment_num = 5 -if vim.eval('exists("g:clang_include_fixer_increment_num")') == "1": - increment_num = max( - 1, - vim.eval('g:clang_include_fixer_increment_num')) - -jump_to_include = False -if vim.eval('exists("g:clang_include_fixer_jump_to_include")') == "1": - jump_to_include = vim.eval('g:clang_include_fixer_jump_to_include') != "0" - -query_mode = False -if vim.eval('exists("g:clang_include_fixer_query_mode")') == "1": - query_mode = vim.eval('g:clang_include_fixer_query_mode') != "0" - - -def GetUserSelection(message, headers, maximum_suggested_headers): - eval_message = message + '\n' - for idx, header in enumerate(headers[0:maximum_suggested_headers]): - eval_message += "({0}). {1}\n".format(idx + 1, header) - eval_message += "Enter (q) to quit;" - if maximum_suggested_headers < len(headers): - eval_message += " (m) to show {0} more candidates.".format( - min(increment_num, len(headers) - maximum_suggested_headers)) - - eval_message += "\nSelect (default 1): " - res = vim.eval("input('{0}')".format(eval_message)) - if res == '': - # choose the top ranked header by default - idx = 1 - elif res == 'q': - raise Exception(' Insertion cancelled...') - elif res == 'm': - return GetUserSelection(message, - headers, maximum_suggested_headers + increment_num) - else: - try: - idx = int(res) - if idx <= 0 or idx > len(headers): - raise Exception() - except Exception: - # Show a new prompt on invalid option instead of aborting so that users - # don't need to wait for another include-fixer run. - print >> sys.stderr, "Invalid option:", res - return GetUserSelection(message, headers, maximum_suggested_headers) - return headers[idx - 1] - - -def execute(command, text): - p = subprocess.Popen(command, - stdout=subprocess.PIPE, stderr=subprocess.PIPE, - stdin=subprocess.PIPE) - return p.communicate(input=text) - - -def InsertHeaderToVimBuffer(header, text): - command = [binary, "-stdin", "-insert-header=" + json.dumps(header), - vim.current.buffer.name] - stdout, stderr = execute(command, text) - if stderr: - raise Exception(stderr) - if stdout: - lines = stdout.splitlines() - sequence = difflib.SequenceMatcher(None, vim.current.buffer, lines) - line_num = None - for op in reversed(sequence.get_opcodes()): - if op[0] != 'equal': - vim.current.buffer[op[1]:op[2]] = lines[op[3]:op[4]] - if op[0] == 'insert': - # line_num in vim is 1-based. - line_num = op[1] + 1 - - if jump_to_include and line_num: - vim.current.window.cursor = (line_num, 0) - - -# The vim internal implementation (expand("cword"/"cWORD")) doesn't support -# our use case very well, we re-implement our own one. -def get_symbol_under_cursor(): - line = vim.eval("line(\".\")") - # column number in vim is 1-based. - col = int(vim.eval("col(\".\")")) - 1 - line_text = vim.eval("getline({0})".format(line)) - if len(line_text) == 0: return "" - symbol_pos_begin = col - p = re.compile('[a-zA-Z0-9:_]') - while symbol_pos_begin >= 0 and p.match(line_text[symbol_pos_begin]): - symbol_pos_begin -= 1 - - symbol_pos_end = col - while symbol_pos_end < len(line_text) and p.match(line_text[symbol_pos_end]): - symbol_pos_end += 1 - return line_text[symbol_pos_begin+1:symbol_pos_end] - - -def main(): - parser = argparse.ArgumentParser( - description='Vim integration for clang-include-fixer') - parser.add_argument('-db', default='yaml', - help='clang-include-fixer input format.') - parser.add_argument('-input', default='', - help='String to initialize the database.') - # Don't throw exception when parsing unknown arguements to make the script - # work in neovim. - # Neovim (at least v0.2.1) somehow mangles the sys.argv in a weird way: it - # will pass additional arguments (e.g. "-c script_host.py") to sys.argv, - # which makes the script fail. - args, _ = parser.parse_known_args() - - # Get the current text. - buf = vim.current.buffer - text = '\n'.join(buf) - - if query_mode: - symbol = get_symbol_under_cursor() - if len(symbol) == 0: - print "Skip querying empty symbol." - return - command = [binary, "-stdin", "-query-symbol="+get_symbol_under_cursor(), - "-db=" + args.db, "-input=" + args.input, - vim.current.buffer.name] - else: - # Run command to get all headers. - command = [binary, "-stdin", "-output-headers", "-db=" + args.db, - "-input=" + args.input, vim.current.buffer.name] - stdout, stderr = execute(command, text) - if stderr: - print >> sys.stderr, "Error while running clang-include-fixer: " + stderr - return - - include_fixer_context = json.loads(stdout) - query_symbol_infos = include_fixer_context["QuerySymbolInfos"] - if not query_symbol_infos: - print "The file is fine, no need to add a header." - return - symbol = query_symbol_infos[0]["RawIdentifier"] - # The header_infos is already sorted by include-fixer. - header_infos = include_fixer_context["HeaderInfos"] - # Deduplicate headers while keeping the order, so that the same header would - # not be suggested twice. - unique_headers = [] - seen = set() - for header_info in header_infos: - header = header_info["Header"] - if header not in seen: - seen.add(header) - unique_headers.append(header) - - if not unique_headers: - print "Couldn't find a header for {0}.".format(symbol) - return - - try: - selected = unique_headers[0] - inserted_header_infos = header_infos - if len(unique_headers) > 1: - selected = GetUserSelection( - "choose a header file for {0}.".format(symbol), - unique_headers, maximum_suggested_headers) - inserted_header_infos = [ - header for header in header_infos if header["Header"] == selected] - include_fixer_context["HeaderInfos"] = inserted_header_infos - - InsertHeaderToVimBuffer(include_fixer_context, text) - print "Added #include {0} for {1}.".format(selected, symbol) - except Exception as error: - print >> sys.stderr, error.message - return - - -if __name__ == '__main__': - main() Index: test/clang-include-fixer/Inputs/database_template.json =================================================================== --- test/clang-include-fixer/Inputs/database_template.json +++ test/clang-include-fixer/Inputs/database_template.json @@ -0,0 +1,7 @@ +[ +{ + "directory": "test_dir/build", + "command": "clang++ -I../include -o bar.o test_dir/src/bar.cpp", + "file": "test_dir/src/bar.cpp" +} +] Index: test/clang-include-fixer/Inputs/fake_yaml_db.yaml =================================================================== --- test/clang-include-fixer/Inputs/fake_yaml_db.yaml +++ test/clang-include-fixer/Inputs/fake_yaml_db.yaml @@ -0,0 +1,71 @@ +--- +Name: foo +Contexts: + - ContextType: Namespace + ContextName: a + - ContextType: Namespace + ContextName: b +FilePath: foo.h +Type: Class +Seen: 1 +Used: 0 +--- +Name: foo_bar +Contexts: + - ContextType: Namespace + ContextName: a + - ContextType: Namespace + ContextName: b +FilePath: foobar.h +Type: Class +Seen: 0 +Used: 0 +--- +Name: bar +Contexts: + - ContextType: Namespace + ContextName: a + - ContextType: Namespace + ContextName: b +FilePath: ../include/bar.h +Type: Class +Seen: 1 +Used: 0 +--- +Name: bar +Contexts: + - ContextType: Namespace + ContextName: a + - ContextType: Namespace + ContextName: b +FilePath: ../include/bar.h +Type: Class +Seen: 3 +Used: 0 +--- +Name: bar +Contexts: + - ContextType: Namespace + ContextName: a + - ContextType: Namespace + ContextName: b +FilePath: ../include/zbar.h +Type: Class +Seen: 3 +Used: 0 +--- +Name: b +Contexts: +FilePath: var.h +Type: Variable +Seen: 1 +Used: 0 +--- +Name: bar +Contexts: + - ContextType: Namespace + ContextName: c +FilePath: test/clang-include-fixer/baz.h +Type: Class +Seen: 1 +Used: 0 Index: test/clang-include-fixer/Inputs/merge/a.yaml =================================================================== --- test/clang-include-fixer/Inputs/merge/a.yaml +++ test/clang-include-fixer/Inputs/merge/a.yaml @@ -0,0 +1,20 @@ +--- +Name: foo +Contexts: + - ContextType: Namespace + ContextName: a +FilePath: foo.h +Type: Class +Seen: 1 +Used: 1 +... +--- +Name: bar +Contexts: + - ContextType: Namespace + ContextName: a +FilePath: ../include/bar.h +Type: Class +Seen: 1 +Used: 2 +... Index: test/clang-include-fixer/Inputs/merge/b.yaml =================================================================== --- test/clang-include-fixer/Inputs/merge/b.yaml +++ test/clang-include-fixer/Inputs/merge/b.yaml @@ -0,0 +1,20 @@ +--- +Name: foo +Contexts: + - ContextType: Namespace + ContextName: a +FilePath: foo.h +Type: Class +Seen: 1 +Used: 2 +... +--- +Name: bar +Contexts: + - ContextType: Namespace + ContextName: a +FilePath: ../include/barbar.h +Type: Class +Seen: 1 +Used: 0 +... Index: test/clang-include-fixer/commandline_options.cpp =================================================================== --- test/clang-include-fixer/commandline_options.cpp +++ test/clang-include-fixer/commandline_options.cpp @@ -0,0 +1,15 @@ +// RUN: echo "foo f;" > %t.cpp +// RUN: clang-include-fixer -db=fixed -input='foo= "foo.h","bar.h"' -output-headers %t.cpp -- | FileCheck %s +// RUN: cat %t.cpp | clang-include-fixer -stdin -insert-header='{FilePath: "%/t.cpp", QuerySymbolInfos: [{RawIdentifier: foo, Range: {Offset: 0, Length: 3}}], HeaderInfos: [{Header: "\"foo.h\"", QualifiedName: "foo"}]}' %t.cpp | FileCheck %s -check-prefix=CHECK-CODE +// RUN: cat %t.cpp | not clang-include-fixer -stdin -insert-header='{FilePath: "%/t.cpp", QuerySymbolInfos: [{RawIdentifier: foo, Range: {Offset: 0, Length: 3}}], HeaderInfos: [{Header: "\"foo.h\"", QualifiedName: "foo"},{Header: "\"foo2.h\"", QualifiedName: "foo"}]}' %t.cpp +// RUN: cat %t.cpp | clang-include-fixer -stdin -insert-header='{FilePath: "%/t.cpp", QuerySymbolInfos: [{RawIdentifier: foo, Range: {Offset: 0, Length: 3}}], HeaderInfos: [{Header: "\"foo.h\"", QualifiedName: "a:foo"},{Header: "\"foo.h\"", QualifiedName: "b:foo"}]}' %t.cpp +// +// CHECK: "HeaderInfos": [ +// CHECK-NEXT: {"Header": "\"foo.h\"", +// CHECK-NEXT: "QualifiedName": "foo"}, +// CHECK-NEXT: {"Header": "\"bar.h\"", +// CHECK-NEXT: "QualifiedName": "foo"} +// CHECK-NEXT:] +// +// CHECK-CODE: #include "foo.h" +// CHECK-CODE: foo f; Index: test/clang-include-fixer/exit_on_fatal.cpp =================================================================== --- test/clang-include-fixer/exit_on_fatal.cpp +++ test/clang-include-fixer/exit_on_fatal.cpp @@ -0,0 +1,10 @@ +// RUN: sed -e 's#//.*$##' %s > %t.cpp +// RUN: not clang-include-fixer -db=fixed -input='foo= "foo.h"' %t.cpp -- +// RUN: FileCheck %s -input-file=%t.cpp + +// CHECK-NOT: #include +// CHECK: #include "doesnotexist.h" +// CHECK-NEXT: foo f; + +#include "doesnotexist.h" +foo f; Index: test/clang-include-fixer/fixeddb.cpp =================================================================== --- test/clang-include-fixer/fixeddb.cpp +++ test/clang-include-fixer/fixeddb.cpp @@ -0,0 +1,8 @@ +// RUN: sed -e 's#//.*$##' %s > %t.cpp +// RUN: clang-include-fixer -db=fixed -input='foo= "foo.h","bar.h"' %t.cpp -- +// RUN: FileCheck %s -input-file=%t.cpp + +// CHECK: #include "foo.h" +// CHECK: foo f; + +foo f; Index: test/clang-include-fixer/include_path.cpp =================================================================== --- test/clang-include-fixer/include_path.cpp +++ test/clang-include-fixer/include_path.cpp @@ -0,0 +1,19 @@ +// RUN: mkdir -p %T/clang-include-fixer/include +// RUN: mkdir -p %T/clang-include-fixer/symbols +// RUN: mkdir -p %T/clang-include-fixer/build +// RUN: mkdir -p %T/clang-include-fixer/src +// RUN: sed 's|test_dir|%/T/clang-include-fixer|g' %S/Inputs/database_template.json > %T/clang-include-fixer/build/compile_commands.json +// RUN: echo -e '#include "bar.h"\nb::a::bar f;' > %T/clang-include-fixer/src/bar.cpp +// RUN: echo 'namespace b { namespace a { class bar {}; } }' > %T/clang-include-fixer/include/bar.h +// RUN: cd %T/clang-include-fixer/build +// RUN: find-all-symbols -output-dir=%T/clang-include-fixer/symbols -p=. %T/clang-include-fixer/src/bar.cpp +// RUN: find-all-symbols -merge-dir=%T/clang-include-fixer/symbols %T/clang-include-fixer/build/find_all_symbols.yaml +// RUN: FileCheck -input-file=%T/clang-include-fixer/build/find_all_symbols.yaml -check-prefix=CHECK-YAML %s +// +// RUN: echo 'b::a::bar f;' > %T/clang-include-fixer/src/bar.cpp +// RUN: clang-include-fixer -db=yaml -input=%T/clang-include-fixer/build/find_all_symbols.yaml -minimize-paths=true -p=. %T/clang-include-fixer/src/bar.cpp +// RUN: FileCheck -input-file=%T/clang-include-fixer/src/bar.cpp %s + +// CHECK-YAML: ..{{[/\\]}}include{{[/\\]}}bar.h +// CHECK: #include "bar.h" +// CHECK: b::a::bar f; Index: test/clang-include-fixer/merge.test =================================================================== --- test/clang-include-fixer/merge.test +++ test/clang-include-fixer/merge.test @@ -0,0 +1,33 @@ +# RUN: find-all-symbols -merge-dir=%S/Inputs/merge %t.merged +# RUN: sed '/^#/d' %s > %t.golden +# RUN: diff -u %t.golden %t.merged +--- +Name: bar +Contexts: + - ContextType: Namespace + ContextName: a +FilePath: '../include/bar.h' +Type: Class +Seen: 1 +Used: 1 +... +--- +Name: bar +Contexts: + - ContextType: Namespace + ContextName: a +FilePath: '../include/barbar.h' +Type: Class +Seen: 1 +Used: 0 +... +--- +Name: foo +Contexts: + - ContextType: Namespace + ContextName: a +FilePath: foo.h +Type: Class +Seen: 2 +Used: 2 +... Index: test/clang-include-fixer/multiple_fixes.cpp =================================================================== --- test/clang-include-fixer/multiple_fixes.cpp +++ test/clang-include-fixer/multiple_fixes.cpp @@ -0,0 +1,13 @@ +// REQUIRES: shell +// RUN: sed -e 's#//.*$##' %s > %t.cpp +// RUN: mkdir -p %T/clang-include-fixer/multiple-fixes +// RUN: echo 'foo f;' > %T/clang-include-fixer/multiple-fixes/foo.cpp +// RUN: echo 'bar b;' > %T/clang-include-fixer/multiple-fixes/bar.cpp +// RUN: clang-include-fixer -db=fixed -input='foo= "foo.h";bar= "bar.h"' %T/clang-include-fixer/multiple-fixes/*.cpp -- +// RUN: FileCheck -input-file=%T/clang-include-fixer/multiple-fixes/bar.cpp %s -check-prefix=CHECK-BAR +// RUN: FileCheck -input-file=%T/clang-include-fixer/multiple-fixes/foo.cpp %s -check-prefix=CHECK-FOO +// +// CHECK-FOO: #include "foo.h" +// CHECK-FOO: foo f; +// CHECK-BAR: #include "bar.h" +// CHECK-BAR: bar b; Index: test/clang-include-fixer/prefix_variable.cpp =================================================================== --- test/clang-include-fixer/prefix_variable.cpp +++ test/clang-include-fixer/prefix_variable.cpp @@ -0,0 +1,10 @@ +// RUN: sed -e 's#//.*$##' %s > %t.cpp +// RUN: clang-include-fixer -db=yaml -input=%p/Inputs/fake_yaml_db.yaml %t.cpp -- +// RUN: FileCheck %s -input-file=%t.cpp + +// CHECK-NOT: #include +// CHECK: doesnotexist f; + +namespace b { +doesnotexist f; +} Index: test/clang-include-fixer/query_symbol.cpp =================================================================== --- test/clang-include-fixer/query_symbol.cpp +++ test/clang-include-fixer/query_symbol.cpp @@ -0,0 +1,13 @@ +// RUN: clang-include-fixer -db=fixed -input='foo= "foo.h","bar.h"' -query-symbol="foo" test.cpp -- | FileCheck %s + +// CHECK: "FilePath": "test.cpp", +// CHECK-NEXT:"QuerySymbolInfos": [ +// CHECK-NEXT: {"RawIdentifier": "foo", +// CHECK-NEXT: "Range":{"Offset":0,"Length":0}} +// CHECK-NEXT:], +// CHECK-NEXT:"HeaderInfos": [ +// CHECK-NEXT: {"Header": "\"foo.h\"", +// CHECK-NEXT: "QualifiedName": "foo"}, +// CHECK-NEXT: {"Header": "\"bar.h\"", +// CHECK-NEXT: "QualifiedName": "foo"} +// CHECK-NEXT:] Index: test/clang-include-fixer/ranking.cpp =================================================================== --- test/clang-include-fixer/ranking.cpp +++ test/clang-include-fixer/ranking.cpp @@ -0,0 +1,13 @@ +// RUN: clang-include-fixer -db=yaml -input=%S/Inputs/fake_yaml_db.yaml -output-headers %s -- | FileCheck %s +// RUN: clang-include-fixer -query-symbol bar -db=yaml -input=%S/Inputs/fake_yaml_db.yaml -output-headers %s -- | FileCheck %s + +// CHECK: "HeaderInfos": [ +// CHECK-NEXT: {"Header": "\"test/clang-include-fixer/baz.h\"", +// CHECK-NEXT: "QualifiedName": "c::bar"}, +// CHECK-NEXT: {"Header": "\"../include/bar.h\"", +// CHECK-NEXT: "QualifiedName": "b::a::bar"}, +// CHECK-NEXT: {"Header": "\"../include/zbar.h\"", +// CHECK-NEXT: "QualifiedName": "b::a::bar"} +// CHECK-NEXT:] + +bar b; Index: test/clang-include-fixer/yaml_fuzzy.cpp =================================================================== --- test/clang-include-fixer/yaml_fuzzy.cpp +++ test/clang-include-fixer/yaml_fuzzy.cpp @@ -0,0 +1,9 @@ +// RUN: sed -e 's#//.*$##' %s > %t.cpp +// RUN: clang-include-fixer -db=fuzzyYaml -input=%p/Inputs/fake_yaml_db.yaml %t.cpp -- +// RUN: FileCheck %s -input-file=%t.cpp + +// clang-include-fixer will add the include, but doesn't complete the symbol. +// CHECK: #include "foobar.h" +// CHECK: fba f; + +b::a::fba f; Index: test/clang-include-fixer/yamldb.cpp =================================================================== --- test/clang-include-fixer/yamldb.cpp +++ test/clang-include-fixer/yamldb.cpp @@ -0,0 +1,8 @@ +// RUN: sed -e 's#//.*$##' %s > %t.cpp +// RUN: clang-include-fixer -db=yaml -input=%p/Inputs/fake_yaml_db.yaml %t.cpp -- +// RUN: FileCheck %s -input-file=%t.cpp + +// CHECK: #include "foo.h" +// CHECK: b::a::foo f; + +b::a::foo f; Index: test/clang-include-fixer/yamldb_autodetect.cpp =================================================================== --- test/clang-include-fixer/yamldb_autodetect.cpp +++ test/clang-include-fixer/yamldb_autodetect.cpp @@ -0,0 +1,11 @@ +// RUN: mkdir -p %T/foo/bar +// RUN: cp %p/Inputs/fake_yaml_db.yaml %T/find_all_symbols_db.yaml +// RUN: cd %T/foo +// RUN: sed -e 's#//.*$##' %s > bar/test.cpp +// RUN: clang-include-fixer -db=yaml bar/test.cpp -- +// RUN: FileCheck %s -input-file=bar/test.cpp + +// CHECK: #include "foo.h" +// CHECK: b::a::foo f; + +b::a::foo f; Index: test/include-fixer/Inputs/database_template.json =================================================================== --- test/include-fixer/Inputs/database_template.json +++ test/include-fixer/Inputs/database_template.json @@ -1,7 +0,0 @@ -[ -{ - "directory": "test_dir/build", - "command": "clang++ -I../include -o bar.o test_dir/src/bar.cpp", - "file": "test_dir/src/bar.cpp" -} -] Index: test/include-fixer/Inputs/fake_yaml_db.yaml =================================================================== --- test/include-fixer/Inputs/fake_yaml_db.yaml +++ test/include-fixer/Inputs/fake_yaml_db.yaml @@ -1,71 +0,0 @@ ---- -Name: foo -Contexts: - - ContextType: Namespace - ContextName: a - - ContextType: Namespace - ContextName: b -FilePath: foo.h -Type: Class -Seen: 1 -Used: 0 ---- -Name: foo_bar -Contexts: - - ContextType: Namespace - ContextName: a - - ContextType: Namespace - ContextName: b -FilePath: foobar.h -Type: Class -Seen: 0 -Used: 0 ---- -Name: bar -Contexts: - - ContextType: Namespace - ContextName: a - - ContextType: Namespace - ContextName: b -FilePath: ../include/bar.h -Type: Class -Seen: 1 -Used: 0 ---- -Name: bar -Contexts: - - ContextType: Namespace - ContextName: a - - ContextType: Namespace - ContextName: b -FilePath: ../include/bar.h -Type: Class -Seen: 3 -Used: 0 ---- -Name: bar -Contexts: - - ContextType: Namespace - ContextName: a - - ContextType: Namespace - ContextName: b -FilePath: ../include/zbar.h -Type: Class -Seen: 3 -Used: 0 ---- -Name: b -Contexts: -FilePath: var.h -Type: Variable -Seen: 1 -Used: 0 ---- -Name: bar -Contexts: - - ContextType: Namespace - ContextName: c -FilePath: test/include-fixer/baz.h -Type: Class -Seen: 1 -Used: 0 Index: test/include-fixer/Inputs/merge/a.yaml =================================================================== --- test/include-fixer/Inputs/merge/a.yaml +++ test/include-fixer/Inputs/merge/a.yaml @@ -1,20 +0,0 @@ ---- -Name: foo -Contexts: - - ContextType: Namespace - ContextName: a -FilePath: foo.h -Type: Class -Seen: 1 -Used: 1 -... ---- -Name: bar -Contexts: - - ContextType: Namespace - ContextName: a -FilePath: ../include/bar.h -Type: Class -Seen: 1 -Used: 2 -... Index: test/include-fixer/Inputs/merge/b.yaml =================================================================== --- test/include-fixer/Inputs/merge/b.yaml +++ test/include-fixer/Inputs/merge/b.yaml @@ -1,20 +0,0 @@ ---- -Name: foo -Contexts: - - ContextType: Namespace - ContextName: a -FilePath: foo.h -Type: Class -Seen: 1 -Used: 2 -... ---- -Name: bar -Contexts: - - ContextType: Namespace - ContextName: a -FilePath: ../include/barbar.h -Type: Class -Seen: 1 -Used: 0 -... Index: test/include-fixer/commandline_options.cpp =================================================================== --- test/include-fixer/commandline_options.cpp +++ test/include-fixer/commandline_options.cpp @@ -1,15 +0,0 @@ -// RUN: echo "foo f;" > %t.cpp -// RUN: clang-include-fixer -db=fixed -input='foo= "foo.h","bar.h"' -output-headers %t.cpp -- | FileCheck %s -// RUN: cat %t.cpp | clang-include-fixer -stdin -insert-header='{FilePath: "%/t.cpp", QuerySymbolInfos: [{RawIdentifier: foo, Range: {Offset: 0, Length: 3}}], HeaderInfos: [{Header: "\"foo.h\"", QualifiedName: "foo"}]}' %t.cpp | FileCheck %s -check-prefix=CHECK-CODE -// RUN: cat %t.cpp | not clang-include-fixer -stdin -insert-header='{FilePath: "%/t.cpp", QuerySymbolInfos: [{RawIdentifier: foo, Range: {Offset: 0, Length: 3}}], HeaderInfos: [{Header: "\"foo.h\"", QualifiedName: "foo"},{Header: "\"foo2.h\"", QualifiedName: "foo"}]}' %t.cpp -// RUN: cat %t.cpp | clang-include-fixer -stdin -insert-header='{FilePath: "%/t.cpp", QuerySymbolInfos: [{RawIdentifier: foo, Range: {Offset: 0, Length: 3}}], HeaderInfos: [{Header: "\"foo.h\"", QualifiedName: "a:foo"},{Header: "\"foo.h\"", QualifiedName: "b:foo"}]}' %t.cpp -// -// CHECK: "HeaderInfos": [ -// CHECK-NEXT: {"Header": "\"foo.h\"", -// CHECK-NEXT: "QualifiedName": "foo"}, -// CHECK-NEXT: {"Header": "\"bar.h\"", -// CHECK-NEXT: "QualifiedName": "foo"} -// CHECK-NEXT:] -// -// CHECK-CODE: #include "foo.h" -// CHECK-CODE: foo f; Index: test/include-fixer/exit_on_fatal.cpp =================================================================== --- test/include-fixer/exit_on_fatal.cpp +++ test/include-fixer/exit_on_fatal.cpp @@ -1,10 +0,0 @@ -// RUN: sed -e 's#//.*$##' %s > %t.cpp -// RUN: not clang-include-fixer -db=fixed -input='foo= "foo.h"' %t.cpp -- -// RUN: FileCheck %s -input-file=%t.cpp - -// CHECK-NOT: #include -// CHECK: #include "doesnotexist.h" -// CHECK-NEXT: foo f; - -#include "doesnotexist.h" -foo f; Index: test/include-fixer/fixeddb.cpp =================================================================== --- test/include-fixer/fixeddb.cpp +++ test/include-fixer/fixeddb.cpp @@ -1,8 +0,0 @@ -// RUN: sed -e 's#//.*$##' %s > %t.cpp -// RUN: clang-include-fixer -db=fixed -input='foo= "foo.h","bar.h"' %t.cpp -- -// RUN: FileCheck %s -input-file=%t.cpp - -// CHECK: #include "foo.h" -// CHECK: foo f; - -foo f; Index: test/include-fixer/include_path.cpp =================================================================== --- test/include-fixer/include_path.cpp +++ test/include-fixer/include_path.cpp @@ -1,19 +0,0 @@ -// RUN: mkdir -p %T/include-fixer/include -// RUN: mkdir -p %T/include-fixer/symbols -// RUN: mkdir -p %T/include-fixer/build -// RUN: mkdir -p %T/include-fixer/src -// RUN: sed 's|test_dir|%/T/include-fixer|g' %S/Inputs/database_template.json > %T/include-fixer/build/compile_commands.json -// RUN: echo -e '#include "bar.h"\nb::a::bar f;' > %T/include-fixer/src/bar.cpp -// RUN: echo 'namespace b { namespace a { class bar {}; } }' > %T/include-fixer/include/bar.h -// RUN: cd %T/include-fixer/build -// RUN: find-all-symbols -output-dir=%T/include-fixer/symbols -p=. %T/include-fixer/src/bar.cpp -// RUN: find-all-symbols -merge-dir=%T/include-fixer/symbols %T/include-fixer/build/find_all_symbols.yaml -// RUN: FileCheck -input-file=%T/include-fixer/build/find_all_symbols.yaml -check-prefix=CHECK-YAML %s -// -// RUN: echo 'b::a::bar f;' > %T/include-fixer/src/bar.cpp -// RUN: clang-include-fixer -db=yaml -input=%T/include-fixer/build/find_all_symbols.yaml -minimize-paths=true -p=. %T/include-fixer/src/bar.cpp -// RUN: FileCheck -input-file=%T/include-fixer/src/bar.cpp %s - -// CHECK-YAML: ..{{[/\\]}}include{{[/\\]}}bar.h -// CHECK: #include "bar.h" -// CHECK: b::a::bar f; Index: test/include-fixer/merge.test =================================================================== --- test/include-fixer/merge.test +++ test/include-fixer/merge.test @@ -1,33 +0,0 @@ -# RUN: find-all-symbols -merge-dir=%S/Inputs/merge %t.merged -# RUN: sed '/^#/d' %s > %t.golden -# RUN: diff -u %t.golden %t.merged ---- -Name: bar -Contexts: - - ContextType: Namespace - ContextName: a -FilePath: '../include/bar.h' -Type: Class -Seen: 1 -Used: 1 -... ---- -Name: bar -Contexts: - - ContextType: Namespace - ContextName: a -FilePath: '../include/barbar.h' -Type: Class -Seen: 1 -Used: 0 -... ---- -Name: foo -Contexts: - - ContextType: Namespace - ContextName: a -FilePath: foo.h -Type: Class -Seen: 2 -Used: 2 -... Index: test/include-fixer/multiple_fixes.cpp =================================================================== --- test/include-fixer/multiple_fixes.cpp +++ test/include-fixer/multiple_fixes.cpp @@ -1,13 +0,0 @@ -// REQUIRES: shell -// RUN: sed -e 's#//.*$##' %s > %t.cpp -// RUN: mkdir -p %T/include-fixer/multiple-fixes -// RUN: echo 'foo f;' > %T/include-fixer/multiple-fixes/foo.cpp -// RUN: echo 'bar b;' > %T/include-fixer/multiple-fixes/bar.cpp -// RUN: clang-include-fixer -db=fixed -input='foo= "foo.h";bar= "bar.h"' %T/include-fixer/multiple-fixes/*.cpp -- -// RUN: FileCheck -input-file=%T/include-fixer/multiple-fixes/bar.cpp %s -check-prefix=CHECK-BAR -// RUN: FileCheck -input-file=%T/include-fixer/multiple-fixes/foo.cpp %s -check-prefix=CHECK-FOO -// -// CHECK-FOO: #include "foo.h" -// CHECK-FOO: foo f; -// CHECK-BAR: #include "bar.h" -// CHECK-BAR: bar b; Index: test/include-fixer/prefix_variable.cpp =================================================================== --- test/include-fixer/prefix_variable.cpp +++ test/include-fixer/prefix_variable.cpp @@ -1,10 +0,0 @@ -// RUN: sed -e 's#//.*$##' %s > %t.cpp -// RUN: clang-include-fixer -db=yaml -input=%p/Inputs/fake_yaml_db.yaml %t.cpp -- -// RUN: FileCheck %s -input-file=%t.cpp - -// CHECK-NOT: #include -// CHECK: doesnotexist f; - -namespace b { -doesnotexist f; -} Index: test/include-fixer/query_symbol.cpp =================================================================== --- test/include-fixer/query_symbol.cpp +++ test/include-fixer/query_symbol.cpp @@ -1,13 +0,0 @@ -// RUN: clang-include-fixer -db=fixed -input='foo= "foo.h","bar.h"' -query-symbol="foo" test.cpp -- | FileCheck %s - -// CHECK: "FilePath": "test.cpp", -// CHECK-NEXT:"QuerySymbolInfos": [ -// CHECK-NEXT: {"RawIdentifier": "foo", -// CHECK-NEXT: "Range":{"Offset":0,"Length":0}} -// CHECK-NEXT:], -// CHECK-NEXT:"HeaderInfos": [ -// CHECK-NEXT: {"Header": "\"foo.h\"", -// CHECK-NEXT: "QualifiedName": "foo"}, -// CHECK-NEXT: {"Header": "\"bar.h\"", -// CHECK-NEXT: "QualifiedName": "foo"} -// CHECK-NEXT:] Index: test/include-fixer/ranking.cpp =================================================================== --- test/include-fixer/ranking.cpp +++ test/include-fixer/ranking.cpp @@ -1,13 +0,0 @@ -// RUN: clang-include-fixer -db=yaml -input=%S/Inputs/fake_yaml_db.yaml -output-headers %s -- | FileCheck %s -// RUN: clang-include-fixer -query-symbol bar -db=yaml -input=%S/Inputs/fake_yaml_db.yaml -output-headers %s -- | FileCheck %s - -// CHECK: "HeaderInfos": [ -// CHECK-NEXT: {"Header": "\"test/include-fixer/baz.h\"", -// CHECK-NEXT: "QualifiedName": "c::bar"}, -// CHECK-NEXT: {"Header": "\"../include/bar.h\"", -// CHECK-NEXT: "QualifiedName": "b::a::bar"}, -// CHECK-NEXT: {"Header": "\"../include/zbar.h\"", -// CHECK-NEXT: "QualifiedName": "b::a::bar"} -// CHECK-NEXT:] - -bar b; Index: test/include-fixer/yaml_fuzzy.cpp =================================================================== --- test/include-fixer/yaml_fuzzy.cpp +++ test/include-fixer/yaml_fuzzy.cpp @@ -1,9 +0,0 @@ -// RUN: sed -e 's#//.*$##' %s > %t.cpp -// RUN: clang-include-fixer -db=fuzzyYaml -input=%p/Inputs/fake_yaml_db.yaml %t.cpp -- -// RUN: FileCheck %s -input-file=%t.cpp - -// include-fixer will add the include, but doesn't complete the symbol. -// CHECK: #include "foobar.h" -// CHECK: fba f; - -b::a::fba f; Index: test/include-fixer/yamldb.cpp =================================================================== --- test/include-fixer/yamldb.cpp +++ test/include-fixer/yamldb.cpp @@ -1,8 +0,0 @@ -// RUN: sed -e 's#//.*$##' %s > %t.cpp -// RUN: clang-include-fixer -db=yaml -input=%p/Inputs/fake_yaml_db.yaml %t.cpp -- -// RUN: FileCheck %s -input-file=%t.cpp - -// CHECK: #include "foo.h" -// CHECK: b::a::foo f; - -b::a::foo f; Index: test/include-fixer/yamldb_autodetect.cpp =================================================================== --- test/include-fixer/yamldb_autodetect.cpp +++ test/include-fixer/yamldb_autodetect.cpp @@ -1,11 +0,0 @@ -// RUN: mkdir -p %T/foo/bar -// RUN: cp %p/Inputs/fake_yaml_db.yaml %T/find_all_symbols_db.yaml -// RUN: cd %T/foo -// RUN: sed -e 's#//.*$##' %s > bar/test.cpp -// RUN: clang-include-fixer -db=yaml bar/test.cpp -- -// RUN: FileCheck %s -input-file=bar/test.cpp - -// CHECK: #include "foo.h" -// CHECK: b::a::foo f; - -b::a::foo f; Index: unittests/CMakeLists.txt =================================================================== --- unittests/CMakeLists.txt +++ unittests/CMakeLists.txt @@ -17,8 +17,8 @@ add_subdirectory(clang-apply-replacements) add_subdirectory(clang-change-namespace) add_subdirectory(clang-doc) +add_subdirectory(clang-include-fixer) add_subdirectory(clang-move) add_subdirectory(clang-query) add_subdirectory(clang-tidy) add_subdirectory(clangd) -add_subdirectory(include-fixer) Index: unittests/clang-include-fixer/CMakeLists.txt =================================================================== --- unittests/clang-include-fixer/CMakeLists.txt +++ unittests/clang-include-fixer/CMakeLists.txt @@ -0,0 +1,32 @@ +set(LLVM_LINK_COMPONENTS + support + ) + +get_filename_component(INCLUDE_FIXER_SOURCE_DIR + ${CMAKE_CURRENT_SOURCE_DIR}/../../clang-include-fixer REALPATH) +include_directories( + ${INCLUDE_FIXER_SOURCE_DIR} + ) + +# We'd like to clang/unittests/Tooling/RewriterTestContext.h in the test. +include_directories(${CLANG_SOURCE_DIR}) + +add_extra_unittest(IncludeFixerTests + IncludeFixerTest.cpp + FuzzySymbolIndexTests.cpp + ) + +target_link_libraries(IncludeFixerTests + PRIVATE + clangBasic + clangFormat + clangFrontend + clangIncludeFixer + clangRewrite + clangSerialization + clangTooling + clangToolingCore + findAllSymbols + ) + +add_subdirectory(find-all-symbols) Index: unittests/clang-include-fixer/FuzzySymbolIndexTests.cpp =================================================================== --- unittests/clang-include-fixer/FuzzySymbolIndexTests.cpp +++ unittests/clang-include-fixer/FuzzySymbolIndexTests.cpp @@ -0,0 +1,60 @@ +//===-- FuzzySymbolIndexTests.cpp - Fuzzy symbol index unit tests ---------===// +// +// 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 "FuzzySymbolIndex.h" +#include "gmock/gmock.h" +#include "llvm/Support/Regex.h" +#include "gtest/gtest.h" + +using testing::ElementsAre; +using testing::Not; + +namespace clang { +namespace include_fixer { +namespace { + +TEST(FuzzySymbolIndexTest, Tokenize) { + EXPECT_THAT(FuzzySymbolIndex::tokenize("URLHandlerCallback"), + ElementsAre("url", "handler", "callback")); + EXPECT_THAT(FuzzySymbolIndex::tokenize("snake_case11"), + ElementsAre("snake", "case", "11")); + EXPECT_THAT(FuzzySymbolIndex::tokenize("__$42!!BOB\nbob"), + ElementsAre("42", "bob", "bob")); +} + +MATCHER_P(MatchesSymbol, Identifier, "") { + llvm::Regex Pattern("^" + arg); + std::string err; + if (!Pattern.isValid(err)) { + *result_listener << "invalid regex: " << err; + return false; + } + auto Tokens = FuzzySymbolIndex::tokenize(Identifier); + std::string Target = llvm::join(Tokens.begin(), Tokens.end(), " "); + *result_listener << "matching against '" << Target << "'"; + return llvm::Regex("^" + arg).match(Target); +} + +TEST(FuzzySymbolIndexTest, QueryRegexp) { + auto QueryRegexp = [](const std::string &query) { + return FuzzySymbolIndex::queryRegexp(FuzzySymbolIndex::tokenize(query)); + }; + EXPECT_THAT(QueryRegexp("uhc"), MatchesSymbol("URLHandlerCallback")); + EXPECT_THAT(QueryRegexp("urhaca"), MatchesSymbol("URLHandlerCallback")); + EXPECT_THAT(QueryRegexp("uhcb"), Not(MatchesSymbol("URLHandlerCallback"))) + << "Non-prefix"; + EXPECT_THAT(QueryRegexp("uc"), Not(MatchesSymbol("URLHandlerCallback"))) + << "Skip token"; + + EXPECT_THAT(QueryRegexp("uptr"), MatchesSymbol("unique_ptr")); + EXPECT_THAT(QueryRegexp("UniP"), MatchesSymbol("unique_ptr")); +} + +} // namespace +} // namespace include_fixer +} // namespace clang Index: unittests/clang-include-fixer/IncludeFixerTest.cpp =================================================================== --- unittests/clang-include-fixer/IncludeFixerTest.cpp +++ unittests/clang-include-fixer/IncludeFixerTest.cpp @@ -0,0 +1,371 @@ +//===-- IncludeFixerTest.cpp - Include fixer unit tests -------------------===// +// +// 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 "InMemorySymbolIndex.h" +#include "IncludeFixer.h" +#include "SymbolIndexManager.h" +#include "unittests/Tooling/RewriterTestContext.h" +#include "clang/Tooling/Tooling.h" +#include "gtest/gtest.h" + +namespace clang { +namespace include_fixer { +namespace { + +using find_all_symbols::SymbolInfo; +using find_all_symbols::SymbolAndSignals; + +static bool runOnCode(tooling::ToolAction *ToolAction, StringRef Code, + StringRef FileName, + const std::vector &ExtraArgs) { + llvm::IntrusiveRefCntPtr InMemoryFileSystem( + new llvm::vfs::InMemoryFileSystem); + llvm::IntrusiveRefCntPtr Files( + new FileManager(FileSystemOptions(), InMemoryFileSystem)); + // FIXME: Investigate why -fms-compatibility breaks tests. + std::vector Args = {"include_fixer", "-fsyntax-only", + "-fno-ms-compatibility", FileName}; + Args.insert(Args.end(), ExtraArgs.begin(), ExtraArgs.end()); + tooling::ToolInvocation Invocation( + Args, ToolAction, Files.get(), + std::make_shared()); + + InMemoryFileSystem->addFile(FileName, 0, + llvm::MemoryBuffer::getMemBuffer(Code)); + + InMemoryFileSystem->addFile("foo.h", 0, + llvm::MemoryBuffer::getMemBuffer("\n")); + InMemoryFileSystem->addFile("dir/bar.h", 0, + llvm::MemoryBuffer::getMemBuffer("\n")); + InMemoryFileSystem->addFile("dir/otherdir/qux.h", 0, + llvm::MemoryBuffer::getMemBuffer("\n")); + InMemoryFileSystem->addFile("header.h", 0, + llvm::MemoryBuffer::getMemBuffer("bar b;")); + return Invocation.run(); +} + +static std::string runIncludeFixer( + StringRef Code, + const std::vector &ExtraArgs = std::vector()) { + std::vector Symbols = { + {SymbolInfo("string", SymbolInfo::SymbolKind::Class, "", + {{SymbolInfo::ContextType::Namespace, "std"}}), + SymbolInfo::Signals{}}, + {SymbolInfo("sting", SymbolInfo::SymbolKind::Class, "\"sting\"", + {{SymbolInfo::ContextType::Namespace, "std"}}), + SymbolInfo::Signals{}}, + {SymbolInfo("foo", SymbolInfo::SymbolKind::Class, + "\"dir/otherdir/qux.h\"", + {{SymbolInfo::ContextType::Namespace, "b"}, + {SymbolInfo::ContextType::Namespace, "a"}}), + SymbolInfo::Signals{}}, + {SymbolInfo("bar", SymbolInfo::SymbolKind::Class, "\"bar.h\"", + {{SymbolInfo::ContextType::Namespace, "b"}, + {SymbolInfo::ContextType::Namespace, "a"}}), + SymbolInfo::Signals{}}, + {SymbolInfo("bar", SymbolInfo::SymbolKind::Class, "\"bar2.h\"", + {{SymbolInfo::ContextType::Namespace, "c"}, + {SymbolInfo::ContextType::Namespace, "a"}}), + SymbolInfo::Signals{}}, + {SymbolInfo("Green", SymbolInfo::SymbolKind::Class, "\"color.h\"", + {{SymbolInfo::ContextType::EnumDecl, "Color"}, + {SymbolInfo::ContextType::Namespace, "b"}, + {SymbolInfo::ContextType::Namespace, "a"}}), + SymbolInfo::Signals{}}, + {SymbolInfo("Vector", SymbolInfo::SymbolKind::Class, "\"Vector.h\"", + {{SymbolInfo::ContextType::Namespace, "__a"}, + {SymbolInfo::ContextType::Namespace, "a"}}), + SymbolInfo::Signals{/*Seen=*/2, 0}}, + {SymbolInfo("Vector", SymbolInfo::SymbolKind::Class, "\"Vector.h\"", + {{SymbolInfo::ContextType::Namespace, "a"}}), + SymbolInfo::Signals{/*Seen=*/2, 0}}, + {SymbolInfo("StrCat", SymbolInfo::SymbolKind::Class, "\"strcat.h\"", + {{SymbolInfo::ContextType::Namespace, "str"}}), + SymbolInfo::Signals{}}, + {SymbolInfo("str", SymbolInfo::SymbolKind::Class, "\"str.h\"", {}), + SymbolInfo::Signals{}}, + {SymbolInfo("foo2", SymbolInfo::SymbolKind::Class, "\"foo2.h\"", {}), + SymbolInfo::Signals{}}, + }; + auto SymbolIndexMgr = llvm::make_unique(); + SymbolIndexMgr->addSymbolIndex( + [=]() { return llvm::make_unique(Symbols); }); + + std::vector FixerContexts; + IncludeFixerActionFactory Factory(*SymbolIndexMgr, FixerContexts, "llvm"); + std::string FakeFileName = "input.cc"; + runOnCode(&Factory, Code, FakeFileName, ExtraArgs); + assert(FixerContexts.size() == 1); + if (FixerContexts.front().getHeaderInfos().empty()) + return Code; + auto Replaces = createIncludeFixerReplacements(Code, FixerContexts.front()); + EXPECT_TRUE(static_cast(Replaces)) + << llvm::toString(Replaces.takeError()) << "\n"; + if (!Replaces) + return ""; + RewriterTestContext Context; + FileID ID = Context.createInMemoryFile(FakeFileName, Code); + tooling::applyAllReplacements(*Replaces, Context.Rewrite); + return Context.getRewrittenText(ID); +} + +TEST(IncludeFixer, Typo) { + EXPECT_EQ("#include \nstd::string foo;\n", + runIncludeFixer("std::string foo;\n")); + + EXPECT_EQ("// comment\n#include \"foo.h\"\n#include \n" + "std::string foo;\n#include \"dir/bar.h\"\n", + runIncludeFixer("// comment\n#include \"foo.h\"\nstd::string foo;\n" + "#include \"dir/bar.h\"\n")); + + EXPECT_EQ("#include \"foo.h\"\n#include \nstd::string foo;\n", + runIncludeFixer("#include \"foo.h\"\nstd::string foo;\n")); + + EXPECT_EQ( + "#include \"foo.h\"\n#include \nstd::string::size_type foo;\n", + runIncludeFixer("#include \"foo.h\"\nstd::string::size_type foo;\n")); + + EXPECT_EQ("#include \nstd::string foo;\n", + runIncludeFixer("string foo;\n")); + + // Should not match std::string. + EXPECT_EQ("::string foo;\n", runIncludeFixer("::string foo;\n")); +} + +TEST(IncludeFixer, IncompleteType) { + EXPECT_EQ( + "#include \"foo.h\"\n#include \n" + "namespace std {\nclass string;\n}\nstd::string foo;\n", + runIncludeFixer("#include \"foo.h\"\n" + "namespace std {\nclass string;\n}\nstring foo;\n")); + + EXPECT_EQ("#include \n" + "class string;\ntypedef string foo;\nfoo f;\n", + runIncludeFixer("class string;\ntypedef string foo;\nfoo f;\n")); +} + +TEST(IncludeFixer, MinimizeInclude) { + std::vector IncludePath = {"-Idir/"}; + EXPECT_EQ("#include \"otherdir/qux.h\"\na::b::foo bar;\n", + runIncludeFixer("a::b::foo bar;\n", IncludePath)); + + IncludePath = {"-isystemdir"}; + EXPECT_EQ("#include \na::b::foo bar;\n", + runIncludeFixer("a::b::foo bar;\n", IncludePath)); + + IncludePath = {"-iquotedir"}; + EXPECT_EQ("#include \"otherdir/qux.h\"\na::b::foo bar;\n", + runIncludeFixer("a::b::foo bar;\n", IncludePath)); + + IncludePath = {"-Idir", "-Idir/otherdir"}; + EXPECT_EQ("#include \"qux.h\"\na::b::foo bar;\n", + runIncludeFixer("a::b::foo bar;\n", IncludePath)); +} + +TEST(IncludeFixer, NestedName) { + EXPECT_EQ("#include \"dir/otherdir/qux.h\"\n" + "int x = a::b::foo(0);\n", + runIncludeFixer("int x = a::b::foo(0);\n")); + + // FIXME: Handle simple macros. + EXPECT_EQ("#define FOO a::b::foo\nint x = FOO;\n", + runIncludeFixer("#define FOO a::b::foo\nint x = FOO;\n")); + EXPECT_EQ("#define FOO(x) a::##x\nint x = FOO(b::foo);\n", + runIncludeFixer("#define FOO(x) a::##x\nint x = FOO(b::foo);\n")); + + // The empty namespace is cleaned up by clang-format after clang-include-fixer + // finishes. + EXPECT_EQ("#include \"dir/otherdir/qux.h\"\n" + "\nint a = a::b::foo(0);\n", + runIncludeFixer("namespace a {}\nint a = a::b::foo(0);\n")); +} + +TEST(IncludeFixer, MultipleMissingSymbols) { + EXPECT_EQ("#include \nstd::string bar;\nstd::sting foo;\n", + runIncludeFixer("std::string bar;\nstd::sting foo;\n")); +} + +TEST(IncludeFixer, ScopedNamespaceSymbols) { + EXPECT_EQ("#include \"bar.h\"\nnamespace a {\nb::bar b;\n}", + runIncludeFixer("namespace a {\nb::bar b;\n}")); + EXPECT_EQ("#include \"bar.h\"\nnamespace A {\na::b::bar b;\n}", + runIncludeFixer("namespace A {\na::b::bar b;\n}")); + EXPECT_EQ("#include \"bar.h\"\nnamespace a {\nvoid func() { b::bar b; }\n} " + "// namespace a", + runIncludeFixer("namespace a {\nvoid func() { b::bar b; }\n}")); + EXPECT_EQ("namespace A { c::b::bar b; }\n", + runIncludeFixer("namespace A { c::b::bar b; }\n")); + // FIXME: The header should not be added here. Remove this after we support + // full match. + EXPECT_EQ("#include \"bar.h\"\nnamespace A {\na::b::bar b;\n}", + runIncludeFixer("namespace A {\nb::bar b;\n}")); + + // Finds candidates for "str::StrCat". + EXPECT_EQ("#include \"strcat.h\"\nnamespace foo2 {\nstr::StrCat b;\n}", + runIncludeFixer("namespace foo2 {\nstr::StrCat b;\n}")); + // str::StrCat2 doesn't exist. + // In these two cases, StrCat2 is a nested class of class str. + EXPECT_EQ("#include \"str.h\"\nnamespace foo2 {\nstr::StrCat2 b;\n}", + runIncludeFixer("namespace foo2 {\nstr::StrCat2 b;\n}")); + EXPECT_EQ("#include \"str.h\"\nnamespace ns {\nstr::StrCat2 b;\n}", + runIncludeFixer("namespace ns {\nstr::StrCat2 b;\n}")); +} + +TEST(IncludeFixer, EnumConstantSymbols) { + EXPECT_EQ("#include \"color.h\"\nint test = a::b::Green;\n", + runIncludeFixer("int test = a::b::Green;\n")); +} + +TEST(IncludeFixer, IgnoreSymbolFromHeader) { + std::string Code = "#include \"header.h\""; + EXPECT_EQ(Code, runIncludeFixer(Code)); +} + +// FIXME: add test cases for inserting and sorting multiple headers when +// clang-include-fixer supports multiple headers insertion. +TEST(IncludeFixer, InsertAndSortSingleHeader) { + // Insert one header. + std::string Code = "#include \"a.h\"\n" + "#include \"foo.h\"\n" + "\n" + "namespace a {\nb::bar b;\n}\n"; + std::string Expected = "#include \"a.h\"\n" + "#include \"bar.h\"\n" + "#include \"foo.h\"\n" + "\n" + "namespace a {\nb::bar b;\n}\n"; + EXPECT_EQ(Expected, runIncludeFixer(Code)); +} + +TEST(IncludeFixer, DoNotDeleteMatchedSymbol) { + EXPECT_EQ("#include \"Vector.h\"\na::Vector v;", + runIncludeFixer("a::Vector v;")); +} + +TEST(IncludeFixer, FixNamespaceQualifiers) { + EXPECT_EQ("#include \"bar.h\"\na::b::bar b;\n", + runIncludeFixer("b::bar b;\n")); + EXPECT_EQ("#include \"bar.h\"\na::b::bar b;\n", + runIncludeFixer("a::b::bar b;\n")); + EXPECT_EQ("#include \"bar.h\"\na::b::bar b;\n", + runIncludeFixer("bar b;\n")); + EXPECT_EQ("#include \"bar.h\"\nnamespace a {\nb::bar b;\n}\n", + runIncludeFixer("namespace a {\nb::bar b;\n}\n")); + EXPECT_EQ("#include \"bar.h\"\nnamespace a {\nb::bar b;\n}\n", + runIncludeFixer("namespace a {\nbar b;\n}\n")); + EXPECT_EQ("#include \"bar.h\"\nnamespace a {\nnamespace b{\nbar b;\n}\n} " + "// namespace a\n", + runIncludeFixer("namespace a {\nnamespace b{\nbar b;\n}\n}\n")); + EXPECT_EQ("c::b::bar b;\n", + runIncludeFixer("c::b::bar b;\n")); + EXPECT_EQ("#include \"bar.h\"\nnamespace d {\na::b::bar b;\n}\n", + runIncludeFixer("namespace d {\nbar b;\n}\n")); + EXPECT_EQ("#include \"bar2.h\"\nnamespace c {\na::c::bar b;\n}\n", + runIncludeFixer("namespace c {\nbar b;\n}\n")); + + // Test common qualifers reduction. + EXPECT_EQ("#include \"bar.h\"\nnamespace a {\nnamespace d {\nb::bar b;\n}\n} " + "// namespace a\n", + runIncludeFixer("namespace a {\nnamespace d {\nbar b;\n}\n}\n")); + EXPECT_EQ("#include \"bar.h\"\nnamespace d {\nnamespace a {\na::b::bar " + "b;\n}\n} // namespace d\n", + runIncludeFixer("namespace d {\nnamespace a {\nbar b;\n}\n}\n")); + + // Test nested classes. + EXPECT_EQ("#include \"bar.h\"\nnamespace d {\na::b::bar::t b;\n}\n", + runIncludeFixer("namespace d {\nbar::t b;\n}\n")); + EXPECT_EQ("#include \"bar.h\"\nnamespace c {\na::b::bar::t b;\n}\n", + runIncludeFixer("namespace c {\nbar::t b;\n}\n")); + EXPECT_EQ("#include \"bar.h\"\nnamespace a {\nb::bar::t b;\n}\n", + runIncludeFixer("namespace a {\nbar::t b;\n}\n")); + + EXPECT_EQ("#include \"color.h\"\nint test = a::b::Green;\n", + runIncludeFixer("int test = Green;\n")); + EXPECT_EQ("#include \"color.h\"\nnamespace d {\nint test = a::b::Green;\n}\n", + runIncludeFixer("namespace d {\nint test = Green;\n}\n")); + EXPECT_EQ("#include \"color.h\"\nnamespace a {\nint test = b::Green;\n}\n", + runIncludeFixer("namespace a {\nint test = Green;\n}\n")); + + // Test global scope operator. + EXPECT_EQ("#include \"bar.h\"\n::a::b::bar b;\n", + runIncludeFixer("::a::b::bar b;\n")); + EXPECT_EQ("#include \"bar.h\"\nnamespace a {\n::a::b::bar b;\n}\n", + runIncludeFixer("namespace a {\n::a::b::bar b;\n}\n")); +} + +TEST(IncludeFixer, FixNamespaceQualifiersForAllInstances) { + const char TestCode[] = R"( +namespace a { +bar b; +int func1() { + bar a; + bar *p = new bar(); + return 0; +} +} // namespace a + +namespace a { +bar func2() { + bar f; + return f; +} +} // namespace a + +// Non-fixed cases: +void f() { + bar b; +} + +namespace a { +namespace c { + bar b; +} // namespace c +} // namespace a +)"; + + const char ExpectedCode[] = R"( +#include "bar.h" +namespace a { +b::bar b; +int func1() { + b::bar a; + b::bar *p = new b::bar(); + return 0; +} +} // namespace a + +namespace a { +b::bar func2() { + b::bar f; + return f; +} +} // namespace a + +// Non-fixed cases: +void f() { + bar b; +} + +namespace a { +namespace c { + bar b; +} // namespace c +} // namespace a +)"; + + EXPECT_EQ(ExpectedCode, runIncludeFixer(TestCode)); +} + +TEST(IncludeFixer, DontAddQualifiersForMissingCompleteType) { + EXPECT_EQ("#include \"bar.h\"\nclass bar;\nvoid f() {\nbar* b;\nb->f();\n}", + runIncludeFixer("class bar;\nvoid f() {\nbar* b;\nb->f();\n}")); +} + +} // namespace +} // namespace include_fixer +} // namespace clang Index: unittests/clang-include-fixer/find-all-symbols/CMakeLists.txt =================================================================== --- unittests/clang-include-fixer/find-all-symbols/CMakeLists.txt +++ unittests/clang-include-fixer/find-all-symbols/CMakeLists.txt @@ -0,0 +1,25 @@ +set(LLVM_LINK_COMPONENTS + support + ) + +get_filename_component(INCLUDE_FIXER_SOURCE_DIR + ${CMAKE_CURRENT_SOURCE_DIR}/../../../clang-include-fixer/find-all-symbols REALPATH) +include_directories( + ${INCLUDE_FIXER_SOURCE_DIR} + ) + +add_extra_unittest(FindAllSymbolsTests + FindAllSymbolsTests.cpp + ) + +target_link_libraries(FindAllSymbolsTests + PRIVATE + clangAST + clangASTMatchers + clangBasic + clangFrontend + clangLex + clangSerialization + clangTooling + findAllSymbols + ) Index: unittests/clang-include-fixer/find-all-symbols/FindAllSymbolsTests.cpp =================================================================== --- unittests/clang-include-fixer/find-all-symbols/FindAllSymbolsTests.cpp +++ unittests/clang-include-fixer/find-all-symbols/FindAllSymbolsTests.cpp @@ -0,0 +1,577 @@ +//===-- FindAllSymbolsTests.cpp - find all symbols unit tests ---*- C++ -*-===// +// +// 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 "FindAllSymbolsAction.h" +#include "HeaderMapCollector.h" +#include "SymbolInfo.h" +#include "SymbolReporter.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Basic/FileManager.h" +#include "clang/Basic/FileSystemOptions.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/PCHContainerOperations.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/ADT/IntrusiveRefCntPtr.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/VirtualFileSystem.h" +#include "gtest/gtest.h" +#include +#include +#include + +namespace clang { +namespace find_all_symbols { + +static const char HeaderName[] = "symbols.h"; + +class TestSymbolReporter : public SymbolReporter { +public: + ~TestSymbolReporter() override {} + + void reportSymbols(llvm::StringRef FileName, + const SymbolInfo::SignalMap &NewSymbols) override { + for (const auto &Entry : NewSymbols) + Symbols[Entry.first] += Entry.second; + } + + int seen(const SymbolInfo &Symbol) const { + auto it = Symbols.find(Symbol); + return it == Symbols.end() ? 0 : it->second.Seen; + } + + int used(const SymbolInfo &Symbol) const { + auto it = Symbols.find(Symbol); + return it == Symbols.end() ? 0 : it->second.Used; + } + +private: + SymbolInfo::SignalMap Symbols; +}; + +class FindAllSymbolsTest : public ::testing::Test { +public: + int seen(const SymbolInfo &Symbol) { return Reporter.seen(Symbol); } + + int used(const SymbolInfo &Symbol) { return Reporter.used(Symbol); } + + bool runFindAllSymbols(StringRef HeaderCode, StringRef MainCode) { + llvm::IntrusiveRefCntPtr InMemoryFileSystem( + new llvm::vfs::InMemoryFileSystem); + llvm::IntrusiveRefCntPtr Files( + new FileManager(FileSystemOptions(), InMemoryFileSystem)); + + std::string FileName = "symbol.cc"; + + const std::string InternalHeader = "internal/internal_header.h"; + const std::string TopHeader = ""; + // Test .inc header path. The header for `IncHeaderClass` should be + // internal.h, which will eventually be mapped to . + std::string IncHeader = "internal/private.inc"; + std::string IncHeaderCode = "class IncHeaderClass {};"; + + HeaderMapCollector::RegexHeaderMap RegexMap = { + {R"(internal_.*\.h$)", TopHeader.c_str()}, + }; + + std::string InternalCode = + "#include \"private.inc\"\nclass Internal {};"; + SymbolInfo InternalSymbol("Internal", SymbolInfo::SymbolKind::Class, + TopHeader, {}); + SymbolInfo IncSymbol("IncHeaderClass", SymbolInfo::SymbolKind::Class, + TopHeader, {}); + InMemoryFileSystem->addFile( + IncHeader, 0, llvm::MemoryBuffer::getMemBuffer(IncHeaderCode)); + InMemoryFileSystem->addFile(InternalHeader, 0, + llvm::MemoryBuffer::getMemBuffer(InternalCode)); + + std::unique_ptr Factory( + new FindAllSymbolsActionFactory(&Reporter, &RegexMap)); + + tooling::ToolInvocation Invocation( + {std::string("find_all_symbols"), std::string("-fsyntax-only"), + std::string("-std=c++11"), FileName}, + Factory->create(), Files.get(), + std::make_shared()); + + InMemoryFileSystem->addFile(HeaderName, 0, + llvm::MemoryBuffer::getMemBuffer(HeaderCode)); + + std::string Content = "#include\"" + std::string(HeaderName) + + "\"\n" + "#include \"" + + InternalHeader + "\""; +#if !defined(_MSC_VER) && !defined(__MINGW32__) + // Test path cleaning for both decls and macros. + const std::string DirtyHeader = "./internal/./a/b.h"; + Content += "\n#include \"" + DirtyHeader + "\""; + const std::string CleanHeader = "internal/a/b.h"; + const std::string DirtyHeaderContent = + "#define INTERNAL 1\nclass ExtraInternal {};"; + InMemoryFileSystem->addFile( + DirtyHeader, 0, llvm::MemoryBuffer::getMemBuffer(DirtyHeaderContent)); + SymbolInfo DirtyMacro("INTERNAL", SymbolInfo::SymbolKind::Macro, + CleanHeader, {}); + SymbolInfo DirtySymbol("ExtraInternal", SymbolInfo::SymbolKind::Class, + CleanHeader, {}); +#endif // _MSC_VER && __MINGW32__ + Content += "\n" + MainCode.str(); + InMemoryFileSystem->addFile(FileName, 0, + llvm::MemoryBuffer::getMemBuffer(Content)); + Invocation.run(); + EXPECT_EQ(1, seen(InternalSymbol)); + EXPECT_EQ(1, seen(IncSymbol)); +#if !defined(_MSC_VER) && !defined(__MINGW32__) + EXPECT_EQ(1, seen(DirtySymbol)); + EXPECT_EQ(1, seen(DirtyMacro)); +#endif // _MSC_VER && __MINGW32__ + return true; + } + +protected: + TestSymbolReporter Reporter; +}; + +TEST_F(FindAllSymbolsTest, VariableSymbols) { + static const char Header[] = R"( + extern int xargc; + namespace na { + static bool SSSS = false; + namespace nb { const long long *XXXX; } + })"; + static const char Main[] = R"( + auto y = &na::nb::XXXX; + int main() { if (na::SSSS) return xargc; } + )"; + runFindAllSymbols(Header, Main); + + SymbolInfo Symbol = + SymbolInfo("xargc", SymbolInfo::SymbolKind::Variable, HeaderName, {}); + EXPECT_EQ(1, seen(Symbol)); + EXPECT_EQ(1, used(Symbol)); + + Symbol = SymbolInfo("SSSS", SymbolInfo::SymbolKind::Variable, HeaderName, + {{SymbolInfo::ContextType::Namespace, "na"}}); + EXPECT_EQ(1, seen(Symbol)); + EXPECT_EQ(1, used(Symbol)); + + Symbol = SymbolInfo("XXXX", SymbolInfo::SymbolKind::Variable, HeaderName, + {{SymbolInfo::ContextType::Namespace, "nb"}, + {SymbolInfo::ContextType::Namespace, "na"}}); + EXPECT_EQ(1, seen(Symbol)); + EXPECT_EQ(1, used(Symbol)); +} + +TEST_F(FindAllSymbolsTest, ExternCSymbols) { + static const char Header[] = R"( + extern "C" { + int C_Func() { return 0; } + struct C_struct { + int Member; + }; + })"; + static const char Main[] = R"( + C_struct q() { + int(*ptr)() = C_Func; + return {0}; + } + )"; + runFindAllSymbols(Header, Main); + + SymbolInfo Symbol = + SymbolInfo("C_Func", SymbolInfo::SymbolKind::Function, HeaderName, {}); + EXPECT_EQ(1, seen(Symbol)); + EXPECT_EQ(1, used(Symbol)); + + Symbol = + SymbolInfo("C_struct", SymbolInfo::SymbolKind::Class, HeaderName, {}); + EXPECT_EQ(1, seen(Symbol)); + EXPECT_EQ(1, used(Symbol)); +} + +TEST_F(FindAllSymbolsTest, CXXRecordSymbols) { + static const char Header[] = R"( + struct Glob {}; + struct A; // Not a defintion, ignored. + class NOP; // Not a defintion, ignored + namespace na { + struct A { + struct AAAA {}; + int x; + int y; + void f() {} + }; + }; // + )"; + static const char Main[] = R"( + static Glob glob; + static na::A::AAAA* a; + )"; + runFindAllSymbols(Header, Main); + + SymbolInfo Symbol = + SymbolInfo("Glob", SymbolInfo::SymbolKind::Class, HeaderName, {}); + EXPECT_EQ(1, seen(Symbol)); + EXPECT_EQ(1, used(Symbol)); + + Symbol = SymbolInfo("A", SymbolInfo::SymbolKind::Class, HeaderName, + {{SymbolInfo::ContextType::Namespace, "na"}}); + EXPECT_EQ(1, seen(Symbol)); + EXPECT_EQ(1, used(Symbol)); + + Symbol = SymbolInfo("AAA", SymbolInfo::SymbolKind::Class, HeaderName, + {{SymbolInfo::ContextType::Record, "A"}, + {SymbolInfo::ContextType::Namespace, "na"}}); + EXPECT_EQ(0, seen(Symbol)); + EXPECT_EQ(0, used(Symbol)); +} + +TEST_F(FindAllSymbolsTest, CXXRecordSymbolsTemplate) { + static const char Header[] = R"( + template + struct T_TEMP { + template + struct rebind { typedef T_TEMP<_Tp1> other; }; + }; + // Ignore specialization. + template class T_TEMP; + + template + class Observer { + }; + // Ignore specialization. + template <> class Observer {}; + )"; + static const char Main[] = R"( + extern T_TEMP::rebind weirdo; + )"; + runFindAllSymbols(Header, Main); + + SymbolInfo Symbol = + SymbolInfo("T_TEMP", SymbolInfo::SymbolKind::Class, HeaderName, {}); + EXPECT_EQ(1, seen(Symbol)); + EXPECT_EQ(1, used(Symbol)); +} + +TEST_F(FindAllSymbolsTest, DontIgnoreTemplatePartialSpecialization) { + static const char Code[] = R"( + template class Class; // undefined + template + class Class { + }; + + template void f() {}; + template<> void f() {}; + )"; + runFindAllSymbols(Code, ""); + SymbolInfo Symbol = + SymbolInfo("Class", SymbolInfo::SymbolKind::Class, HeaderName, {}); + EXPECT_EQ(1, seen(Symbol)); + Symbol = SymbolInfo("f", SymbolInfo::SymbolKind::Function, HeaderName, {}); + EXPECT_EQ(1, seen(Symbol)); +} + +TEST_F(FindAllSymbolsTest, FunctionSymbols) { + static const char Header[] = R"( + namespace na { + int gg(int); + int f(const int &a) { int Local; static int StaticLocal; return 0; } + static void SSSFFF() {} + } // namespace na + namespace na { + namespace nb { + template + void fun(T t) {}; + } // namespace nb + } // namespace na"; + )"; + static const char Main[] = R"( + int(*gg)(int) = &na::gg; + int main() { + (void)na::SSSFFF; + na::nb::fun(0); + return na::f(gg(0)); + } + )"; + runFindAllSymbols(Header, Main); + + SymbolInfo Symbol = + SymbolInfo("gg", SymbolInfo::SymbolKind::Function, HeaderName, + {{SymbolInfo::ContextType::Namespace, "na"}}); + EXPECT_EQ(1, seen(Symbol)); + EXPECT_EQ(1, used(Symbol)); + + Symbol = SymbolInfo("f", SymbolInfo::SymbolKind::Function, HeaderName, + {{SymbolInfo::ContextType::Namespace, "na"}}); + EXPECT_EQ(1, seen(Symbol)); + EXPECT_EQ(1, used(Symbol)); + + Symbol = SymbolInfo("SSSFFF", SymbolInfo::SymbolKind::Function, HeaderName, + {{SymbolInfo::ContextType::Namespace, "na"}}); + EXPECT_EQ(1, seen(Symbol)); + EXPECT_EQ(1, used(Symbol)); + + Symbol = SymbolInfo("fun", SymbolInfo::SymbolKind::Function, HeaderName, + {{SymbolInfo::ContextType::Namespace, "nb"}, + {SymbolInfo::ContextType::Namespace, "na"}}); + EXPECT_EQ(1, seen(Symbol)); + EXPECT_EQ(1, used(Symbol)); +} + +TEST_F(FindAllSymbolsTest, NamespaceTest) { + static const char Header[] = R"( + int X1; + namespace { int X2; } + namespace { namespace { int X3; } } + namespace { namespace nb { int X4; } } + namespace na { inline namespace __1 { int X5; } } + )"; + static const char Main[] = R"( + using namespace nb; + int main() { + X1 = X2; + X3 = X4; + (void)na::X5; + } + )"; + runFindAllSymbols(Header, Main); + + SymbolInfo Symbol = + SymbolInfo("X1", SymbolInfo::SymbolKind::Variable, HeaderName, {}); + EXPECT_EQ(1, seen(Symbol)); + EXPECT_EQ(1, used(Symbol)); + + Symbol = SymbolInfo("X2", SymbolInfo::SymbolKind::Variable, HeaderName, + {{SymbolInfo::ContextType::Namespace, ""}}); + EXPECT_EQ(1, seen(Symbol)); + EXPECT_EQ(1, used(Symbol)); + + Symbol = SymbolInfo("X3", SymbolInfo::SymbolKind::Variable, HeaderName, + {{SymbolInfo::ContextType::Namespace, ""}, + {SymbolInfo::ContextType::Namespace, ""}}); + EXPECT_EQ(1, seen(Symbol)); + EXPECT_EQ(1, used(Symbol)); + + Symbol = SymbolInfo("X4", SymbolInfo::SymbolKind::Variable, HeaderName, + {{SymbolInfo::ContextType::Namespace, "nb"}, + {SymbolInfo::ContextType::Namespace, ""}}); + EXPECT_EQ(1, seen(Symbol)); + EXPECT_EQ(1, used(Symbol)); + + Symbol = SymbolInfo("X5", SymbolInfo::SymbolKind::Variable, HeaderName, + {{SymbolInfo::ContextType::Namespace, "na"}}); + EXPECT_EQ(1, seen(Symbol)); + EXPECT_EQ(1, used(Symbol)); +} + +TEST_F(FindAllSymbolsTest, DecayedTypeTest) { + static const char Header[] = "void DecayedFunc(int x[], int y[10]) {}"; + static const char Main[] = R"(int main() { DecayedFunc(nullptr, nullptr); })"; + runFindAllSymbols(Header, Main); + SymbolInfo Symbol = SymbolInfo( + "DecayedFunc", SymbolInfo::SymbolKind::Function, HeaderName, {}); + EXPECT_EQ(1, seen(Symbol)); + EXPECT_EQ(1, used(Symbol)); +} + +TEST_F(FindAllSymbolsTest, CTypedefTest) { + static const char Header[] = R"( + typedef unsigned size_t_; + typedef struct { int x; } X; + using XX = X; + )"; + static const char Main[] = R"( + size_t_ f; + template struct vector{}; + vector list; + void foo(const XX&){} + )"; + runFindAllSymbols(Header, Main); + + SymbolInfo Symbol = SymbolInfo("size_t_", SymbolInfo::SymbolKind::TypedefName, + HeaderName, {}); + EXPECT_EQ(1, seen(Symbol)); + EXPECT_EQ(1, used(Symbol)); + + Symbol = SymbolInfo("X", SymbolInfo::SymbolKind::TypedefName, HeaderName, {}); + EXPECT_EQ(1, seen(Symbol)); + EXPECT_EQ(1, used(Symbol)); + + Symbol = + SymbolInfo("XX", SymbolInfo::SymbolKind::TypedefName, HeaderName, {}); + EXPECT_EQ(1, seen(Symbol)); + EXPECT_EQ(1, used(Symbol)); +} + +TEST_F(FindAllSymbolsTest, EnumTest) { + static const char Header[] = R"( + enum Glob_E { G1, G2 }; + enum class Altitude { high='h', low='l'}; + enum { A1, A2 }; + class A { + public: + enum A_ENUM { X1, X2 }; + }; + enum DECL : int; + )"; + static const char Main[] = R"( + static auto flags = G1 | G2; + static auto alt = Altitude::high; + static auto nested = A::X1; + extern DECL whatever; + static auto flags2 = A1 | A2; + )"; + runFindAllSymbols(Header, Main); + + SymbolInfo Symbol = + SymbolInfo("Glob_E", SymbolInfo::SymbolKind::EnumDecl, HeaderName, {}); + EXPECT_EQ(1, seen(Symbol)); + EXPECT_EQ(0, used(Symbol)); + + Symbol = + SymbolInfo("G1", SymbolInfo::SymbolKind::EnumConstantDecl, HeaderName, + {{SymbolInfo::ContextType::EnumDecl, "Glob_E"}}); + EXPECT_EQ(1, seen(Symbol)); + EXPECT_EQ(1, used(Symbol)); + + Symbol = + SymbolInfo("G2", SymbolInfo::SymbolKind::EnumConstantDecl, HeaderName, + {{SymbolInfo::ContextType::EnumDecl, "Glob_E"}}); + EXPECT_EQ(1, seen(Symbol)); + EXPECT_EQ(1, used(Symbol)); + + Symbol = + SymbolInfo("Altitude", SymbolInfo::SymbolKind::EnumDecl, HeaderName, {}); + EXPECT_EQ(1, seen(Symbol)); + EXPECT_EQ(1, used(Symbol)); + Symbol = + SymbolInfo("high", SymbolInfo::SymbolKind::EnumConstantDecl, HeaderName, + {{SymbolInfo::ContextType::EnumDecl, "Altitude"}}); + EXPECT_EQ(0, seen(Symbol)); + EXPECT_EQ(0, used(Symbol)); + + Symbol = SymbolInfo("A1", SymbolInfo::SymbolKind::EnumConstantDecl, + HeaderName, {{SymbolInfo::ContextType::EnumDecl, ""}}); + EXPECT_EQ(1, seen(Symbol)); + EXPECT_EQ(1, used(Symbol)); + Symbol = SymbolInfo("A2", SymbolInfo::SymbolKind::EnumConstantDecl, + HeaderName, {{SymbolInfo::ContextType::EnumDecl, ""}}); + EXPECT_EQ(1, seen(Symbol)); + EXPECT_EQ(1, used(Symbol)); + Symbol = SymbolInfo("", SymbolInfo::SymbolKind::EnumDecl, HeaderName, {}); + EXPECT_EQ(0, seen(Symbol)); + EXPECT_EQ(0, used(Symbol)); + + Symbol = SymbolInfo("A_ENUM", SymbolInfo::SymbolKind::EnumDecl, HeaderName, + {{SymbolInfo::ContextType::Record, "A"}}); + EXPECT_EQ(0, seen(Symbol)); + EXPECT_EQ(0, used(Symbol)); + + Symbol = SymbolInfo("X1", SymbolInfo::SymbolKind::EnumDecl, HeaderName, + {{SymbolInfo::ContextType::EnumDecl, "A_ENUM"}, + {SymbolInfo::ContextType::Record, "A"}}); + EXPECT_EQ(0, seen(Symbol)); + + Symbol = SymbolInfo("DECL", SymbolInfo::SymbolKind::EnumDecl, HeaderName, {}); + EXPECT_EQ(0, seen(Symbol)); +} + +TEST_F(FindAllSymbolsTest, IWYUPrivatePragmaTest) { + static const char Header[] = R"( + // IWYU pragma: private, include "bar.h" + struct Bar { + }; + )"; + static const char Main[] = R"( + Bar bar; + )"; + runFindAllSymbols(Header, Main); + + SymbolInfo Symbol = + SymbolInfo("Bar", SymbolInfo::SymbolKind::Class, "bar.h", {}); + EXPECT_EQ(1, seen(Symbol)); + EXPECT_EQ(1, used(Symbol)); +} + +TEST_F(FindAllSymbolsTest, MacroTest) { + static const char Header[] = R"( + #define X + #define Y 1 + #define MAX(X, Y) ((X) > (Y) ? (X) : (Y)) + )"; + static const char Main[] = R"( + #ifdef X + int main() { return MAX(0,Y); } + #endif + )"; + runFindAllSymbols(Header, Main); + SymbolInfo Symbol = + SymbolInfo("X", SymbolInfo::SymbolKind::Macro, HeaderName, {}); + EXPECT_EQ(1, seen(Symbol)); + EXPECT_EQ(1, used(Symbol)); + + Symbol = SymbolInfo("Y", SymbolInfo::SymbolKind::Macro, HeaderName, {}); + EXPECT_EQ(1, seen(Symbol)); + EXPECT_EQ(1, used(Symbol)); + + Symbol = SymbolInfo("MAX", SymbolInfo::SymbolKind::Macro, HeaderName, {}); + EXPECT_EQ(1, seen(Symbol)); + EXPECT_EQ(1, used(Symbol)); +} + +TEST_F(FindAllSymbolsTest, MacroTestWithIWYU) { + static const char Header[] = R"( + // IWYU pragma: private, include "bar.h" + #define X 1 + #define Y 1 + #define MAX(X, Y) ((X) > (Y) ? (X) : (Y)) + )"; + static const char Main[] = R"( + #ifdef X + int main() { return MAX(0,Y); } + #endif + )"; + runFindAllSymbols(Header, Main); + SymbolInfo Symbol = + SymbolInfo("X", SymbolInfo::SymbolKind::Macro, "bar.h", {}); + EXPECT_EQ(1, seen(Symbol)); + EXPECT_EQ(1, used(Symbol)); + + Symbol = SymbolInfo("Y", SymbolInfo::SymbolKind::Macro, "bar.h", {}); + EXPECT_EQ(1, seen(Symbol)); + EXPECT_EQ(1, used(Symbol)); + + Symbol = SymbolInfo("MAX", SymbolInfo::SymbolKind::Macro, "bar.h", {}); + EXPECT_EQ(1, seen(Symbol)); + EXPECT_EQ(1, used(Symbol)); +} + +TEST_F(FindAllSymbolsTest, NoFriendTest) { + static const char Header[] = R"( + class WorstFriend { + friend void Friend(); + friend class BestFriend; + }; + )"; + runFindAllSymbols(Header, ""); + SymbolInfo Symbol = + SymbolInfo("WorstFriend", SymbolInfo::SymbolKind::Class, HeaderName, {}); + EXPECT_EQ(1, seen(Symbol)); + + Symbol = + SymbolInfo("Friend", SymbolInfo::SymbolKind::Function, HeaderName, {}); + EXPECT_EQ(0, seen(Symbol)); + + Symbol = + SymbolInfo("BestFriend", SymbolInfo::SymbolKind::Class, HeaderName, {}); + EXPECT_EQ(0, seen(Symbol)); +} + +} // namespace find_all_symbols +} // namespace clang Index: unittests/include-fixer/CMakeLists.txt =================================================================== --- unittests/include-fixer/CMakeLists.txt +++ unittests/include-fixer/CMakeLists.txt @@ -1,32 +0,0 @@ -set(LLVM_LINK_COMPONENTS - support - ) - -get_filename_component(INCLUDE_FIXER_SOURCE_DIR - ${CMAKE_CURRENT_SOURCE_DIR}/../../include-fixer REALPATH) -include_directories( - ${INCLUDE_FIXER_SOURCE_DIR} - ) - -# We'd like to clang/unittests/Tooling/RewriterTestContext.h in the test. -include_directories(${CLANG_SOURCE_DIR}) - -add_extra_unittest(IncludeFixerTests - IncludeFixerTest.cpp - FuzzySymbolIndexTests.cpp - ) - -target_link_libraries(IncludeFixerTests - PRIVATE - clangBasic - clangFormat - clangFrontend - clangIncludeFixer - clangRewrite - clangSerialization - clangTooling - clangToolingCore - findAllSymbols - ) - -add_subdirectory(find-all-symbols) Index: unittests/include-fixer/FuzzySymbolIndexTests.cpp =================================================================== --- unittests/include-fixer/FuzzySymbolIndexTests.cpp +++ unittests/include-fixer/FuzzySymbolIndexTests.cpp @@ -1,60 +0,0 @@ -//===-- FuzzySymbolIndexTests.cpp - Fuzzy symbol index unit tests ---------===// -// -// 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 "FuzzySymbolIndex.h" -#include "gmock/gmock.h" -#include "llvm/Support/Regex.h" -#include "gtest/gtest.h" - -using testing::ElementsAre; -using testing::Not; - -namespace clang { -namespace include_fixer { -namespace { - -TEST(FuzzySymbolIndexTest, Tokenize) { - EXPECT_THAT(FuzzySymbolIndex::tokenize("URLHandlerCallback"), - ElementsAre("url", "handler", "callback")); - EXPECT_THAT(FuzzySymbolIndex::tokenize("snake_case11"), - ElementsAre("snake", "case", "11")); - EXPECT_THAT(FuzzySymbolIndex::tokenize("__$42!!BOB\nbob"), - ElementsAre("42", "bob", "bob")); -} - -MATCHER_P(MatchesSymbol, Identifier, "") { - llvm::Regex Pattern("^" + arg); - std::string err; - if (!Pattern.isValid(err)) { - *result_listener << "invalid regex: " << err; - return false; - } - auto Tokens = FuzzySymbolIndex::tokenize(Identifier); - std::string Target = llvm::join(Tokens.begin(), Tokens.end(), " "); - *result_listener << "matching against '" << Target << "'"; - return llvm::Regex("^" + arg).match(Target); -} - -TEST(FuzzySymbolIndexTest, QueryRegexp) { - auto QueryRegexp = [](const std::string &query) { - return FuzzySymbolIndex::queryRegexp(FuzzySymbolIndex::tokenize(query)); - }; - EXPECT_THAT(QueryRegexp("uhc"), MatchesSymbol("URLHandlerCallback")); - EXPECT_THAT(QueryRegexp("urhaca"), MatchesSymbol("URLHandlerCallback")); - EXPECT_THAT(QueryRegexp("uhcb"), Not(MatchesSymbol("URLHandlerCallback"))) - << "Non-prefix"; - EXPECT_THAT(QueryRegexp("uc"), Not(MatchesSymbol("URLHandlerCallback"))) - << "Skip token"; - - EXPECT_THAT(QueryRegexp("uptr"), MatchesSymbol("unique_ptr")); - EXPECT_THAT(QueryRegexp("UniP"), MatchesSymbol("unique_ptr")); -} - -} // namespace -} // namespace include_fixer -} // namespace clang Index: unittests/include-fixer/IncludeFixerTest.cpp =================================================================== --- unittests/include-fixer/IncludeFixerTest.cpp +++ unittests/include-fixer/IncludeFixerTest.cpp @@ -1,371 +0,0 @@ -//===-- IncludeFixerTest.cpp - Include fixer unit tests -------------------===// -// -// 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 "InMemorySymbolIndex.h" -#include "IncludeFixer.h" -#include "SymbolIndexManager.h" -#include "unittests/Tooling/RewriterTestContext.h" -#include "clang/Tooling/Tooling.h" -#include "gtest/gtest.h" - -namespace clang { -namespace include_fixer { -namespace { - -using find_all_symbols::SymbolInfo; -using find_all_symbols::SymbolAndSignals; - -static bool runOnCode(tooling::ToolAction *ToolAction, StringRef Code, - StringRef FileName, - const std::vector &ExtraArgs) { - llvm::IntrusiveRefCntPtr InMemoryFileSystem( - new llvm::vfs::InMemoryFileSystem); - llvm::IntrusiveRefCntPtr Files( - new FileManager(FileSystemOptions(), InMemoryFileSystem)); - // FIXME: Investigate why -fms-compatibility breaks tests. - std::vector Args = {"include_fixer", "-fsyntax-only", - "-fno-ms-compatibility", FileName}; - Args.insert(Args.end(), ExtraArgs.begin(), ExtraArgs.end()); - tooling::ToolInvocation Invocation( - Args, ToolAction, Files.get(), - std::make_shared()); - - InMemoryFileSystem->addFile(FileName, 0, - llvm::MemoryBuffer::getMemBuffer(Code)); - - InMemoryFileSystem->addFile("foo.h", 0, - llvm::MemoryBuffer::getMemBuffer("\n")); - InMemoryFileSystem->addFile("dir/bar.h", 0, - llvm::MemoryBuffer::getMemBuffer("\n")); - InMemoryFileSystem->addFile("dir/otherdir/qux.h", 0, - llvm::MemoryBuffer::getMemBuffer("\n")); - InMemoryFileSystem->addFile("header.h", 0, - llvm::MemoryBuffer::getMemBuffer("bar b;")); - return Invocation.run(); -} - -static std::string runIncludeFixer( - StringRef Code, - const std::vector &ExtraArgs = std::vector()) { - std::vector Symbols = { - {SymbolInfo("string", SymbolInfo::SymbolKind::Class, "", - {{SymbolInfo::ContextType::Namespace, "std"}}), - SymbolInfo::Signals{}}, - {SymbolInfo("sting", SymbolInfo::SymbolKind::Class, "\"sting\"", - {{SymbolInfo::ContextType::Namespace, "std"}}), - SymbolInfo::Signals{}}, - {SymbolInfo("foo", SymbolInfo::SymbolKind::Class, - "\"dir/otherdir/qux.h\"", - {{SymbolInfo::ContextType::Namespace, "b"}, - {SymbolInfo::ContextType::Namespace, "a"}}), - SymbolInfo::Signals{}}, - {SymbolInfo("bar", SymbolInfo::SymbolKind::Class, "\"bar.h\"", - {{SymbolInfo::ContextType::Namespace, "b"}, - {SymbolInfo::ContextType::Namespace, "a"}}), - SymbolInfo::Signals{}}, - {SymbolInfo("bar", SymbolInfo::SymbolKind::Class, "\"bar2.h\"", - {{SymbolInfo::ContextType::Namespace, "c"}, - {SymbolInfo::ContextType::Namespace, "a"}}), - SymbolInfo::Signals{}}, - {SymbolInfo("Green", SymbolInfo::SymbolKind::Class, "\"color.h\"", - {{SymbolInfo::ContextType::EnumDecl, "Color"}, - {SymbolInfo::ContextType::Namespace, "b"}, - {SymbolInfo::ContextType::Namespace, "a"}}), - SymbolInfo::Signals{}}, - {SymbolInfo("Vector", SymbolInfo::SymbolKind::Class, "\"Vector.h\"", - {{SymbolInfo::ContextType::Namespace, "__a"}, - {SymbolInfo::ContextType::Namespace, "a"}}), - SymbolInfo::Signals{/*Seen=*/2, 0}}, - {SymbolInfo("Vector", SymbolInfo::SymbolKind::Class, "\"Vector.h\"", - {{SymbolInfo::ContextType::Namespace, "a"}}), - SymbolInfo::Signals{/*Seen=*/2, 0}}, - {SymbolInfo("StrCat", SymbolInfo::SymbolKind::Class, "\"strcat.h\"", - {{SymbolInfo::ContextType::Namespace, "str"}}), - SymbolInfo::Signals{}}, - {SymbolInfo("str", SymbolInfo::SymbolKind::Class, "\"str.h\"", {}), - SymbolInfo::Signals{}}, - {SymbolInfo("foo2", SymbolInfo::SymbolKind::Class, "\"foo2.h\"", {}), - SymbolInfo::Signals{}}, - }; - auto SymbolIndexMgr = llvm::make_unique(); - SymbolIndexMgr->addSymbolIndex( - [=]() { return llvm::make_unique(Symbols); }); - - std::vector FixerContexts; - IncludeFixerActionFactory Factory(*SymbolIndexMgr, FixerContexts, "llvm"); - std::string FakeFileName = "input.cc"; - runOnCode(&Factory, Code, FakeFileName, ExtraArgs); - assert(FixerContexts.size() == 1); - if (FixerContexts.front().getHeaderInfos().empty()) - return Code; - auto Replaces = createIncludeFixerReplacements(Code, FixerContexts.front()); - EXPECT_TRUE(static_cast(Replaces)) - << llvm::toString(Replaces.takeError()) << "\n"; - if (!Replaces) - return ""; - RewriterTestContext Context; - FileID ID = Context.createInMemoryFile(FakeFileName, Code); - tooling::applyAllReplacements(*Replaces, Context.Rewrite); - return Context.getRewrittenText(ID); -} - -TEST(IncludeFixer, Typo) { - EXPECT_EQ("#include \nstd::string foo;\n", - runIncludeFixer("std::string foo;\n")); - - EXPECT_EQ("// comment\n#include \"foo.h\"\n#include \n" - "std::string foo;\n#include \"dir/bar.h\"\n", - runIncludeFixer("// comment\n#include \"foo.h\"\nstd::string foo;\n" - "#include \"dir/bar.h\"\n")); - - EXPECT_EQ("#include \"foo.h\"\n#include \nstd::string foo;\n", - runIncludeFixer("#include \"foo.h\"\nstd::string foo;\n")); - - EXPECT_EQ( - "#include \"foo.h\"\n#include \nstd::string::size_type foo;\n", - runIncludeFixer("#include \"foo.h\"\nstd::string::size_type foo;\n")); - - EXPECT_EQ("#include \nstd::string foo;\n", - runIncludeFixer("string foo;\n")); - - // Should not match std::string. - EXPECT_EQ("::string foo;\n", runIncludeFixer("::string foo;\n")); -} - -TEST(IncludeFixer, IncompleteType) { - EXPECT_EQ( - "#include \"foo.h\"\n#include \n" - "namespace std {\nclass string;\n}\nstd::string foo;\n", - runIncludeFixer("#include \"foo.h\"\n" - "namespace std {\nclass string;\n}\nstring foo;\n")); - - EXPECT_EQ("#include \n" - "class string;\ntypedef string foo;\nfoo f;\n", - runIncludeFixer("class string;\ntypedef string foo;\nfoo f;\n")); -} - -TEST(IncludeFixer, MinimizeInclude) { - std::vector IncludePath = {"-Idir/"}; - EXPECT_EQ("#include \"otherdir/qux.h\"\na::b::foo bar;\n", - runIncludeFixer("a::b::foo bar;\n", IncludePath)); - - IncludePath = {"-isystemdir"}; - EXPECT_EQ("#include \na::b::foo bar;\n", - runIncludeFixer("a::b::foo bar;\n", IncludePath)); - - IncludePath = {"-iquotedir"}; - EXPECT_EQ("#include \"otherdir/qux.h\"\na::b::foo bar;\n", - runIncludeFixer("a::b::foo bar;\n", IncludePath)); - - IncludePath = {"-Idir", "-Idir/otherdir"}; - EXPECT_EQ("#include \"qux.h\"\na::b::foo bar;\n", - runIncludeFixer("a::b::foo bar;\n", IncludePath)); -} - -TEST(IncludeFixer, NestedName) { - EXPECT_EQ("#include \"dir/otherdir/qux.h\"\n" - "int x = a::b::foo(0);\n", - runIncludeFixer("int x = a::b::foo(0);\n")); - - // FIXME: Handle simple macros. - EXPECT_EQ("#define FOO a::b::foo\nint x = FOO;\n", - runIncludeFixer("#define FOO a::b::foo\nint x = FOO;\n")); - EXPECT_EQ("#define FOO(x) a::##x\nint x = FOO(b::foo);\n", - runIncludeFixer("#define FOO(x) a::##x\nint x = FOO(b::foo);\n")); - - // The empty namespace is cleaned up by clang-format after include-fixer - // finishes. - EXPECT_EQ("#include \"dir/otherdir/qux.h\"\n" - "\nint a = a::b::foo(0);\n", - runIncludeFixer("namespace a {}\nint a = a::b::foo(0);\n")); -} - -TEST(IncludeFixer, MultipleMissingSymbols) { - EXPECT_EQ("#include \nstd::string bar;\nstd::sting foo;\n", - runIncludeFixer("std::string bar;\nstd::sting foo;\n")); -} - -TEST(IncludeFixer, ScopedNamespaceSymbols) { - EXPECT_EQ("#include \"bar.h\"\nnamespace a {\nb::bar b;\n}", - runIncludeFixer("namespace a {\nb::bar b;\n}")); - EXPECT_EQ("#include \"bar.h\"\nnamespace A {\na::b::bar b;\n}", - runIncludeFixer("namespace A {\na::b::bar b;\n}")); - EXPECT_EQ("#include \"bar.h\"\nnamespace a {\nvoid func() { b::bar b; }\n} " - "// namespace a", - runIncludeFixer("namespace a {\nvoid func() { b::bar b; }\n}")); - EXPECT_EQ("namespace A { c::b::bar b; }\n", - runIncludeFixer("namespace A { c::b::bar b; }\n")); - // FIXME: The header should not be added here. Remove this after we support - // full match. - EXPECT_EQ("#include \"bar.h\"\nnamespace A {\na::b::bar b;\n}", - runIncludeFixer("namespace A {\nb::bar b;\n}")); - - // Finds candidates for "str::StrCat". - EXPECT_EQ("#include \"strcat.h\"\nnamespace foo2 {\nstr::StrCat b;\n}", - runIncludeFixer("namespace foo2 {\nstr::StrCat b;\n}")); - // str::StrCat2 doesn't exist. - // In these two cases, StrCat2 is a nested class of class str. - EXPECT_EQ("#include \"str.h\"\nnamespace foo2 {\nstr::StrCat2 b;\n}", - runIncludeFixer("namespace foo2 {\nstr::StrCat2 b;\n}")); - EXPECT_EQ("#include \"str.h\"\nnamespace ns {\nstr::StrCat2 b;\n}", - runIncludeFixer("namespace ns {\nstr::StrCat2 b;\n}")); -} - -TEST(IncludeFixer, EnumConstantSymbols) { - EXPECT_EQ("#include \"color.h\"\nint test = a::b::Green;\n", - runIncludeFixer("int test = a::b::Green;\n")); -} - -TEST(IncludeFixer, IgnoreSymbolFromHeader) { - std::string Code = "#include \"header.h\""; - EXPECT_EQ(Code, runIncludeFixer(Code)); -} - -// FIXME: add test cases for inserting and sorting multiple headers when -// include-fixer supports multiple headers insertion. -TEST(IncludeFixer, InsertAndSortSingleHeader) { - // Insert one header. - std::string Code = "#include \"a.h\"\n" - "#include \"foo.h\"\n" - "\n" - "namespace a {\nb::bar b;\n}\n"; - std::string Expected = "#include \"a.h\"\n" - "#include \"bar.h\"\n" - "#include \"foo.h\"\n" - "\n" - "namespace a {\nb::bar b;\n}\n"; - EXPECT_EQ(Expected, runIncludeFixer(Code)); -} - -TEST(IncludeFixer, DoNotDeleteMatchedSymbol) { - EXPECT_EQ("#include \"Vector.h\"\na::Vector v;", - runIncludeFixer("a::Vector v;")); -} - -TEST(IncludeFixer, FixNamespaceQualifiers) { - EXPECT_EQ("#include \"bar.h\"\na::b::bar b;\n", - runIncludeFixer("b::bar b;\n")); - EXPECT_EQ("#include \"bar.h\"\na::b::bar b;\n", - runIncludeFixer("a::b::bar b;\n")); - EXPECT_EQ("#include \"bar.h\"\na::b::bar b;\n", - runIncludeFixer("bar b;\n")); - EXPECT_EQ("#include \"bar.h\"\nnamespace a {\nb::bar b;\n}\n", - runIncludeFixer("namespace a {\nb::bar b;\n}\n")); - EXPECT_EQ("#include \"bar.h\"\nnamespace a {\nb::bar b;\n}\n", - runIncludeFixer("namespace a {\nbar b;\n}\n")); - EXPECT_EQ("#include \"bar.h\"\nnamespace a {\nnamespace b{\nbar b;\n}\n} " - "// namespace a\n", - runIncludeFixer("namespace a {\nnamespace b{\nbar b;\n}\n}\n")); - EXPECT_EQ("c::b::bar b;\n", - runIncludeFixer("c::b::bar b;\n")); - EXPECT_EQ("#include \"bar.h\"\nnamespace d {\na::b::bar b;\n}\n", - runIncludeFixer("namespace d {\nbar b;\n}\n")); - EXPECT_EQ("#include \"bar2.h\"\nnamespace c {\na::c::bar b;\n}\n", - runIncludeFixer("namespace c {\nbar b;\n}\n")); - - // Test common qualifers reduction. - EXPECT_EQ("#include \"bar.h\"\nnamespace a {\nnamespace d {\nb::bar b;\n}\n} " - "// namespace a\n", - runIncludeFixer("namespace a {\nnamespace d {\nbar b;\n}\n}\n")); - EXPECT_EQ("#include \"bar.h\"\nnamespace d {\nnamespace a {\na::b::bar " - "b;\n}\n} // namespace d\n", - runIncludeFixer("namespace d {\nnamespace a {\nbar b;\n}\n}\n")); - - // Test nested classes. - EXPECT_EQ("#include \"bar.h\"\nnamespace d {\na::b::bar::t b;\n}\n", - runIncludeFixer("namespace d {\nbar::t b;\n}\n")); - EXPECT_EQ("#include \"bar.h\"\nnamespace c {\na::b::bar::t b;\n}\n", - runIncludeFixer("namespace c {\nbar::t b;\n}\n")); - EXPECT_EQ("#include \"bar.h\"\nnamespace a {\nb::bar::t b;\n}\n", - runIncludeFixer("namespace a {\nbar::t b;\n}\n")); - - EXPECT_EQ("#include \"color.h\"\nint test = a::b::Green;\n", - runIncludeFixer("int test = Green;\n")); - EXPECT_EQ("#include \"color.h\"\nnamespace d {\nint test = a::b::Green;\n}\n", - runIncludeFixer("namespace d {\nint test = Green;\n}\n")); - EXPECT_EQ("#include \"color.h\"\nnamespace a {\nint test = b::Green;\n}\n", - runIncludeFixer("namespace a {\nint test = Green;\n}\n")); - - // Test global scope operator. - EXPECT_EQ("#include \"bar.h\"\n::a::b::bar b;\n", - runIncludeFixer("::a::b::bar b;\n")); - EXPECT_EQ("#include \"bar.h\"\nnamespace a {\n::a::b::bar b;\n}\n", - runIncludeFixer("namespace a {\n::a::b::bar b;\n}\n")); -} - -TEST(IncludeFixer, FixNamespaceQualifiersForAllInstances) { - const char TestCode[] = R"( -namespace a { -bar b; -int func1() { - bar a; - bar *p = new bar(); - return 0; -} -} // namespace a - -namespace a { -bar func2() { - bar f; - return f; -} -} // namespace a - -// Non-fixed cases: -void f() { - bar b; -} - -namespace a { -namespace c { - bar b; -} // namespace c -} // namespace a -)"; - - const char ExpectedCode[] = R"( -#include "bar.h" -namespace a { -b::bar b; -int func1() { - b::bar a; - b::bar *p = new b::bar(); - return 0; -} -} // namespace a - -namespace a { -b::bar func2() { - b::bar f; - return f; -} -} // namespace a - -// Non-fixed cases: -void f() { - bar b; -} - -namespace a { -namespace c { - bar b; -} // namespace c -} // namespace a -)"; - - EXPECT_EQ(ExpectedCode, runIncludeFixer(TestCode)); -} - -TEST(IncludeFixer, DontAddQualifiersForMissingCompleteType) { - EXPECT_EQ("#include \"bar.h\"\nclass bar;\nvoid f() {\nbar* b;\nb->f();\n}", - runIncludeFixer("class bar;\nvoid f() {\nbar* b;\nb->f();\n}")); -} - -} // namespace -} // namespace include_fixer -} // namespace clang Index: unittests/include-fixer/find-all-symbols/CMakeLists.txt =================================================================== --- unittests/include-fixer/find-all-symbols/CMakeLists.txt +++ unittests/include-fixer/find-all-symbols/CMakeLists.txt @@ -1,25 +0,0 @@ -set(LLVM_LINK_COMPONENTS - support - ) - -get_filename_component(INCLUDE_FIXER_SOURCE_DIR - ${CMAKE_CURRENT_SOURCE_DIR}/../../../include-fixer/find-all-symbols REALPATH) -include_directories( - ${INCLUDE_FIXER_SOURCE_DIR} - ) - -add_extra_unittest(FindAllSymbolsTests - FindAllSymbolsTests.cpp - ) - -target_link_libraries(FindAllSymbolsTests - PRIVATE - clangAST - clangASTMatchers - clangBasic - clangFrontend - clangLex - clangSerialization - clangTooling - findAllSymbols - ) Index: unittests/include-fixer/find-all-symbols/FindAllSymbolsTests.cpp =================================================================== --- unittests/include-fixer/find-all-symbols/FindAllSymbolsTests.cpp +++ unittests/include-fixer/find-all-symbols/FindAllSymbolsTests.cpp @@ -1,577 +0,0 @@ -//===-- FindAllSymbolsTests.cpp - find all symbols unit tests ---*- C++ -*-===// -// -// 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 "FindAllSymbolsAction.h" -#include "HeaderMapCollector.h" -#include "SymbolInfo.h" -#include "SymbolReporter.h" -#include "clang/ASTMatchers/ASTMatchFinder.h" -#include "clang/Basic/FileManager.h" -#include "clang/Basic/FileSystemOptions.h" -#include "clang/Frontend/CompilerInstance.h" -#include "clang/Frontend/PCHContainerOperations.h" -#include "clang/Tooling/Tooling.h" -#include "llvm/ADT/IntrusiveRefCntPtr.h" -#include "llvm/ADT/STLExtras.h" -#include "llvm/ADT/StringRef.h" -#include "llvm/Support/MemoryBuffer.h" -#include "llvm/Support/VirtualFileSystem.h" -#include "gtest/gtest.h" -#include -#include -#include - -namespace clang { -namespace find_all_symbols { - -static const char HeaderName[] = "symbols.h"; - -class TestSymbolReporter : public SymbolReporter { -public: - ~TestSymbolReporter() override {} - - void reportSymbols(llvm::StringRef FileName, - const SymbolInfo::SignalMap &NewSymbols) override { - for (const auto &Entry : NewSymbols) - Symbols[Entry.first] += Entry.second; - } - - int seen(const SymbolInfo &Symbol) const { - auto it = Symbols.find(Symbol); - return it == Symbols.end() ? 0 : it->second.Seen; - } - - int used(const SymbolInfo &Symbol) const { - auto it = Symbols.find(Symbol); - return it == Symbols.end() ? 0 : it->second.Used; - } - -private: - SymbolInfo::SignalMap Symbols; -}; - -class FindAllSymbolsTest : public ::testing::Test { -public: - int seen(const SymbolInfo &Symbol) { return Reporter.seen(Symbol); } - - int used(const SymbolInfo &Symbol) { return Reporter.used(Symbol); } - - bool runFindAllSymbols(StringRef HeaderCode, StringRef MainCode) { - llvm::IntrusiveRefCntPtr InMemoryFileSystem( - new llvm::vfs::InMemoryFileSystem); - llvm::IntrusiveRefCntPtr Files( - new FileManager(FileSystemOptions(), InMemoryFileSystem)); - - std::string FileName = "symbol.cc"; - - const std::string InternalHeader = "internal/internal_header.h"; - const std::string TopHeader = ""; - // Test .inc header path. The header for `IncHeaderClass` should be - // internal.h, which will eventually be mapped to . - std::string IncHeader = "internal/private.inc"; - std::string IncHeaderCode = "class IncHeaderClass {};"; - - HeaderMapCollector::RegexHeaderMap RegexMap = { - {R"(internal_.*\.h$)", TopHeader.c_str()}, - }; - - std::string InternalCode = - "#include \"private.inc\"\nclass Internal {};"; - SymbolInfo InternalSymbol("Internal", SymbolInfo::SymbolKind::Class, - TopHeader, {}); - SymbolInfo IncSymbol("IncHeaderClass", SymbolInfo::SymbolKind::Class, - TopHeader, {}); - InMemoryFileSystem->addFile( - IncHeader, 0, llvm::MemoryBuffer::getMemBuffer(IncHeaderCode)); - InMemoryFileSystem->addFile(InternalHeader, 0, - llvm::MemoryBuffer::getMemBuffer(InternalCode)); - - std::unique_ptr Factory( - new FindAllSymbolsActionFactory(&Reporter, &RegexMap)); - - tooling::ToolInvocation Invocation( - {std::string("find_all_symbols"), std::string("-fsyntax-only"), - std::string("-std=c++11"), FileName}, - Factory->create(), Files.get(), - std::make_shared()); - - InMemoryFileSystem->addFile(HeaderName, 0, - llvm::MemoryBuffer::getMemBuffer(HeaderCode)); - - std::string Content = "#include\"" + std::string(HeaderName) + - "\"\n" - "#include \"" + - InternalHeader + "\""; -#if !defined(_MSC_VER) && !defined(__MINGW32__) - // Test path cleaning for both decls and macros. - const std::string DirtyHeader = "./internal/./a/b.h"; - Content += "\n#include \"" + DirtyHeader + "\""; - const std::string CleanHeader = "internal/a/b.h"; - const std::string DirtyHeaderContent = - "#define INTERNAL 1\nclass ExtraInternal {};"; - InMemoryFileSystem->addFile( - DirtyHeader, 0, llvm::MemoryBuffer::getMemBuffer(DirtyHeaderContent)); - SymbolInfo DirtyMacro("INTERNAL", SymbolInfo::SymbolKind::Macro, - CleanHeader, {}); - SymbolInfo DirtySymbol("ExtraInternal", SymbolInfo::SymbolKind::Class, - CleanHeader, {}); -#endif // _MSC_VER && __MINGW32__ - Content += "\n" + MainCode.str(); - InMemoryFileSystem->addFile(FileName, 0, - llvm::MemoryBuffer::getMemBuffer(Content)); - Invocation.run(); - EXPECT_EQ(1, seen(InternalSymbol)); - EXPECT_EQ(1, seen(IncSymbol)); -#if !defined(_MSC_VER) && !defined(__MINGW32__) - EXPECT_EQ(1, seen(DirtySymbol)); - EXPECT_EQ(1, seen(DirtyMacro)); -#endif // _MSC_VER && __MINGW32__ - return true; - } - -protected: - TestSymbolReporter Reporter; -}; - -TEST_F(FindAllSymbolsTest, VariableSymbols) { - static const char Header[] = R"( - extern int xargc; - namespace na { - static bool SSSS = false; - namespace nb { const long long *XXXX; } - })"; - static const char Main[] = R"( - auto y = &na::nb::XXXX; - int main() { if (na::SSSS) return xargc; } - )"; - runFindAllSymbols(Header, Main); - - SymbolInfo Symbol = - SymbolInfo("xargc", SymbolInfo::SymbolKind::Variable, HeaderName, {}); - EXPECT_EQ(1, seen(Symbol)); - EXPECT_EQ(1, used(Symbol)); - - Symbol = SymbolInfo("SSSS", SymbolInfo::SymbolKind::Variable, HeaderName, - {{SymbolInfo::ContextType::Namespace, "na"}}); - EXPECT_EQ(1, seen(Symbol)); - EXPECT_EQ(1, used(Symbol)); - - Symbol = SymbolInfo("XXXX", SymbolInfo::SymbolKind::Variable, HeaderName, - {{SymbolInfo::ContextType::Namespace, "nb"}, - {SymbolInfo::ContextType::Namespace, "na"}}); - EXPECT_EQ(1, seen(Symbol)); - EXPECT_EQ(1, used(Symbol)); -} - -TEST_F(FindAllSymbolsTest, ExternCSymbols) { - static const char Header[] = R"( - extern "C" { - int C_Func() { return 0; } - struct C_struct { - int Member; - }; - })"; - static const char Main[] = R"( - C_struct q() { - int(*ptr)() = C_Func; - return {0}; - } - )"; - runFindAllSymbols(Header, Main); - - SymbolInfo Symbol = - SymbolInfo("C_Func", SymbolInfo::SymbolKind::Function, HeaderName, {}); - EXPECT_EQ(1, seen(Symbol)); - EXPECT_EQ(1, used(Symbol)); - - Symbol = - SymbolInfo("C_struct", SymbolInfo::SymbolKind::Class, HeaderName, {}); - EXPECT_EQ(1, seen(Symbol)); - EXPECT_EQ(1, used(Symbol)); -} - -TEST_F(FindAllSymbolsTest, CXXRecordSymbols) { - static const char Header[] = R"( - struct Glob {}; - struct A; // Not a defintion, ignored. - class NOP; // Not a defintion, ignored - namespace na { - struct A { - struct AAAA {}; - int x; - int y; - void f() {} - }; - }; // - )"; - static const char Main[] = R"( - static Glob glob; - static na::A::AAAA* a; - )"; - runFindAllSymbols(Header, Main); - - SymbolInfo Symbol = - SymbolInfo("Glob", SymbolInfo::SymbolKind::Class, HeaderName, {}); - EXPECT_EQ(1, seen(Symbol)); - EXPECT_EQ(1, used(Symbol)); - - Symbol = SymbolInfo("A", SymbolInfo::SymbolKind::Class, HeaderName, - {{SymbolInfo::ContextType::Namespace, "na"}}); - EXPECT_EQ(1, seen(Symbol)); - EXPECT_EQ(1, used(Symbol)); - - Symbol = SymbolInfo("AAA", SymbolInfo::SymbolKind::Class, HeaderName, - {{SymbolInfo::ContextType::Record, "A"}, - {SymbolInfo::ContextType::Namespace, "na"}}); - EXPECT_EQ(0, seen(Symbol)); - EXPECT_EQ(0, used(Symbol)); -} - -TEST_F(FindAllSymbolsTest, CXXRecordSymbolsTemplate) { - static const char Header[] = R"( - template - struct T_TEMP { - template - struct rebind { typedef T_TEMP<_Tp1> other; }; - }; - // Ignore specialization. - template class T_TEMP; - - template - class Observer { - }; - // Ignore specialization. - template <> class Observer {}; - )"; - static const char Main[] = R"( - extern T_TEMP::rebind weirdo; - )"; - runFindAllSymbols(Header, Main); - - SymbolInfo Symbol = - SymbolInfo("T_TEMP", SymbolInfo::SymbolKind::Class, HeaderName, {}); - EXPECT_EQ(1, seen(Symbol)); - EXPECT_EQ(1, used(Symbol)); -} - -TEST_F(FindAllSymbolsTest, DontIgnoreTemplatePartialSpecialization) { - static const char Code[] = R"( - template class Class; // undefined - template - class Class { - }; - - template void f() {}; - template<> void f() {}; - )"; - runFindAllSymbols(Code, ""); - SymbolInfo Symbol = - SymbolInfo("Class", SymbolInfo::SymbolKind::Class, HeaderName, {}); - EXPECT_EQ(1, seen(Symbol)); - Symbol = SymbolInfo("f", SymbolInfo::SymbolKind::Function, HeaderName, {}); - EXPECT_EQ(1, seen(Symbol)); -} - -TEST_F(FindAllSymbolsTest, FunctionSymbols) { - static const char Header[] = R"( - namespace na { - int gg(int); - int f(const int &a) { int Local; static int StaticLocal; return 0; } - static void SSSFFF() {} - } // namespace na - namespace na { - namespace nb { - template - void fun(T t) {}; - } // namespace nb - } // namespace na"; - )"; - static const char Main[] = R"( - int(*gg)(int) = &na::gg; - int main() { - (void)na::SSSFFF; - na::nb::fun(0); - return na::f(gg(0)); - } - )"; - runFindAllSymbols(Header, Main); - - SymbolInfo Symbol = - SymbolInfo("gg", SymbolInfo::SymbolKind::Function, HeaderName, - {{SymbolInfo::ContextType::Namespace, "na"}}); - EXPECT_EQ(1, seen(Symbol)); - EXPECT_EQ(1, used(Symbol)); - - Symbol = SymbolInfo("f", SymbolInfo::SymbolKind::Function, HeaderName, - {{SymbolInfo::ContextType::Namespace, "na"}}); - EXPECT_EQ(1, seen(Symbol)); - EXPECT_EQ(1, used(Symbol)); - - Symbol = SymbolInfo("SSSFFF", SymbolInfo::SymbolKind::Function, HeaderName, - {{SymbolInfo::ContextType::Namespace, "na"}}); - EXPECT_EQ(1, seen(Symbol)); - EXPECT_EQ(1, used(Symbol)); - - Symbol = SymbolInfo("fun", SymbolInfo::SymbolKind::Function, HeaderName, - {{SymbolInfo::ContextType::Namespace, "nb"}, - {SymbolInfo::ContextType::Namespace, "na"}}); - EXPECT_EQ(1, seen(Symbol)); - EXPECT_EQ(1, used(Symbol)); -} - -TEST_F(FindAllSymbolsTest, NamespaceTest) { - static const char Header[] = R"( - int X1; - namespace { int X2; } - namespace { namespace { int X3; } } - namespace { namespace nb { int X4; } } - namespace na { inline namespace __1 { int X5; } } - )"; - static const char Main[] = R"( - using namespace nb; - int main() { - X1 = X2; - X3 = X4; - (void)na::X5; - } - )"; - runFindAllSymbols(Header, Main); - - SymbolInfo Symbol = - SymbolInfo("X1", SymbolInfo::SymbolKind::Variable, HeaderName, {}); - EXPECT_EQ(1, seen(Symbol)); - EXPECT_EQ(1, used(Symbol)); - - Symbol = SymbolInfo("X2", SymbolInfo::SymbolKind::Variable, HeaderName, - {{SymbolInfo::ContextType::Namespace, ""}}); - EXPECT_EQ(1, seen(Symbol)); - EXPECT_EQ(1, used(Symbol)); - - Symbol = SymbolInfo("X3", SymbolInfo::SymbolKind::Variable, HeaderName, - {{SymbolInfo::ContextType::Namespace, ""}, - {SymbolInfo::ContextType::Namespace, ""}}); - EXPECT_EQ(1, seen(Symbol)); - EXPECT_EQ(1, used(Symbol)); - - Symbol = SymbolInfo("X4", SymbolInfo::SymbolKind::Variable, HeaderName, - {{SymbolInfo::ContextType::Namespace, "nb"}, - {SymbolInfo::ContextType::Namespace, ""}}); - EXPECT_EQ(1, seen(Symbol)); - EXPECT_EQ(1, used(Symbol)); - - Symbol = SymbolInfo("X5", SymbolInfo::SymbolKind::Variable, HeaderName, - {{SymbolInfo::ContextType::Namespace, "na"}}); - EXPECT_EQ(1, seen(Symbol)); - EXPECT_EQ(1, used(Symbol)); -} - -TEST_F(FindAllSymbolsTest, DecayedTypeTest) { - static const char Header[] = "void DecayedFunc(int x[], int y[10]) {}"; - static const char Main[] = R"(int main() { DecayedFunc(nullptr, nullptr); })"; - runFindAllSymbols(Header, Main); - SymbolInfo Symbol = SymbolInfo( - "DecayedFunc", SymbolInfo::SymbolKind::Function, HeaderName, {}); - EXPECT_EQ(1, seen(Symbol)); - EXPECT_EQ(1, used(Symbol)); -} - -TEST_F(FindAllSymbolsTest, CTypedefTest) { - static const char Header[] = R"( - typedef unsigned size_t_; - typedef struct { int x; } X; - using XX = X; - )"; - static const char Main[] = R"( - size_t_ f; - template struct vector{}; - vector list; - void foo(const XX&){} - )"; - runFindAllSymbols(Header, Main); - - SymbolInfo Symbol = SymbolInfo("size_t_", SymbolInfo::SymbolKind::TypedefName, - HeaderName, {}); - EXPECT_EQ(1, seen(Symbol)); - EXPECT_EQ(1, used(Symbol)); - - Symbol = SymbolInfo("X", SymbolInfo::SymbolKind::TypedefName, HeaderName, {}); - EXPECT_EQ(1, seen(Symbol)); - EXPECT_EQ(1, used(Symbol)); - - Symbol = - SymbolInfo("XX", SymbolInfo::SymbolKind::TypedefName, HeaderName, {}); - EXPECT_EQ(1, seen(Symbol)); - EXPECT_EQ(1, used(Symbol)); -} - -TEST_F(FindAllSymbolsTest, EnumTest) { - static const char Header[] = R"( - enum Glob_E { G1, G2 }; - enum class Altitude { high='h', low='l'}; - enum { A1, A2 }; - class A { - public: - enum A_ENUM { X1, X2 }; - }; - enum DECL : int; - )"; - static const char Main[] = R"( - static auto flags = G1 | G2; - static auto alt = Altitude::high; - static auto nested = A::X1; - extern DECL whatever; - static auto flags2 = A1 | A2; - )"; - runFindAllSymbols(Header, Main); - - SymbolInfo Symbol = - SymbolInfo("Glob_E", SymbolInfo::SymbolKind::EnumDecl, HeaderName, {}); - EXPECT_EQ(1, seen(Symbol)); - EXPECT_EQ(0, used(Symbol)); - - Symbol = - SymbolInfo("G1", SymbolInfo::SymbolKind::EnumConstantDecl, HeaderName, - {{SymbolInfo::ContextType::EnumDecl, "Glob_E"}}); - EXPECT_EQ(1, seen(Symbol)); - EXPECT_EQ(1, used(Symbol)); - - Symbol = - SymbolInfo("G2", SymbolInfo::SymbolKind::EnumConstantDecl, HeaderName, - {{SymbolInfo::ContextType::EnumDecl, "Glob_E"}}); - EXPECT_EQ(1, seen(Symbol)); - EXPECT_EQ(1, used(Symbol)); - - Symbol = - SymbolInfo("Altitude", SymbolInfo::SymbolKind::EnumDecl, HeaderName, {}); - EXPECT_EQ(1, seen(Symbol)); - EXPECT_EQ(1, used(Symbol)); - Symbol = - SymbolInfo("high", SymbolInfo::SymbolKind::EnumConstantDecl, HeaderName, - {{SymbolInfo::ContextType::EnumDecl, "Altitude"}}); - EXPECT_EQ(0, seen(Symbol)); - EXPECT_EQ(0, used(Symbol)); - - Symbol = SymbolInfo("A1", SymbolInfo::SymbolKind::EnumConstantDecl, - HeaderName, {{SymbolInfo::ContextType::EnumDecl, ""}}); - EXPECT_EQ(1, seen(Symbol)); - EXPECT_EQ(1, used(Symbol)); - Symbol = SymbolInfo("A2", SymbolInfo::SymbolKind::EnumConstantDecl, - HeaderName, {{SymbolInfo::ContextType::EnumDecl, ""}}); - EXPECT_EQ(1, seen(Symbol)); - EXPECT_EQ(1, used(Symbol)); - Symbol = SymbolInfo("", SymbolInfo::SymbolKind::EnumDecl, HeaderName, {}); - EXPECT_EQ(0, seen(Symbol)); - EXPECT_EQ(0, used(Symbol)); - - Symbol = SymbolInfo("A_ENUM", SymbolInfo::SymbolKind::EnumDecl, HeaderName, - {{SymbolInfo::ContextType::Record, "A"}}); - EXPECT_EQ(0, seen(Symbol)); - EXPECT_EQ(0, used(Symbol)); - - Symbol = SymbolInfo("X1", SymbolInfo::SymbolKind::EnumDecl, HeaderName, - {{SymbolInfo::ContextType::EnumDecl, "A_ENUM"}, - {SymbolInfo::ContextType::Record, "A"}}); - EXPECT_EQ(0, seen(Symbol)); - - Symbol = SymbolInfo("DECL", SymbolInfo::SymbolKind::EnumDecl, HeaderName, {}); - EXPECT_EQ(0, seen(Symbol)); -} - -TEST_F(FindAllSymbolsTest, IWYUPrivatePragmaTest) { - static const char Header[] = R"( - // IWYU pragma: private, include "bar.h" - struct Bar { - }; - )"; - static const char Main[] = R"( - Bar bar; - )"; - runFindAllSymbols(Header, Main); - - SymbolInfo Symbol = - SymbolInfo("Bar", SymbolInfo::SymbolKind::Class, "bar.h", {}); - EXPECT_EQ(1, seen(Symbol)); - EXPECT_EQ(1, used(Symbol)); -} - -TEST_F(FindAllSymbolsTest, MacroTest) { - static const char Header[] = R"( - #define X - #define Y 1 - #define MAX(X, Y) ((X) > (Y) ? (X) : (Y)) - )"; - static const char Main[] = R"( - #ifdef X - int main() { return MAX(0,Y); } - #endif - )"; - runFindAllSymbols(Header, Main); - SymbolInfo Symbol = - SymbolInfo("X", SymbolInfo::SymbolKind::Macro, HeaderName, {}); - EXPECT_EQ(1, seen(Symbol)); - EXPECT_EQ(1, used(Symbol)); - - Symbol = SymbolInfo("Y", SymbolInfo::SymbolKind::Macro, HeaderName, {}); - EXPECT_EQ(1, seen(Symbol)); - EXPECT_EQ(1, used(Symbol)); - - Symbol = SymbolInfo("MAX", SymbolInfo::SymbolKind::Macro, HeaderName, {}); - EXPECT_EQ(1, seen(Symbol)); - EXPECT_EQ(1, used(Symbol)); -} - -TEST_F(FindAllSymbolsTest, MacroTestWithIWYU) { - static const char Header[] = R"( - // IWYU pragma: private, include "bar.h" - #define X 1 - #define Y 1 - #define MAX(X, Y) ((X) > (Y) ? (X) : (Y)) - )"; - static const char Main[] = R"( - #ifdef X - int main() { return MAX(0,Y); } - #endif - )"; - runFindAllSymbols(Header, Main); - SymbolInfo Symbol = - SymbolInfo("X", SymbolInfo::SymbolKind::Macro, "bar.h", {}); - EXPECT_EQ(1, seen(Symbol)); - EXPECT_EQ(1, used(Symbol)); - - Symbol = SymbolInfo("Y", SymbolInfo::SymbolKind::Macro, "bar.h", {}); - EXPECT_EQ(1, seen(Symbol)); - EXPECT_EQ(1, used(Symbol)); - - Symbol = SymbolInfo("MAX", SymbolInfo::SymbolKind::Macro, "bar.h", {}); - EXPECT_EQ(1, seen(Symbol)); - EXPECT_EQ(1, used(Symbol)); -} - -TEST_F(FindAllSymbolsTest, NoFriendTest) { - static const char Header[] = R"( - class WorstFriend { - friend void Friend(); - friend class BestFriend; - }; - )"; - runFindAllSymbols(Header, ""); - SymbolInfo Symbol = - SymbolInfo("WorstFriend", SymbolInfo::SymbolKind::Class, HeaderName, {}); - EXPECT_EQ(1, seen(Symbol)); - - Symbol = - SymbolInfo("Friend", SymbolInfo::SymbolKind::Function, HeaderName, {}); - EXPECT_EQ(0, seen(Symbol)); - - Symbol = - SymbolInfo("BestFriend", SymbolInfo::SymbolKind::Class, HeaderName, {}); - EXPECT_EQ(0, seen(Symbol)); -} - -} // namespace find_all_symbols -} // namespace clang