Files
qt-creator/tests/unit/unittest/clangformat-test.cpp
Christian Kandeler b88b73d9c1 ClangFormat: Sprinkle some more magic pixie dust
Apparently, we can prevent clang-format from removing line breaks by
adding an empty comment at the end of the line.

Change-Id: Ia78ecb9e7351d059c544cbda11d33af5734e2218
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
2021-04-12 09:28:55 +00:00

963 lines
29 KiB
C++

/****************************************************************************
**
** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "googletest.h"
#include <clangformat/clangformatbaseindenter.h>
#include <utils/fileutils.h>
#include <QTextDocument>
namespace TextEditor {
class TabSettings
{};
} // namespace TextEditor
namespace {
class ClangFormatIndenter : public ClangFormat::ClangFormatBaseIndenter
{
public:
ClangFormatIndenter(QTextDocument *doc)
: ClangFormat::ClangFormatBaseIndenter(doc)
{}
Utils::optional<TextEditor::TabSettings> tabSettings() const override
{
return Utils::optional<TextEditor::TabSettings>();
}
};
class ClangFormatExtendedIndenter : public ClangFormatIndenter
{
public:
ClangFormatExtendedIndenter(QTextDocument *doc)
: ClangFormatIndenter(doc)
{}
bool formatWhileTyping() const override { return true; }
};
class ClangFormat : public ::testing::Test
{
protected:
void SetUp() final
{
indenter.setFileName(Utils::FilePath::fromString(TESTDATA_DIR "/clangformat/test.cpp"));
extendedIndenter.setFileName(
Utils::FilePath::fromString(TESTDATA_DIR "/clangformat/test.cpp"));
}
void insertLines(const std::vector<QString> &lines)
{
doc.clear();
cursor.setPosition(0);
for (size_t lineNumber = 1; lineNumber <= lines.size(); ++lineNumber) {
if (lineNumber > 1)
cursor.insertBlock();
cursor.insertText(lines[lineNumber - 1]);
}
cursor.setPosition(0);
cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
}
std::vector<QString> documentLines()
{
std::vector<QString> result;
const int lines = doc.blockCount();
result.reserve(static_cast<size_t>(lines));
for (int line = 0; line < lines; ++line)
result.push_back(doc.findBlockByNumber(line).text());
return result;
}
QTextDocument doc;
ClangFormatIndenter indenter{&doc};
ClangFormatExtendedIndenter extendedIndenter{&doc};
QTextCursor cursor{&doc};
};
// clang-format off
TEST_F(ClangFormat, IndentBasicFile)
{
insertLines({"int main()",
"{",
"int a;",
"}"});
indenter.indent(cursor, QChar::Null, TextEditor::TabSettings());
ASSERT_THAT(documentLines(), ElementsAre("int main()",
"{",
" int a;",
"}"));
}
TEST_F(ClangFormat, IndentEmptyLine)
{
insertLines({"int main()",
"{",
"",
"}"});
indenter.indent(cursor, QChar::Null, TextEditor::TabSettings());
ASSERT_THAT(documentLines(), ElementsAre("int main()",
"{",
" ",
"}"));
}
TEST_F(ClangFormat, IndentLambda)
{
insertLines({"int b = foo([](){",
"",
"});"});
indenter.indent(cursor, QChar::Null, TextEditor::TabSettings());
ASSERT_THAT(documentLines(), ElementsAre("int b = foo([](){",
" ",
"});"));
}
TEST_F(ClangFormat, IndentNestedIfElse)
{
insertLines({"if (a)",
"if (b)",
"foo();",
"else",
"bar();",
"else",
"baz();"});
indenter.indent(cursor, QChar::Null, TextEditor::TabSettings());
ASSERT_THAT(documentLines(),
ElementsAre("if (a)",
" if (b)",
" foo();",
" else",
" bar();",
"else",
" baz();"));
}
TEST_F(ClangFormat, IndentInitializerListInArguments)
{
insertLines({"foo(arg1,",
"args,",
"{1, 2});"});
indenter.indent(cursor, QChar::Null, TextEditor::TabSettings());
ASSERT_THAT(documentLines(), ElementsAre("foo(arg1,",
" args,",
" {1, 2});"));
}
TEST_F(ClangFormat, IndentLambdaWithReturnType)
{
insertLines({"{",
"auto lambda = []() -> bool {",
"",
"};",
"}"});
indenter.indent(cursor, QChar::Null, TextEditor::TabSettings());
ASSERT_THAT(documentLines(),
ElementsAre("{",
" auto lambda = []() -> bool {",
" ",
" };",
"}"));
}
TEST_F(ClangFormat, ClangFormatIndentFunctionArgumentLambdaWithNextLineScope)
{
insertLines({"foo([]()",
"{",
"",
"});"});
indenter.indent(cursor, QChar::Null, TextEditor::TabSettings());
ASSERT_THAT(documentLines(),
ElementsAre("foo([]()",
" {",
" ",
" });"));
}
TEST_F(ClangFormat, IndentScopeAsFunctionArgument)
{
insertLines({"foo(",
"{",
"",
"});"});
indenter.indent(cursor, QChar::Null, TextEditor::TabSettings());
ASSERT_THAT(documentLines(),
ElementsAre("foo(",
" {",
" ",
" });"));
}
TEST_F(ClangFormat, IndentInsideStructuredBinding)
{
insertLines({"auto [a,",
"b] = c;"});
indenter.indent(cursor, QChar::Null, TextEditor::TabSettings());
ASSERT_THAT(documentLines(), ElementsAre("auto [a,",
" b] = c;"));
}
TEST_F(ClangFormat, IndentMacrosWithoutSemicolon)
{
insertLines({"void test()",
"{",
"ASSERT(1);",
"ASSERT(2)",
"ASSERT(3)",
"ASSERT(4);",
"ASSERT(5)",
"}"});
indenter.indent(cursor, QChar::Null, TextEditor::TabSettings());
ASSERT_THAT(documentLines(), ElementsAre("void test()",
"{",
" ASSERT(1);",
" ASSERT(2)",
" ASSERT(3)",
" ASSERT(4);",
" ASSERT(5)",
"}"));
}
TEST_F(ClangFormat, IndentAfterSquareBracesInsideBraceInitialization)
{
insertLines({"int foo() {",
"char a = char{b[0]};",
"int c;",
"}"});
indenter.indent(cursor, QChar::Null, TextEditor::TabSettings());
ASSERT_THAT(documentLines(), ElementsAre("int foo() {",
" char a = char{b[0]};",
" int c;",
"}"));
}
TEST_F(ClangFormat, IndentStringLiteralContinuation)
{
insertLines({"foo(bar, \"foo\"",
"\"bar\");"});
indenter.indent(cursor, QChar::Null, TextEditor::TabSettings());
ASSERT_THAT(documentLines(), ElementsAre("foo(bar, \"foo\"",
" \"bar\");"));
}
TEST_F(ClangFormat, IndentTemplateparameters)
{
insertLines({"using Alias = Template<A,",
"B,",
"C>"});
indenter.indent(cursor, QChar::Null, TextEditor::TabSettings());
ASSERT_THAT(documentLines(), ElementsAre("using Alias = Template<A,",
" B,",
" C>"));
}
TEST_F(ClangFormat, NoExtraIndentAfterStatementInsideSquareBraces)
{
insertLines({"{",
" x[y=z];",
" int a;",
"}"});
indenter.indent(cursor, QChar::Null, TextEditor::TabSettings());
ASSERT_THAT(documentLines(), ElementsAre("{",
" x[y=z];",
" int a;",
"}"));
}
TEST_F(ClangFormat, NoExtraIndentAfterBraceInitialization)
{
insertLines({"int j{i?5:10};",
"return 0;"});
indenter.indent(cursor, QChar::Null, TextEditor::TabSettings());
ASSERT_THAT(documentLines(), ElementsAre("int j{i?5:10};",
"return 0;"));
}
TEST_F(ClangFormat, IndentMultipleEmptyLines)
{
insertLines({"{",
"",
"",
"",
"}"});
indenter.indent(cursor, QChar::Null, TextEditor::TabSettings());
ASSERT_THAT(documentLines(), ElementsAre("{",
" ",
" ",
" ",
"}"));
}
TEST_F(ClangFormat, IndentEmptyLineAndKeepPreviousEmptyLines)
{
insertLines({"{",
" ",
" ",
"",
"}"});
indenter.indentBlock(doc.findBlockByNumber(3), QChar::Null, TextEditor::TabSettings());
ASSERT_THAT(documentLines(), ElementsAre("{",
" ",
" ",
" ",
"}"));
}
TEST_F(ClangFormat, IndentOnElectricCharacterButNotRemoveEmptyLinesBefore)
{
insertLines({"{",
" ",
" ",
"if ()",
"}"});
indenter.indentBlock(doc.findBlockByNumber(3), '(', TextEditor::TabSettings());
ASSERT_THAT(documentLines(), ElementsAre("{",
" ",
" ",
" if ()",
"}"));
}
TEST_F(ClangFormat, IndentAfterExtraSpaceInpreviousLine)
{
insertLines({"if (a ",
"&& b)"});
indenter.indentBlock(doc.findBlockByNumber(1), QChar::Null, TextEditor::TabSettings());
ASSERT_THAT(documentLines(), ElementsAre("if (a",
" && b)"));
}
TEST_F(ClangFormat, IndentEmptyLineInsideParantheses)
{
insertLines({"if (a ",
"",
" && b)"});
indenter.indentBlock(doc.findBlockByNumber(1), QChar::Null, TextEditor::TabSettings());
ASSERT_THAT(documentLines(), ElementsAre("if (a",
" ",
" && b)"));
}
TEST_F(ClangFormat, IndentInsideIf)
{
insertLines({"if (a && b",
")"});
indenter.indentBlock(doc.findBlockByNumber(1), QChar::Null, TextEditor::TabSettings());
ASSERT_THAT(documentLines(), ElementsAre("if (a && b",
" )"));
}
TEST_F(ClangFormat, IndentInsideIf2)
{
insertLines({"if (a && b &&",
")"});
indenter.indentBlock(doc.findBlockByNumber(1), QChar::Null, TextEditor::TabSettings());
ASSERT_THAT(documentLines(), ElementsAre("if (a && b &&",
" )"));
}
TEST_F(ClangFormat, IndentInsideIf3)
{
insertLines({"if (a || b",
")"});
indenter.indentBlock(doc.findBlockByNumber(1), QChar::Null, TextEditor::TabSettings());
ASSERT_THAT(documentLines(), ElementsAre("if (a || b",
" )"));
}
TEST_F(ClangFormat, EmptyLineInInitializerList)
{
insertLines({"Bar foo{a,",
"",
"};"});
indenter.indentBlock(doc.findBlockByNumber(1), QChar::Null, TextEditor::TabSettings());
ASSERT_THAT(documentLines(), ElementsAre("Bar foo{a,",
" ",
"};"));
}
TEST_F(ClangFormat, IndentClosingBraceAfterComma)
{
insertLines({"Bar foo{a,",
"}"});
indenter.indentBlock(doc.findBlockByNumber(1), QChar::Null, TextEditor::TabSettings());
ASSERT_THAT(documentLines(), ElementsAre("Bar foo{a,",
" }"));
}
TEST_F(ClangFormat, DoNotIndentClosingBraceAfterSemicolon)
{
insertLines({"{",
" a;"
"}"});
indenter.indentBlock(doc.findBlockByNumber(2), QChar::Null, TextEditor::TabSettings());
ASSERT_THAT(documentLines(), ElementsAre("{",
" a;"
"}"));
}
TEST_F(ClangFormat, IndentAfterIf)
{
insertLines({"if (a)",
""});
indenter.indentBlock(doc.findBlockByNumber(1), QChar::Null, TextEditor::TabSettings());
ASSERT_THAT(documentLines(), ElementsAre("if (a)",
" "));
}
TEST_F(ClangFormat, IndentAfterElse)
{
insertLines({"if (a)",
" foo();",
"else",
""});
indenter.indentBlock(doc.findBlockByNumber(3), QChar::Null, TextEditor::TabSettings());
ASSERT_THAT(documentLines(), ElementsAre("if (a)",
" foo();",
"else",
" "));
}
TEST_F(ClangFormat, SameIndentAfterSecondNewLineAfterIf)
{
insertLines({"if (a)",
" ",
""});
indenter.indentBlock(doc.findBlockByNumber(2), QChar::Null, TextEditor::TabSettings());
ASSERT_THAT(documentLines(), ElementsAre("if (a)",
" ",
" "));
}
TEST_F(ClangFormat, IndentAfterNewLineInsideIfWithFunctionCall)
{
insertLines({"if (foo()",
")"});
indenter.indentBlock(doc.findBlockByNumber(1), QChar::Null, TextEditor::TabSettings());
ASSERT_THAT(documentLines(), ElementsAre("if (foo()",
" )"));
}
TEST_F(ClangFormat, SameIndentAfterSecondNewLineInsideIfWithFunctionCall)
{
insertLines({"if (foo()",
" ",
")"});
indenter.indentBlock(doc.findBlockByNumber(2), QChar::Null, TextEditor::TabSettings());
ASSERT_THAT(documentLines(), ElementsAre("if (foo()",
" ",
" )"));
}
TEST_F(ClangFormat, SameIndentAfterSecondNonEmptyNewLineInsideIfWithFunctionCall)
{
insertLines({"if (foo()",
" ",
"bar)"});
indenter.indentBlock(doc.findBlockByNumber(2), QChar::Null, TextEditor::TabSettings());
ASSERT_THAT(documentLines(), ElementsAre("if (foo()",
" ",
" bar)"));
}
TEST_F(ClangFormat, SameIndentsOnNewLinesAfterComments)
{
insertLines({"namespace {} //comment",
"",
""});
indenter.indentBlock(doc.findBlockByNumber(2), QChar::Null, TextEditor::TabSettings());
ASSERT_THAT(documentLines(), ElementsAre("namespace {} //comment",
"",
""));
}
TEST_F(ClangFormat, IndentAfterEmptyLineAfterAngledIncludeDirective)
{
insertLines({"#include <string>",
"",
"using namespace std;"});
indenter.indentBlock(doc.findBlockByNumber(2), QChar::Null, TextEditor::TabSettings());
ASSERT_THAT(documentLines(), ElementsAre("#include <string>",
"",
"using namespace std;"));
}
TEST_F(ClangFormat, IndentAfterEmptyLineAfterQuotedIncludeDirective)
{
insertLines({"#include \"foo.h\"",
"",
"using namespace std;"});
indenter.indentBlock(doc.findBlockByNumber(2), QChar::Null, TextEditor::TabSettings());
ASSERT_THAT(documentLines(), ElementsAre("#include \"foo.h\"",
"",
"using namespace std;"));
}
TEST_F(ClangFormat, IndentAfterLineComment)
{
insertLines({"int foo()",
"{",
" // Comment",
" ",
" if (",
"}"});
indenter.indentBlock(doc.findBlockByNumber(4), '(', TextEditor::TabSettings());
ASSERT_THAT(documentLines(), ElementsAre("int foo()",
"{",
" // Comment",
" ",
" if (",
"}"));
}
TEST_F(ClangFormat, IndentAfterBlockComment)
{
insertLines({"int foo()",
"{",
" bar(); /* Comment */",
" ",
" if (",
"}"});
indenter.indentBlock(doc.findBlockByNumber(4), '(', TextEditor::TabSettings());
ASSERT_THAT(documentLines(), ElementsAre("int foo()",
"{",
" bar(); /* Comment */",
" ",
" if (",
"}"));
}
TEST_F(ClangFormat, IndentAfterIfdef)
{
insertLines({"int foo()",
"{",
"#ifdef FOO",
"#endif",
" ",
" if (",
"}"});
indenter.indentBlock(doc.findBlockByNumber(5), '(', TextEditor::TabSettings());
ASSERT_THAT(documentLines(), ElementsAre("int foo()",
"{",
"#ifdef FOO",
"#endif",
" ",
" if (",
"}"));
}
TEST_F(ClangFormat, IndentAfterEmptyLineInTheFileBeginning)
{
insertLines({"",
"void foo()"});
indenter.indentBlock(doc.findBlockByNumber(1), ')', TextEditor::TabSettings());
ASSERT_THAT(documentLines(), ElementsAre("",
"void foo()"));
}
TEST_F(ClangFormat, IndentFunctionBodyButNotFormatBeforeIt)
{
insertLines({"int foo(int a, int b,",
" int c, int d",
" ) {",
"",
"}"});
extendedIndenter.indentBlock(doc.findBlockByNumber(3), QChar::Null, TextEditor::TabSettings());
ASSERT_THAT(documentLines(), ElementsAre("int foo(int a, int b,",
" int c, int d",
" ) {",
" ",
"}"));
}
TEST_F(ClangFormat, IndentAfterFunctionBodyAndNotFormatBefore)
{
insertLines({"int foo(int a, int b, int c, int d)",
"{",
" ",
"}"});
extendedIndenter.indentBlock(doc.findBlockByNumber(3),
QChar::Null,
TextEditor::TabSettings(),
doc.characterCount() - 3);
ASSERT_THAT(documentLines(), ElementsAre("int foo(int a, int b, int c, int d)",
"{",
" ",
"}"));
}
TEST_F(ClangFormat, ReformatToEmptyFunction)
{
insertLines({"int foo(int a, int b, int c, int d)",
"{",
" ",
"}"});
extendedIndenter.indentBlock(doc.findBlockByNumber(3), '}', TextEditor::TabSettings());
ASSERT_THAT(documentLines(), ElementsAre("int foo(int a, int b, int c, int d) {}"));
}
TEST_F(ClangFormat, ReformatToNonEmptyFunction)
{
insertLines({"int foo(int a, int b) {",
"",
"}"});
extendedIndenter.indentBlock(doc.findBlockByNumber(1), QChar::Null, TextEditor::TabSettings());
ASSERT_THAT(documentLines(), ElementsAre("int foo(int a, int b) {",
" ",
"}"));
}
TEST_F(ClangFormat, IndentClosingScopeAndFormatBeforeIt)
{
insertLines({"if(a && b",
" &&c && d",
" ) {",
"",
"}"});
extendedIndenter.indentBlock(doc.findBlockByNumber(4), '}', TextEditor::TabSettings());
ASSERT_THAT(documentLines(), ElementsAre("if (a && b && c && d) {",
"}"));
}
TEST_F(ClangFormat, DoNotFormatAfterTheFirstColon)
{
insertLines({"{",
" Qt:",
"}"});
extendedIndenter.indentBlock(doc.findBlockByNumber(1), ':', TextEditor::TabSettings(), 9);
ASSERT_THAT(documentLines(), ElementsAre("{",
" Qt:",
"}"));
}
TEST_F(ClangFormat, OnlyIndentIncompleteStatementOnElectricalCharacter)
{
insertLines({"{bar();",
"foo()",
"}"});
extendedIndenter.indentBlock(doc.findBlockByNumber(1), '(', TextEditor::TabSettings(), 12);
ASSERT_THAT(documentLines(), ElementsAre("{bar();",
" foo()",
"}"));
}
TEST_F(ClangFormat, IndentAndFormatCompleteStatementOnSemicolon)
{
insertLines({"{bar();",
"foo();",
"}"});
extendedIndenter.indentBlock(doc.findBlockByNumber(1), ';', TextEditor::TabSettings(), 14);
ASSERT_THAT(documentLines(), ElementsAre("{",
" bar();",
" foo();",
"}"));
}
TEST_F(ClangFormat, IndentAndFormatCompleteStatementOnClosingScope)
{
insertLines({"{bar();",
"foo();",
"}"});
extendedIndenter.indentBlock(doc.findBlockByNumber(1), '}', TextEditor::TabSettings(), 16);
ASSERT_THAT(documentLines(), ElementsAre("{",
" bar();",
" foo();",
"}"));
}
TEST_F(ClangFormat, OnlyIndentClosingParenthesis)
{
insertLines({"foo(a,",
" ",
")"});
extendedIndenter.indentBlock(doc.findBlockByNumber(2), QChar::Null, TextEditor::TabSettings());
ASSERT_THAT(documentLines(), ElementsAre("foo(a,",
" ",
" )"));
}
TEST_F(ClangFormat, EquallyIndentInsideParenthesis)
{
insertLines({"if (a",
")"});
extendedIndenter.indentBlock(doc.findBlockByNumber(1), QChar::Null, TextEditor::TabSettings());
auto linesAfterFirstLineBreak = documentLines();
insertLines({"if (a",
" ",
")"});
extendedIndenter.indentBlock(doc.findBlockByNumber(2), QChar::Null, TextEditor::TabSettings());
ASSERT_THAT(linesAfterFirstLineBreak, ElementsAre("if (a",
" )"));
ASSERT_THAT(documentLines(), ElementsAre("if (a",
" ",
" )"));
}
TEST_F(ClangFormat, FormatBasicFile)
{
insertLines({"int main()",
"{",
"int a;",
"}"});
indenter.format({{1, 4}});
ASSERT_THAT(documentLines(), ElementsAre("int main()",
"{",
" int a;",
"}"));
}
TEST_F(ClangFormat, FormatEmptyLine)
{
insertLines({"int main()",
"{",
"",
"}"});
indenter.format({{1, 4}});
ASSERT_THAT(documentLines(), ElementsAre("int main() {}"));
}
TEST_F(ClangFormat, FormatLambda)
{
insertLines({"int b = foo([](){",
"",
"});"});
indenter.format({{1, 3}});
ASSERT_THAT(documentLines(), ElementsAre("int b = foo([]() {",
"",
"});"));
}
TEST_F(ClangFormat, FormatInitializerListInArguments)
{
insertLines({"foo(arg1,",
"args,",
"{1, 2});"});
indenter.format({{1, 3}});
ASSERT_THAT(documentLines(), ElementsAre("foo(arg1, args, {1, 2});"));
}
TEST_F(ClangFormat, FormatFunctionArgumentLambdaWithScope)
{
insertLines({"foo([]()",
"{",
"",
"});"});
indenter.format({{1, 4}});
ASSERT_THAT(documentLines(),
ElementsAre("foo([]() {",
"",
"});"));
}
TEST_F(ClangFormat, FormatScopeAsFunctionArgument)
{
insertLines({"foo(",
"{",
"",
"});"});
indenter.format({{1, 4}});
ASSERT_THAT(documentLines(),
ElementsAre("foo({",
"",
"});"));
}
TEST_F(ClangFormat, FormatStructuredBinding)
{
insertLines({"auto [a,",
"b] = c;"});
indenter.format({{1, 2}});
ASSERT_THAT(documentLines(), ElementsAre("auto [a, b] = c;"));
}
TEST_F(ClangFormat, FormatStringLiteralContinuation)
{
insertLines({"foo(bar, \"foo\"",
"\"bar\");"});
indenter.format({{1, 2}});
ASSERT_THAT(documentLines(), ElementsAre("foo(bar,",
" \"foo\"",
" \"bar\");"));
}
TEST_F(ClangFormat, FormatTemplateparameters)
{
insertLines({"using Alias = Template<A,",
"B,",
"C>"});
indenter.format({{1, 3}});
ASSERT_THAT(documentLines(), ElementsAre("using Alias = Template<A, B, C>"));
}
TEST_F(ClangFormat, SortIncludes)
{
insertLines({"#include \"b.h\"",
"#include \"a.h\"",
"",
"#include <bb.h>",
"#include <aa.h>"});
indenter.format({{1, 5}});
ASSERT_THAT(documentLines(), ElementsAre("#include \"a.h\"",
"#include \"b.h\"",
"",
"#include <aa.h>",
"#include <bb.h>"));
}
TEST_F(ClangFormat, ChainedMemberFunctionCalls)
{
insertLines({"S().func().func()",
".func();"});
indenter.indent(cursor, QChar::Null, TextEditor::TabSettings());
ASSERT_THAT(documentLines(), ElementsAre("S().func().func()",
" .func();"));
}
// clang-format on
} // namespace