| //===-- 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 "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()).starts_with(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 |