219 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			219 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			C++
		
	
	
	
| //===-- ExtractFunctionTests.cpp --------------------------------*- C++ -*-===//
 | |
| //
 | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 | |
| // See https://llvm.org/LICENSE.txt for license information.
 | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 | |
| //
 | |
| //===----------------------------------------------------------------------===//
 | |
| 
 | |
| #include "TestTU.h"
 | |
| #include "TweakTesting.h"
 | |
| #include "gmock/gmock-matchers.h"
 | |
| #include "gmock/gmock.h"
 | |
| #include "gtest/gtest.h"
 | |
| 
 | |
| using ::testing::HasSubstr;
 | |
| using ::testing::StartsWith;
 | |
| 
 | |
| namespace clang {
 | |
| namespace clangd {
 | |
| namespace {
 | |
| 
 | |
| TWEAK_TEST(ExtractFunction);
 | |
| 
 | |
| TEST_F(ExtractFunctionTest, FunctionTest) {
 | |
|   Context = Function;
 | |
| 
 | |
|   // Root statements should have common parent.
 | |
|   EXPECT_EQ(apply("for(;;) [[1+2; 1+2;]]"), "unavailable");
 | |
|   // Expressions aren't extracted.
 | |
|   EXPECT_EQ(apply("int x = 0; [[x++;]]"), "unavailable");
 | |
|   // We don't support extraction from lambdas.
 | |
|   EXPECT_EQ(apply("auto lam = [](){ [[int x;]] }; "), "unavailable");
 | |
|   // Partial statements aren't extracted.
 | |
|   EXPECT_THAT(apply("int [[x = 0]];"), "unavailable");
 | |
|   // FIXME: Support hoisting.
 | |
|   EXPECT_THAT(apply(" [[int a = 5;]] a++; "), "unavailable");
 | |
| 
 | |
|   // Ensure that end of Zone and Beginning of PostZone being adjacent doesn't
 | |
|   // lead to break being included in the extraction zone.
 | |
|   EXPECT_THAT(apply("for(;;) { [[int x;]]break; }"), HasSubstr("extracted"));
 | |
|   // FIXME: ExtractFunction should be unavailable inside loop construct
 | |
|   // initializer/condition.
 | |
|   EXPECT_THAT(apply(" for([[int i = 0;]];);"), HasSubstr("extracted"));
 | |
|   // Extract certain return
 | |
|   EXPECT_THAT(apply(" if(true) [[{ return; }]] "), HasSubstr("extracted"));
 | |
|   // Don't extract uncertain return
 | |
|   EXPECT_THAT(apply(" if(true) [[if (false) return;]] "),
 | |
|               StartsWith("unavailable"));
 | |
|   EXPECT_THAT(
 | |
|       apply("#define RETURN_IF_ERROR(x) if (x) return\nRETU^RN_IF_ERROR(4);"),
 | |
|       StartsWith("unavailable"));
 | |
| 
 | |
|   FileName = "a.c";
 | |
|   EXPECT_THAT(apply(" for([[int i = 0;]];);"), HasSubstr("unavailable"));
 | |
| }
 | |
| 
 | |
| TEST_F(ExtractFunctionTest, FileTest) {
 | |
|   // Check all parameters are in order
 | |
|   std::string ParameterCheckInput = R"cpp(
 | |
| struct Foo {
 | |
|   int x;
 | |
| };
 | |
| void f(int a) {
 | |
|   int b;
 | |
|   int *ptr = &a;
 | |
|   Foo foo;
 | |
|   [[a += foo.x + b;
 | |
|   *ptr++;]]
 | |
| })cpp";
 | |
|   std::string ParameterCheckOutput = R"cpp(
 | |
| struct Foo {
 | |
|   int x;
 | |
| };
 | |
| void extracted(int &a, int &b, int * &ptr, Foo &foo) {
 | |
| a += foo.x + b;
 | |
|   *ptr++;
 | |
| }
 | |
| void f(int a) {
 | |
|   int b;
 | |
|   int *ptr = &a;
 | |
|   Foo foo;
 | |
|   extracted(a, b, ptr, foo);
 | |
| })cpp";
 | |
|   EXPECT_EQ(apply(ParameterCheckInput), ParameterCheckOutput);
 | |
| 
 | |
|   // Check const qualifier
 | |
|   std::string ConstCheckInput = R"cpp(
 | |
| void f(const int c) {
 | |
|   [[while(c) {}]]
 | |
| })cpp";
 | |
|   std::string ConstCheckOutput = R"cpp(
 | |
| void extracted(const int &c) {
 | |
| while(c) {}
 | |
| }
 | |
| void f(const int c) {
 | |
|   extracted(c);
 | |
| })cpp";
 | |
|   EXPECT_EQ(apply(ConstCheckInput), ConstCheckOutput);
 | |
| 
 | |
|   // Check const qualifier with namespace
 | |
|   std::string ConstNamespaceCheckInput = R"cpp(
 | |
| namespace X { struct Y { int z; }; }
 | |
| int f(const X::Y &y) {
 | |
|   [[return y.z + y.z;]]
 | |
| })cpp";
 | |
|   std::string ConstNamespaceCheckOutput = R"cpp(
 | |
| namespace X { struct Y { int z; }; }
 | |
| int extracted(const X::Y &y) {
 | |
| return y.z + y.z;
 | |
| }
 | |
| int f(const X::Y &y) {
 | |
|   return extracted(y);
 | |
| })cpp";
 | |
|   EXPECT_EQ(apply(ConstNamespaceCheckInput), ConstNamespaceCheckOutput);
 | |
| 
 | |
|   // Don't extract when we need to make a function as a parameter.
 | |
|   EXPECT_THAT(apply("void f() { [[int a; f();]] }"), StartsWith("fail"));
 | |
| 
 | |
|   // We don't extract from methods for now since they may involve multi-file
 | |
|   // edits
 | |
|   std::string MethodFailInput = R"cpp(
 | |
|     class T {
 | |
|       void f() {
 | |
|         [[int x;]]
 | |
|       }
 | |
|     };
 | |
|   )cpp";
 | |
|   EXPECT_EQ(apply(MethodFailInput), "unavailable");
 | |
| 
 | |
|   // We don't extract from templated functions for now as templates are hard
 | |
|   // to deal with.
 | |
|   std::string TemplateFailInput = R"cpp(
 | |
|     template<typename T>
 | |
|     void f() {
 | |
|       [[int x;]]
 | |
|     }
 | |
|   )cpp";
 | |
|   EXPECT_EQ(apply(TemplateFailInput), "unavailable");
 | |
| 
 | |
|   std::string MacroInput = R"cpp(
 | |
|     #define F(BODY) void f() { BODY }
 | |
|     F ([[int x = 0;]])
 | |
|   )cpp";
 | |
|   std::string MacroOutput = R"cpp(
 | |
|     #define F(BODY) void f() { BODY }
 | |
|     void extracted() {
 | |
| int x = 0;
 | |
| }
 | |
| F (extracted();)
 | |
|   )cpp";
 | |
|   EXPECT_EQ(apply(MacroInput), MacroOutput);
 | |
| 
 | |
|   // Shouldn't crash.
 | |
|   EXPECT_EQ(apply("void f([[int a]]);"), "unavailable");
 | |
|   // Don't extract if we select the entire function body (CompoundStmt).
 | |
|   std::string CompoundFailInput = R"cpp(
 | |
|     void f() [[{
 | |
|       int a;
 | |
|     }]]
 | |
|   )cpp";
 | |
|   EXPECT_EQ(apply(CompoundFailInput), "unavailable");
 | |
| }
 | |
| 
 | |
| TEST_F(ExtractFunctionTest, ControlFlow) {
 | |
|   Context = Function;
 | |
|   // We should be able to extract break/continue with a parent loop/switch.
 | |
|   EXPECT_THAT(apply(" [[for(;;) if(1) break;]] "), HasSubstr("extracted"));
 | |
|   EXPECT_THAT(apply(" for(;;) [[while(1) break;]] "), HasSubstr("extracted"));
 | |
|   EXPECT_THAT(apply(" [[switch(1) { break; }]]"), HasSubstr("extracted"));
 | |
|   EXPECT_THAT(apply(" [[while(1) switch(1) { continue; }]]"),
 | |
|               HasSubstr("extracted"));
 | |
|   // Don't extract break and continue without a loop/switch parent.
 | |
|   EXPECT_THAT(apply(" for(;;) [[if(1) continue;]] "), StartsWith("fail"));
 | |
|   EXPECT_THAT(apply(" while(1) [[if(1) break;]] "), StartsWith("fail"));
 | |
|   EXPECT_THAT(apply(" switch(1) { [[break;]] }"), StartsWith("fail"));
 | |
|   EXPECT_THAT(apply(" for(;;) { [[while(1) break; break;]] }"),
 | |
|               StartsWith("fail"));
 | |
| }
 | |
| 
 | |
| TEST_F(ExtractFunctionTest, ExistingReturnStatement) {
 | |
|   Context = File;
 | |
|   const char *Before = R"cpp(
 | |
|     bool lucky(int N);
 | |
|     int getNum(bool Superstitious, int Min, int Max) {
 | |
|       if (Superstitious) [[{
 | |
|         for (int I = Min; I <= Max; ++I)
 | |
|           if (lucky(I))
 | |
|             return I;
 | |
|         return -1;
 | |
|       }]] else {
 | |
|         return (Min + Max) / 2;
 | |
|       }
 | |
|     }
 | |
|   )cpp";
 | |
|   // FIXME: min/max should be by value.
 | |
|   // FIXME: avoid emitting redundant braces
 | |
|   const char *After = R"cpp(
 | |
|     bool lucky(int N);
 | |
|     int extracted(int &Min, int &Max) {
 | |
| {
 | |
|         for (int I = Min; I <= Max; ++I)
 | |
|           if (lucky(I))
 | |
|             return I;
 | |
|         return -1;
 | |
|       }
 | |
| }
 | |
| int getNum(bool Superstitious, int Min, int Max) {
 | |
|       if (Superstitious) return extracted(Min, Max); else {
 | |
|         return (Min + Max) / 2;
 | |
|       }
 | |
|     }
 | |
|   )cpp";
 | |
|   EXPECT_EQ(apply(Before), After);
 | |
| }
 | |
| 
 | |
| } // namespace
 | |
| } // namespace clangd
 | |
| } // namespace clang
 |