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
18TWEAK_TEST(DefineOutline);
19
20TEST_F(DefineOutlineTest, TriggersOnFunctionDecl) {
21 FileName = "Test.cpp";
22 // Not available unless in a header file.
23 EXPECT_UNAVAILABLE(R"cpp(
24 [[void [[f^o^o]]() [[{
25 return;
26 }]]]])cpp");
27
28 FileName = "Test.hpp";
29 // Not available unless function name or fully body is selected.
30 EXPECT_UNAVAILABLE(R"cpp(
31 // Not a definition
32 vo^i[[d^ ^f]]^oo();
33
34 [[vo^id ]]foo[[()]] {[[
35 [[(void)(5+3);
36 return;]]
37 }]])cpp");
38
39 // Available even if there are no implementation files.
40 EXPECT_AVAILABLE(R"cpp(
41 [[void [[f^o^o]]() [[{
42 return;
43 }]]]])cpp");
44
45 // Not available for out-of-line methods.
46 EXPECT_UNAVAILABLE(R"cpp(
47 class Bar {
48 void baz();
49 };
50
51 [[void [[Bar::[[b^a^z]]]]() [[{
52 return;
53 }]]]])cpp");
54
55 // Basic check for function body and signature.
56 EXPECT_AVAILABLE(R"cpp(
57 class Bar {
58 [[void [[f^o^o^]]() [[{ return; }]]]]
59 };
60
61 void foo();
62 [[void [[f^o^o]]() [[{
63 return;
64 }]]]])cpp");
65
66 // Not available on defaulted/deleted members.
67 EXPECT_UNAVAILABLE(R"cpp(
68 class Foo {
69 Fo^o() = default;
70 F^oo(const Foo&) = delete;
71 };)cpp");
72
73 // Not available within templated classes, as it is hard to spell class name
74 // out-of-line in such cases.
75 EXPECT_UNAVAILABLE(R"cpp(
76 template <typename> struct Foo { void fo^o(){} };
77 )cpp");
78
79 // Not available on function templates and specializations, as definition must
80 // be visible to all translation units.
81 EXPECT_UNAVAILABLE(R"cpp(
82 template <typename> void fo^o() {};
83 template <> void fo^o<int>() {};
84 )cpp");
85
86 // Not available on methods of unnamed classes.
87 EXPECT_UNAVAILABLE(R"cpp(
88 struct Foo {
89 struct { void b^ar() {} } Bar;
90 };
91 )cpp");
92
93 // Not available on methods of named classes with unnamed parent in parents
94 // nesting.
95 EXPECT_UNAVAILABLE(R"cpp(
96 struct Foo {
97 struct {
98 struct Bar { void b^ar() {} };
99 } Baz;
100 };
101 )cpp");
102
103 // Not available on definitions within unnamed namespaces
104 EXPECT_UNAVAILABLE(R"cpp(
105 namespace {
106 struct Foo {
107 void f^oo() {}
108 };
109 } // namespace
110 )cpp");
111}
112
113TEST_F(DefineOutlineTest, FailsWithoutSource) {
114 FileName = "Test.hpp";
115 llvm::StringRef Test = "void fo^o() { return; }";
116 llvm::StringRef Expected =
117 "fail: Couldn't find a suitable implementation file.";
118 EXPECT_EQ(apply(Test), Expected);
119}
120
121TEST_F(DefineOutlineTest, ApplyTest) {
122 llvm::StringMap<std::string> EditedFiles;
123 ExtraFiles["Test.cpp"] = "";
124 FileName = "Test.hpp";
125
126 struct {
127 llvm::StringRef Test;
128 llvm::StringRef ExpectedHeader;
129 llvm::StringRef ExpectedSource;
130 } Cases[] = {
131 // Simple check
132 {
133 .Test: "void fo^o() { return; }",
134 .ExpectedHeader: "void foo() ;",
135 .ExpectedSource: "void foo() { return; }",
136 },
137 // Inline specifier.
138 {
139 .Test: "inline void fo^o() { return; }",
140 .ExpectedHeader: " void foo() ;",
141 .ExpectedSource: " void foo() { return; }",
142 },
143 // Default args.
144 {
145 .Test: "void fo^o(int x, int y = 5, int = 2, int (*foo)(int) = nullptr) {}",
146 .ExpectedHeader: "void foo(int x, int y = 5, int = 2, int (*foo)(int) = nullptr) ;",
147 .ExpectedSource: "void foo(int x, int y , int , int (*foo)(int) ) {}",
148 },
149 {
150 .Test: "struct Bar{Bar();}; void fo^o(Bar x = {}) {}",
151 .ExpectedHeader: "struct Bar{Bar();}; void foo(Bar x = {}) ;",
152 .ExpectedSource: "void foo(Bar x ) {}",
153 },
154 // Constructors
155 {
156 .Test: R"cpp(
157 class Foo {public: Foo(); Foo(int);};
158 class Bar {
159 Ba^r() {}
160 Bar(int x) : f1(x) {}
161 Foo f1;
162 Foo f2 = 2;
163 };)cpp",
164 .ExpectedHeader: R"cpp(
165 class Foo {public: Foo(); Foo(int);};
166 class Bar {
167 Bar() ;
168 Bar(int x) : f1(x) {}
169 Foo f1;
170 Foo f2 = 2;
171 };)cpp",
172 .ExpectedSource: "Bar::Bar() {}\n",
173 },
174 // Ctor with initializer.
175 {
176 .Test: R"cpp(
177 class Foo {public: Foo(); Foo(int);};
178 class Bar {
179 Bar() {}
180 B^ar(int x) : f1(x), f2(3) {}
181 Foo f1;
182 Foo f2 = 2;
183 };)cpp",
184 .ExpectedHeader: R"cpp(
185 class Foo {public: Foo(); Foo(int);};
186 class Bar {
187 Bar() {}
188 Bar(int x) ;
189 Foo f1;
190 Foo f2 = 2;
191 };)cpp",
192 .ExpectedSource: "Bar::Bar(int x) : f1(x), f2(3) {}\n",
193 },
194 // Ctor initializer with attribute.
195 {
196 .Test: R"cpp(
197 class Foo {
198 F^oo(int z) __attribute__((weak)) : bar(2){}
199 int bar;
200 };)cpp",
201 .ExpectedHeader: R"cpp(
202 class Foo {
203 Foo(int z) __attribute__((weak)) ;
204 int bar;
205 };)cpp",
206 .ExpectedSource: "Foo::Foo(int z) __attribute__((weak)) : bar(2){}\n",
207 },
208 // Virt specifiers.
209 {
210 .Test: R"cpp(
211 struct A {
212 virtual void f^oo() {}
213 };)cpp",
214 .ExpectedHeader: R"cpp(
215 struct A {
216 virtual void foo() ;
217 };)cpp",
218 .ExpectedSource: " void A::foo() {}\n",
219 },
220 {
221 .Test: R"cpp(
222 struct A {
223 virtual virtual void virtual f^oo() {}
224 };)cpp",
225 .ExpectedHeader: R"cpp(
226 struct A {
227 virtual virtual void virtual foo() ;
228 };)cpp",
229 .ExpectedSource: " void A::foo() {}\n",
230 },
231 {
232 .Test: R"cpp(
233 struct A {
234 virtual void foo() = 0;
235 };
236 struct B : A {
237 void fo^o() override {}
238 };)cpp",
239 .ExpectedHeader: R"cpp(
240 struct A {
241 virtual void foo() = 0;
242 };
243 struct B : A {
244 void foo() override ;
245 };)cpp",
246 .ExpectedSource: "void B::foo() {}\n",
247 },
248 {
249 .Test: R"cpp(
250 struct A {
251 virtual void foo() = 0;
252 };
253 struct B : A {
254 void fo^o() final {}
255 };)cpp",
256 .ExpectedHeader: R"cpp(
257 struct A {
258 virtual void foo() = 0;
259 };
260 struct B : A {
261 void foo() final ;
262 };)cpp",
263 .ExpectedSource: "void B::foo() {}\n",
264 },
265 {
266 .Test: R"cpp(
267 struct A {
268 virtual void foo() = 0;
269 };
270 struct B : A {
271 void fo^o() final override {}
272 };)cpp",
273 .ExpectedHeader: R"cpp(
274 struct A {
275 virtual void foo() = 0;
276 };
277 struct B : A {
278 void foo() final override ;
279 };)cpp",
280 .ExpectedSource: "void B::foo() {}\n",
281 },
282 {
283 .Test: R"cpp(
284 struct A {
285 static void fo^o() {}
286 };)cpp",
287 .ExpectedHeader: R"cpp(
288 struct A {
289 static void foo() ;
290 };)cpp",
291 .ExpectedSource: " void A::foo() {}\n",
292 },
293 {
294 .Test: R"cpp(
295 struct A {
296 static static void fo^o() {}
297 };)cpp",
298 .ExpectedHeader: R"cpp(
299 struct A {
300 static static void foo() ;
301 };)cpp",
302 .ExpectedSource: " void A::foo() {}\n",
303 },
304 {
305 .Test: R"cpp(
306 struct Foo {
307 explicit Fo^o(int) {}
308 };)cpp",
309 .ExpectedHeader: R"cpp(
310 struct Foo {
311 explicit Foo(int) ;
312 };)cpp",
313 .ExpectedSource: " Foo::Foo(int) {}\n",
314 },
315 {
316 .Test: R"cpp(
317 struct Foo {
318 explicit explicit Fo^o(int) {}
319 };)cpp",
320 .ExpectedHeader: R"cpp(
321 struct Foo {
322 explicit explicit Foo(int) ;
323 };)cpp",
324 .ExpectedSource: " Foo::Foo(int) {}\n",
325 },
326 {
327 .Test: R"cpp(
328 struct A {
329 inline void f^oo(int) {}
330 };)cpp",
331 .ExpectedHeader: R"cpp(
332 struct A {
333 void foo(int) ;
334 };)cpp",
335 .ExpectedSource: " void A::foo(int) {}\n",
336 },
337 // Destrctors
338 {
339 .Test: "class A { ~A^(){} };",
340 .ExpectedHeader: "class A { ~A(); };",
341 .ExpectedSource: "A::~A(){} ",
342 },
343 };
344 for (const auto &Case : Cases) {
345 SCOPED_TRACE(Case.Test);
346 EXPECT_EQ(apply(Case.Test, &EditedFiles), Case.ExpectedHeader);
347 EXPECT_THAT(EditedFiles, testing::ElementsAre(FileWithContents(
348 testPath("Test.cpp"), Case.ExpectedSource)));
349 }
350}
351
352TEST_F(DefineOutlineTest, HandleMacros) {
353 llvm::StringMap<std::string> EditedFiles;
354 ExtraFiles["Test.cpp"] = "";
355 FileName = "Test.hpp";
356 ExtraArgs.push_back(x: "-DVIRTUAL=virtual");
357 ExtraArgs.push_back(x: "-DOVER=override");
358
359 struct {
360 llvm::StringRef Test;
361 llvm::StringRef ExpectedHeader;
362 llvm::StringRef ExpectedSource;
363 } Cases[] = {
364 {.Test: R"cpp(
365 #define BODY { return; }
366 void f^oo()BODY)cpp",
367 .ExpectedHeader: R"cpp(
368 #define BODY { return; }
369 void foo();)cpp",
370 .ExpectedSource: "void foo()BODY"},
371
372 {.Test: R"cpp(
373 #define BODY return;
374 void f^oo(){BODY})cpp",
375 .ExpectedHeader: R"cpp(
376 #define BODY return;
377 void foo();)cpp",
378 .ExpectedSource: "void foo(){BODY}"},
379
380 {.Test: R"cpp(
381 #define TARGET void foo()
382 [[TARGET]]{ return; })cpp",
383 .ExpectedHeader: R"cpp(
384 #define TARGET void foo()
385 TARGET;)cpp",
386 .ExpectedSource: "TARGET{ return; }"},
387
388 {.Test: R"cpp(
389 #define TARGET foo
390 void [[TARGET]](){ return; })cpp",
391 .ExpectedHeader: R"cpp(
392 #define TARGET foo
393 void TARGET();)cpp",
394 .ExpectedSource: "void TARGET(){ return; }"},
395 {.Test: R"cpp(#define VIRT virtual
396 struct A {
397 VIRT void f^oo() {}
398 };)cpp",
399 .ExpectedHeader: R"cpp(#define VIRT virtual
400 struct A {
401 VIRT void foo() ;
402 };)cpp",
403 .ExpectedSource: " void A::foo() {}\n"},
404 {.Test: R"cpp(
405 struct A {
406 VIRTUAL void f^oo() {}
407 };)cpp",
408 .ExpectedHeader: R"cpp(
409 struct A {
410 VIRTUAL void foo() ;
411 };)cpp",
412 .ExpectedSource: " void A::foo() {}\n"},
413 {.Test: R"cpp(
414 struct A {
415 virtual void foo() = 0;
416 };
417 struct B : A {
418 void fo^o() OVER {}
419 };)cpp",
420 .ExpectedHeader: R"cpp(
421 struct A {
422 virtual void foo() = 0;
423 };
424 struct B : A {
425 void foo() OVER ;
426 };)cpp",
427 .ExpectedSource: "void B::foo() {}\n"},
428 {.Test: R"cpp(#define STUPID_MACRO(X) virtual
429 struct A {
430 STUPID_MACRO(sizeof sizeof int) void f^oo() {}
431 };)cpp",
432 .ExpectedHeader: R"cpp(#define STUPID_MACRO(X) virtual
433 struct A {
434 STUPID_MACRO(sizeof sizeof int) void foo() ;
435 };)cpp",
436 .ExpectedSource: " void A::foo() {}\n"},
437 {.Test: R"cpp(#define STAT static
438 struct A {
439 STAT void f^oo() {}
440 };)cpp",
441 .ExpectedHeader: R"cpp(#define STAT static
442 struct A {
443 STAT void foo() ;
444 };)cpp",
445 .ExpectedSource: " void A::foo() {}\n"},
446 {.Test: R"cpp(#define STUPID_MACRO(X) static
447 struct A {
448 STUPID_MACRO(sizeof sizeof int) void f^oo() {}
449 };)cpp",
450 .ExpectedHeader: R"cpp(#define STUPID_MACRO(X) static
451 struct A {
452 STUPID_MACRO(sizeof sizeof int) void foo() ;
453 };)cpp",
454 .ExpectedSource: " void A::foo() {}\n"},
455 };
456 for (const auto &Case : Cases) {
457 SCOPED_TRACE(Case.Test);
458 EXPECT_EQ(apply(Case.Test, &EditedFiles), Case.ExpectedHeader);
459 EXPECT_THAT(EditedFiles, testing::ElementsAre(FileWithContents(
460 testPath("Test.cpp"), Case.ExpectedSource)));
461 }
462}
463
464TEST_F(DefineOutlineTest, QualifyReturnValue) {
465 FileName = "Test.hpp";
466 ExtraFiles["Test.cpp"] = "";
467
468 struct {
469 llvm::StringRef Test;
470 llvm::StringRef ExpectedHeader;
471 llvm::StringRef ExpectedSource;
472 } Cases[] = {
473 {.Test: R"cpp(
474 namespace a { class Foo{}; }
475 using namespace a;
476 Foo fo^o() { return {}; })cpp",
477 .ExpectedHeader: R"cpp(
478 namespace a { class Foo{}; }
479 using namespace a;
480 Foo foo() ;)cpp",
481 .ExpectedSource: "a::Foo foo() { return {}; }"},
482 {.Test: R"cpp(
483 namespace a {
484 class Foo {
485 class Bar {};
486 Bar fo^o() { return {}; }
487 };
488 })cpp",
489 .ExpectedHeader: R"cpp(
490 namespace a {
491 class Foo {
492 class Bar {};
493 Bar foo() ;
494 };
495 })cpp",
496 .ExpectedSource: "a::Foo::Bar a::Foo::foo() { return {}; }\n"},
497 {.Test: R"cpp(
498 class Foo {};
499 Foo fo^o() { return {}; })cpp",
500 .ExpectedHeader: R"cpp(
501 class Foo {};
502 Foo foo() ;)cpp",
503 .ExpectedSource: "Foo foo() { return {}; }"},
504 };
505 llvm::StringMap<std::string> EditedFiles;
506 for (auto &Case : Cases) {
507 apply(MarkedCode: Case.Test, EditedFiles: &EditedFiles);
508 EXPECT_EQ(apply(Case.Test, &EditedFiles), Case.ExpectedHeader);
509 EXPECT_THAT(EditedFiles, testing::ElementsAre(FileWithContents(
510 testPath("Test.cpp"), Case.ExpectedSource)));
511 }
512}
513
514TEST_F(DefineOutlineTest, QualifyFunctionName) {
515 FileName = "Test.hpp";
516 struct {
517 llvm::StringRef TestHeader;
518 llvm::StringRef TestSource;
519 llvm::StringRef ExpectedHeader;
520 llvm::StringRef ExpectedSource;
521 } Cases[] = {
522 {
523 .TestHeader: R"cpp(
524 namespace a {
525 namespace b {
526 class Foo {
527 void fo^o() {}
528 };
529 }
530 })cpp",
531 .TestSource: "",
532 .ExpectedHeader: R"cpp(
533 namespace a {
534 namespace b {
535 class Foo {
536 void foo() ;
537 };
538 }
539 })cpp",
540 .ExpectedSource: "void a::b::Foo::foo() {}\n",
541 },
542 {
543 .TestHeader: "namespace a { namespace b { void f^oo() {} } }",
544 .TestSource: "namespace a{}",
545 .ExpectedHeader: "namespace a { namespace b { void foo() ; } }",
546 .ExpectedSource: "namespace a{void b::foo() {} }",
547 },
548 {
549 .TestHeader: "namespace a { namespace b { void f^oo() {} } }",
550 .TestSource: "using namespace a;",
551 .ExpectedHeader: "namespace a { namespace b { void foo() ; } }",
552 // FIXME: Take using namespace directives in the source file into
553 // account. This can be spelled as b::foo instead.
554 .ExpectedSource: "using namespace a;void a::b::foo() {} ",
555 },
556 {
557 .TestHeader: "namespace a { class A { ~A^(){} }; }",
558 .TestSource: "",
559 .ExpectedHeader: "namespace a { class A { ~A(); }; }",
560 .ExpectedSource: "a::A::~A(){} ",
561 },
562 {
563 .TestHeader: "namespace a { class A { ~A^(){} }; }",
564 .TestSource: "namespace a{}",
565 .ExpectedHeader: "namespace a { class A { ~A(); }; }",
566 .ExpectedSource: "namespace a{A::~A(){} }",
567 },
568 };
569 llvm::StringMap<std::string> EditedFiles;
570 for (auto &Case : Cases) {
571 ExtraFiles["Test.cpp"] = std::string(Case.TestSource);
572 EXPECT_EQ(apply(Case.TestHeader, &EditedFiles), Case.ExpectedHeader);
573 EXPECT_THAT(EditedFiles, testing::ElementsAre(FileWithContents(
574 testPath("Test.cpp"), Case.ExpectedSource)))
575 << Case.TestHeader;
576 }
577}
578
579TEST_F(DefineOutlineTest, FailsMacroSpecifier) {
580 FileName = "Test.hpp";
581 ExtraFiles["Test.cpp"] = "";
582 ExtraArgs.push_back(x: "-DFINALOVER=final override");
583
584 std::pair<StringRef, StringRef> Cases[] = {
585 {
586 R"cpp(
587 #define VIRT virtual void
588 struct A {
589 VIRT fo^o() {}
590 };)cpp",
591 "fail: define outline: couldn't remove `virtual` keyword."},
592 {
593 R"cpp(
594 #define OVERFINAL final override
595 struct A {
596 virtual void foo() {}
597 };
598 struct B : A {
599 void fo^o() OVERFINAL {}
600 };)cpp",
601 "fail: define outline: Can't move out of line as function has a "
602 "macro `override` specifier.\ndefine outline: Can't move out of line "
603 "as function has a macro `final` specifier."},
604 {
605 R"cpp(
606 struct A {
607 virtual void foo() {}
608 };
609 struct B : A {
610 void fo^o() FINALOVER {}
611 };)cpp",
612 "fail: define outline: Can't move out of line as function has a "
613 "macro `override` specifier.\ndefine outline: Can't move out of line "
614 "as function has a macro `final` specifier."},
615 };
616 for (const auto &Case : Cases) {
617 EXPECT_EQ(apply(Case.first), Case.second);
618 }
619}
620
621} // namespace
622} // namespace clangd
623} // namespace clang
624

source code of clang-tools-extra/clangd/unittests/tweaks/DefineOutlineTests.cpp