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