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
13using ::testing::HasSubstr;
14using ::testing::StartsWith;
15
16namespace clang {
17namespace clangd {
18namespace {
19
20TWEAK_TEST(ExtractFunction);
21
22TEST_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
55TEST_F(ExtractFunctionTest, FileTest) {
56 // Check all parameters are in order
57 std::string ParameterCheckInput = R"cpp(
58struct Foo {
59 int x;
60};
61void 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(
69struct Foo {
70 int x;
71};
72void extracted(int &a, int &b, int * &ptr, Foo &foo) {
73a += foo.x + b;
74 *ptr++;
75}
76void 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(
86void f(const int c) {
87 [[while(c) {}]]
88})cpp";
89 std::string ConstCheckOutput = R"cpp(
90void extracted(const int &c) {
91while(c) {}
92}
93void 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(
100namespace X { struct Y { int z; }; }
101int f(const X::Y &y) {
102 [[return y.z + y.z;]]
103})cpp";
104 std::string ConstNamespaceCheckOutput = R"cpp(
105namespace X { struct Y { int z; }; }
106int extracted(const X::Y &y) {
107return y.z + y.z;
108}
109int 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() {
127int x;
128}
129void 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();
148void f();
149 };
150
151 void T::extracted() {
152int x;
153}
154void 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() {
177int x = 0;
178}
179F (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
195TEST_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() {
210int x;
211}
212void SomeClass::f() {
213 extracted();
214 }
215 )cpp";
216
217 std::string HeaderOutputCheck = R"cpp(
218 class SomeClass {
219 void extracted();
220void 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
230TEST_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() {
247int x;
248}
249void T::SomeClass::f() {
250 extracted();
251 }
252 )cpp";
253
254 std::string NestedHeaderOutputCheck = R"cpp(
255 class T {
256 class SomeClass {
257 void extracted();
258void 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
270TEST_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 {
285int x;
286}
287constexpr void SomeClass::f() const {
288 extracted();
289 }
290 )cpp";
291
292 std::string HeaderOutputCheck = R"cpp(
293 class SomeClass {
294 constexpr void extracted() const;
295constexpr 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
311TEST_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 {
327int x;
328}
329consteval void SomeClass::f() const {
330 extracted();
331 }
332 )cpp";
333
334 std::string HeaderOutputCheck = R"cpp(
335 class SomeClass {
336 consteval void extracted() const;
337consteval 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
353TEST_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 {
368int x;
369}
370void SomeClass::f() const {
371 extracted();
372 }
373 )cpp";
374
375 std::string HeaderOutputCheck = R"cpp(
376 class SomeClass {
377 void extracted() const;
378void 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
394TEST_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() {
409int x;
410}
411void SomeClass::f() {
412 extracted();
413 }
414 )cpp";
415
416 std::string HeaderOutputCheck = R"cpp(
417 class SomeClass {
418 static void extracted();
419static 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
435TEST_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() {
462A::C::RType x;
463 return x;
464}
465ns::A::C::RType ns::A::T::SomeClass::f() {
466 return extracted();
467 }
468 )cpp";
469
470 std::string HeaderOutputCheck = 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();
481static 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
494TEST_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();
508void f();
509 }
510
511 void ns::extracted() {
512int x;
513}
514void ns::f() {
515 extracted();
516 }
517 )cpp";
518
519 EXPECT_EQ(apply(OutOfLineSource), OutOfLineSourceOutputCheck);
520}
521
522TEST_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
538TEST_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}
565int 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

source code of clang-tools-extra/clangd/unittests/tweaks/ExtractFunctionTests.cpp