|  | //===--- LocateSymbolTest.cpp -------------------------------------- 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 "AnalysisInternal.h" | 
|  | #include "TypesInternal.h" | 
|  | #include "clang-include-cleaner/Types.h" | 
|  | #include "clang/AST/Decl.h" | 
|  | #include "clang/AST/DeclBase.h" | 
|  | #include "clang/AST/RecursiveASTVisitor.h" | 
|  | #include "clang/Basic/LangOptions.h" | 
|  | #include "clang/Basic/SourceLocation.h" | 
|  | #include "clang/Lex/Preprocessor.h" | 
|  | #include "clang/Testing/TestAST.h" | 
|  | #include "clang/Tooling/Inclusions/StandardLibrary.h" | 
|  | #include "llvm/ADT/StringRef.h" | 
|  | #include "llvm/Support/Casting.h" | 
|  | #include "llvm/Testing/Annotations/Annotations.h" | 
|  | #include "gmock/gmock.h" | 
|  | #include "gtest/gtest.h" | 
|  | #include <tuple> | 
|  | #include <vector> | 
|  |  | 
|  | namespace clang::include_cleaner { | 
|  | namespace { | 
|  | using testing::Each; | 
|  | using testing::ElementsAre; | 
|  | using testing::ElementsAreArray; | 
|  | using testing::Eq; | 
|  | using testing::Field; | 
|  |  | 
|  | // A helper for building ASTs and getting decls out of it by name. Example usage | 
|  | // looks like: | 
|  | //   LocateExample X("void ^foo();"); | 
|  | //   Decl &Foo = X.findDecl("foo"); | 
|  | //   X.points(); // returns all the points in annotated test input. | 
|  | struct LocateExample { | 
|  | private: | 
|  | llvm::Annotations Target; | 
|  | TestAST AST; | 
|  |  | 
|  | public: | 
|  | LocateExample(llvm::StringRef AnnotatedCode) | 
|  | : Target(AnnotatedCode), AST([this] { | 
|  | TestInputs Inputs(Target.code()); | 
|  | Inputs.ExtraArgs.push_back("-std=c++17"); | 
|  | return Inputs; | 
|  | }()) {} | 
|  |  | 
|  | const Decl &findDecl(llvm::StringRef SymbolName) { | 
|  | struct Visitor : RecursiveASTVisitor<Visitor> { | 
|  | llvm::StringRef NameToFind; | 
|  | const NamedDecl *Out = nullptr; | 
|  | bool VisitNamedDecl(const NamedDecl *ND) { | 
|  | // Skip the templated decls, as they have the same name and matches in | 
|  | // this file care about the outer template name. | 
|  | if (auto *TD = ND->getDescribedTemplate()) | 
|  | ND = TD; | 
|  | if (ND->getName() == NameToFind) { | 
|  | EXPECT_TRUE(Out == nullptr || Out == ND->getCanonicalDecl()) | 
|  | << "Found multiple matches for " << NameToFind.str(); | 
|  | Out = llvm::cast<NamedDecl>(ND->getCanonicalDecl()); | 
|  | } | 
|  | return true; | 
|  | } | 
|  | }; | 
|  | Visitor V; | 
|  | V.NameToFind = SymbolName; | 
|  | V.TraverseDecl(AST.context().getTranslationUnitDecl()); | 
|  | if (!V.Out) | 
|  | ADD_FAILURE() << "Couldn't find any decls with name: " << SymbolName; | 
|  | assert(V.Out); | 
|  | return *V.Out; | 
|  | } | 
|  |  | 
|  | Macro findMacro(llvm::StringRef Name) { | 
|  | auto &PP = AST.preprocessor(); | 
|  | auto *II = PP.getIdentifierInfo(Name); | 
|  | if (!II || !II->hasMacroDefinition()) { | 
|  | ADD_FAILURE() << "Couldn't find any macros with name: " << Name; | 
|  | return {}; | 
|  | } | 
|  | auto MD = PP.getMacroDefinition(II); | 
|  | assert(MD.getMacroInfo()); | 
|  | return {II, MD.getMacroInfo()->getDefinitionLoc()}; | 
|  | } | 
|  |  | 
|  | std::vector<SymbolLocation> points() { | 
|  | auto &SM = AST.sourceManager(); | 
|  | auto FID = SM.getMainFileID(); | 
|  | auto Offsets = Target.points(); | 
|  | std::vector<SymbolLocation> Results; | 
|  | for (auto &Offset : Offsets) | 
|  | Results.emplace_back(SM.getComposedLoc(FID, Offset)); | 
|  | return Results; | 
|  | } | 
|  |  | 
|  | const LangOptions &langOpts() { return AST.preprocessor().getLangOpts(); } | 
|  | }; | 
|  |  | 
|  | TEST(LocateSymbol, Decl) { | 
|  | // Looks for decl with name 'foo' and performs locateSymbol on it. | 
|  | // Expects all the locations in the case to be returned as a location. | 
|  | const llvm::StringLiteral Cases[] = { | 
|  | "struct ^foo; struct ^foo {};", | 
|  | "namespace ns { void ^foo(); void ^foo() {} }", | 
|  | "enum class ^foo; enum class ^foo {};", | 
|  | }; | 
|  |  | 
|  | for (auto &Case : Cases) { | 
|  | SCOPED_TRACE(Case); | 
|  | LocateExample Test(Case); | 
|  | EXPECT_THAT(locateSymbol(Test.findDecl("foo"), Test.langOpts()), | 
|  | ElementsAreArray(Test.points())); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST(LocateSymbol, Stdlib) { | 
|  | { | 
|  | LocateExample Test("namespace std { struct vector; }"); | 
|  | EXPECT_THAT( | 
|  | locateSymbol(Test.findDecl("vector"), Test.langOpts()), | 
|  | ElementsAre(*tooling::stdlib::Symbol::named("std::", "vector"))); | 
|  | } | 
|  | { | 
|  | LocateExample Test("#define assert(x)\nvoid foo() { assert(true); }"); | 
|  | EXPECT_THAT(locateSymbol(Test.findMacro("assert"), Test.langOpts()), | 
|  | ElementsAre(*tooling::stdlib::Symbol::named("", "assert"))); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST(LocateSymbol, Macros) { | 
|  | // Make sure we preserve the last one. | 
|  | LocateExample Test("#define FOO\n#undef FOO\n#define ^FOO"); | 
|  | EXPECT_THAT(locateSymbol(Test.findMacro("FOO"), Test.langOpts()), | 
|  | ElementsAreArray(Test.points())); | 
|  | } | 
|  |  | 
|  | MATCHER_P2(HintedSymbol, Symbol, Hint, "") { | 
|  | return std::tie(arg.Hint, arg) == std::tie(Hint, Symbol); | 
|  | } | 
|  | TEST(LocateSymbol, CompleteSymbolHint) { | 
|  | { | 
|  | // stdlib symbols are always complete. | 
|  | LocateExample Test("namespace std { struct vector; }"); | 
|  | EXPECT_THAT(locateSymbol(Test.findDecl("vector"), Test.langOpts()), | 
|  | ElementsAre(HintedSymbol( | 
|  | *tooling::stdlib::Symbol::named("std::", "vector"), | 
|  | Hints::CompleteSymbol))); | 
|  | } | 
|  | { | 
|  | // macros are always complete. | 
|  | LocateExample Test("#define ^FOO"); | 
|  | EXPECT_THAT(locateSymbol(Test.findMacro("FOO"), Test.langOpts()), | 
|  | ElementsAre(HintedSymbol(Test.points().front(), | 
|  | Hints::CompleteSymbol))); | 
|  | } | 
|  | { | 
|  | // Completeness is only absent in cases that matters. | 
|  | const llvm::StringLiteral Cases[] = { | 
|  | "struct ^foo; struct ^foo {};", | 
|  | "template <typename> struct ^foo; template <typename> struct ^foo {};", | 
|  | "template <typename> void ^foo(); template <typename> void ^foo() {};", | 
|  | }; | 
|  | for (auto &Case : Cases) { | 
|  | SCOPED_TRACE(Case); | 
|  | LocateExample Test(Case); | 
|  | EXPECT_THAT(locateSymbol(Test.findDecl("foo"), Test.langOpts()), | 
|  | ElementsAre(HintedSymbol(Test.points().front(), Hints::None), | 
|  | HintedSymbol(Test.points().back(), | 
|  | Hints::CompleteSymbol))); | 
|  | } | 
|  | } | 
|  | { | 
|  | // All declarations should be marked as complete in cases that a definition | 
|  | // is not usually needed. | 
|  | const llvm::StringLiteral Cases[] = { | 
|  | "void foo(); void foo() {}", | 
|  | "extern int foo; int foo;", | 
|  | }; | 
|  | for (auto &Case : Cases) { | 
|  | SCOPED_TRACE(Case); | 
|  | LocateExample Test(Case); | 
|  | EXPECT_THAT(locateSymbol(Test.findDecl("foo"), Test.langOpts()), | 
|  | Each(Field(&Hinted<SymbolLocation>::Hint, | 
|  | Eq(Hints::CompleteSymbol)))); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | } // namespace | 
|  | } // namespace clang::include_cleaner |