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 | |
14 | namespace clang { |
15 | namespace clangd { |
16 | namespace { |
17 | |
18 | TWEAK_TEST(DefineOutline); |
19 | |
20 | TEST_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 | |
113 | TEST_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 | |
121 | TEST_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 ; |
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 | |
352 | TEST_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 ; |
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 | |
464 | TEST_F(DefineOutlineTest, QualifyReturnValue) { |
465 | FileName = "Test.hpp" ; |
466 | ExtraFiles["Test.cpp" ] = "" ; |
467 | |
468 | struct { |
469 | llvm::StringRef Test; |
470 | llvm::StringRef ; |
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 | |
514 | TEST_F(DefineOutlineTest, QualifyFunctionName) { |
515 | FileName = "Test.hpp" ; |
516 | struct { |
517 | llvm::StringRef ; |
518 | llvm::StringRef TestSource; |
519 | llvm::StringRef ; |
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 | |
579 | TEST_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 | |