xref: /llvm-project/clang-tools-extra/include-cleaner/unittests/LocateSymbolTest.cpp (revision ec6c3448d31056db5d63d7aed3e9f207edb49321)
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