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

source code of clang-tools-extra/include-cleaner/unittests/LocateSymbolTest.cpp