xref: /llvm-project/clang-tools-extra/clangd/unittests/InsertionPointTests.cpp (revision d5953e3e3092f7142a07aa012fc9665ede09e53b)
1 //===-- InsertionPointTess.cpp  -------------------------------------------===//
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 
9 #include "Annotations.h"
10 #include "Protocol.h"
11 #include "SourceCode.h"
12 #include "TestTU.h"
13 #include "XRefs.h"
14 #include "refactor/InsertionPoint.h"
15 #include "clang/AST/DeclBase.h"
16 #include "llvm/Testing/Support/Error.h"
17 #include "gmock/gmock.h"
18 #include "gtest/gtest.h"
19 
20 namespace clang {
21 namespace clangd {
22 namespace {
23 using llvm::HasValue;
24 
TEST(InsertionPointTests,Generic)25 TEST(InsertionPointTests, Generic) {
26   Annotations Code(R"cpp(
27   namespace ns {
28     $a^int a1;
29     $b^// leading comment
30     int b;
31     $c^int c1; // trailing comment
32     int c2;
33     $a2^int a2;
34   $end^};
35   )cpp");
36 
37   auto StartsWith =
38       [&](llvm::StringLiteral S) -> std::function<bool(const Decl *)> {
39     return [S](const Decl *D) {
40       if (const auto *ND = llvm::dyn_cast<NamedDecl>(D))
41         return llvm::StringRef(ND->getNameAsString()).starts_with(S);
42       return false;
43     };
44   };
45 
46   auto AST = TestTU::withCode(Code.code()).build();
47   auto &NS = cast<NamespaceDecl>(findDecl(AST, "ns"));
48 
49   // Test single anchors.
50   auto Point = [&](llvm::StringLiteral Prefix, Anchor::Dir Direction) {
51     auto Loc = insertionPoint(NS, {Anchor{StartsWith(Prefix), Direction}});
52     return sourceLocToPosition(AST.getSourceManager(), Loc);
53   };
54   EXPECT_EQ(Point("a", Anchor::Above), Code.point("a"));
55   EXPECT_EQ(Point("a", Anchor::Below), Code.point("b"));
56   EXPECT_EQ(Point("b", Anchor::Above), Code.point("b"));
57   EXPECT_EQ(Point("b", Anchor::Below), Code.point("c"));
58   EXPECT_EQ(Point("c", Anchor::Above), Code.point("c"));
59   EXPECT_EQ(Point("c", Anchor::Below), Code.point("a2"));
60   EXPECT_EQ(Point("", Anchor::Above), Code.point("a"));
61   EXPECT_EQ(Point("", Anchor::Below), Code.point("end"));
62   EXPECT_EQ(Point("no_match", Anchor::Below), Position{});
63 
64   // Test anchor chaining.
65   auto Chain = [&](llvm::StringLiteral P1, llvm::StringLiteral P2) {
66     auto Loc = insertionPoint(NS, {Anchor{StartsWith(P1), Anchor::Above},
67                                    Anchor{StartsWith(P2), Anchor::Above}});
68     return sourceLocToPosition(AST.getSourceManager(), Loc);
69   };
70   EXPECT_EQ(Chain("a", "b"), Code.point("a"));
71   EXPECT_EQ(Chain("b", "a"), Code.point("b"));
72   EXPECT_EQ(Chain("no_match", "a"), Code.point("a"));
73 
74   // Test edit generation.
75   auto Edit = insertDecl("foo;", NS, {Anchor{StartsWith("a"), Anchor::Below}});
76   ASSERT_THAT_EXPECTED(Edit, llvm::Succeeded());
77   EXPECT_EQ(offsetToPosition(Code.code(), Edit->getOffset()), Code.point("b"));
78   EXPECT_EQ(Edit->getReplacementText(), "foo;");
79   // If no match, the edit is inserted at the end.
80   Edit = insertDecl("x;", NS, {Anchor{StartsWith("no_match"), Anchor::Below}});
81   ASSERT_THAT_EXPECTED(Edit, llvm::Succeeded());
82   EXPECT_EQ(offsetToPosition(Code.code(), Edit->getOffset()),
83             Code.point("end"));
84 }
85 
86 // For CXX, we should check:
87 // - special handling for access specifiers
88 // - unwrapping of template decls
TEST(InsertionPointTests,CXX)89 TEST(InsertionPointTests, CXX) {
90   Annotations Code(R"cpp(
91     class C {
92     public:
93       $Method^void pubMethod();
94       $Field^int PubField;
95 
96     $private^private:
97       $field^int PrivField;
98       $method^void privMethod();
99       template <typename T> void privTemplateMethod();
100     $end^};
101   )cpp");
102 
103   auto AST = TestTU::withCode(Code.code()).build();
104   const CXXRecordDecl &C = cast<CXXRecordDecl>(findDecl(AST, "C"));
105 
106   auto IsMethod = [](const Decl *D) { return llvm::isa<CXXMethodDecl>(D); };
107   auto Any = [](const Decl *D) { return true; };
108 
109   // Test single anchors.
110   auto Point = [&](Anchor A, AccessSpecifier Protection) {
111     auto Loc = insertionPoint(C, {A}, Protection);
112     return sourceLocToPosition(AST.getSourceManager(), Loc);
113   };
114   EXPECT_EQ(Point({IsMethod, Anchor::Above}, AS_public), Code.point("Method"));
115   EXPECT_EQ(Point({IsMethod, Anchor::Below}, AS_public), Code.point("Field"));
116   EXPECT_EQ(Point({Any, Anchor::Above}, AS_public), Code.point("Method"));
117   EXPECT_EQ(Point({Any, Anchor::Below}, AS_public), Code.point("private"));
118   EXPECT_EQ(Point({IsMethod, Anchor::Above}, AS_private), Code.point("method"));
119   EXPECT_EQ(Point({IsMethod, Anchor::Below}, AS_private), Code.point("end"));
120   EXPECT_EQ(Point({Any, Anchor::Above}, AS_private), Code.point("field"));
121   EXPECT_EQ(Point({Any, Anchor::Below}, AS_private), Code.point("end"));
122   EXPECT_EQ(Point({IsMethod, Anchor::Above}, AS_protected), Position{});
123   EXPECT_EQ(Point({IsMethod, Anchor::Below}, AS_protected), Position{});
124   EXPECT_EQ(Point({Any, Anchor::Above}, AS_protected), Position{});
125   EXPECT_EQ(Point({Any, Anchor::Below}, AS_protected), Position{});
126 
127   // Edits when there's no match --> end of matching access control section.
128   auto Edit = insertDecl("x", C, {}, AS_public);
129   ASSERT_THAT_EXPECTED(Edit, llvm::Succeeded());
130   EXPECT_EQ(offsetToPosition(Code.code(), Edit->getOffset()),
131             Code.point("private"));
132 
133   Edit = insertDecl("x", C, {}, AS_private);
134   ASSERT_THAT_EXPECTED(Edit, llvm::Succeeded());
135   EXPECT_EQ(offsetToPosition(Code.code(), Edit->getOffset()),
136             Code.point("end"));
137 
138   Edit = insertDecl("x", C, {}, AS_protected);
139   ASSERT_THAT_EXPECTED(Edit, llvm::Succeeded());
140   EXPECT_EQ(offsetToPosition(Code.code(), Edit->getOffset()),
141             Code.point("end"));
142   EXPECT_EQ(Edit->getReplacementText(), "protected:\nx");
143 }
144 
145 MATCHER_P(replacementText, Text, "") {
146   if (arg.getReplacementText() != Text) {
147     *result_listener << "replacement is " << arg.getReplacementText().str();
148     return false;
149   }
150   return true;
151 }
152 
TEST(InsertionPointTests,CXXAccessProtection)153 TEST(InsertionPointTests, CXXAccessProtection) {
154   // Empty class uses default access.
155   auto AST = TestTU::withCode("struct S{};").build();
156   const CXXRecordDecl &S = cast<CXXRecordDecl>(findDecl(AST, "S"));
157   ASSERT_THAT_EXPECTED(insertDecl("x", S, {}, AS_public),
158                        HasValue(replacementText("x")));
159   ASSERT_THAT_EXPECTED(insertDecl("x", S, {}, AS_private),
160                        HasValue(replacementText("private:\nx")));
161 
162   // We won't insert above the first access specifier if there's nothing there.
163   AST = TestTU::withCode("struct T{private:};").build();
164   const CXXRecordDecl &T = cast<CXXRecordDecl>(findDecl(AST, "T"));
165   ASSERT_THAT_EXPECTED(insertDecl("x", T, {}, AS_public),
166                        HasValue(replacementText("public:\nx")));
167   ASSERT_THAT_EXPECTED(insertDecl("x", T, {}, AS_private),
168                        HasValue(replacementText("x")));
169 
170   // But we will if there are declarations.
171   AST = TestTU::withCode("struct U{int i;private:};").build();
172   const CXXRecordDecl &U = cast<CXXRecordDecl>(findDecl(AST, "U"));
173   ASSERT_THAT_EXPECTED(insertDecl("x", U, {}, AS_public),
174                        HasValue(replacementText("x")));
175   ASSERT_THAT_EXPECTED(insertDecl("x", U, {}, AS_private),
176                        HasValue(replacementText("x")));
177 }
178 
179 // In ObjC we need to take care to get the @end fallback right.
TEST(InsertionPointTests,ObjC)180 TEST(InsertionPointTests, ObjC) {
181   Annotations Code(R"objc(
182     @interface Foo
183      -(void) v;
184     $endIface^@end
185     @implementation Foo
186      -(void) v {}
187     $endImpl^@end
188   )objc");
189   auto TU = TestTU::withCode(Code.code());
190   TU.Filename = "TestTU.m";
191   auto AST = TU.build();
192 
193   auto &Impl =
194       cast<ObjCImplementationDecl>(findDecl(AST, [&](const NamedDecl &D) {
195         return llvm::isa<ObjCImplementationDecl>(D);
196       }));
197   auto &Iface = *Impl.getClassInterface();
198   Anchor End{[](const Decl *) { return true; }, Anchor::Below};
199 
200   const auto &SM = AST.getSourceManager();
201   EXPECT_EQ(sourceLocToPosition(SM, insertionPoint(Iface, {End})),
202             Code.point("endIface"));
203   EXPECT_EQ(sourceLocToPosition(SM, insertionPoint(Impl, {End})),
204             Code.point("endImpl"));
205 }
206 
207 } // namespace
208 } // namespace clangd
209 } // namespace clang
210