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(x: "-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 = 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 = 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 = 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(x: "--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 = 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 = 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 = 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 = 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 | |