xref: /llvm-project/clang-tools-extra/clangd/unittests/tweaks/ExtractFunctionTests.cpp (revision 9ae21b073ab48b376687ecd7fbae12e08b4ae86e)
1 //===-- ExtractFunctionTests.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 "TweakTesting.h"
10 #include "gmock/gmock.h"
11 #include "gtest/gtest.h"
12 
13 using ::testing::HasSubstr;
14 using ::testing::StartsWith;
15 
16 namespace clang {
17 namespace clangd {
18 namespace {
19 
20 TWEAK_TEST(ExtractFunction);
21 
22 TEST_F(ExtractFunctionTest, FunctionTest) {
23   Context = Function;
24 
25   // Root statements should have common parent.
26   EXPECT_EQ(apply("for(;;) [[1+2; 1+2;]]"), "unavailable");
27   // Expressions aren't extracted.
28   EXPECT_EQ(apply("int x = 0; [[x++;]]"), "unavailable");
29   // We don't support extraction from lambdas.
30   EXPECT_EQ(apply("auto lam = [](){ [[int x;]] }; "), "unavailable");
31   // Partial statements aren't extracted.
32   EXPECT_THAT(apply("int [[x = 0]];"), "unavailable");
33   // FIXME: Support hoisting.
34   EXPECT_THAT(apply(" [[int a = 5;]] a++; "), "unavailable");
35 
36   // Ensure that end of Zone and Beginning of PostZone being adjacent doesn't
37   // lead to break being included in the extraction zone.
38   EXPECT_THAT(apply("for(;;) { [[int x;]]break; }"), HasSubstr("extracted"));
39   // FIXME: ExtractFunction should be unavailable inside loop construct
40   // initializer/condition.
41   EXPECT_THAT(apply(" for([[int i = 0;]];);"), HasSubstr("extracted"));
42   // Extract certain return
43   EXPECT_THAT(apply(" if(true) [[{ return; }]] "), HasSubstr("extracted"));
44   // Don't extract uncertain return
45   EXPECT_THAT(apply(" if(true) [[if (false) return;]] "),
46               StartsWith("unavailable"));
47   EXPECT_THAT(
48       apply("#define RETURN_IF_ERROR(x) if (x) return\nRETU^RN_IF_ERROR(4);"),
49       StartsWith("unavailable"));
50 
51   FileName = "a.c";
52   EXPECT_THAT(apply(" for([[int i = 0;]];);"), HasSubstr("unavailable"));
53 }
54 
55 TEST_F(ExtractFunctionTest, FileTest) {
56   // Check all parameters are in order
57   std::string ParameterCheckInput = R"cpp(
58 struct Foo {
59   int x;
60 };
61 void f(int a) {
62   int b;
63   int *ptr = &a;
64   Foo foo;
65   [[a += foo.x + b;
66   *ptr++;]]
67 })cpp";
68   std::string ParameterCheckOutput = R"cpp(
69 struct Foo {
70   int x;
71 };
72 void extracted(int &a, int &b, int * &ptr, Foo &foo) {
73 a += foo.x + b;
74   *ptr++;
75 }
76 void f(int a) {
77   int b;
78   int *ptr = &a;
79   Foo foo;
80   extracted(a, b, ptr, foo);
81 })cpp";
82   EXPECT_EQ(apply(ParameterCheckInput), ParameterCheckOutput);
83 
84   // Check const qualifier
85   std::string ConstCheckInput = R"cpp(
86 void f(const int c) {
87   [[while(c) {}]]
88 })cpp";
89   std::string ConstCheckOutput = R"cpp(
90 void extracted(const int &c) {
91 while(c) {}
92 }
93 void f(const int c) {
94   extracted(c);
95 })cpp";
96   EXPECT_EQ(apply(ConstCheckInput), ConstCheckOutput);
97 
98   // Check const qualifier with namespace
99   std::string ConstNamespaceCheckInput = R"cpp(
100 namespace X { struct Y { int z; }; }
101 int f(const X::Y &y) {
102   [[return y.z + y.z;]]
103 })cpp";
104   std::string ConstNamespaceCheckOutput = R"cpp(
105 namespace X { struct Y { int z; }; }
106 int extracted(const X::Y &y) {
107 return y.z + y.z;
108 }
109 int f(const X::Y &y) {
110   return extracted(y);
111 })cpp";
112   EXPECT_EQ(apply(ConstNamespaceCheckInput), ConstNamespaceCheckOutput);
113 
114   // Don't extract when we need to make a function as a parameter.
115   EXPECT_THAT(apply("void f() { [[int a; f();]] }"), StartsWith("fail"));
116 
117   std::string MethodInput = R"cpp(
118     class T {
119       void f() {
120         [[int x;]]
121       }
122     };
123   )cpp";
124   std::string MethodCheckOutput = R"cpp(
125     class T {
126       void extracted() {
127 int x;
128 }
129 void f() {
130         extracted();
131       }
132     };
133   )cpp";
134   EXPECT_EQ(apply(MethodInput), MethodCheckOutput);
135 
136   std::string OutOfLineMethodInput = R"cpp(
137     class T {
138       void f();
139     };
140 
141     void T::f() {
142       [[int x;]]
143     }
144   )cpp";
145   std::string OutOfLineMethodCheckOutput = R"cpp(
146     class T {
147       void extracted();
148 void f();
149     };
150 
151     void T::extracted() {
152 int x;
153 }
154 void T::f() {
155       extracted();
156     }
157   )cpp";
158   EXPECT_EQ(apply(OutOfLineMethodInput), OutOfLineMethodCheckOutput);
159 
160   // We don't extract from templated functions for now as templates are hard
161   // to deal with.
162   std::string TemplateFailInput = R"cpp(
163     template<typename T>
164     void f() {
165       [[int x;]]
166     }
167   )cpp";
168   EXPECT_EQ(apply(TemplateFailInput), "unavailable");
169 
170   std::string MacroInput = R"cpp(
171     #define F(BODY) void f() { BODY }
172     F ([[int x = 0;]])
173   )cpp";
174   std::string MacroOutput = R"cpp(
175     #define F(BODY) void f() { BODY }
176     void extracted() {
177 int x = 0;
178 }
179 F (extracted();)
180   )cpp";
181   EXPECT_EQ(apply(MacroInput), MacroOutput);
182 
183   // Shouldn't crash.
184   EXPECT_EQ(apply("void f([[int a]]);"), "unavailable");
185   EXPECT_EQ(apply("void f(int a = [[1]]);"), "unavailable");
186   // Don't extract if we select the entire function body (CompoundStmt).
187   std::string CompoundFailInput = R"cpp(
188     void f() [[{
189       int a;
190     }]]
191   )cpp";
192   EXPECT_EQ(apply(CompoundFailInput), "unavailable");
193 
194   ExtraArgs.push_back("-std=c++14");
195   // FIXME: Expressions are currently not extracted
196   EXPECT_EQ(apply(R"cpp(
197                 void call() { [[1+1]]; }
198             )cpp"),
199             "unavailable");
200   // FIXME: Single expression statements are currently not extracted
201   EXPECT_EQ(apply(R"cpp(
202                 void call() { [[1+1;]] }
203             )cpp"),
204             "unavailable");
205 }
206 
207 TEST_F(ExtractFunctionTest, DifferentHeaderSourceTest) {
208   Header = R"cpp(
209     class SomeClass {
210       void f();
211     };
212   )cpp";
213 
214   std::string OutOfLineSource = R"cpp(
215     void SomeClass::f() {
216       [[int x;]]
217     }
218   )cpp";
219 
220   std::string OutOfLineSourceOutputCheck = R"cpp(
221     void SomeClass::extracted() {
222 int x;
223 }
224 void SomeClass::f() {
225       extracted();
226     }
227   )cpp";
228 
229   std::string HeaderOutputCheck = R"cpp(
230     class SomeClass {
231       void extracted();
232 void f();
233     };
234   )cpp";
235 
236   llvm::StringMap<std::string> EditedFiles;
237 
238   EXPECT_EQ(apply(OutOfLineSource, &EditedFiles), OutOfLineSourceOutputCheck);
239   EXPECT_EQ(EditedFiles.begin()->second, HeaderOutputCheck);
240 }
241 
242 TEST_F(ExtractFunctionTest, DifferentFilesNestedTest) {
243   Header = R"cpp(
244     class T {
245     class SomeClass {
246       void f();
247     };
248     };
249   )cpp";
250 
251   std::string NestedOutOfLineSource = R"cpp(
252     void T::SomeClass::f() {
253       [[int x;]]
254     }
255   )cpp";
256 
257   std::string NestedOutOfLineSourceOutputCheck = R"cpp(
258     void T::SomeClass::extracted() {
259 int x;
260 }
261 void T::SomeClass::f() {
262       extracted();
263     }
264   )cpp";
265 
266   std::string NestedHeaderOutputCheck = R"cpp(
267     class T {
268     class SomeClass {
269       void extracted();
270 void f();
271     };
272     };
273   )cpp";
274 
275   llvm::StringMap<std::string> EditedFiles;
276 
277   EXPECT_EQ(apply(NestedOutOfLineSource, &EditedFiles),
278             NestedOutOfLineSourceOutputCheck);
279   EXPECT_EQ(EditedFiles.begin()->second, NestedHeaderOutputCheck);
280 }
281 
282 TEST_F(ExtractFunctionTest, ConstexprDifferentHeaderSourceTest) {
283   Header = R"cpp(
284     class SomeClass {
285       constexpr void f() const;
286     };
287   )cpp";
288 
289   std::string OutOfLineSource = R"cpp(
290     constexpr void SomeClass::f() const {
291       [[int x;]]
292     }
293   )cpp";
294 
295   std::string OutOfLineSourceOutputCheck = R"cpp(
296     constexpr void SomeClass::extracted() const {
297 int x;
298 }
299 constexpr void SomeClass::f() const {
300       extracted();
301     }
302   )cpp";
303 
304   std::string HeaderOutputCheck = R"cpp(
305     class SomeClass {
306       constexpr void extracted() const;
307 constexpr void f() const;
308     };
309   )cpp";
310 
311   llvm::StringMap<std::string> EditedFiles;
312 
313   EXPECT_EQ(apply(OutOfLineSource, &EditedFiles), OutOfLineSourceOutputCheck);
314   EXPECT_NE(EditedFiles.begin(), EditedFiles.end())
315       << "The header should be edited and receives the declaration of the new "
316          "function";
317 
318   if (EditedFiles.begin() != EditedFiles.end()) {
319     EXPECT_EQ(EditedFiles.begin()->second, HeaderOutputCheck);
320   }
321 }
322 
323 TEST_F(ExtractFunctionTest, ConstevalDifferentHeaderSourceTest) {
324   ExtraArgs.push_back("--std=c++20");
325   Header = R"cpp(
326     class SomeClass {
327       consteval void f() const;
328     };
329   )cpp";
330 
331   std::string OutOfLineSource = R"cpp(
332     consteval void SomeClass::f() const {
333       [[int x;]]
334     }
335   )cpp";
336 
337   std::string OutOfLineSourceOutputCheck = R"cpp(
338     consteval void SomeClass::extracted() const {
339 int x;
340 }
341 consteval void SomeClass::f() const {
342       extracted();
343     }
344   )cpp";
345 
346   std::string HeaderOutputCheck = R"cpp(
347     class SomeClass {
348       consteval void extracted() const;
349 consteval void f() const;
350     };
351   )cpp";
352 
353   llvm::StringMap<std::string> EditedFiles;
354 
355   EXPECT_EQ(apply(OutOfLineSource, &EditedFiles), OutOfLineSourceOutputCheck);
356   EXPECT_NE(EditedFiles.begin(), EditedFiles.end())
357       << "The header should be edited and receives the declaration of the new "
358          "function";
359 
360   if (EditedFiles.begin() != EditedFiles.end()) {
361     EXPECT_EQ(EditedFiles.begin()->second, HeaderOutputCheck);
362   }
363 }
364 
365 TEST_F(ExtractFunctionTest, ConstDifferentHeaderSourceTest) {
366   Header = R"cpp(
367     class SomeClass {
368       void f() const;
369     };
370   )cpp";
371 
372   std::string OutOfLineSource = R"cpp(
373     void SomeClass::f() const {
374       [[int x;]]
375     }
376   )cpp";
377 
378   std::string OutOfLineSourceOutputCheck = R"cpp(
379     void SomeClass::extracted() const {
380 int x;
381 }
382 void SomeClass::f() const {
383       extracted();
384     }
385   )cpp";
386 
387   std::string HeaderOutputCheck = R"cpp(
388     class SomeClass {
389       void extracted() const;
390 void f() const;
391     };
392   )cpp";
393 
394   llvm::StringMap<std::string> EditedFiles;
395 
396   EXPECT_EQ(apply(OutOfLineSource, &EditedFiles), OutOfLineSourceOutputCheck);
397   EXPECT_NE(EditedFiles.begin(), EditedFiles.end())
398       << "The header should be edited and receives the declaration of the new "
399          "function";
400 
401   if (EditedFiles.begin() != EditedFiles.end()) {
402     EXPECT_EQ(EditedFiles.begin()->second, HeaderOutputCheck);
403   }
404 }
405 
406 TEST_F(ExtractFunctionTest, StaticDifferentHeaderSourceTest) {
407   Header = R"cpp(
408     class SomeClass {
409       static void f();
410     };
411   )cpp";
412 
413   std::string OutOfLineSource = R"cpp(
414     void SomeClass::f() {
415       [[int x;]]
416     }
417   )cpp";
418 
419   std::string OutOfLineSourceOutputCheck = R"cpp(
420     void SomeClass::extracted() {
421 int x;
422 }
423 void SomeClass::f() {
424       extracted();
425     }
426   )cpp";
427 
428   std::string HeaderOutputCheck = R"cpp(
429     class SomeClass {
430       static void extracted();
431 static void f();
432     };
433   )cpp";
434 
435   llvm::StringMap<std::string> EditedFiles;
436 
437   EXPECT_EQ(apply(OutOfLineSource, &EditedFiles), OutOfLineSourceOutputCheck);
438   EXPECT_NE(EditedFiles.begin(), EditedFiles.end())
439       << "The header should be edited and receives the declaration of the new "
440          "function";
441 
442   if (EditedFiles.begin() != EditedFiles.end()) {
443     EXPECT_EQ(EditedFiles.begin()->second, HeaderOutputCheck);
444   }
445 }
446 
447 TEST_F(ExtractFunctionTest, DifferentContextHeaderSourceTest) {
448   Header = R"cpp(
449     namespace ns{
450     class A {
451       class C {
452       public:
453         class RType {};
454       };
455 
456       class T {
457         class SomeClass {
458           static C::RType f();
459         };
460       };
461     };
462     } // ns
463   )cpp";
464 
465   std::string OutOfLineSource = R"cpp(
466     ns::A::C::RType ns::A::T::SomeClass::f() {
467       [[A::C::RType x;
468       return x;]]
469     }
470   )cpp";
471 
472   std::string OutOfLineSourceOutputCheck = R"cpp(
473     ns::A::C::RType ns::A::T::SomeClass::extracted() {
474 A::C::RType x;
475       return x;
476 }
477 ns::A::C::RType ns::A::T::SomeClass::f() {
478       return extracted();
479     }
480   )cpp";
481 
482   std::string HeaderOutputCheck = R"cpp(
483     namespace ns{
484     class A {
485       class C {
486       public:
487         class RType {};
488       };
489 
490       class T {
491         class SomeClass {
492           static ns::A::C::RType extracted();
493 static C::RType f();
494         };
495       };
496     };
497     } // ns
498   )cpp";
499 
500   llvm::StringMap<std::string> EditedFiles;
501 
502   EXPECT_EQ(apply(OutOfLineSource, &EditedFiles), OutOfLineSourceOutputCheck);
503   EXPECT_EQ(EditedFiles.begin()->second, HeaderOutputCheck);
504 }
505 
506 TEST_F(ExtractFunctionTest, DifferentSyntacticContextNamespace) {
507   std::string OutOfLineSource = R"cpp(
508     namespace ns {
509       void f();
510     }
511 
512     void ns::f() {
513       [[int x;]]
514     }
515   )cpp";
516 
517   std::string OutOfLineSourceOutputCheck = R"cpp(
518     namespace ns {
519       void extracted();
520 void f();
521     }
522 
523     void ns::extracted() {
524 int x;
525 }
526 void ns::f() {
527       extracted();
528     }
529   )cpp";
530 
531   EXPECT_EQ(apply(OutOfLineSource), OutOfLineSourceOutputCheck);
532 }
533 
534 TEST_F(ExtractFunctionTest, ControlFlow) {
535   Context = Function;
536   // We should be able to extract break/continue with a parent loop/switch.
537   EXPECT_THAT(apply(" [[for(;;) if(1) break;]] "), HasSubstr("extracted"));
538   EXPECT_THAT(apply(" for(;;) [[while(1) break;]] "), HasSubstr("extracted"));
539   EXPECT_THAT(apply(" [[switch(1) { break; }]]"), HasSubstr("extracted"));
540   EXPECT_THAT(apply(" [[while(1) switch(1) { continue; }]]"),
541               HasSubstr("extracted"));
542   // Don't extract break and continue without a loop/switch parent.
543   EXPECT_THAT(apply(" for(;;) [[if(1) continue;]] "), StartsWith("fail"));
544   EXPECT_THAT(apply(" while(1) [[if(1) break;]] "), StartsWith("fail"));
545   EXPECT_THAT(apply(" switch(1) { [[break;]] }"), StartsWith("fail"));
546   EXPECT_THAT(apply(" for(;;) { [[while(1) break; break;]] }"),
547               StartsWith("fail"));
548 }
549 
550 TEST_F(ExtractFunctionTest, ExistingReturnStatement) {
551   Context = File;
552   const char *Before = R"cpp(
553     bool lucky(int N);
554     int getNum(bool Superstitious, int Min, int Max) {
555       if (Superstitious) [[{
556         for (int I = Min; I <= Max; ++I)
557           if (lucky(I))
558             return I;
559         return -1;
560       }]] else {
561         return (Min + Max) / 2;
562       }
563     }
564   )cpp";
565   // FIXME: min/max should be by value.
566   // FIXME: avoid emitting redundant braces
567   const char *After = R"cpp(
568     bool lucky(int N);
569     int extracted(int &Min, int &Max) {
570 {
571         for (int I = Min; I <= Max; ++I)
572           if (lucky(I))
573             return I;
574         return -1;
575       }
576 }
577 int getNum(bool Superstitious, int Min, int Max) {
578       if (Superstitious) return extracted(Min, Max); else {
579         return (Min + Max) / 2;
580       }
581     }
582   )cpp";
583   EXPECT_EQ(apply(Before), After);
584 }
585 
586 TEST_F(ExtractFunctionTest, OverloadedOperators) {
587   Context = File;
588   std::string Before = R"cpp(struct A {
589                 int operator+(int x) { return x; }
590               };
591               A &operator<<(A &, int);
592               A &operator|(A &, int);
593 
594               A stream{};
595 
596               void foo(int, int);
597 
598               int main() {
599                 [[foo(1, 2);
600                 foo(3, 4);
601                 stream << 42;
602                 stream + 42;
603                 stream | 42;
604                 foo(1, 2);
605                 foo(3, 4);]]
606               })cpp";
607   std::string After =
608       R"cpp(struct A {
609                 int operator+(int x) { return x; }
610               };
611               A &operator<<(A &, int);
612               A &operator|(A &, int);
613 
614               A stream{};
615 
616               void foo(int, int);
617 
618               void extracted() {
619 foo(1, 2);
620                 foo(3, 4);
621                 stream << 42;
622                 stream + 42;
623                 stream | 42;
624                 foo(1, 2);
625                 foo(3, 4);
626 }
627 int main() {
628                 extracted();
629               })cpp";
630   EXPECT_EQ(apply(Before), After);
631 }
632 
633 } // namespace
634 } // namespace clangd
635 } // namespace clang
636