clang-tools 22.0.0git
DefineOutlineTests.cpp
Go to the documentation of this file.
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-exception
6//
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.
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 N
44 )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 } // namespace
53 )cpp");
54
55 // Not available for out-of-line method.
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.
68 // Not a definition
69 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.
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 is
111 // 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 function
117 // 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 parents
131 // 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 namespaces
141 EXPECT_UNAVAILABLE(R"cpp(
142 namespace {
143 struct Foo {
144 void f^oo() {}
145 };
146 } // namespace
147 )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 check
168 {
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 // Constructors
191 {
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 template
375 {
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 // Destructors
399 {
400 "class A { ~A^(){} };",
401 "class A { ~A(); };",
402 "A::~A(){} ",
403 },
404
405 // Member template
406 {
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 template
422 {
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
452 struct {
453 llvm::StringRef Test;
454 llvm::StringRef ExpectedSource;
455 } Cases[] = {
456 {
457 R"cpp(
458 namespace foo {
459 namespace {
460 struct Foo { void ba^r() {} };
461 struct Bar { void foo(); };
462 void Bar::foo() {}
463 }
464 }
465 )cpp",
466 R"cpp(
467 namespace foo {
468 namespace {
469 struct Foo { void bar() ; };void Foo::bar() {}
470 struct Bar { void foo(); };
471 void Bar::foo() {}
472 }
473 }
474 )cpp"},
475 };
476
477 for (const auto &Case : Cases) {
478 SCOPED_TRACE(Case.Test);
479 EXPECT_EQ(apply(Case.Test, nullptr), Case.ExpectedSource);
480 }
481}
482
483TEST_F(DefineOutlineTest, HandleMacros) {
484 llvm::StringMap<std::string> EditedFiles;
485 ExtraFiles["Test.cpp"] = "";
486 FileName = "Test.hpp";
487 ExtraArgs.push_back("-DVIRTUAL=virtual");
488 ExtraArgs.push_back("-DOVER=override");
489
490 struct {
491 llvm::StringRef Test;
492 llvm::StringRef ExpectedHeader;
493 llvm::StringRef ExpectedSource;
494 } Cases[] = {
495 {R"cpp(
496 #define BODY { return; }
497 void f^oo()BODY)cpp",
498 R"cpp(
499 #define BODY { return; }
500 void foo();)cpp",
501 "void foo()BODY"},
502
503 {R"cpp(
504 #define BODY return;
505 void f^oo(){BODY})cpp",
506 R"cpp(
507 #define BODY return;
508 void foo();)cpp",
509 "void foo(){BODY}"},
510
511 {R"cpp(
512 #define TARGET void foo()
513 [[TARGET]]{ return; })cpp",
514 R"cpp(
515 #define TARGET void foo()
516 TARGET;)cpp",
517 "TARGET{ return; }"},
518
519 {R"cpp(
520 #define TARGET foo
521 void [[TARGET]](){ return; })cpp",
522 R"cpp(
523 #define TARGET foo
524 void TARGET();)cpp",
525 "void TARGET(){ return; }"},
526 {R"cpp(#define VIRT virtual
527 struct A {
528 VIRT void f^oo() {}
529 };)cpp",
530 R"cpp(#define VIRT virtual
531 struct A {
532 VIRT void foo() ;
533 };)cpp",
534 " void A::foo() {}\n"},
535 {R"cpp(
536 struct A {
537 VIRTUAL void f^oo() {}
538 };)cpp",
539 R"cpp(
540 struct A {
541 VIRTUAL void foo() ;
542 };)cpp",
543 " void A::foo() {}\n"},
544 {R"cpp(
545 struct A {
546 virtual void foo() = 0;
547 };
548 struct B : A {
549 void fo^o() OVER {}
550 };)cpp",
551 R"cpp(
552 struct A {
553 virtual void foo() = 0;
554 };
555 struct B : A {
556 void foo() OVER ;
557 };)cpp",
558 "void B::foo() {}\n"},
559 {R"cpp(#define STUPID_MACRO(X) virtual
560 struct A {
561 STUPID_MACRO(sizeof sizeof int) void f^oo() {}
562 };)cpp",
563 R"cpp(#define STUPID_MACRO(X) virtual
564 struct A {
565 STUPID_MACRO(sizeof sizeof int) void foo() ;
566 };)cpp",
567 " void A::foo() {}\n"},
568 {R"cpp(#define STAT static
569 struct A {
570 STAT void f^oo() {}
571 };)cpp",
572 R"cpp(#define STAT static
573 struct A {
574 STAT void foo() ;
575 };)cpp",
576 " void A::foo() {}\n"},
577 {R"cpp(#define STUPID_MACRO(X) static
578 struct A {
579 STUPID_MACRO(sizeof sizeof int) void f^oo() {}
580 };)cpp",
581 R"cpp(#define STUPID_MACRO(X) static
582 struct A {
583 STUPID_MACRO(sizeof sizeof int) void foo() ;
584 };)cpp",
585 " void A::foo() {}\n"},
586 };
587 for (const auto &Case : Cases) {
588 SCOPED_TRACE(Case.Test);
589 EXPECT_EQ(apply(Case.Test, &EditedFiles), Case.ExpectedHeader);
590 EXPECT_THAT(EditedFiles, testing::ElementsAre(FileWithContents(
591 testPath("Test.cpp"), Case.ExpectedSource)));
592 }
593}
594
595TEST_F(DefineOutlineTest, QualifyReturnValue) {
596 FileName = "Test.hpp";
597 ExtraFiles["Test.cpp"] = "";
598
599 struct {
600 llvm::StringRef Test;
601 llvm::StringRef ExpectedHeader;
602 llvm::StringRef ExpectedSource;
603 } Cases[] = {
604 {R"cpp(
605 namespace a { class Foo{}; }
606 using namespace a;
607 Foo fo^o() { return {}; })cpp",
608 R"cpp(
609 namespace a { class Foo{}; }
610 using namespace a;
611 Foo foo() ;)cpp",
612 "a::Foo foo() { return {}; }"},
613 {R"cpp(
614 namespace a {
615 class Foo {
616 class Bar {};
617 Bar fo^o() { return {}; }
618 };
619 })cpp",
620 R"cpp(
621 namespace a {
622 class Foo {
623 class Bar {};
624 Bar foo() ;
625 };
626 })cpp",
627 "a::Foo::Bar a::Foo::foo() { return {}; }\n"},
628 {R"cpp(
629 class Foo {};
630 Foo fo^o() { return {}; })cpp",
631 R"cpp(
632 class Foo {};
633 Foo foo() ;)cpp",
634 "Foo foo() { return {}; }"},
635 };
636 llvm::StringMap<std::string> EditedFiles;
637 for (auto &Case : Cases) {
638 apply(Case.Test, &EditedFiles);
639 EXPECT_EQ(apply(Case.Test, &EditedFiles), Case.ExpectedHeader);
640 EXPECT_THAT(EditedFiles, testing::ElementsAre(FileWithContents(
641 testPath("Test.cpp"), Case.ExpectedSource)));
642 }
643}
644
645TEST_F(DefineOutlineTest, QualifyFunctionName) {
646 FileName = "Test.hpp";
647 struct {
648 llvm::StringRef TestHeader;
649 llvm::StringRef TestSource;
650 llvm::StringRef ExpectedHeader;
651 llvm::StringRef ExpectedSource;
652 } Cases[] = {
653 {
654 R"cpp(
655 namespace a {
656 namespace b {
657 class Foo {
658 void fo^o() {}
659 };
660 }
661 })cpp",
662 "",
663 R"cpp(
664 namespace a {
665 namespace b {
666 class Foo {
667 void foo() ;
668 };
669 }
670 })cpp",
671 "void a::b::Foo::foo() {}\n",
672 },
673 {
674 "namespace a { namespace b { void f^oo() {} } }",
675 "namespace a{}",
676 "namespace a { namespace b { void foo() ; } }",
677 "namespace a{void b::foo() {} }",
678 },
679 {
680 "namespace a { namespace b { void f^oo() {} } }",
681 "using namespace a;",
682 "namespace a { namespace b { void foo() ; } }",
683 // FIXME: Take using namespace directives in the source file into
684 // account. This can be spelled as b::foo instead.
685 "using namespace a;void a::b::foo() {} ",
686 },
687 {
688 "namespace a { class A { ~A^(){} }; }",
689 "",
690 "namespace a { class A { ~A(); }; }",
691 "a::A::~A(){} ",
692 },
693 {
694 "namespace a { class A { ~A^(){} }; }",
695 "namespace a{}",
696 "namespace a { class A { ~A(); }; }",
697 "namespace a{A::~A(){} }",
698 },
699 };
700 llvm::StringMap<std::string> EditedFiles;
701 for (auto &Case : Cases) {
702 ExtraFiles["Test.cpp"] = std::string(Case.TestSource);
703 EXPECT_EQ(apply(Case.TestHeader, &EditedFiles), Case.ExpectedHeader);
704 EXPECT_THAT(EditedFiles, testing::ElementsAre(FileWithContents(
705 testPath("Test.cpp"), Case.ExpectedSource)))
706 << Case.TestHeader;
707 }
708}
709
710TEST_F(DefineOutlineTest, FailsMacroSpecifier) {
711 FileName = "Test.hpp";
712 ExtraFiles["Test.cpp"] = "";
713 ExtraArgs.push_back("-DFINALOVER=final override");
714
715 std::pair<StringRef, StringRef> Cases[] = {
716 {
717 R"cpp(
718 #define VIRT virtual void
719 struct A {
720 VIRT fo^o() {}
721 };)cpp",
722 "fail: define outline: couldn't remove `virtual` keyword."},
723 {
724 R"cpp(
725 #define OVERFINAL final override
726 struct A {
727 virtual void foo() {}
728 };
729 struct B : A {
730 void fo^o() OVERFINAL {}
731 };)cpp",
732 "fail: define outline: Can't move out of line as function has a "
733 "macro `override` specifier.\ndefine outline: Can't move out of line "
734 "as function has a macro `final` specifier."},
735 {
736 R"cpp(
737 struct A {
738 virtual void foo() {}
739 };
740 struct B : A {
741 void fo^o() FINALOVER {}
742 };)cpp",
743 "fail: define outline: Can't move out of line as function has a "
744 "macro `override` specifier.\ndefine outline: Can't move out of line "
745 "as function has a macro `final` specifier."},
746 };
747 for (const auto &Case : Cases) {
748 EXPECT_EQ(apply(Case.first), Case.second);
749 }
750}
751
752TWEAK_WORKSPACE_TEST(DefineOutline);
753
754// Test that DefineOutline's use of getCorrespondingHeaderOrSource()
755// to find the source file corresponding to a header file in which the
756// tweak is invoked is working as intended.
757TEST_F(DefineOutlineWorkspaceTest, FindsCorrespondingSource) {
758 llvm::Annotations HeaderBefore(R"cpp(
759class A {
760 void bar();
761 void f^oo(){}
762};
763)cpp");
764 std::string SourceBefore(R"cpp(
765#include "a.hpp"
766void A::bar(){}
767)cpp");
768 std::string HeaderAfter = R"cpp(
769class A {
770 void bar();
771 void foo();
772};
773)cpp";
774 std::string SourceAfter = R"cpp(
775#include "a.hpp"
776void A::bar(){}
777void A::foo(){}
778)cpp";
779 Workspace.addSource("a.hpp", HeaderBefore.code());
780 Workspace.addMainFile("a.cpp", SourceBefore);
781 auto Result = apply("a.hpp", {HeaderBefore.point(), HeaderBefore.point()});
782 EXPECT_THAT(Result,
783 AllOf(withStatus("success"),
784 editedFiles(UnorderedElementsAre(
785 FileWithContents(testPath("a.hpp"), HeaderAfter),
786 FileWithContents(testPath("a.cpp"), SourceAfter)))));
787}
788
789} // namespace
790} // namespace clangd
791} // namespace clang
#define TWEAK_TEST(TweakID)
#define TWEAK_WORKSPACE_TEST(TweakID)
#define EXPECT_AVAILABLE(MarkedCode)
#define EXPECT_UNAVAILABLE(MarkedCode)
FIXME: Skip testing on windows temporarily due to the different escaping code mode.
Definition AST.cpp:45
TEST_F(BackgroundIndexTest, NoCrashOnErrorFile)
std::string testPath(PathRef File, llvm::sys::path::Style Style)
Definition TestFS.cpp:93
::testing::Matcher< TweakResult > editedFiles(EditedFilesMatcher M)
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//