xref: /llvm-project/clang-tools-extra/clangd/unittests/tweaks/ExtractFunctionTests.cpp (revision d4af86581e80ef0f7a6f4a4fff1c97260a726e71)
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   // Check const qualifier with namespace
101   std::string ConstNamespaceCheckInput = R"cpp(
102 namespace X { struct Y { int z; }; }
103 int f(const X::Y &y) {
104   [[return y.z + y.z;]]
105 })cpp";
106   std::string ConstNamespaceCheckOutput = R"cpp(
107 namespace X { struct Y { int z; }; }
108 int extracted(const X::Y &y) {
109 return y.z + y.z;
110 }
111 int f(const X::Y &y) {
112   return extracted(y);
113 })cpp";
114   EXPECT_EQ(apply(ConstNamespaceCheckInput), ConstNamespaceCheckOutput);
115 
116   // Don't extract when we need to make a function as a parameter.
117   EXPECT_THAT(apply("void f() { [[int a; f();]] }"), StartsWith("fail"));
118 
119   // We don't extract from methods for now since they may involve multi-file
120   // edits
121   std::string MethodFailInput = R"cpp(
122     class T {
123       void f() {
124         [[int x;]]
125       }
126     };
127   )cpp";
128   EXPECT_EQ(apply(MethodFailInput), "unavailable");
129 
130   // We don't extract from templated functions for now as templates are hard
131   // to deal with.
132   std::string TemplateFailInput = R"cpp(
133     template<typename T>
134     void f() {
135       [[int x;]]
136     }
137   )cpp";
138   EXPECT_EQ(apply(TemplateFailInput), "unavailable");
139 
140   std::string MacroInput = R"cpp(
141     #define F(BODY) void f() { BODY }
142     F ([[int x = 0;]])
143   )cpp";
144   std::string MacroOutput = R"cpp(
145     #define F(BODY) void f() { BODY }
146     void extracted() {
147 int x = 0;
148 }
149 F (extracted();)
150   )cpp";
151   EXPECT_EQ(apply(MacroInput), MacroOutput);
152 
153   // Shouldn't crash.
154   EXPECT_EQ(apply("void f([[int a]]);"), "unavailable");
155   // Don't extract if we select the entire function body (CompoundStmt).
156   std::string CompoundFailInput = R"cpp(
157     void f() [[{
158       int a;
159     }]]
160   )cpp";
161   EXPECT_EQ(apply(CompoundFailInput), "unavailable");
162 }
163 
164 TEST_F(ExtractFunctionTest, ControlFlow) {
165   Context = Function;
166   // We should be able to extract break/continue with a parent loop/switch.
167   EXPECT_THAT(apply(" [[for(;;) if(1) break;]] "), HasSubstr("extracted"));
168   EXPECT_THAT(apply(" for(;;) [[while(1) break;]] "), HasSubstr("extracted"));
169   EXPECT_THAT(apply(" [[switch(1) { break; }]]"), HasSubstr("extracted"));
170   EXPECT_THAT(apply(" [[while(1) switch(1) { continue; }]]"),
171               HasSubstr("extracted"));
172   // Don't extract break and continue without a loop/switch parent.
173   EXPECT_THAT(apply(" for(;;) [[if(1) continue;]] "), StartsWith("fail"));
174   EXPECT_THAT(apply(" while(1) [[if(1) break;]] "), StartsWith("fail"));
175   EXPECT_THAT(apply(" switch(1) { [[break;]] }"), StartsWith("fail"));
176   EXPECT_THAT(apply(" for(;;) { [[while(1) break; break;]] }"),
177               StartsWith("fail"));
178 }
179 
180 TEST_F(ExtractFunctionTest, ExistingReturnStatement) {
181   Context = File;
182   const char *Before = R"cpp(
183     bool lucky(int N);
184     int getNum(bool Superstitious, int Min, int Max) {
185       if (Superstitious) [[{
186         for (int I = Min; I <= Max; ++I)
187           if (lucky(I))
188             return I;
189         return -1;
190       }]] else {
191         return (Min + Max) / 2;
192       }
193     }
194   )cpp";
195   // FIXME: min/max should be by value.
196   // FIXME: avoid emitting redundant braces
197   const char *After = R"cpp(
198     bool lucky(int N);
199     int extracted(int &Min, int &Max) {
200 {
201         for (int I = Min; I <= Max; ++I)
202           if (lucky(I))
203             return I;
204         return -1;
205       }
206 }
207 int getNum(bool Superstitious, int Min, int Max) {
208       if (Superstitious) return extracted(Min, Max); else {
209         return (Min + Max) / 2;
210       }
211     }
212   )cpp";
213   EXPECT_EQ(apply(Before), After);
214 }
215 
216 } // namespace
217 } // namespace clangd
218 } // namespace clang
219