diff --git a/CodeFormatCore/include/CodeFormatCore/Config/LuaStyle.h b/CodeFormatCore/include/CodeFormatCore/Config/LuaStyle.h index d6c8088e..4f3fe6b0 100644 --- a/CodeFormatCore/include/CodeFormatCore/Config/LuaStyle.h +++ b/CodeFormatCore/include/CodeFormatCore/Config/LuaStyle.h @@ -159,6 +159,9 @@ class LuaStyle { bool break_before_braces = false; BreakTableList break_table_list = BreakTableList::Smart; + + // keep/always/never: break between '=' and its value + BreakAfterAssign break_after_assignment_statement = BreakAfterAssign::Never; // [preference] bool ignore_space_after_colon = false; diff --git a/CodeFormatCore/include/CodeFormatCore/Config/LuaStyleEnum.h b/CodeFormatCore/include/CodeFormatCore/Config/LuaStyleEnum.h index 1fdcd4ac..a4a3f22b 100644 --- a/CodeFormatCore/include/CodeFormatCore/Config/LuaStyleEnum.h +++ b/CodeFormatCore/include/CodeFormatCore/Config/LuaStyleEnum.h @@ -98,6 +98,16 @@ enum class BreakTableList { Lazy }; +// Controls the line break between an assignment/table-field '=' and its value. +enum class BreakAfterAssign { + // Leave the source as it is. + Keep, + // Always place the value on a new line after '='. + Always, + // Never break after '='; collapse the value onto the '=' line. + Never, +}; + enum class SpaceAroundStyle { // for false and none None, diff --git a/CodeFormatCore/include/CodeFormatCore/Format/Analyzer/LineBreakAnalyzer.h b/CodeFormatCore/include/CodeFormatCore/Format/Analyzer/LineBreakAnalyzer.h index 32fb7302..af455b56 100644 --- a/CodeFormatCore/include/CodeFormatCore/Format/Analyzer/LineBreakAnalyzer.h +++ b/CodeFormatCore/include/CodeFormatCore/Format/Analyzer/LineBreakAnalyzer.h @@ -44,6 +44,8 @@ class LineBreakAnalyzer : public FormatAnalyzer { void AnalyzeTableExpr(FormatState &f, LuaSyntaxNode table, const LuaSyntaxTree &t); + void AnalyzeAssignBreak(FormatState &f, LuaSyntaxNode assignNode, const LuaSyntaxTree &t); + bool CanBreakAll(FormatState &f, LuaSyntaxNode n, const LuaSyntaxTree &t); bool CanCollapseLines(FormatState &f, LuaSyntaxNode &n, const LuaSyntaxTree &t); diff --git a/CodeFormatCore/src/Config/LuaStyle.cpp b/CodeFormatCore/src/Config/LuaStyle.cpp index 246d587c..bedf3078 100644 --- a/CodeFormatCore/src/Config/LuaStyle.cpp +++ b/CodeFormatCore/src/Config/LuaStyle.cpp @@ -392,6 +392,16 @@ void LuaStyle::Parse(std::map> &configMap) } } + IF_EXIST(break_after_assignment_statement) { + if (value == "keep") { + break_after_assignment_statement = BreakAfterAssign::Keep; + } else if (value == "always") { + break_after_assignment_statement = BreakAfterAssign::Always; + } else if (value == "never") { + break_after_assignment_statement = BreakAfterAssign::Never; + } + } + BOOL_OPTION(ignore_space_after_colon) BOOL_OPTION(remove_call_expression_list_finish_comma) diff --git a/CodeFormatCore/src/Format/Analyzer/LineBreakAnalyzer.cpp b/CodeFormatCore/src/Format/Analyzer/LineBreakAnalyzer.cpp index 9bb668a0..e58f86e6 100644 --- a/CodeFormatCore/src/Format/Analyzer/LineBreakAnalyzer.cpp +++ b/CodeFormatCore/src/Format/Analyzer/LineBreakAnalyzer.cpp @@ -134,12 +134,17 @@ void LineBreakAnalyzer::ComplexAnalyze(FormatState &f, const LuaSyntaxTree &t) { case LuaSyntaxNodeKind::LocalStatement: case LuaSyntaxNodeKind::AssignStatement: case LuaSyntaxNodeKind::ReturnStatement: { + AnalyzeAssignBreak(f, syntaxNode, t); auto exprList = syntaxNode.GetChildSyntaxNode(NodeKind::ExpressionList, t); if (exprList.IsNode(t)) { AnalyzeExprList(f, exprList, t); } break; } + case LuaSyntaxNodeKind::TableField: { + AnalyzeAssignBreak(f, syntaxNode, t); + break; + } case LuaSyntaxNodeKind::IfStatement: { auto exprs = syntaxNode.GetChildSyntaxNodes(LuaSyntaxMultiKind::Expression, t); for (auto expr: exprs) { @@ -317,6 +322,39 @@ void LineBreakAnalyzer::AnalyzeSuffixedExpr(FormatState &f, LuaSyntaxNode expr, } } +void LineBreakAnalyzer::AnalyzeAssignBreak(FormatState &f, LuaSyntaxNode assignNode, const LuaSyntaxTree &t) { + auto strategy = f.GetStyle().break_after_assignment_statement; + if (strategy == BreakAfterAssign::Keep) { + return; + } + + auto eq = assignNode.GetChildToken('=', t); + if (!eq.IsToken(t)) { + return; + } + + auto next = eq.GetNextToken(t); + if (!next.IsToken(t)) { + return; + } + + // a comment after '=' must stay on its own line + auto nextKind = next.GetTokenKind(t); + if (nextKind == TK_SHORT_COMMENT || nextKind == TK_LONG_COMMENT) { + return; + } + + if (strategy == BreakAfterAssign::Never) { + // break_before_braces wins for a table value + if (f.GetStyle().break_before_braces && nextKind == '{') { + return; + } + CancelBreakAfter(eq, t); + } else { + BreakAfter(eq, t, LineSpace(1)); + } +} + // 是格式化的重点内容 void LineBreakAnalyzer::AnalyzeTableExpr(FormatState &f, LuaSyntaxNode table, const LuaSyntaxTree &t) { @@ -335,6 +373,14 @@ void LineBreakAnalyzer::AnalyzeTableExpr(FormatState &f, LuaSyntaxNode table, co return; } + // a multi-line table puts its closing '}' on its own line + if (!table.IsSingleLineNode(t)) { + auto rightBrace = table.GetChildToken('}', t); + if (rightBrace.IsToken(t)) { + BreakBefore(rightBrace, t, LineSpace(LineSpaceType::Keep)); + } + } + // force break bool breakAllList = f.GetStyle().break_all_list_when_line_exceed && CanBreakAll(f, tableFieldList, t); if (!breakAllList && f.GetStyle().break_table_list == BreakTableList::Smart) { diff --git a/Test/src/FormatResult_unitest.cpp b/Test/src/FormatResult_unitest.cpp index e7bf6954..10941cc5 100644 --- a/Test/src/FormatResult_unitest.cpp +++ b/Test/src/FormatResult_unitest.cpp @@ -36,8 +36,7 @@ local t = } )", R"( -local t = -{ +local t = { a = 123 } )")); @@ -107,13 +106,34 @@ a, b, c, d } )", R"( -t = -{ +t = { a, b, c, d } )")); } +TEST(Format, MultiLineTableRightBrace) { + EXPECT_TRUE(TestHelper::TestFormatted( + R"( +t = { + { aaa = 1, bbb = 2 }, + { ccc = 3, ddd = 4 } } +)", + R"( +t = { + { aaa = 1, bbb = 2 }, + { ccc = 3, ddd = 4 } +} +)")); + EXPECT_TRUE(TestHelper::TestFormatted( + R"( +t = { a, b, c } +)", + R"( +t = { a, b, c } +)")); +} + TEST(Format, IfStatement) { EXPECT_TRUE(TestHelper::TestFormatted( R"( diff --git a/Test/src/FormatStyle_unitest.cpp b/Test/src/FormatStyle_unitest.cpp index 8cdbabd4..20ad090c 100644 --- a/Test/src/FormatStyle_unitest.cpp +++ b/Test/src/FormatStyle_unitest.cpp @@ -407,11 +407,13 @@ local t = { { R"( local t = { function() local t = 123 -end } +end +} local t = { { okokok = 123, -} } +} +} )", style)); } @@ -1682,6 +1684,82 @@ local t = style)); } +TEST(FormatByStyleOption, break_after_assignment_statement) { + LuaStyle style; + + style.break_after_assignment_statement = BreakAfterAssign::Never; + EXPECT_TRUE(TestHelper::TestFormatted( + R"( +local t = +{ + a = 123 +} +local d = +{ + aaa = + { 1, 2, 3 } +} +)", + R"( +local t = { + a = 123 +} +local d = { + aaa = { 1, 2, 3 } +} +)", + style)); + + style.break_after_assignment_statement = BreakAfterAssign::Keep; + EXPECT_TRUE(TestHelper::TestFormatted( + R"( +local t = +{ + a = 123 +} +local d = { a = 123 } +)", + R"( +local t = +{ + a = 123 +} +local d = { a = 123 } +)", + style)); + + style.break_after_assignment_statement = BreakAfterAssign::Always; + EXPECT_TRUE(TestHelper::TestFormatted( + R"( +local t = 123 +local d = { a = 123 } +)", + R"( +local t = +123 +local d = +{ a = +123 } +)", + style)); + + style.break_after_assignment_statement = BreakAfterAssign::Never; + style.break_before_braces = true; + EXPECT_TRUE(TestHelper::TestFormatted( + R"( +local t = { + a = 123 +} +)", + R"( +local t = +{ + a = 123 +} +)", + style)); +} + TEST(FormatByStyleOption, end_statement_with_semicolon) { LuaStyle style; diff --git a/docs/format_config_EN.md b/docs/format_config_EN.md index 37ae5c59..c2023b5e 100644 --- a/docs/format_config_EN.md +++ b/docs/format_config_EN.md @@ -516,6 +516,20 @@ This option means that when an expression with a list exceeds the row width sett This option means that expressions on the list should not be interrupted after evaluation, they will all collapse to the same row, the default value is false, and it is currently only valid for tables +### break_after_assignment_statement + +This option controls the line break between an assignment/table-field `=` and its value. It applies to assignment statements, local statements and table fields. + +The possible values are: +- never (default) + never break after `=`; the value is collapsed onto the same line as `=`, for example `key =\n{ ... }` becomes `key = { ... }` +- keep + leave the source layout as it is +- always + always place the value on a new line after `=` + +When the value is a table and `break_before_braces` is enabled, `break_before_braces` takes precedence and the `never` behaviour is skipped for that value. A comment immediately after `=` is never merged onto the `=` line. + ## Preferences ### ignore_space_after_colon diff --git a/lua.template.editorconfig b/lua.template.editorconfig index 37546ff5..c2ae1e01 100644 --- a/lua.template.editorconfig +++ b/lua.template.editorconfig @@ -163,6 +163,9 @@ auto_collapse_lines = false break_before_braces = false +# never / keep / always +break_after_assignment_statement = never + # [preference] ignore_space_after_colon = false