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
20namespace clang {
21namespace clangd {
22namespace {
23using llvm::HasValue;
24
25TEST(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>(Val: D))
41 return llvm::StringRef(ND->getNameAsString()).starts_with(Prefix: S);
42 return false;
43 };
44 };
45
46 auto AST = TestTU::withCode(Code: Code.code()).build();
47 auto &NS = cast<NamespaceDecl>(Val: findDecl(AST, QName: "ns"));
48
49 // Test single anchors.
50 auto Point = [&](llvm::StringLiteral Prefix, Anchor::Dir Direction) {
51 auto Loc = insertionPoint(NS, {Anchor{.Match: StartsWith(Prefix), .Direction: 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{.Match: StartsWith(P1), .Direction: Anchor::Above},
67 Anchor{.Match: StartsWith(P2), .Direction: 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{.Match: StartsWith("a"), .Direction: 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{.Match: StartsWith("no_match"), .Direction: 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
89TEST(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.code()).build();
104 const CXXRecordDecl &C = cast<CXXRecordDecl>(Val: findDecl(AST, QName: "C"));
105
106 auto IsMethod = [](const Decl *D) { return llvm::isa<CXXMethodDecl>(Val: 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(InClass: C, Anchors: {A}, Protection);
112 return sourceLocToPosition(SM: 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(Code: "x", InClass: C, Anchors: {}, Protection: 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(Code: "x", InClass: C, Anchors: {}, Protection: 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(Code: "x", InClass: C, Anchors: {}, Protection: 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
145MATCHER_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
153TEST(InsertionPointTests, CXXAccessProtection) {
154 // Empty class uses default access.
155 auto AST = TestTU::withCode(Code: "struct S{};").build();
156 const CXXRecordDecl &S = cast<CXXRecordDecl>(Val: findDecl(AST, QName: "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(Code: "struct T{private:};").build();
164 const CXXRecordDecl &T = cast<CXXRecordDecl>(Val: findDecl(AST, QName: "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(Code: "struct U{int i;private:};").build();
172 const CXXRecordDecl &U = cast<CXXRecordDecl>(Val: findDecl(AST, QName: "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.
180TEST(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.code());
190 TU.Filename = "TestTU.m";
191 auto AST = TU.build();
192
193 auto &Impl =
194 cast<ObjCImplementationDecl>(Val: findDecl(AST, Filter: [&](const NamedDecl &D) {
195 return llvm::isa<ObjCImplementationDecl>(Val: D);
196 }));
197 auto &Iface = *Impl.getClassInterface();
198 Anchor End{.Match: [](const Decl *) { return true; }, .Direction: 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

source code of clang-tools-extra/clangd/unittests/InsertionPointTests.cpp