xref: /llvm-project/clang-tools-extra/clangd/unittests/tweaks/ExtractFunctionTests.cpp (revision a0e4ba4b4607267d96c3ab8bc1c38f5a09830692)
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   // Don't extract if we select the entire function body (CompoundStmt).
186   std::string CompoundFailInput = R"cpp(
187     void f() [[{
188       int a;
189     }]]
190   )cpp";
191   EXPECT_EQ(apply(CompoundFailInput), "unavailable");
192 }
193 
194 TEST_F(ExtractFunctionTest, DifferentHeaderSourceTest) {
195   Header = R"cpp(
196     class SomeClass {
197       void f();
198     };
199   )cpp";
200 
201   std::string OutOfLineSource = R"cpp(
202     void SomeClass::f() {
203       [[int x;]]
204     }
205   )cpp";
206 
207   std::string OutOfLineSourceOutputCheck = R"cpp(
208     void SomeClass::extracted() {
209 int x;
210 }
211 void SomeClass::f() {
212       extracted();
213     }
214   )cpp";
215 
216   std::string HeaderOutputCheck = R"cpp(
217     class SomeClass {
218       void extracted();
219 void f();
220     };
221   )cpp";
222 
223   llvm::StringMap<std::string> EditedFiles;
224 
225   EXPECT_EQ(apply(OutOfLineSource, &EditedFiles), OutOfLineSourceOutputCheck);
226   EXPECT_EQ(EditedFiles.begin()->second, HeaderOutputCheck);
227 }
228 
229 TEST_F(ExtractFunctionTest, DifferentFilesNestedTest) {
230   Header = R"cpp(
231     class T {
232     class SomeClass {
233       void f();
234     };
235     };
236   )cpp";
237 
238   std::string NestedOutOfLineSource = R"cpp(
239     void T::SomeClass::f() {
240       [[int x;]]
241     }
242   )cpp";
243 
244   std::string NestedOutOfLineSourceOutputCheck = R"cpp(
245     void T::SomeClass::extracted() {
246 int x;
247 }
248 void T::SomeClass::f() {
249       extracted();
250     }
251   )cpp";
252 
253   std::string NestedHeaderOutputCheck = R"cpp(
254     class T {
255     class SomeClass {
256       void extracted();
257 void f();
258     };
259     };
260   )cpp";
261 
262   llvm::StringMap<std::string> EditedFiles;
263 
264   EXPECT_EQ(apply(NestedOutOfLineSource, &EditedFiles),
265             NestedOutOfLineSourceOutputCheck);
266   EXPECT_EQ(EditedFiles.begin()->second, NestedHeaderOutputCheck);
267 }
268 
269 TEST_F(ExtractFunctionTest, ConstexprDifferentHeaderSourceTest) {
270   Header = R"cpp(
271     class SomeClass {
272       constexpr void f() const;
273     };
274   )cpp";
275 
276   std::string OutOfLineSource = R"cpp(
277     constexpr void SomeClass::f() const {
278       [[int x;]]
279     }
280   )cpp";
281 
282   std::string OutOfLineSourceOutputCheck = R"cpp(
283     constexpr void SomeClass::extracted() const {
284 int x;
285 }
286 constexpr void SomeClass::f() const {
287       extracted();
288     }
289   )cpp";
290 
291   std::string HeaderOutputCheck = R"cpp(
292     class SomeClass {
293       constexpr void extracted() const;
294 constexpr void f() const;
295     };
296   )cpp";
297 
298   llvm::StringMap<std::string> EditedFiles;
299 
300   EXPECT_EQ(apply(OutOfLineSource, &EditedFiles), OutOfLineSourceOutputCheck);
301   EXPECT_NE(EditedFiles.begin(), EditedFiles.end())
302       << "The header should be edited and receives the declaration of the new "
303          "function";
304 
305   if (EditedFiles.begin() != EditedFiles.end()) {
306     EXPECT_EQ(EditedFiles.begin()->second, HeaderOutputCheck);
307   }
308 }
309 
310 TEST_F(ExtractFunctionTest, ConstevalDifferentHeaderSourceTest) {
311   ExtraArgs.push_back("--std=c++20");
312   Header = R"cpp(
313     class SomeClass {
314       consteval void f() const;
315     };
316   )cpp";
317 
318   std::string OutOfLineSource = R"cpp(
319     consteval void SomeClass::f() const {
320       [[int x;]]
321     }
322   )cpp";
323 
324   std::string OutOfLineSourceOutputCheck = R"cpp(
325     consteval void SomeClass::extracted() const {
326 int x;
327 }
328 consteval void SomeClass::f() const {
329       extracted();
330     }
331   )cpp";
332 
333   std::string HeaderOutputCheck = R"cpp(
334     class SomeClass {
335       consteval void extracted() const;
336 consteval void f() const;
337     };
338   )cpp";
339 
340   llvm::StringMap<std::string> EditedFiles;
341 
342   EXPECT_EQ(apply(OutOfLineSource, &EditedFiles), OutOfLineSourceOutputCheck);
343   EXPECT_NE(EditedFiles.begin(), EditedFiles.end())
344       << "The header should be edited and receives the declaration of the new "
345          "function";
346 
347   if (EditedFiles.begin() != EditedFiles.end()) {
348     EXPECT_EQ(EditedFiles.begin()->second, HeaderOutputCheck);
349   }
350 }
351 
352 TEST_F(ExtractFunctionTest, ConstDifferentHeaderSourceTest) {
353   Header = R"cpp(
354     class SomeClass {
355       void f() const;
356     };
357   )cpp";
358 
359   std::string OutOfLineSource = R"cpp(
360     void SomeClass::f() const {
361       [[int x;]]
362     }
363   )cpp";
364 
365   std::string OutOfLineSourceOutputCheck = R"cpp(
366     void SomeClass::extracted() const {
367 int x;
368 }
369 void SomeClass::f() const {
370       extracted();
371     }
372   )cpp";
373 
374   std::string HeaderOutputCheck = R"cpp(
375     class SomeClass {
376       void extracted() const;
377 void f() const;
378     };
379   )cpp";
380 
381   llvm::StringMap<std::string> EditedFiles;
382 
383   EXPECT_EQ(apply(OutOfLineSource, &EditedFiles), OutOfLineSourceOutputCheck);
384   EXPECT_NE(EditedFiles.begin(), EditedFiles.end())
385       << "The header should be edited and receives the declaration of the new "
386          "function";
387 
388   if (EditedFiles.begin() != EditedFiles.end()) {
389     EXPECT_EQ(EditedFiles.begin()->second, HeaderOutputCheck);
390   }
391 }
392 
393 TEST_F(ExtractFunctionTest, StaticDifferentHeaderSourceTest) {
394   Header = R"cpp(
395     class SomeClass {
396       static void f();
397     };
398   )cpp";
399 
400   std::string OutOfLineSource = R"cpp(
401     void SomeClass::f() {
402       [[int x;]]
403     }
404   )cpp";
405 
406   std::string OutOfLineSourceOutputCheck = R"cpp(
407     void SomeClass::extracted() {
408 int x;
409 }
410 void SomeClass::f() {
411       extracted();
412     }
413   )cpp";
414 
415   std::string HeaderOutputCheck = R"cpp(
416     class SomeClass {
417       static void extracted();
418 static void f();
419     };
420   )cpp";
421 
422   llvm::StringMap<std::string> EditedFiles;
423 
424   EXPECT_EQ(apply(OutOfLineSource, &EditedFiles), OutOfLineSourceOutputCheck);
425   EXPECT_NE(EditedFiles.begin(), EditedFiles.end())
426       << "The header should be edited and receives the declaration of the new "
427          "function";
428 
429   if (EditedFiles.begin() != EditedFiles.end()) {
430     EXPECT_EQ(EditedFiles.begin()->second, HeaderOutputCheck);
431   }
432 }
433 
434 TEST_F(ExtractFunctionTest, DifferentContextHeaderSourceTest) {
435   Header = R"cpp(
436     namespace ns{
437     class A {
438       class C {
439       public:
440         class RType {};
441       };
442 
443       class T {
444         class SomeClass {
445           static C::RType f();
446         };
447       };
448     };
449     } // ns
450   )cpp";
451 
452   std::string OutOfLineSource = R"cpp(
453     ns::A::C::RType ns::A::T::SomeClass::f() {
454       [[A::C::RType x;
455       return x;]]
456     }
457   )cpp";
458 
459   std::string OutOfLineSourceOutputCheck = R"cpp(
460     ns::A::C::RType ns::A::T::SomeClass::extracted() {
461 A::C::RType x;
462       return x;
463 }
464 ns::A::C::RType ns::A::T::SomeClass::f() {
465       return extracted();
466     }
467   )cpp";
468 
469   std::string HeaderOutputCheck = R"cpp(
470     namespace ns{
471     class A {
472       class C {
473       public:
474         class RType {};
475       };
476 
477       class T {
478         class SomeClass {
479           static ns::A::C::RType extracted();
480 static C::RType f();
481         };
482       };
483     };
484     } // ns
485   )cpp";
486 
487   llvm::StringMap<std::string> EditedFiles;
488 
489   EXPECT_EQ(apply(OutOfLineSource, &EditedFiles), OutOfLineSourceOutputCheck);
490   EXPECT_EQ(EditedFiles.begin()->second, HeaderOutputCheck);
491 }
492 
493 TEST_F(ExtractFunctionTest, DifferentSyntacticContextNamespace) {
494   std::string OutOfLineSource = R"cpp(
495     namespace ns {
496       void f();
497     }
498 
499     void ns::f() {
500       [[int x;]]
501     }
502   )cpp";
503 
504   std::string OutOfLineSourceOutputCheck = R"cpp(
505     namespace ns {
506       void extracted();
507 void f();
508     }
509 
510     void ns::extracted() {
511 int x;
512 }
513 void ns::f() {
514       extracted();
515     }
516   )cpp";
517 
518   EXPECT_EQ(apply(OutOfLineSource), OutOfLineSourceOutputCheck);
519 }
520 
521 TEST_F(ExtractFunctionTest, ControlFlow) {
522   Context = Function;
523   // We should be able to extract break/continue with a parent loop/switch.
524   EXPECT_THAT(apply(" [[for(;;) if(1) break;]] "), HasSubstr("extracted"));
525   EXPECT_THAT(apply(" for(;;) [[while(1) break;]] "), HasSubstr("extracted"));
526   EXPECT_THAT(apply(" [[switch(1) { break; }]]"), HasSubstr("extracted"));
527   EXPECT_THAT(apply(" [[while(1) switch(1) { continue; }]]"),
528               HasSubstr("extracted"));
529   // Don't extract break and continue without a loop/switch parent.
530   EXPECT_THAT(apply(" for(;;) [[if(1) continue;]] "), StartsWith("fail"));
531   EXPECT_THAT(apply(" while(1) [[if(1) break;]] "), StartsWith("fail"));
532   EXPECT_THAT(apply(" switch(1) { [[break;]] }"), StartsWith("fail"));
533   EXPECT_THAT(apply(" for(;;) { [[while(1) break; break;]] }"),
534               StartsWith("fail"));
535 }
536 
537 TEST_F(ExtractFunctionTest, ExistingReturnStatement) {
538   Context = File;
539   const char *Before = R"cpp(
540     bool lucky(int N);
541     int getNum(bool Superstitious, int Min, int Max) {
542       if (Superstitious) [[{
543         for (int I = Min; I <= Max; ++I)
544           if (lucky(I))
545             return I;
546         return -1;
547       }]] else {
548         return (Min + Max) / 2;
549       }
550     }
551   )cpp";
552   // FIXME: min/max should be by value.
553   // FIXME: avoid emitting redundant braces
554   const char *After = R"cpp(
555     bool lucky(int N);
556     int extracted(int &Min, int &Max) {
557 {
558         for (int I = Min; I <= Max; ++I)
559           if (lucky(I))
560             return I;
561         return -1;
562       }
563 }
564 int getNum(bool Superstitious, int Min, int Max) {
565       if (Superstitious) return extracted(Min, Max); else {
566         return (Min + Max) / 2;
567       }
568     }
569   )cpp";
570   EXPECT_EQ(apply(Before), After);
571 }
572 
573 } // namespace
574 } // namespace clangd
575 } // namespace clang
576