980 lines · cpp
1//===-- DefineOutline.cpp ---------------------------------------*- C++ -*-===//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 "TestFS.h"10#include "TweakTesting.h"11#include "gmock/gmock.h"12#include "gtest/gtest.h"13 14namespace clang {15namespace clangd {16namespace {17 18using ::testing::UnorderedElementsAre;19 20TWEAK_TEST(DefineOutline);21 22TEST_F(DefineOutlineTest, TriggersOnFunctionDecl) {23 FileName = "Test.cpp";24 // Not available for free function unless in a header file.25 EXPECT_UNAVAILABLE(R"cpp(26 [[void [[f^o^o]]() [[{27 return;28 }]]]])cpp");29 30 // Available in soure file.31 EXPECT_AVAILABLE(R"cpp(32 struct Foo {33 void f^oo() {}34 };35 )cpp");36 37 // Available within named namespace in source file.38 EXPECT_AVAILABLE(R"cpp(39 namespace N {40 struct Foo {41 void f^oo() {}42 };43 } // namespace N44 )cpp");45 46 // Available within anonymous namespace in source file.47 EXPECT_AVAILABLE(R"cpp(48 namespace {49 struct Foo {50 void f^oo() {}51 };52 } // namespace53 )cpp");54 55 // Not available for out-of-line method.56 EXPECT_UNAVAILABLE(R"cpp(57 class Bar {58 void baz();59 };60 61 [[void [[Bar::[[b^a^z]]]]() [[{62 return;63 }]]]])cpp");64 65 FileName = "Test.hpp";66 // Not available unless function name or fully body is selected.67 EXPECT_UNAVAILABLE(R"cpp(68 // Not a definition69 vo^i[[d^ ^f]]^oo();70 71 [[vo^id ]]foo[[()]] {[[72 [[(void)(5+3);73 return;]]74 }]])cpp");75 76 // Available even if there are no implementation files.77 EXPECT_AVAILABLE(R"cpp(78 [[void [[f^o^o]]() [[{79 return;80 }]]]])cpp");81 82 // Not available for out-of-line methods.83 EXPECT_UNAVAILABLE(R"cpp(84 class Bar {85 void baz();86 };87 88 [[void [[Bar::[[b^a^z]]]]() [[{89 return;90 }]]]])cpp");91 92 // Basic check for function body and signature.93 EXPECT_AVAILABLE(R"cpp(94 class Bar {95 [[void [[f^o^o^]]() [[{ return; }]]]]96 };97 98 void foo();99 [[void [[f^o^o]]() [[{100 return;101 }]]]])cpp");102 103 // Not available on defaulted/deleted members.104 EXPECT_UNAVAILABLE(R"cpp(105 class Foo {106 Fo^o() = default;107 F^oo(const Foo&) = delete;108 };)cpp");109 110 // Not available within templated classes with unnamed parameters, as it is111 // hard to spell class name out-of-line in such cases.112 EXPECT_UNAVAILABLE(R"cpp(113 template <typename> struct Foo { void fo^o(){} };114 )cpp");115 116 // Not available on function template specializations and free function117 // templates.118 EXPECT_UNAVAILABLE(R"cpp(119 template <typename T> void fo^o() {}120 template <> void fo^o<int>() {}121 )cpp");122 123 // Not available on methods of unnamed classes.124 EXPECT_UNAVAILABLE(R"cpp(125 struct Foo {126 struct { void b^ar() {} } Bar;127 };128 )cpp");129 130 // Not available on methods of named classes with unnamed parent in parents131 // nesting.132 EXPECT_UNAVAILABLE(R"cpp(133 struct Foo {134 struct {135 struct Bar { void b^ar() {} };136 } Baz;137 };138 )cpp");139 140 // Not available on definitions in header file within unnamed namespaces141 EXPECT_UNAVAILABLE(R"cpp(142 namespace {143 struct Foo {144 void f^oo() {}145 };146 } // namespace147 )cpp");148}149 150TEST_F(DefineOutlineTest, FailsWithoutSource) {151 FileName = "Test.hpp";152 llvm::StringRef Test = "void fo^o() { return; }";153 llvm::StringRef Expected =154 "fail: Couldn't find a suitable implementation file.";155 EXPECT_EQ(apply(Test), Expected);156}157 158TEST_F(DefineOutlineTest, ApplyTest) {159 ExtraFiles["Test.cpp"] = "";160 FileName = "Test.hpp";161 162 struct {163 llvm::StringRef Test;164 llvm::StringRef ExpectedHeader;165 llvm::StringRef ExpectedSource;166 } Cases[] = {167 // Simple check168 {169 "void fo^o() { return; }",170 "void foo() ;",171 "void foo() { return; }",172 },173 // Inline specifier.174 {175 "inline void fo^o() { return; }",176 " void foo() ;",177 " void foo() { return; }",178 },179 // Default args.180 {181 "void fo^o(int x, int y = 5, int = 2, int (*foo)(int) = nullptr) {}",182 "void foo(int x, int y = 5, int = 2, int (*foo)(int) = nullptr) ;",183 "void foo(int x, int y , int , int (*foo)(int) ) {}",184 },185 {186 "struct Bar{Bar();}; void fo^o(Bar x = {}) {}",187 "struct Bar{Bar();}; void foo(Bar x = {}) ;",188 "void foo(Bar x ) {}",189 },190 // Constructors191 {192 R"cpp(193 class Foo {public: Foo(); Foo(int);};194 class Bar {195 Ba^r() {}196 Bar(int x) : f1(x) {}197 Foo f1;198 Foo f2 = 2;199 };)cpp",200 R"cpp(201 class Foo {public: Foo(); Foo(int);};202 class Bar {203 Bar() ;204 Bar(int x) : f1(x) {}205 Foo f1;206 Foo f2 = 2;207 };)cpp",208 "Bar::Bar() {}\n",209 },210 // Ctor with initializer.211 {212 R"cpp(213 class Foo {public: Foo(); Foo(int);};214 class Bar {215 Bar() {}216 B^ar(int x) : f1(x), f2(3) {}217 Foo f1;218 Foo f2 = 2;219 };)cpp",220 R"cpp(221 class Foo {public: Foo(); Foo(int);};222 class Bar {223 Bar() {}224 Bar(int x) ;225 Foo f1;226 Foo f2 = 2;227 };)cpp",228 "Bar::Bar(int x) : f1(x), f2(3) {}\n",229 },230 // Ctor initializer with attribute.231 {232 R"cpp(233 template <typename T> class Foo {234 F^oo(T z) __attribute__((weak)) : bar(2){}235 int bar;236 };)cpp",237 R"cpp(238 template <typename T> class Foo {239 Foo(T z) __attribute__((weak)) ;240 int bar;241 };template <typename T>242inline Foo<T>::Foo(T z) __attribute__((weak)) : bar(2){}243)cpp",244 ""},245 // Virt specifiers.246 {247 R"cpp(248 struct A {249 virtual void f^oo() {}250 };)cpp",251 R"cpp(252 struct A {253 virtual void foo() ;254 };)cpp",255 " void A::foo() {}\n",256 },257 {258 R"cpp(259 struct A {260 virtual virtual void virtual f^oo() {}261 };)cpp",262 R"cpp(263 struct A {264 virtual virtual void virtual foo() ;265 };)cpp",266 " void A::foo() {}\n",267 },268 {269 R"cpp(270 struct A {271 virtual void foo() = 0;272 };273 struct B : A {274 void fo^o() override {}275 };)cpp",276 R"cpp(277 struct A {278 virtual void foo() = 0;279 };280 struct B : A {281 void foo() override ;282 };)cpp",283 "void B::foo() {}\n",284 },285 {286 R"cpp(287 struct A {288 virtual void foo() = 0;289 };290 struct B : A {291 void fo^o() final {}292 };)cpp",293 R"cpp(294 struct A {295 virtual void foo() = 0;296 };297 struct B : A {298 void foo() final ;299 };)cpp",300 "void B::foo() {}\n",301 },302 {303 R"cpp(304 struct A {305 virtual void foo() = 0;306 };307 struct B : A {308 void fo^o() final override {}309 };)cpp",310 R"cpp(311 struct A {312 virtual void foo() = 0;313 };314 struct B : A {315 void foo() final override ;316 };)cpp",317 "void B::foo() {}\n",318 },319 {320 R"cpp(321 struct A {322 static void fo^o() {}323 };)cpp",324 R"cpp(325 struct A {326 static void foo() ;327 };)cpp",328 " void A::foo() {}\n",329 },330 {331 R"cpp(332 struct A {333 static static void fo^o() {}334 };)cpp",335 R"cpp(336 struct A {337 static static void foo() ;338 };)cpp",339 " void A::foo() {}\n",340 },341 {342 R"cpp(343 struct Foo {344 explicit Fo^o(int) {}345 };)cpp",346 R"cpp(347 struct Foo {348 explicit Foo(int) ;349 };)cpp",350 " Foo::Foo(int) {}\n",351 },352 {353 R"cpp(354 struct Foo {355 explicit explicit Fo^o(int) {}356 };)cpp",357 R"cpp(358 struct Foo {359 explicit explicit Foo(int) ;360 };)cpp",361 " Foo::Foo(int) {}\n",362 },363 {364 R"cpp(365 struct A {366 inline void f^oo(int) {}367 };)cpp",368 R"cpp(369 struct A {370 void foo(int) ;371 };)cpp",372 " void A::foo(int) {}\n",373 },374 // Complex class template375 {376 R"cpp(377 template <typename T, typename ...U> struct O1 {378 template <class V, int A> struct O2 {379 enum E { E1, E2 };380 struct I {381 E f^oo(T, U..., V, E) { return E1; }382 };383 };384 };)cpp",385 R"cpp(386 template <typename T, typename ...U> struct O1 {387 template <class V, int A> struct O2 {388 enum E { E1, E2 };389 struct I {390 E foo(T, U..., V, E) ;391 };392 };393 };template <typename T, typename ...U>394template <class V, int A>395inline typename O1<T, U...>::template O2<V, A>::E O1<T, U...>::template O2<V, A>::I::foo(T, U..., V, E) { return E1; }396)cpp",397 ""},398 // Destructors399 {400 "class A { ~A^(){} };",401 "class A { ~A(); };",402 "A::~A(){} ",403 },404 405 // Member template406 {407 R"cpp(408 struct Foo {409 template <typename T, typename, bool B = true>410 T ^bar() { return {}; }411 };)cpp",412 R"cpp(413 struct Foo {414 template <typename T, typename, bool B = true>415 T bar() ;416 };template <typename T, typename, bool B>417inline T Foo::bar() { return {}; }418)cpp",419 ""},420 421 // Class template with member template422 {423 R"cpp(424 template <typename T> struct Foo {425 template <typename U, bool> T ^bar(const T& t, const U& u) { return {}; }426 };)cpp",427 R"cpp(428 template <typename T> struct Foo {429 template <typename U, bool> T bar(const T& t, const U& u) ;430 };template <typename T>431template <typename U, bool>432inline T Foo<T>::bar(const T& t, const U& u) { return {}; }433)cpp",434 ""},435 };436 for (const auto &Case : Cases) {437 SCOPED_TRACE(Case.Test);438 llvm::StringMap<std::string> EditedFiles;439 EXPECT_EQ(apply(Case.Test, &EditedFiles), Case.ExpectedHeader);440 if (Case.ExpectedSource.empty()) {441 EXPECT_TRUE(EditedFiles.empty());442 } else {443 EXPECT_THAT(EditedFiles, testing::ElementsAre(FileWithContents(444 testPath("Test.cpp"), Case.ExpectedSource)));445 }446 }447}448 449TEST_F(DefineOutlineTest, InCppFile) {450 FileName = "Test.cpp";451 struct {452 llvm::StringRef Test;453 llvm::StringRef ExpectedSource;454 } Cases[] = {455 {456 R"cpp(457 namespace foo {458 namespace {459 struct Foo { void ba^r() {} };460 struct Bar { void foo(); };461 void Bar::foo() {}462 }463 }464 )cpp",465 R"cpp(466 namespace foo {467 namespace {468 struct Foo { void bar() ; };void Foo::bar() {} 469 struct Bar { void foo(); };470 void Bar::foo() {}471 }472 }473 )cpp"},474 };475 476 for (const auto &Case : Cases) {477 SCOPED_TRACE(Case.Test);478 EXPECT_EQ(apply(Case.Test, nullptr), Case.ExpectedSource);479 }480}481 482TEST_F(DefineOutlineTest, HandleMacros) {483 llvm::StringMap<std::string> EditedFiles;484 ExtraFiles["Test.cpp"] = "";485 FileName = "Test.hpp";486 ExtraArgs.push_back("-DVIRTUAL=virtual");487 ExtraArgs.push_back("-DOVER=override");488 489 struct {490 llvm::StringRef Test;491 llvm::StringRef ExpectedHeader;492 llvm::StringRef ExpectedSource;493 } Cases[] = {494 {R"cpp(495 #define BODY { return; }496 void f^oo()BODY)cpp",497 R"cpp(498 #define BODY { return; }499 void foo();)cpp",500 "void foo()BODY"},501 502 {R"cpp(503 #define BODY return;504 void f^oo(){BODY})cpp",505 R"cpp(506 #define BODY return;507 void foo();)cpp",508 "void foo(){BODY}"},509 510 {R"cpp(511 #define TARGET void foo()512 [[TARGET]]{ return; })cpp",513 R"cpp(514 #define TARGET void foo()515 TARGET;)cpp",516 "TARGET{ return; }"},517 518 {R"cpp(519 #define TARGET foo520 void [[TARGET]](){ return; })cpp",521 R"cpp(522 #define TARGET foo523 void TARGET();)cpp",524 "void TARGET(){ return; }"},525 {R"cpp(#define VIRT virtual526 struct A {527 VIRT void f^oo() {}528 };)cpp",529 R"cpp(#define VIRT virtual530 struct A {531 VIRT void foo() ;532 };)cpp",533 " void A::foo() {}\n"},534 {R"cpp(535 struct A {536 VIRTUAL void f^oo() {}537 };)cpp",538 R"cpp(539 struct A {540 VIRTUAL void foo() ;541 };)cpp",542 " void A::foo() {}\n"},543 {R"cpp(544 struct A {545 virtual void foo() = 0;546 };547 struct B : A {548 void fo^o() OVER {}549 };)cpp",550 R"cpp(551 struct A {552 virtual void foo() = 0;553 };554 struct B : A {555 void foo() OVER ;556 };)cpp",557 "void B::foo() {}\n"},558 {R"cpp(#define STUPID_MACRO(X) virtual559 struct A {560 STUPID_MACRO(sizeof sizeof int) void f^oo() {}561 };)cpp",562 R"cpp(#define STUPID_MACRO(X) virtual563 struct A {564 STUPID_MACRO(sizeof sizeof int) void foo() ;565 };)cpp",566 " void A::foo() {}\n"},567 {R"cpp(#define STAT static568 struct A {569 STAT void f^oo() {}570 };)cpp",571 R"cpp(#define STAT static572 struct A {573 STAT void foo() ;574 };)cpp",575 " void A::foo() {}\n"},576 {R"cpp(#define STUPID_MACRO(X) static577 struct A {578 STUPID_MACRO(sizeof sizeof int) void f^oo() {}579 };)cpp",580 R"cpp(#define STUPID_MACRO(X) static581 struct A {582 STUPID_MACRO(sizeof sizeof int) void foo() ;583 };)cpp",584 " void A::foo() {}\n"},585 };586 for (const auto &Case : Cases) {587 SCOPED_TRACE(Case.Test);588 EXPECT_EQ(apply(Case.Test, &EditedFiles), Case.ExpectedHeader);589 EXPECT_THAT(EditedFiles, testing::ElementsAre(FileWithContents(590 testPath("Test.cpp"), Case.ExpectedSource)));591 }592}593 594TEST_F(DefineOutlineTest, QualifyReturnValue) {595 FileName = "Test.hpp";596 ExtraFiles["Test.cpp"] = "";597 598 struct {599 llvm::StringRef Test;600 llvm::StringRef ExpectedHeader;601 llvm::StringRef ExpectedSource;602 } Cases[] = {603 {R"cpp(604 namespace a { class Foo{}; }605 using namespace a;606 Foo fo^o() { return {}; })cpp",607 R"cpp(608 namespace a { class Foo{}; }609 using namespace a;610 Foo foo() ;)cpp",611 "a::Foo foo() { return {}; }"},612 {R"cpp(613 namespace a {614 class Foo {615 class Bar {};616 Bar fo^o() { return {}; }617 };618 })cpp",619 R"cpp(620 namespace a {621 class Foo {622 class Bar {};623 Bar foo() ;624 };625 })cpp",626 "a::Foo::Bar a::Foo::foo() { return {}; }\n"},627 {R"cpp(628 class Foo {};629 Foo fo^o() { return {}; })cpp",630 R"cpp(631 class Foo {};632 Foo foo() ;)cpp",633 "Foo foo() { return {}; }"},634 };635 llvm::StringMap<std::string> EditedFiles;636 for (auto &Case : Cases) {637 apply(Case.Test, &EditedFiles);638 EXPECT_EQ(apply(Case.Test, &EditedFiles), Case.ExpectedHeader);639 EXPECT_THAT(EditedFiles, testing::ElementsAre(FileWithContents(640 testPath("Test.cpp"), Case.ExpectedSource)));641 }642}643 644TEST_F(DefineOutlineTest, QualifyFunctionName) {645 FileName = "Test.hpp";646 struct {647 llvm::StringRef TestHeader;648 llvm::StringRef TestSource;649 llvm::StringRef ExpectedHeader;650 llvm::StringRef ExpectedSource;651 } Cases[] = {652 {653 R"cpp(654 namespace a {655 namespace b {656 class Foo {657 void fo^o() {}658 };659 }660 })cpp",661 "",662 R"cpp(663 namespace a {664 namespace b {665 class Foo {666 void foo() ;667 };668 }669 })cpp",670 "void a::b::Foo::foo() {}\n",671 },672 {673 "namespace a { namespace b { void f^oo() {} } }",674 "namespace a{}",675 "namespace a { namespace b { void foo() ; } }",676 "namespace a{void b::foo() {} }",677 },678 {679 "namespace a { namespace b { void f^oo() {} } }",680 "using namespace a;",681 "namespace a { namespace b { void foo() ; } }",682 // FIXME: Take using namespace directives in the source file into683 // account. This can be spelled as b::foo instead.684 "using namespace a;void a::b::foo() {} ",685 },686 {687 "namespace a { class A { ~A^(){} }; }",688 "",689 "namespace a { class A { ~A(); }; }",690 "a::A::~A(){} ",691 },692 {693 "namespace a { class A { ~A^(){} }; }",694 "namespace a{}",695 "namespace a { class A { ~A(); }; }",696 "namespace a{A::~A(){} }",697 },698 };699 llvm::StringMap<std::string> EditedFiles;700 for (auto &Case : Cases) {701 ExtraFiles["Test.cpp"] = std::string(Case.TestSource);702 EXPECT_EQ(apply(Case.TestHeader, &EditedFiles), Case.ExpectedHeader);703 EXPECT_THAT(EditedFiles, testing::ElementsAre(FileWithContents(704 testPath("Test.cpp"), Case.ExpectedSource)))705 << Case.TestHeader;706 }707}708 709TEST_F(DefineOutlineTest, FailsMacroSpecifier) {710 FileName = "Test.hpp";711 ExtraFiles["Test.cpp"] = "";712 ExtraArgs.push_back("-DFINALOVER=final override");713 714 std::pair<StringRef, StringRef> Cases[] = {715 {716 R"cpp(717 #define VIRT virtual void718 struct A {719 VIRT fo^o() {}720 };)cpp",721 "fail: define outline: couldn't remove `virtual` keyword."},722 {723 R"cpp(724 #define OVERFINAL final override725 struct A {726 virtual void foo() {}727 };728 struct B : A {729 void fo^o() OVERFINAL {}730 };)cpp",731 "fail: define outline: Can't move out of line as function has a "732 "macro `override` specifier.\ndefine outline: Can't move out of line "733 "as function has a macro `final` specifier."},734 {735 R"cpp(736 struct A {737 virtual void foo() {}738 };739 struct B : A {740 void fo^o() FINALOVER {}741 };)cpp",742 "fail: define outline: Can't move out of line as function has a "743 "macro `override` specifier.\ndefine outline: Can't move out of line "744 "as function has a macro `final` specifier."},745 };746 for (const auto &Case : Cases) {747 EXPECT_EQ(apply(Case.first), Case.second);748 }749}750 751TWEAK_WORKSPACE_TEST(DefineOutline);752 753// Test that DefineOutline's use of getCorrespondingHeaderOrSource()754// to find the source file corresponding to a header file in which the755// tweak is invoked is working as intended.756TEST_F(DefineOutlineWorkspaceTest, FindsCorrespondingSource) {757 llvm::Annotations HeaderBefore(R"cpp(758class A {759 void bar();760 void f^oo(){}761};762)cpp");763 std::string SourceBefore(R"cpp(764#include "a.hpp"765void A::bar(){}766)cpp");767 std::string HeaderAfter = R"cpp(768class A {769 void bar();770 void foo();771};772)cpp";773 std::string SourceAfter = R"cpp(774#include "a.hpp"775void A::bar(){}void A::foo(){}776 777)cpp";778 Workspace.addSource("a.hpp", HeaderBefore.code());779 Workspace.addMainFile("a.cpp", SourceBefore);780 auto Result = apply("a.hpp", {HeaderBefore.point(), HeaderBefore.point()});781 EXPECT_THAT(Result,782 AllOf(withStatus("success"),783 editedFiles(UnorderedElementsAre(784 FileWithContents(testPath("a.hpp"), HeaderAfter),785 FileWithContents(testPath("a.cpp"), SourceAfter)))));786}787 788// Test that the definition is inserted in a sensible location789// under various circumstances.790// Note that the formatting looks a little off here and there,791// which is because in contrast to the actual tweak, the test procedure792// does not run clang-format on the resulting code.793TEST_F(DefineOutlineWorkspaceTest, SensibleInsertionLocations) {794 const struct {795 llvm::StringRef HeaderBefore;796 llvm::StringRef SourceBefore;797 llvm::StringRef HeaderAfter;798 llvm::StringRef SourceAfter;799 } Cases[] = {800 // Criterion 1: Distance801 {802 R"cpp(803struct Foo {804 void ignored1(); // Too far away805 void ignored2(); // No definition806 void ignored3() {} // Defined inline807 void fo^o() {}808 void neighbor();809};810)cpp",811 R"cpp(812#include "a.hpp"813void Foo::ignored1() {}814void Foo::neighbor() {}815)cpp",816 R"cpp(817struct Foo {818 void ignored1(); // Too far away819 void ignored2(); // No definition820 void ignored3() {} // Defined inline821 void foo() ;822 void neighbor();823};824)cpp",825 R"cpp(826#include "a.hpp"827void Foo::ignored1() {}828void Foo::foo() {}829void Foo::neighbor() {}830)cpp"},831 832 // Criterion 2: Prefer preceding833 {834 R"cpp(835struct Foo {836 void neighbor();837 void fo^o() {}838 void ignored();839};840)cpp",841 R"cpp(842#include "a.hpp"843void Foo::neighbor() {}844void Foo::ignored() {}845)cpp",846 R"cpp(847struct Foo {848 void neighbor();849 void foo() ;850 void ignored();851};852)cpp",853 R"cpp(854#include "a.hpp"855void Foo::neighbor() {}void Foo::foo() {}856 857void Foo::ignored() {}858)cpp"},859 860 // Like above, but with a namespace861 {862 R"cpp(863namespace NS {864struct Foo {865 void neighbor();866 void fo^o() {}867 void ignored();868};869}870)cpp",871 R"cpp(872#include "a.hpp"873namespace NS {874void Foo::neighbor() {}875void Foo::ignored() {}876}877)cpp",878 R"cpp(879namespace NS {880struct Foo {881 void neighbor();882 void foo() ;883 void ignored();884};885}886)cpp",887 R"cpp(888#include "a.hpp"889namespace NS {890void Foo::neighbor() {}void Foo::foo() {}891 892void Foo::ignored() {}893}894)cpp"},895 896 // Like above, but there is no namespace at the definition site897 {898 R"cpp(899namespace NS {900struct Foo {901 void neighbor();902 void fo^o() {}903 void ignored();904};905}906)cpp",907 R"cpp(908#include "a.hpp"909void NS::Foo::neighbor() {}910void NS::Foo::ignored() {}911)cpp",912 R"cpp(913namespace NS {914struct Foo {915 void neighbor();916 void foo() ;917 void ignored();918};919}920)cpp",921 R"cpp(922#include "a.hpp"923void NS::Foo::neighbor() {}void NS::Foo::foo() {}924 925void NS::Foo::ignored() {}926)cpp"},927 928 // Neighbor's definition is in header929 {930 R"cpp(931struct Foo {932 void fo^o() {}933 void neighbor();934 void ignored();935};936inline void Foo::neighbor() {}937)cpp",938 R"cpp(939#include "a.hpp"940void Foo::ignored() {}941)cpp",942 R"cpp(943struct Foo {944 void foo() ;945 void neighbor();946 void ignored();947};948inline void Foo::foo() {}949inline void Foo::neighbor() {}950)cpp",951 {}},952 953 };954 955 for (const auto &Case : Cases) {956 Workspace = {};957 llvm::Annotations Hdr(Case.HeaderBefore);958 Workspace.addSource("a.hpp", Hdr.code());959 Workspace.addMainFile("a.cpp", Case.SourceBefore);960 auto Result = apply("a.hpp", {Hdr.point(), Hdr.point()});961 if (Case.SourceAfter.empty()) {962 EXPECT_THAT(Result,963 AllOf(withStatus("success"),964 editedFiles(UnorderedElementsAre(FileWithContents(965 testPath("a.hpp"), Case.HeaderAfter)))));966 967 } else {968 EXPECT_THAT(969 Result,970 AllOf(withStatus("success"),971 editedFiles(UnorderedElementsAre(972 FileWithContents(testPath("a.hpp"), Case.HeaderAfter),973 FileWithContents(testPath("a.cpp"), Case.SourceAfter)))));974 }975 }976}977} // namespace978} // namespace clangd979} // namespace clang980