1 //===--- LocateSymbolTest.cpp -------------------------------------- C++-*-===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 #include "AnalysisInternal.h" 9 #include "TypesInternal.h" 10 #include "clang-include-cleaner/Types.h" 11 #include "clang/AST/Decl.h" 12 #include "clang/AST/DeclBase.h" 13 #include "clang/AST/RecursiveASTVisitor.h" 14 #include "clang/Basic/LangOptions.h" 15 #include "clang/Basic/SourceLocation.h" 16 #include "clang/Lex/Preprocessor.h" 17 #include "clang/Testing/TestAST.h" 18 #include "clang/Tooling/Inclusions/StandardLibrary.h" 19 #include "llvm/ADT/StringRef.h" 20 #include "llvm/Support/Casting.h" 21 #include "llvm/Testing/Annotations/Annotations.h" 22 #include "gmock/gmock.h" 23 #include "gtest/gtest.h" 24 #include <tuple> 25 #include <vector> 26 27 namespace clang::include_cleaner { 28 namespace { 29 using testing::Each; 30 using testing::ElementsAre; 31 using testing::ElementsAreArray; 32 using testing::Eq; 33 using testing::Field; 34 35 // A helper for building ASTs and getting decls out of it by name. Example usage 36 // looks like: 37 // LocateExample X("void ^foo();"); 38 // Decl &Foo = X.findDecl("foo"); 39 // X.points(); // returns all the points in annotated test input. 40 struct LocateExample { 41 private: 42 llvm::Annotations Target; 43 TestAST AST; 44 45 public: 46 LocateExample(llvm::StringRef AnnotatedCode) 47 : Target(AnnotatedCode), AST([this] { 48 TestInputs Inputs(Target.code()); 49 Inputs.ExtraArgs.push_back("-std=c++17"); 50 return Inputs; 51 }()) {} 52 53 const Decl &findDecl(llvm::StringRef SymbolName) { 54 struct Visitor : RecursiveASTVisitor<Visitor> { 55 llvm::StringRef NameToFind; 56 const NamedDecl *Out = nullptr; 57 bool VisitNamedDecl(const NamedDecl *ND) { 58 // Skip the templated decls, as they have the same name and matches in 59 // this file care about the outer template name. 60 if (auto *TD = ND->getDescribedTemplate()) 61 ND = TD; 62 if (ND->getName() == NameToFind) { 63 EXPECT_TRUE(Out == nullptr || Out == ND->getCanonicalDecl()) 64 << "Found multiple matches for " << NameToFind.str(); 65 Out = llvm::cast<NamedDecl>(ND->getCanonicalDecl()); 66 } 67 return true; 68 } 69 }; 70 Visitor V; 71 V.NameToFind = SymbolName; 72 V.TraverseDecl(AST.context().getTranslationUnitDecl()); 73 if (!V.Out) 74 ADD_FAILURE() << "Couldn't find any decls with name: " << SymbolName; 75 assert(V.Out); 76 return *V.Out; 77 } 78 79 Macro findMacro(llvm::StringRef Name) { 80 auto &PP = AST.preprocessor(); 81 auto *II = PP.getIdentifierInfo(Name); 82 if (!II || !II->hasMacroDefinition()) { 83 ADD_FAILURE() << "Couldn't find any macros with name: " << Name; 84 return {}; 85 } 86 auto MD = PP.getMacroDefinition(II); 87 assert(MD.getMacroInfo()); 88 return {II, MD.getMacroInfo()->getDefinitionLoc()}; 89 } 90 91 std::vector<SymbolLocation> points() { 92 auto &SM = AST.sourceManager(); 93 auto FID = SM.getMainFileID(); 94 auto Offsets = Target.points(); 95 std::vector<SymbolLocation> Results; 96 for (auto &Offset : Offsets) 97 Results.emplace_back(SM.getComposedLoc(FID, Offset)); 98 return Results; 99 } 100 101 const LangOptions &langOpts() { return AST.preprocessor().getLangOpts(); } 102 }; 103 104 TEST(LocateSymbol, Decl) { 105 // Looks for decl with name 'foo' and performs locateSymbol on it. 106 // Expects all the locations in the case to be returned as a location. 107 const llvm::StringLiteral Cases[] = { 108 "struct ^foo; struct ^foo {};", 109 "namespace ns { void ^foo(); void ^foo() {} }", 110 "enum class ^foo; enum class ^foo {};", 111 }; 112 113 for (auto &Case : Cases) { 114 SCOPED_TRACE(Case); 115 LocateExample Test(Case); 116 EXPECT_THAT(locateSymbol(Test.findDecl("foo"), Test.langOpts()), 117 ElementsAreArray(Test.points())); 118 } 119 } 120 121 TEST(LocateSymbol, Stdlib) { 122 { 123 LocateExample Test("namespace std { struct vector; }"); 124 EXPECT_THAT( 125 locateSymbol(Test.findDecl("vector"), Test.langOpts()), 126 ElementsAre(*tooling::stdlib::Symbol::named("std::", "vector"))); 127 } 128 { 129 LocateExample Test("#define assert(x)\nvoid foo() { assert(true); }"); 130 EXPECT_THAT(locateSymbol(Test.findMacro("assert"), Test.langOpts()), 131 ElementsAre(*tooling::stdlib::Symbol::named("", "assert"))); 132 } 133 } 134 135 TEST(LocateSymbol, Macros) { 136 // Make sure we preserve the last one. 137 LocateExample Test("#define FOO\n#undef FOO\n#define ^FOO"); 138 EXPECT_THAT(locateSymbol(Test.findMacro("FOO"), Test.langOpts()), 139 ElementsAreArray(Test.points())); 140 } 141 142 MATCHER_P2(HintedSymbol, Symbol, Hint, "") { 143 return std::tie(arg.Hint, arg) == std::tie(Hint, Symbol); 144 } 145 TEST(LocateSymbol, CompleteSymbolHint) { 146 { 147 // stdlib symbols are always complete. 148 LocateExample Test("namespace std { struct vector; }"); 149 EXPECT_THAT(locateSymbol(Test.findDecl("vector"), Test.langOpts()), 150 ElementsAre(HintedSymbol( 151 *tooling::stdlib::Symbol::named("std::", "vector"), 152 Hints::CompleteSymbol))); 153 } 154 { 155 // macros are always complete. 156 LocateExample Test("#define ^FOO"); 157 EXPECT_THAT(locateSymbol(Test.findMacro("FOO"), Test.langOpts()), 158 ElementsAre(HintedSymbol(Test.points().front(), 159 Hints::CompleteSymbol))); 160 } 161 { 162 // Completeness is only absent in cases that matters. 163 const llvm::StringLiteral Cases[] = { 164 "struct ^foo; struct ^foo {};", 165 "template <typename> struct ^foo; template <typename> struct ^foo {};", 166 "template <typename> void ^foo(); template <typename> void ^foo() {};", 167 }; 168 for (auto &Case : Cases) { 169 SCOPED_TRACE(Case); 170 LocateExample Test(Case); 171 EXPECT_THAT(locateSymbol(Test.findDecl("foo"), Test.langOpts()), 172 ElementsAre(HintedSymbol(Test.points().front(), Hints::None), 173 HintedSymbol(Test.points().back(), 174 Hints::CompleteSymbol))); 175 } 176 } 177 { 178 // All declarations should be marked as complete in cases that a definition 179 // is not usually needed. 180 const llvm::StringLiteral Cases[] = { 181 "void foo(); void foo() {}", 182 "extern int foo; int foo;", 183 }; 184 for (auto &Case : Cases) { 185 SCOPED_TRACE(Case); 186 LocateExample Test(Case); 187 EXPECT_THAT(locateSymbol(Test.findDecl("foo"), Test.langOpts()), 188 Each(Field(&Hinted<SymbolLocation>::Hint, 189 Eq(Hints::CompleteSymbol)))); 190 } 191 } 192 } 193 194 } // namespace 195 } // namespace clang::include_cleaner 196