| //===- DefinitionBlockSeparatorTest.cpp - Formatting unit tests -----------===// |
| // |
| // 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 "FormatTestUtils.h" |
| #include "clang/Format/Format.h" |
| |
| #include "llvm/Support/Debug.h" |
| #include "gtest/gtest.h" |
| |
| #define DEBUG_TYPE "definition-block-separator-test" |
| |
| namespace clang { |
| namespace format { |
| namespace { |
| |
| class DefinitionBlockSeparatorTest : public ::testing::Test { |
| protected: |
| static std::string |
| separateDefinitionBlocks(llvm::StringRef Code, |
| const std::vector<tooling::Range> &Ranges, |
| const FormatStyle &Style = getLLVMStyle()) { |
| LLVM_DEBUG(llvm::errs() << "---\n"); |
| LLVM_DEBUG(llvm::errs() << Code << "\n\n"); |
| tooling::Replacements Replaces = reformat(Style, Code, Ranges, "<stdin>"); |
| auto Result = applyAllReplacements(Code, Replaces); |
| EXPECT_TRUE(static_cast<bool>(Result)); |
| LLVM_DEBUG(llvm::errs() << "\n" << *Result << "\n\n"); |
| return *Result; |
| } |
| |
| static std::string |
| separateDefinitionBlocks(llvm::StringRef Code, |
| const FormatStyle &Style = getLLVMStyle()) { |
| return separateDefinitionBlocks( |
| Code, |
| /*Ranges=*/{1, tooling::Range(0, Code.size())}, Style); |
| } |
| |
| static void _verifyFormat(const char *File, int Line, llvm::StringRef Code, |
| const FormatStyle &Style = getLLVMStyle(), |
| llvm::StringRef ExpectedCode = "", |
| bool Inverse = true) { |
| ::testing::ScopedTrace t(File, Line, ::testing::Message() << Code.str()); |
| bool HasOriginalCode = true; |
| if (ExpectedCode == "") { |
| ExpectedCode = Code; |
| HasOriginalCode = false; |
| } |
| |
| EXPECT_EQ(ExpectedCode, separateDefinitionBlocks(ExpectedCode, Style)) |
| << "Expected code is not stable"; |
| if (Inverse) { |
| FormatStyle InverseStyle = Style; |
| if (Style.SeparateDefinitionBlocks == FormatStyle::SDS_Always) |
| InverseStyle.SeparateDefinitionBlocks = FormatStyle::SDS_Never; |
| else |
| InverseStyle.SeparateDefinitionBlocks = FormatStyle::SDS_Always; |
| EXPECT_NE(ExpectedCode, |
| separateDefinitionBlocks(ExpectedCode, InverseStyle)) |
| << "Inverse formatting makes no difference"; |
| } |
| std::string CodeToFormat = |
| HasOriginalCode ? Code.str() : removeEmptyLines(Code); |
| std::string Result = separateDefinitionBlocks(CodeToFormat, Style); |
| EXPECT_EQ(ExpectedCode, Result) << "Test failed. Formatted:\n" << Result; |
| } |
| |
| static std::string removeEmptyLines(llvm::StringRef Code) { |
| std::string Result = ""; |
| for (auto Char : Code.str()) { |
| if (Result.size()) { |
| auto LastChar = Result.back(); |
| if ((Char == '\n' && LastChar == '\n') || |
| (Char == '\r' && (LastChar == '\r' || LastChar == '\n'))) { |
| continue; |
| } |
| } |
| Result.push_back(Char); |
| } |
| return Result; |
| } |
| }; |
| |
| #define verifyFormat(...) _verifyFormat(__FILE__, __LINE__, __VA_ARGS__) |
| |
| TEST_F(DefinitionBlockSeparatorTest, Basic) { |
| FormatStyle Style = getLLVMStyle(); |
| Style.SeparateDefinitionBlocks = FormatStyle::SDS_Always; |
| verifyFormat("int foo(int i, int j) {\n" |
| " int r = i + j;\n" |
| " return r;\n" |
| "}\n" |
| "\n" |
| "int bar(int j, int k) {\n" |
| " int r = j + k;\n" |
| " return r;\n" |
| "}", |
| Style); |
| |
| verifyFormat("struct foo {\n" |
| " int i, j;\n" |
| "};\n" |
| "\n" |
| "struct bar {\n" |
| " int j, k;\n" |
| "};", |
| Style); |
| |
| verifyFormat("union foo {\n" |
| " int i, j;\n" |
| "};\n" |
| "\n" |
| "union bar {\n" |
| " int j, k;\n" |
| "};", |
| Style); |
| |
| verifyFormat("class foo {\n" |
| " int i, j;\n" |
| "};\n" |
| "\n" |
| "class bar {\n" |
| " int j, k;\n" |
| "};", |
| Style); |
| |
| verifyFormat("namespace foo {\n" |
| "int i, j;\n" |
| "}\n" |
| "\n" |
| "namespace bar {\n" |
| "int j, k;\n" |
| "}", |
| Style); |
| |
| verifyFormat("enum Foo { FOO, BAR };\n" |
| "\n" |
| "enum Bar { FOOBAR, BARFOO };\n", |
| Style); |
| |
| FormatStyle BreakAfterReturnTypeStyle = Style; |
| BreakAfterReturnTypeStyle.AlwaysBreakAfterReturnType = FormatStyle::RTBS_All; |
| // Test uppercased long typename |
| verifyFormat("class Foo {\n" |
| " void\n" |
| " Bar(int t, int p) {\n" |
| " int r = t + p;\n" |
| " return r;\n" |
| " }\n" |
| "\n" |
| " HRESULT\n" |
| " Foobar(int t, int p) {\n" |
| " int r = t * p;\n" |
| " return r;\n" |
| " }\n" |
| "}\n", |
| BreakAfterReturnTypeStyle); |
| } |
| |
| TEST_F(DefinitionBlockSeparatorTest, FormatConflict) { |
| FormatStyle Style = getLLVMStyle(); |
| Style.SeparateDefinitionBlocks = FormatStyle::SDS_Always; |
| llvm::StringRef Code = "class Test {\n" |
| "public:\n" |
| " static void foo() {\n" |
| " int t;\n" |
| " return 1;\n" |
| " }\n" |
| "};"; |
| std::vector<tooling::Range> Ranges = {1, tooling::Range(0, Code.size())}; |
| EXPECT_EQ(reformat(Style, Code, Ranges, "<stdin>").size(), 0u); |
| } |
| |
| TEST_F(DefinitionBlockSeparatorTest, CommentBlock) { |
| FormatStyle Style = getLLVMStyle(); |
| Style.SeparateDefinitionBlocks = FormatStyle::SDS_Always; |
| std::string Prefix = "enum Foo { FOO, BAR };\n" |
| "\n" |
| "/*\n" |
| "test1\n" |
| "test2\n" |
| "*/\n" |
| "int foo(int i, int j) {\n" |
| " int r = i + j;\n" |
| " return r;\n" |
| "}\n"; |
| std::string Suffix = "enum Bar { FOOBAR, BARFOO };\n" |
| "\n" |
| "/* Comment block in one line*/\n" |
| "int bar3(int j, int k) {\n" |
| " // A comment\n" |
| " int r = j % k;\n" |
| " return r;\n" |
| "}\n"; |
| std::string CommentedCode = "/*\n" |
| "int bar2(int j, int k) {\n" |
| " int r = j / k;\n" |
| " return r;\n" |
| "}\n" |
| "*/\n"; |
| verifyFormat(removeEmptyLines(Prefix) + "\n" + CommentedCode + "\n" + |
| removeEmptyLines(Suffix), |
| Style, Prefix + "\n" + CommentedCode + "\n" + Suffix); |
| verifyFormat(removeEmptyLines(Prefix) + "\n" + CommentedCode + |
| removeEmptyLines(Suffix), |
| Style, Prefix + "\n" + CommentedCode + Suffix); |
| } |
| |
| TEST_F(DefinitionBlockSeparatorTest, UntouchBlockStartStyle) { |
| // Returns a std::pair of two strings, with the first one for passing into |
| // Always test and the second one be the expected result of the first string. |
| auto MakeUntouchTest = [&](std::string BlockHeader, std::string BlockChanger, |
| std::string BlockFooter, bool BlockEndNewLine) { |
| std::string CodePart1 = "enum Foo { FOO, BAR };\n" |
| "\n" |
| "/*\n" |
| "test1\n" |
| "test2\n" |
| "*/\n" |
| "int foo(int i, int j) {\n" |
| " int r = i + j;\n" |
| " return r;\n" |
| "}\n"; |
| std::string CodePart2 = "/* Comment block in one line*/\n" |
| "enum Bar { FOOBAR, BARFOO };\n" |
| "\n" |
| "int bar3(int j, int k) {\n" |
| " // A comment\n" |
| " int r = j % k;\n" |
| " return r;\n" |
| "}\n"; |
| std::string CodePart3 = "int bar2(int j, int k) {\n" |
| " int r = j / k;\n" |
| " return r;\n" |
| "}\n"; |
| std::string ConcatAll = BlockHeader + CodePart1 + BlockChanger + CodePart2 + |
| BlockFooter + (BlockEndNewLine ? "\n" : "") + |
| CodePart3; |
| return std::make_pair(BlockHeader + removeEmptyLines(CodePart1) + |
| BlockChanger + removeEmptyLines(CodePart2) + |
| BlockFooter + removeEmptyLines(CodePart3), |
| ConcatAll); |
| }; |
| |
| FormatStyle AlwaysStyle = getLLVMStyle(); |
| AlwaysStyle.SeparateDefinitionBlocks = FormatStyle::SDS_Always; |
| |
| FormatStyle NeverStyle = getLLVMStyle(); |
| NeverStyle.SeparateDefinitionBlocks = FormatStyle::SDS_Never; |
| |
| auto TestKit = MakeUntouchTest("/* FOOBAR */\n" |
| "#ifdef FOO\n\n", |
| "\n#elifndef BAR\n\n", "\n#endif\n\n", false); |
| verifyFormat(TestKit.first, AlwaysStyle, TestKit.second); |
| verifyFormat(TestKit.second, NeverStyle, removeEmptyLines(TestKit.second)); |
| |
| TestKit = MakeUntouchTest("/* FOOBAR */\n" |
| "#ifdef FOO\n", |
| "#elifndef BAR\n", "#endif\n", false); |
| verifyFormat(TestKit.first, AlwaysStyle, TestKit.second); |
| verifyFormat(TestKit.second, NeverStyle, removeEmptyLines(TestKit.second)); |
| |
| TestKit = MakeUntouchTest("namespace Ns {\n\n", |
| "\n} // namespace Ns\n\n" |
| "namespace {\n\n", |
| "\n} // namespace\n", true); |
| verifyFormat(TestKit.first, AlwaysStyle, TestKit.second); |
| verifyFormat(TestKit.second, NeverStyle, removeEmptyLines(TestKit.second)); |
| |
| TestKit = MakeUntouchTest("namespace Ns {\n", |
| "} // namespace Ns\n\n" |
| "namespace {\n", |
| "} // namespace\n", true); |
| verifyFormat(TestKit.first, AlwaysStyle, TestKit.second); |
| verifyFormat(TestKit.second, NeverStyle, removeEmptyLines(TestKit.second)); |
| } |
| |
| TEST_F(DefinitionBlockSeparatorTest, Always) { |
| FormatStyle Style = getLLVMStyle(); |
| Style.SeparateDefinitionBlocks = FormatStyle::SDS_Always; |
| std::string Prefix = "namespace {\n"; |
| std::string Infix = "\n" |
| "// Enum test1\n" |
| "// Enum test2\n" |
| "enum Foo { FOO, BAR };\n" |
| "\n" |
| "/*\n" |
| "test1\n" |
| "test2\n" |
| "*/\n" |
| "/*const*/ int foo(int i, int j) {\n" |
| " int r = i + j;\n" |
| " return r;\n" |
| "}\n" |
| "\n" |
| "// Foobar\n" |
| "int i, j, k;\n" |
| "\n" |
| "// Comment for function\n" |
| "// Comment line 2\n" |
| "// Comment line 3\n" |
| "int bar(int j, int k) {\n" |
| " {\n" |
| " int r = j * k;\n" |
| " return r;\n" |
| " }\n" |
| "}\n" |
| "\n" |
| "int bar2(int j, int k) {\n" |
| " int r = j / k;\n" |
| " return r;\n" |
| "}\n" |
| "\n" |
| "/* Comment block in one line*/\n" |
| "enum Bar { FOOBAR, BARFOO };\n" |
| "\n" |
| "int bar3(int j, int k, const enum Bar b) {\n" |
| " // A comment\n" |
| " int r = j % k;\n" |
| " if (struct S = getS()) {\n" |
| " // if condition\n" |
| " }\n" |
| " return r;\n" |
| "}\n"; |
| std::string Postfix = "\n" |
| "} // namespace\n" |
| "\n" |
| "namespace T {\n" |
| "int i, j, k;\n" |
| "} // namespace T"; |
| verifyFormat(Prefix + removeEmptyLines(Infix) + removeEmptyLines(Postfix), |
| Style, Prefix + Infix + Postfix); |
| } |
| |
| TEST_F(DefinitionBlockSeparatorTest, Never) { |
| FormatStyle Style = getLLVMStyle(); |
| Style.SeparateDefinitionBlocks = FormatStyle::SDS_Never; |
| std::string Prefix = "namespace {\n"; |
| std::string Postfix = "// Enum test1\n" |
| "// Enum test2\n" |
| "enum Foo { FOO, BAR };\n" |
| "\n" |
| "/*\n" |
| "test1\n" |
| "test2\n" |
| "*/\n" |
| "/*const*/ int foo(int i, int j) {\n" |
| " int r = i + j;\n" |
| " return r;\n" |
| "}\n" |
| "\n" |
| "// Foobar\n" |
| "int i, j, k;\n" |
| "\n" |
| "// Comment for function\n" |
| "// Comment line 2\n" |
| "// Comment line 3\n" |
| "int bar(int j, int k) {\n" |
| " {\n" |
| " int r = j * k;\n" |
| " return r;\n" |
| " }\n" |
| "}\n" |
| "\n" |
| "int bar2(int j, int k) {\n" |
| " int r = j / k;\n" |
| " return r;\n" |
| "}\n" |
| "\n" |
| "/* Comment block in one line*/\n" |
| "enum Bar { FOOBAR, BARFOO };\n" |
| "\n" |
| "int bar3(int j, int k, const enum Bar b) {\n" |
| " // A comment\n" |
| " int r = j % k;\n" |
| " if (struct S = getS()) {\n" |
| " // if condition\n" |
| " }\n" |
| " return r;\n" |
| "}\n" |
| "} // namespace"; |
| verifyFormat(Prefix + "\n\n\n" + Postfix, Style, |
| Prefix + removeEmptyLines(Postfix)); |
| } |
| |
| TEST_F(DefinitionBlockSeparatorTest, OpeningBracketOwnsLine) { |
| FormatStyle Style = getLLVMStyle(); |
| Style.BreakBeforeBraces = FormatStyle::BS_Allman; |
| Style.SeparateDefinitionBlocks = FormatStyle::SDS_Always; |
| verifyFormat("namespace NS\n" |
| "{\n" |
| "// Enum test1\n" |
| "// Enum test2\n" |
| "enum Foo\n" |
| "{\n" |
| " FOO,\n" |
| " BAR\n" |
| "};\n" |
| "\n" |
| "/*\n" |
| "test1\n" |
| "test2\n" |
| "*/\n" |
| "/*const*/ int foo(int i, int j)\n" |
| "{\n" |
| " int r = i + j;\n" |
| " return r;\n" |
| "}\n" |
| "\n" |
| "// Foobar\n" |
| "int i, j, k;\n" |
| "\n" |
| "// Comment for function\n" |
| "// Comment line 2\n" |
| "// Comment line 3\n" |
| "int bar(int j, int k)\n" |
| "{\n" |
| " {\n" |
| " int r = j * k;\n" |
| " return r;\n" |
| " }\n" |
| "}\n" |
| "\n" |
| "int bar2(int j, int k)\n" |
| "{\n" |
| " int r = j / k;\n" |
| " return r;\n" |
| "}\n" |
| "\n" |
| "enum Bar\n" |
| "{\n" |
| " FOOBAR,\n" |
| " BARFOO\n" |
| "};\n" |
| "\n" |
| "int bar3(int j, int k, const enum Bar b)\n" |
| "{\n" |
| " // A comment\n" |
| " int r = j % k;\n" |
| " if (struct S = getS())\n" |
| " {\n" |
| " // if condition\n" |
| " }\n" |
| " return r;\n" |
| "}\n" |
| "} // namespace NS", |
| Style); |
| } |
| |
| TEST_F(DefinitionBlockSeparatorTest, TryBlocks) { |
| FormatStyle Style = getLLVMStyle(); |
| Style.BreakBeforeBraces = FormatStyle::BS_Allman; |
| Style.SeparateDefinitionBlocks = FormatStyle::SDS_Always; |
| verifyFormat("void FunctionWithInternalTry()\n" |
| "{\n" |
| " try\n" |
| " {\n" |
| " return;\n" |
| " }\n" |
| " catch (const std::exception &)\n" |
| " {\n" |
| " }\n" |
| "}", |
| Style, "", /*Inverse=*/false); |
| verifyFormat("void FunctionWithTryBlock()\n" |
| "try\n" |
| "{\n" |
| " return;\n" |
| "}\n" |
| "catch (const std::exception &)\n" |
| "{\n" |
| "}", |
| Style, "", /*Inverse=*/false); |
| } |
| |
| TEST_F(DefinitionBlockSeparatorTest, Leave) { |
| FormatStyle Style = getLLVMStyle(); |
| Style.SeparateDefinitionBlocks = FormatStyle::SDS_Leave; |
| Style.MaxEmptyLinesToKeep = 3; |
| std::string LeaveAs = "namespace {\n" |
| "\n" |
| "// Enum test1\n" |
| "// Enum test2\n" |
| "enum Foo { FOO, BAR };\n" |
| "\n\n\n" |
| "/*\n" |
| "test1\n" |
| "test2\n" |
| "*/\n" |
| "/*const*/ int foo(int i, int j) {\n" |
| " int r = i + j;\n" |
| " return r;\n" |
| "}\n" |
| "\n" |
| "// Foobar\n" |
| "int i, j, k;\n" |
| "\n" |
| "// Comment for function\n" |
| "// Comment line 2\n" |
| "// Comment line 3\n" |
| "int bar(int j, int k) {\n" |
| " {\n" |
| " int r = j * k;\n" |
| " return r;\n" |
| " }\n" |
| "}\n" |
| "\n" |
| "int bar2(int j, int k) {\n" |
| " int r = j / k;\n" |
| " return r;\n" |
| "}\n" |
| "\n" |
| "// Comment for inline enum\n" |
| "enum Bar { FOOBAR, BARFOO };\n" |
| "int bar3(int j, int k, const enum Bar b) {\n" |
| " // A comment\n" |
| " int r = j % k;\n" |
| " if (struct S = getS()) {\n" |
| " // if condition\n" |
| " }\n" |
| " return r;\n" |
| "}\n" |
| "} // namespace"; |
| verifyFormat(LeaveAs, Style, LeaveAs); |
| } |
| |
| TEST_F(DefinitionBlockSeparatorTest, CSharp) { |
| FormatStyle Style = getLLVMStyle(FormatStyle::LK_CSharp); |
| Style.SeparateDefinitionBlocks = FormatStyle::SDS_Always; |
| Style.AllowShortFunctionsOnASingleLine = FormatStyle::SFS_None; |
| Style.AllowShortEnumsOnASingleLine = false; |
| verifyFormat("namespace {\r\n" |
| "public class SomeTinyClass {\r\n" |
| " int X;\r\n" |
| "}\r\n" |
| "\r\n" |
| "public class AnotherTinyClass {\r\n" |
| " int Y;\r\n" |
| "}\r\n" |
| "\r\n" |
| "internal static String toString() {\r\n" |
| "}\r\n" |
| "\r\n" |
| "// Comment for enum\r\n" |
| "public enum var {\r\n" |
| " none,\r\n" |
| " @string,\r\n" |
| " bool,\r\n" |
| " @enum\r\n" |
| "}\r\n" |
| "\r\n" |
| "// Test\r\n" |
| "[STAThread]\r\n" |
| "static void Main(string[] args) {\r\n" |
| " Console.WriteLine(\"HelloWorld\");\r\n" |
| "}\r\n" |
| "\r\n" |
| "static decimal Test() {\r\n" |
| "}\r\n" |
| "}\r\n" |
| "\r\n" |
| "public class FoobarClass {\r\n" |
| " int foobar;\r\n" |
| "}", |
| Style); |
| } |
| |
| TEST_F(DefinitionBlockSeparatorTest, JavaScript) { |
| FormatStyle Style = getLLVMStyle(FormatStyle::LK_JavaScript); |
| Style.SeparateDefinitionBlocks = FormatStyle::SDS_Always; |
| Style.AllowShortFunctionsOnASingleLine = FormatStyle::SFS_None; |
| Style.AllowShortEnumsOnASingleLine = false; |
| verifyFormat("export const enum Foo {\n" |
| " A = 1,\n" |
| " B\n" |
| "}\n" |
| "\n" |
| "export function A() {\n" |
| "}\n" |
| "\n" |
| "export default function B() {\n" |
| "}\n" |
| "\n" |
| "export function C() {\n" |
| "}\n" |
| "\n" |
| "var t, p, q;\n" |
| "\n" |
| "export abstract class X {\n" |
| " y: number;\n" |
| "}\n" |
| "\n" |
| "export const enum Bar {\n" |
| " D = 1,\n" |
| " E\n" |
| "}", |
| Style); |
| } |
| } // namespace |
| } // namespace format |
| } // namespace clang |