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