diff --git a/clang-tools-extra/clangd/unittests/CMakeLists.txt b/clang-tools-extra/clangd/unittests/CMakeLists.txt --- a/clang-tools-extra/clangd/unittests/CMakeLists.txt +++ b/clang-tools-extra/clangd/unittests/CMakeLists.txt @@ -68,6 +68,7 @@ TraceTests.cpp TypeHierarchyTests.cpp TweakTests.cpp + TweakTesting.cpp URITests.cpp XRefsTests.cpp diff --git a/clang-tools-extra/clangd/unittests/TweakTesting.h b/clang-tools-extra/clangd/unittests/TweakTesting.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clangd/unittests/TweakTesting.h @@ -0,0 +1,93 @@ +//===--- TweakTesting.h - Test helpers for refactoring actions ---*- 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 "gtest/gtest.h" +#include "gmock/gmock.h" + +namespace clang { +namespace clangd { + +// Fixture base for testing tweaks. Intended to be subclassed for each tweak. +// +// Usage: +// TWEAK_TEST(ExpandAutoType); +// +// TEST_F(ExpandAutoTypeTest, ShortensTypes) { +// Header = R"cpp( +// namespace foo { template class X{}; } +// using namespace foo; +// cpp)"; +// Context = Block; +// EXPECT_THAT(apply("[[auto]] X = foo();"), +// "foo X = foo();"); +// EXPECT_UNAVAILABLE("auto ^X^ = ^foo();"); +// } +class TweakTest : public ::testing::Test { + const char *TweakID; + +public: + // Inputs are wrapped in file boilerplate before attempting to apply a tweak. + // Context describes the type of boilerplate. + enum CodeContext { + // Code snippet is placed directly into the source file. e.g. a declaration. + File, + // Snippet will appear within a function body. e.g. a statement. + Function, + // Snippet is an expression. + Expression, + }; + +protected: + TweakTest(const char *TweakID) : TweakID(TweakID) {} + + // Contents of a header file to be implicitly included. + // This typically contains declarations that will be used for a set of related + // testcases. + std::string Header; + + // Context in which snippets of code should be placed to run tweaks. + CodeContext Context = File; + + // Apply the current tweak to the range (or point) in MarkedCode. + // MarkedCode will be wrapped according to the Context. + // - if the tweak produces edits, returns the edited code (without markings). + // The context added to MarkedCode will be stripped away before returning, + // unless the tweak edited it. + // - if the tweak produces a message, returns "message:\n" + // - if prepare() returns false, returns "unavailable" + // - if apply() returns an error, returns "fail: " + std::string apply(llvm::StringRef MarkedCode) const; + + // Accepts a code snippet with many ranges (or points) marked, and returns a + // list of snippets with one range marked each. + // Primarily used from EXPECT_AVAILABLE/EXPECT_UNAVAILABLE macro. + static std::vector expandCases(llvm::StringRef MarkedCode); + + // Returns a matcher that accepts marked code snippets where the tweak is + // available at the marked range. + ::testing::Matcher isAvailable() const; +}; + +#define TWEAK_TEST(TweakID) \ + class TweakID##Test : public ::clang::clangd::TweakTest { \ + protected: \ + TweakID##Test() : TweakTest(#TweakID) {} \ + } + +#define EXPECT_AVAILABLE(MarkedCode) \ + for (const auto &Case : expandCases(MarkedCode)) \ + EXPECT_THAT(Case, isAvailable()) + +#define EXPECT_UNAVAILABLE(MarkedCode) \ + for (const auto &Case : expandCases(MarkedCode)) \ + EXPECT_THAT(Case, ::testing::Not(isAvailable())) + +} // namespace clangd +} // namespace clang diff --git a/clang-tools-extra/clangd/unittests/TweakTesting.cpp b/clang-tools-extra/clangd/unittests/TweakTesting.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clangd/unittests/TweakTesting.cpp @@ -0,0 +1,132 @@ +//===-- TweakTesting.cpp ------------------------------------------------*-===// +// +// 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 "TweakTesting.h" + +#include "Annotations.h" +#include "refactor/Tweak.h" +#include "SourceCode.h" +#include "clang/Tooling/Core/Replacement.h" +#include "llvm/Support/Error.h" + +namespace clang { +namespace clangd { +namespace { +using Context = TweakTest::CodeContext; + +std::pair wrapping(Context Ctx) { + switch (Ctx) { + case TweakTest::File: + return {"",""}; + case TweakTest::Function: + return {"void wrapperFunction(){\n", "\n}"}; + case TweakTest::Expression: + return {"auto expressionWrapper(){return\n", "\n;}"}; + } +} + +std::string wrap(Context Ctx, llvm::StringRef Inner) { + auto Wrapping = wrapping(Ctx); + return (Wrapping.first + Inner + Wrapping.second).str(); +} + +llvm::StringRef unwrap(Context Ctx, llvm::StringRef Outer) { + auto Wrapping = wrapping(Ctx); + // Unwrap only if the code matches the expected wrapping. + // Don't allow the begin/end wrapping to overlap! + if (Outer.startswith(Wrapping.first) && Outer.endswith(Wrapping.second) && + Outer.size() >= Wrapping.first.size() + Wrapping.second.size()) + return Outer.drop_front(Wrapping.first.size()).drop_back(Wrapping.second.size()); + return Outer; +} + +std::pair rangeOrPoint(const Annotations &A) { + Range SelectionRng; + if (A.points().size() != 0) { + assert(A.ranges().size() == 0 && + "both a cursor point and a selection range were specified"); + SelectionRng = Range{A.point(), A.point()}; + } else { + SelectionRng = A.range(); + } + return {cantFail(positionToOffset(A.code(), SelectionRng.start)), + cantFail(positionToOffset(A.code(), SelectionRng.end))}; +} + +MATCHER_P3(TweakIsAvailable, TweakID, Ctx, Header, + (TweakID + (negation ? " is unavailable" : " is available")).str()) { + std::string WrappedCode = wrap(Ctx, arg); + Annotations Input(WrappedCode); + auto Selection = rangeOrPoint(Input); + TestTU TU; + TU.HeaderCode = Header; + TU.Code = Input.code(); + ParsedAST AST = TU.build(); + Tweak::Selection S(AST, Selection.first, Selection.second); + auto PrepareResult = prepareTweak(TweakID, S); + if (PrepareResult) + return true; + llvm::consumeError(PrepareResult.takeError()); + return false; +} + +} // namespace + +std::string TweakTest::apply(llvm::StringRef MarkedCode) const { + std::string WrappedCode = wrap(Context, MarkedCode); + Annotations Input(WrappedCode); + auto Selection = rangeOrPoint(Input); + TestTU TU; + TU.HeaderCode = Header; + TU.Code = Input.code(); + ParsedAST AST = TU.build(); + Tweak::Selection S(AST, Selection.first, Selection.second); + + auto T = prepareTweak(TweakID, S); + if (!T) + return "unavailable"; + llvm::Expected Result = (*T)->apply(S); + if (!Result) + return "fail: " + llvm::toString(Result.takeError()); + if (Result->ShowMessage) + return "message:\n" + *Result->ShowMessage; + if (Result->ApplyEdit) { + if (auto NewText = + tooling::applyAllReplacements(Input.code(), *Result->ApplyEdit)) + return unwrap(Context, *NewText); + else + return "bad edits: " + llvm::toString(NewText.takeError()); + } + return "no effect"; +} + +::testing::Matcher TweakTest::isAvailable() const { + return TweakIsAvailable(llvm::StringRef(TweakID), Context, Header); +} + +std::vector TweakTest::expandCases(llvm::StringRef MarkedCode) { + Annotations Test(MarkedCode); + llvm::StringRef Code = Test.code(); + std::vector Cases; + for (const auto& Point : Test.points()) { + size_t Offset = llvm::cantFail(positionToOffset(Code, Point)); + Cases.push_back((Code.substr(0, Offset) + "^" + Code.substr(Offset)).str()); + } + for (const auto& Range : Test.ranges()) { + size_t Begin = llvm::cantFail(positionToOffset(Code, Range.start)); + size_t End = llvm::cantFail(positionToOffset(Code, Range.end)); + Cases.push_back((Code.substr(0, Begin) + "[[" + + Code.substr(Begin, End - Begin) + "]]" + Code.substr(End)) + .str()); + } + assert(!Cases.empty() && "No markings in MarkedCode?"); + return Cases; +} + +} // namespace clangd +} // namespace clang diff --git a/clang-tools-extra/clangd/unittests/TweakTests.cpp b/clang-tools-extra/clangd/unittests/TweakTests.cpp --- a/clang-tools-extra/clangd/unittests/TweakTests.cpp +++ b/clang-tools-extra/clangd/unittests/TweakTests.cpp @@ -9,6 +9,7 @@ #include "Annotations.h" #include "SourceCode.h" #include "TestTU.h" +#include "TweakTesting.h" #include "refactor/Tweak.h" #include "clang/AST/Expr.h" #include "clang/Basic/LLVM.h" @@ -24,11 +25,16 @@ using llvm::Failed; using llvm::Succeeded; +using ::testing::AllOf; +using ::testing::HasSubstr; +using ::testing::StartsWith; namespace clang { namespace clangd { namespace { +// TODO(sammccall): migrate the rest of the tests to use TweakTesting.h and +// remove these helpers. std::string markRange(llvm::StringRef Code, Range R) { size_t Begin = llvm::cantFail(positionToOffset(Code, R.start)); size_t End = llvm::cantFail(positionToOffset(Code, R.end)); @@ -114,13 +120,6 @@ return applyAllReplacements(Code.code(), *Effect->ApplyEdit); } -std::string getMessage(StringRef ID, llvm::StringRef Input) { - auto Effect = apply(ID, Input); - if (!Effect) - return "error: " + llvm::toString(Effect.takeError()); - return Effect->ShowMessage.getValueOr("no message produced!"); -} - void checkTransform(llvm::StringRef ID, llvm::StringRef Input, std::string Output) { auto Result = applyEdit(ID, Input); @@ -128,148 +127,72 @@ EXPECT_EQ(Output, std::string(*Result)) << Input; } -/// Check if apply returns an error and that the @ErrorMessage is contained -/// in that error -void checkApplyContainsError(llvm::StringRef ID, llvm::StringRef Input, - const std::string& ErrorMessage) { - auto Result = apply(ID, Input); - ASSERT_FALSE(Result) << "expected error message:\n " << ErrorMessage << - "\non input:" << Input; - EXPECT_THAT(llvm::toString(Result.takeError()), - testing::HasSubstr(ErrorMessage)) - << Input; -} - -TEST(TweakTest, SwapIfBranches) { - llvm::StringLiteral ID = "SwapIfBranches"; - - checkAvailable(ID, R"cpp( - void test() { - ^i^f^^(^t^r^u^e^) { return 100; } ^e^l^s^e^ { continue; } - } - )cpp"); - - checkNotAvailable(ID, R"cpp( - void test() { - if (true) {^return ^100;^ } else { ^continue^;^ } - } - )cpp"); - - llvm::StringLiteral Input = R"cpp( - void test() { - ^if (true) { return 100; } else { continue; } - } - )cpp"; - llvm::StringLiteral Output = R"cpp( - void test() { - if (true) { continue; } else { return 100; } - } - )cpp"; - checkTransform(ID, Input, Output); - - Input = R"cpp( - void test() { - ^if () { return 100; } else { continue; } - } - )cpp"; - Output = R"cpp( - void test() { - if () { continue; } else { return 100; } - } - )cpp"; - checkTransform(ID, Input, Output); - - // Available in subexpressions of the condition. - checkAvailable(ID, R"cpp( - void test() { - if(2 + [[2]] + 2) { return 2 + 2 + 2; } else { continue; } - } - )cpp"); +TWEAK_TEST(SwapIfBranches); +TEST_F(SwapIfBranchesTest, Test) { + Context = Function; + EXPECT_EQ(apply("^if (true) {return 100;} else {continue;}"), + "if (true) {continue;} else {return 100;}"); + EXPECT_EQ(apply("^if () {return 100;} else {continue;}"), + "if () {continue;} else {return 100;}") << "broken condition"; + EXPECT_AVAILABLE("^i^f^^(^t^r^u^e^) { return 100; } ^e^l^s^e^ { continue; }"); + EXPECT_UNAVAILABLE("if (true) {^return ^100;^ } else { ^continue^;^ }"); + // Available in subexpressions of the condition; + EXPECT_THAT("if(2 + [[2]] + 2) { return 2 + 2 + 2; } else {continue;}", + isAvailable()); // But not as part of the branches. - checkNotAvailable(ID, R"cpp( - void test() { - if(2 + 2 + 2) { return 2 + [[2]] + 2; } else { continue; } - } - )cpp"); + EXPECT_THAT("if(2 + 2 + 2) { return 2 + [[2]] + 2; } else { continue; }", + Not(isAvailable())); // Range covers the "else" token, so available. - checkAvailable(ID, R"cpp( - void test() { - if(2 + 2 + 2) { return 2 + [[2 + 2; } else { continue;]] } - } - )cpp"); + EXPECT_THAT("if(2 + 2 + 2) { return 2 + [[2 + 2; } else {continue;]]}", + isAvailable()); // Not available in compound statements in condition. - checkNotAvailable(ID, R"cpp( - void test() { - if([]{return [[true]];}()) { return 2 + 2 + 2; } else { continue; } - } - )cpp"); + EXPECT_THAT( + "if([]{return [[true]];}()) { return 2 + 2 + 2; } else { continue; }", + Not(isAvailable())); // Not available if both sides aren't braced. - checkNotAvailable(ID, R"cpp( - void test() { - ^if (1) return; else { return; } - } - )cpp"); + EXPECT_THAT("^if (1) return; else { return; }", Not(isAvailable())); // Only one if statement is supported! - checkNotAvailable(ID, R"cpp( - [[if(1){}else{}if(2){}else{}]] - )cpp"); + EXPECT_THAT("[[if(1){}else{}if(2){}else{}]]", Not(isAvailable())); } -TEST(TweakTest, RawStringLiteral) { - llvm::StringLiteral ID = "RawStringLiteral"; - - checkAvailable(ID, R"cpp( - const char *A = ^"^f^o^o^\^n^"; - const char *B = R"(multi )" ^"token " "str\ning"; - )cpp"); - - checkNotAvailable(ID, R"cpp( - const char *A = ^"f^o^o^o^"; // no chars need escaping - const char *B = R"(multi )" ^"token " u8"str\ning"; // not all ascii - const char *C = ^R^"^(^multi )" "token " "str\ning"; // chunk is raw - const char *D = ^"token\n" __FILE__; // chunk is macro expansion - const char *E = ^"a\r\n"; // contains forbidden escape character - )cpp"); - - const char *Input = R"cpp( - const char *X = R"(multi -token)" "\nst^ring\n" "literal"; - } - )cpp"; - const char *Output = R"cpp( - const char *X = R"(multi +TWEAK_TEST(RawStringLiteral); +TEST_F(RawStringLiteralTest, Test) { + Context = Expression; + EXPECT_AVAILABLE(R"cpp(^"^f^o^o^\^n^")cpp"); + EXPECT_AVAILABLE(R"cpp(R"(multi )" ^"token " "str\ning")cpp"); + EXPECT_UNAVAILABLE(R"cpp(^"f^o^o^o")cpp"); // no chars need escaping + EXPECT_UNAVAILABLE(R"cpp(R"(multi )" ^"token " u8"str\ning")cpp"); // nonascii + EXPECT_UNAVAILABLE(R"cpp(^R^"^(^multi )" "token " "str\ning")cpp"); // raw + EXPECT_UNAVAILABLE(R"cpp(^"token\n" __FILE__)cpp"); // chunk is macro + EXPECT_UNAVAILABLE(R"cpp(^"a\r\n";)cpp"); // forbidden escape char + + const char *Input = R"cpp(R"(multi +token)" "\nst^ring\n" "literal")cpp"; + const char *Output = R"cpp(R"(multi token string -literal)"; - } - )cpp"; - checkTransform(ID, Input, Output); +literal)")cpp"; + EXPECT_EQ(apply(Input), Output); } -TEST(TweakTest, DumpAST) { - llvm::StringLiteral ID = "DumpAST"; - - checkAvailable(ID, "^int f^oo() { re^turn 2 ^+ 2; }"); - checkNotAvailable(ID, "/*c^omment*/ int foo() return 2 ^ + 2; }"); - - const char *Input = "int x = 2 ^+ 2;"; - auto Result = getMessage(ID, Input); - EXPECT_THAT(Result, ::testing::HasSubstr("BinaryOperator")); - EXPECT_THAT(Result, ::testing::HasSubstr("'+'")); - EXPECT_THAT(Result, ::testing::HasSubstr("|-IntegerLiteral")); - EXPECT_THAT(Result, - ::testing::HasSubstr(" 'int' 2\n`-IntegerLiteral")); - EXPECT_THAT(Result, ::testing::HasSubstr(" 'int' 2")); +TWEAK_TEST(DumpAST); +TEST_F(DumpASTTest, Test) { + EXPECT_AVAILABLE("^int f^oo() { re^turn 2 ^+ 2; }"); + EXPECT_UNAVAILABLE("/*c^omment*/ int foo() return 2 ^ + 2; }"); + EXPECT_THAT(apply("int x = 2 ^+ 2;"), + AllOf(StartsWith("message:"), HasSubstr("BinaryOperator"), + HasSubstr("'+'"), HasSubstr("|-IntegerLiteral"), + HasSubstr(" 'int' 2\n`-IntegerLiteral"), + HasSubstr(" 'int' 2"))); } -TEST(TweakTest, ShowSelectionTree) { - llvm::StringLiteral ID = "ShowSelectionTree"; - - checkAvailable(ID, "^int f^oo() { re^turn 2 ^+ 2; }"); - checkAvailable(ID, "/*c^omment*/ int foo() return 2 ^ + 2; }"); +TWEAK_TEST(ShowSelectionTree); +TEST_F(ShowSelectionTreeTest, Test) { + EXPECT_AVAILABLE("^int f^oo() { re^turn 2 ^+ 2; }"); + EXPECT_AVAILABLE("/*c^omment*/ int foo() return 2 ^ + 2; }"); - const char *Input = "int fcall(int); int x = fca[[ll(2 +]]2);"; - const char *Output = R"( TranslationUnitDecl + const char *Output = R"(message: + TranslationUnitDecl VarDecl int x = fcall(2 + 2) .CallExpr fcall(2 + 2) ImplicitCastExpr fcall @@ -277,22 +200,22 @@ .BinaryOperator 2 + 2 *IntegerLiteral 2 )"; - EXPECT_EQ(Output, getMessage(ID, Input)); + EXPECT_EQ(apply("int fcall(int); int x = fca[[ll(2 +]]2);"), Output); } -TEST(TweakTest, DumpRecordLayout) { - llvm::StringLiteral ID = "DumpRecordLayout"; - checkAvailable(ID, "^s^truct ^X ^{ int x; ^};"); - checkNotAvailable(ID, "struct X { int ^a; };"); - checkNotAvailable(ID, "struct ^X;"); - checkNotAvailable(ID, "template struct ^X { T t; };"); - checkNotAvailable(ID, "enum ^X {};"); +TWEAK_TEST(DumpRecordLayout); +TEST_F(DumpRecordLayoutTest, Test) { + EXPECT_AVAILABLE("^s^truct ^X ^{ int x; ^};"); + EXPECT_THAT("struct X { int ^a; };", Not(isAvailable())); + EXPECT_THAT("struct ^X;", Not(isAvailable())); + EXPECT_THAT("template struct ^X { T t; };", Not(isAvailable())); + EXPECT_THAT("enum ^X {};", Not(isAvailable())); - const char *Input = "struct ^X { int x; int y; }"; - EXPECT_THAT(getMessage(ID, Input), ::testing::HasSubstr("0 | int x")); + EXPECT_THAT(apply("struct ^X { int x; int y; }"), + AllOf(StartsWith("message:"), HasSubstr("0 | int x"))); } -TEST(TweakTest, ExtractVariable) { +TEST(TweaksTest, ExtractVariable) { llvm::StringLiteral ID = "ExtractVariable"; checkAvailable(ID, R"cpp( int xyz(int a = 1) { @@ -560,7 +483,7 @@ } } -TEST(TweakTest, AnnotateHighlightings) { +TEST(TweaksTest, AnnotateHighlightings) { llvm::StringLiteral ID = "AnnotateHighlightings"; checkAvailable(ID, "^vo^id^ ^f(^) {^}^"); // available everywhere. checkAvailable(ID, "[[int a; int b;]]"); @@ -590,245 +513,87 @@ )cpp"); } -TEST(TweakTest, ExpandMacro) { - llvm::StringLiteral ID = "ExpandMacro"; +TWEAK_TEST(ExpandMacro); +TEST_F(ExpandMacroTest, Test) { + Header = R"cpp( + #define FOO 1 2 3 + #define FUNC(X) X+X+X + #define EMPTY + #define EMPTY_FN(X) + )cpp"; // Available on macro names, not available anywhere else. - checkAvailable(ID, R"cpp( -#define FOO 1 2 3 -#define FUNC(X) X+X+X -^F^O^O^ BAR ^F^O^O^ -^F^U^N^C^(1) -)cpp"); - checkNotAvailable(ID, R"cpp( -^#^d^efine^ ^FO^O 1 ^2 ^3^ -FOO ^B^A^R^ FOO ^ -FUNC(^1^)^ -)cpp"); + EXPECT_AVAILABLE("^F^O^O^ BAR ^F^O^O^"); + EXPECT_AVAILABLE("^F^U^N^C^(1)"); + EXPECT_UNAVAILABLE("^#^d^efine^ ^XY^Z 1 ^2 ^3^"); + EXPECT_UNAVAILABLE("FOO ^B^A^R^ FOO ^"); + EXPECT_UNAVAILABLE("FUNC(^1^)^"); // Works as expected on object-like macros. - checkTransform(ID, R"cpp( -#define FOO 1 2 3 -^FOO BAR FOO -)cpp", - R"cpp( -#define FOO 1 2 3 -1 2 3 BAR FOO -)cpp"); - checkTransform(ID, R"cpp( -#define FOO 1 2 3 -FOO BAR ^FOO -)cpp", - R"cpp( -#define FOO 1 2 3 -FOO BAR 1 2 3 -)cpp"); - + EXPECT_EQ(apply("^FOO BAR FOO"), "1 2 3 BAR FOO"); + EXPECT_EQ(apply("FOO BAR ^FOO"), "FOO BAR 1 2 3"); // And function-like macros. - checkTransform(ID, R"cpp( -#define FUNC(X) X+X+X -F^UNC(2) -)cpp", - R"cpp( -#define FUNC(X) X+X+X -2 + 2 + 2 -)cpp"); + EXPECT_EQ(apply("F^UNC(2)"), "2 + 2 + 2"); // Works on empty macros. - checkTransform(ID, R"cpp( -#define EMPTY -int a ^EMPTY; - )cpp", - R"cpp( -#define EMPTY -int a ; - )cpp"); - checkTransform(ID, R"cpp( -#define EMPTY_FN(X) -int a ^EMPTY_FN(1 2 3); - )cpp", - R"cpp( -#define EMPTY_FN(X) -int a ; - )cpp"); - checkTransform(ID, R"cpp( -#define EMPTY -#define EMPTY_FN(X) -int a = 123 ^EMPTY EMPTY_FN(1); - )cpp", - R"cpp( -#define EMPTY -#define EMPTY_FN(X) -int a = 123 EMPTY_FN(1); - )cpp"); - checkTransform(ID, R"cpp( -#define EMPTY -#define EMPTY_FN(X) -int a = 123 ^EMPTY_FN(1) EMPTY; - )cpp", - R"cpp( -#define EMPTY -#define EMPTY_FN(X) -int a = 123 EMPTY; - )cpp"); - checkTransform(ID, R"cpp( -#define EMPTY -#define EMPTY_FN(X) -int a = 123 EMPTY_FN(1) ^EMPTY; - )cpp", - R"cpp( -#define EMPTY -#define EMPTY_FN(X) -int a = 123 EMPTY_FN(1) ; - )cpp"); + EXPECT_EQ(apply("int a ^EMPTY;"), "int a ;"); + EXPECT_EQ(apply("int a ^EMPTY_FN(1 2 3);"), "int a ;"); + EXPECT_EQ(apply("int a = 123 ^EMPTY EMPTY_FN(1);"), + "int a = 123 EMPTY_FN(1);"); + EXPECT_EQ(apply("int a = 123 ^EMPTY_FN(1) EMPTY;"), "int a = 123 EMPTY;"); + EXPECT_EQ(apply("int a = 123 EMPTY_FN(1) ^EMPTY;"), + "int a = 123 EMPTY_FN(1) ;"); } -TEST(TweakTest, ExpandAutoType) { - llvm::StringLiteral ID = "ExpandAutoType"; - - checkAvailable(ID, R"cpp( - ^a^u^t^o^ i = 0; - )cpp"); - - checkNotAvailable(ID, R"cpp( - auto ^i^ ^=^ ^0^;^ - )cpp"); - - llvm::StringLiteral Input = R"cpp( - [[auto]] i = 0; - )cpp"; - llvm::StringLiteral Output = R"cpp( - int i = 0; - )cpp"; - checkTransform(ID, Input, Output); - - // check primitive type - Input = R"cpp( - au^to i = 0; - )cpp"; - Output = R"cpp( - int i = 0; - )cpp"; - checkTransform(ID, Input, Output); - - // check classes and namespaces - Input = R"cpp( - namespace testns { - class TestClass { - class SubClass {}; - }; +TWEAK_TEST(ExpandAutoType); +TEST_F(ExpandAutoTypeTest, Test) { + Header = R"cpp( + namespace ns { + struct Class { + struct Nested {}; + } + void Func(); } - ^auto C = testns::TestClass::SubClass(); - )cpp"; - Output = R"cpp( - namespace testns { - class TestClass { - class SubClass {}; - }; + inline namespace inl_ns { + namespace { + struct Visible {}; + } } - testns::TestClass::SubClass C = testns::TestClass::SubClass(); )cpp"; - checkTransform(ID, Input, Output); - // check that namespaces are shortened - Input = R"cpp( - namespace testns { - class TestClass { - }; - void func() { ^auto C = TestClass(); } - } - )cpp"; - Output = R"cpp( - namespace testns { - class TestClass { - }; - void func() { TestClass C = TestClass(); } - } - )cpp"; - checkTransform(ID, Input, Output); + EXPECT_AVAILABLE("^a^u^t^o^ i = 0;"); + EXPECT_UNAVAILABLE("auto ^i^ ^=^ ^0^;^"); + // check primitive type + EXPECT_EQ(apply("[[auto]] i = 0;"), "int i = 0;"); + EXPECT_EQ(apply("au^to i = 0;"), "int i = 0;"); + // check classes and namespaces + EXPECT_EQ(apply("^auto C = ns::Class::Nested();"), + "ns::Class::Nested C = ns::Class::Nested();"); + // check that namespaces are shortened + EXPECT_EQ(apply("namespace ns { void f() { ^auto C = Class(); } }"), + "namespace ns { void f() { Class C = Class(); } }"); // unknown types in a template should not be replaced - Input = R"cpp( - template void x() { - ^auto y = T::z(); - } - )cpp"; - checkApplyContainsError(ID, Input, "Could not deduce type for 'auto' type"); - + EXPECT_THAT(apply("template void x() { ^auto y = T::z(); }"), + StartsWith("fail: Could not deduce type for 'auto' type")); // undefined functions should not be replaced - Input = R"cpp( - a^uto x = doesnt_exist(); - )cpp"; - checkApplyContainsError(ID, Input, "Could not deduce type for 'auto' type"); - + EXPECT_THAT(apply("au^to x = doesnt_exist();"), + StartsWith("fail: Could not deduce type for 'auto' type")); // function pointers should not be replaced - Input = R"cpp( - int foo(); - au^to x = &foo; - )cpp"; - checkApplyContainsError(ID, Input, - "Could not expand type of function pointer"); - + EXPECT_THAT(apply("au^to x = &ns::Func;"), + StartsWith("fail: Could not expand type of function pointer")); // lambda types are not replaced - Input = R"cpp( - au^to x = []{}; - )cpp"; - checkApplyContainsError(ID, Input, - "Could not expand type of lambda expression"); - + EXPECT_THAT(apply("au^to x = []{};"), + StartsWith("fail: Could not expand type of lambda expression")); // inline namespaces - Input = R"cpp( - inline namespace x { - namespace { struct S; } - } - au^to y = S(); - )cpp"; - Output = R"cpp( - inline namespace x { - namespace { struct S; } - } - S y = S(); - )cpp"; - + EXPECT_EQ(apply("au^to x = inl_ns::Visible();"), + "Visible x = inl_ns::Visible();"); // local class - Input = R"cpp( - namespace x { - void y() { - struct S{}; - a^uto z = S(); - }} - )cpp"; - Output = R"cpp( - namespace x { - void y() { - struct S{}; - S z = S(); - }} - )cpp"; - checkTransform(ID, Input, Output); - + EXPECT_EQ(apply("namespace x { void y() { struct S{}; ^auto z = S(); } }"), + "namespace x { void y() { struct S{}; S z = S(); } }"); // replace array types - Input = R"cpp( - au^to x = "test"; - )cpp"; - Output = R"cpp( - const char * x = "test"; - )cpp"; - checkTransform(ID, Input, Output); - - Input = R"cpp( - namespace { - class Foo {}; - } - au^to f = Foo(); - )cpp"; - Output = R"cpp( - namespace { - class Foo {}; - } - Foo f = Foo(); - )cpp"; - checkTransform(ID, Input, Output); + EXPECT_EQ(apply(R"cpp(au^to x = "test")cpp"), + R"cpp(const char * x = "test")cpp"); } } // namespace