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