210 lines · cpp
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-exception6//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 comment30 int b;31 $c^int c1; // trailing comment32 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 specifiers88// - unwrapping of template decls89TEST(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 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("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.180TEST(InsertionPointTests, ObjC) {181 Annotations Code(R"objc(182 @interface Foo183 -(void) v;184 $endIface^@end185 @implementation Foo186 -(void) v {}187 $endImpl^@end188 )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} // namespace208} // namespace clangd209} // namespace clang210