xref: /llvm-project/clang-tools-extra/clangd/unittests/tweaks/ExtractFunctionTests.cpp (revision 5934a79196b937806d8260aab7ae16e74219fad9)
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 "TestTU.h"
10 #include "TweakTesting.h"
11 #include "gmock/gmock-matchers.h"
12 #include "gmock/gmock.h"
13 #include "gtest/gtest.h"
14 
15 using ::testing::HasSubstr;
16 using ::testing::StartsWith;
17 
18 namespace clang {
19 namespace clangd {
20 namespace {
21 
22 TWEAK_TEST(ExtractFunction);
23 
24 TEST_F(ExtractFunctionTest, FunctionTest) {
25   Context = Function;
26 
27   // Root statements should have common parent.
28   EXPECT_EQ(apply("for(;;) [[1+2; 1+2;]]"), "unavailable");
29   // Expressions aren't extracted.
30   EXPECT_EQ(apply("int x = 0; [[x++;]]"), "unavailable");
31   // We don't support extraction from lambdas.
32   EXPECT_EQ(apply("auto lam = [](){ [[int x;]] }; "), "unavailable");
33   // Partial statements aren't extracted.
34   EXPECT_THAT(apply("int [[x = 0]];"), "unavailable");
35   // FIXME: Support hoisting.
36   EXPECT_THAT(apply(" [[int a = 5;]] a++; "), "unavailable");
37 
38   // Ensure that end of Zone and Beginning of PostZone being adjacent doesn't
39   // lead to break being included in the extraction zone.
40   EXPECT_THAT(apply("for(;;) { [[int x;]]break; }"), HasSubstr("extracted"));
41   // FIXME: ExtractFunction should be unavailable inside loop construct
42   // initializer/condition.
43   EXPECT_THAT(apply(" for([[int i = 0;]];);"), HasSubstr("extracted"));
44   // Extract certain return
45   EXPECT_THAT(apply(" if(true) [[{ return; }]] "), HasSubstr("extracted"));
46   // Don't extract uncertain return
47   EXPECT_THAT(apply(" if(true) [[if (false) return;]] "),
48               StartsWith("unavailable"));
49   EXPECT_THAT(
50       apply("#define RETURN_IF_ERROR(x) if (x) return\nRETU^RN_IF_ERROR(4);"),
51       StartsWith("unavailable"));
52 
53   FileName = "a.c";
54   EXPECT_THAT(apply(" for([[int i = 0;]];);"), HasSubstr("unavailable"));
55 }
56 
57 TEST_F(ExtractFunctionTest, FileTest) {
58   // Check all parameters are in order
59   std::string ParameterCheckInput = R"cpp(
60 struct Foo {
61   int x;
62 };
63 void f(int a) {
64   int b;
65   int *ptr = &a;
66   Foo foo;
67   [[a += foo.x + b;
68   *ptr++;]]
69 })cpp";
70   std::string ParameterCheckOutput = R"cpp(
71 struct Foo {
72   int x;
73 };
74 void extracted(int &a, int &b, int * &ptr, Foo &foo) {
75 a += foo.x + b;
76   *ptr++;
77 }
78 void f(int a) {
79   int b;
80   int *ptr = &a;
81   Foo foo;
82   extracted(a, b, ptr, foo);
83 })cpp";
84   EXPECT_EQ(apply(ParameterCheckInput), ParameterCheckOutput);
85 
86   // Check const qualifier
87   std::string ConstCheckInput = R"cpp(
88 void f(const int c) {
89   [[while(c) {}]]
90 })cpp";
91   std::string ConstCheckOutput = R"cpp(
92 void extracted(const int &c) {
93 while(c) {}
94 }
95 void f(const int c) {
96   extracted(c);
97 })cpp";
98   EXPECT_EQ(apply(ConstCheckInput), ConstCheckOutput);
99 
100   // Don't extract when we need to make a function as a parameter.
101   EXPECT_THAT(apply("void f() { [[int a; f();]] }"), StartsWith("fail"));
102 
103   // We don't extract from methods for now since they may involve multi-file
104   // edits
105   std::string MethodFailInput = R"cpp(
106     class T {
107       void f() {
108         [[int x;]]
109       }
110     };
111   )cpp";
112   EXPECT_EQ(apply(MethodFailInput), "unavailable");
113 
114   // We don't extract from templated functions for now as templates are hard
115   // to deal with.
116   std::string TemplateFailInput = R"cpp(
117     template<typename T>
118     void f() {
119       [[int x;]]
120     }
121   )cpp";
122   EXPECT_EQ(apply(TemplateFailInput), "unavailable");
123 
124   std::string MacroInput = R"cpp(
125     #define F(BODY) void f() { BODY }
126     F ([[int x = 0;]])
127   )cpp";
128   std::string MacroOutput = R"cpp(
129     #define F(BODY) void f() { BODY }
130     void extracted() {
131 int x = 0;
132 }
133 F (extracted();)
134   )cpp";
135   EXPECT_EQ(apply(MacroInput), MacroOutput);
136 
137   // Shouldn't crash.
138   EXPECT_EQ(apply("void f([[int a]]);"), "unavailable");
139   // Don't extract if we select the entire function body (CompoundStmt).
140   std::string CompoundFailInput = R"cpp(
141     void f() [[{
142       int a;
143     }]]
144   )cpp";
145   EXPECT_EQ(apply(CompoundFailInput), "unavailable");
146 }
147 
148 TEST_F(ExtractFunctionTest, ControlFlow) {
149   Context = Function;
150   // We should be able to extract break/continue with a parent loop/switch.
151   EXPECT_THAT(apply(" [[for(;;) if(1) break;]] "), HasSubstr("extracted"));
152   EXPECT_THAT(apply(" for(;;) [[while(1) break;]] "), HasSubstr("extracted"));
153   EXPECT_THAT(apply(" [[switch(1) { break; }]]"), HasSubstr("extracted"));
154   EXPECT_THAT(apply(" [[while(1) switch(1) { continue; }]]"),
155               HasSubstr("extracted"));
156   // Don't extract break and continue without a loop/switch parent.
157   EXPECT_THAT(apply(" for(;;) [[if(1) continue;]] "), StartsWith("fail"));
158   EXPECT_THAT(apply(" while(1) [[if(1) break;]] "), StartsWith("fail"));
159   EXPECT_THAT(apply(" switch(1) { [[break;]] }"), StartsWith("fail"));
160   EXPECT_THAT(apply(" for(;;) { [[while(1) break; break;]] }"),
161               StartsWith("fail"));
162 }
163 
164 TEST_F(ExtractFunctionTest, ExistingReturnStatement) {
165   Context = File;
166   const char *Before = R"cpp(
167     bool lucky(int N);
168     int getNum(bool Superstitious, int Min, int Max) {
169       if (Superstitious) [[{
170         for (int I = Min; I <= Max; ++I)
171           if (lucky(I))
172             return I;
173         return -1;
174       }]] else {
175         return (Min + Max) / 2;
176       }
177     }
178   )cpp";
179   // FIXME: min/max should be by value.
180   // FIXME: avoid emitting redundant braces
181   const char *After = R"cpp(
182     bool lucky(int N);
183     int extracted(int &Min, int &Max) {
184 {
185         for (int I = Min; I <= Max; ++I)
186           if (lucky(I))
187             return I;
188         return -1;
189       }
190 }
191 int getNum(bool Superstitious, int Min, int Max) {
192       if (Superstitious) return extracted(Min, Max); else {
193         return (Min + Max) / 2;
194       }
195     }
196   )cpp";
197   EXPECT_EQ(apply(Before), After);
198 }
199 
200 } // namespace
201 } // namespace clangd
202 } // namespace clang
203