//===-- 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 { return [S](const Decl *D) { if (const auto *ND = llvm::dyn_cast(D)) return llvm::StringRef(ND->getNameAsString()).starts_with(S); return false; }; }; auto AST = TestTU::withCode(Code.code()).build(); auto &NS = cast(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 void privTemplateMethod(); $end^}; )cpp"); auto AST = TestTU::withCode(Code.code()).build(); const CXXRecordDecl &C = cast(findDecl(AST, "C")); auto IsMethod = [](const Decl *D) { return llvm::isa(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(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(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(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(findDecl(AST, [&](const NamedDecl &D) { return llvm::isa(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