From 41505a741963aff60d475ed8961dce913462e629 Mon Sep 17 00:00:00 2001 From: Martin Probst <martin@probst.io> Date: Mon, 15 May 2017 11:15:29 +0000 Subject: [PATCH] JavaScript allows parameter lists to include trailing commas: myFunction(param1, param2,); For symmetry with other parenthesized lists ([...], {...}), clang-format should wrap parenthesized lists one-per-line if they contain a trailing comma: myFunction( param1, param2, ); This is particularly useful in function declarations or calls with many arguments, e.g. commonly in constructors. Differential Revision: https://reviews.llvm.org/D33023 git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@303049 91177308-0d34-0410-b5e6-96231b3b80d8 --- lib/Format/ContinuationIndenter.cpp | 12 ++++++++++++ lib/Format/TokenAnnotator.cpp | 16 ++++++++++------ unittests/Format/FormatTest.cpp | 8 ++++---- unittests/Format/FormatTestJS.cpp | 28 ++++++++++++++++++++++++++-- 4 files changed, 52 insertions(+), 12 deletions(-) diff --git a/lib/Format/ContinuationIndenter.cpp b/lib/Format/ContinuationIndenter.cpp index 709eeb1539a..488f9dd582f 100644 --- a/lib/Format/ContinuationIndenter.cpp +++ b/lib/Format/ContinuationIndenter.cpp @@ -674,6 +674,8 @@ unsigned ContinuationIndenter::getNewLineColumn(const LineState &State) { return State.Stack[State.Stack.size() - 2].LastSpace; return State.FirstIndent; } + if (Current.is(tok::r_paren) && State.Stack.size() > 1) + return State.Stack[State.Stack.size() - 2].LastSpace; if (NextNonComment->is(TT_TemplateString) && NextNonComment->closesScope()) return State.Stack[State.Stack.size() - 2].LastSpace; if (Current.is(tok::identifier) && Current.Next && @@ -1038,13 +1040,20 @@ void ContinuationIndenter::moveStatePastScopeOpener(LineState &State, NestedBlockIndent = Column; } + bool EndsInComma = + Current.MatchingParen && + Current.MatchingParen->getPreviousNonComment() && + Current.MatchingParen->getPreviousNonComment()->is(tok::comma); + AvoidBinPacking = + (Style.Language == FormatStyle::LK_JavaScript && EndsInComma) || (State.Line->MustBeDeclaration && !Style.BinPackParameters) || (!State.Line->MustBeDeclaration && !Style.BinPackArguments) || (Style.ExperimentalAutoDetectBinPacking && (Current.PackingKind == PPK_OnePerLine || (!BinPackInconclusiveFunctions && Current.PackingKind == PPK_Inconclusive))); + if (Current.is(TT_ObjCMethodExpr) && Current.MatchingParen) { if (Style.ColumnLimit) { // If this '[' opens an ObjC call, determine whether all parameters fit @@ -1065,6 +1074,9 @@ void ContinuationIndenter::moveStatePastScopeOpener(LineState &State, } } } + + if (Style.Language == FormatStyle::LK_JavaScript && EndsInComma) + BreakBeforeParameter = true; } // Generally inherit NoLineBreak from the current scope to nested scope. // However, don't do this for non-empty nested blocks, dict literals and diff --git a/lib/Format/TokenAnnotator.cpp b/lib/Format/TokenAnnotator.cpp index bd5e2ed2f41..ad2166cfd3b 100644 --- a/lib/Format/TokenAnnotator.cpp +++ b/lib/Format/TokenAnnotator.cpp @@ -2463,16 +2463,20 @@ bool TokenAnnotator::mustBreakBefore(const AnnotatedLine &Line, return true; } - // If the last token before a '}' is a comma or a trailing comment, the - // intention is to insert a line break after it in order to make shuffling - // around entries easier. + // If the last token before a '}', ']', or ')' is a comma or a trailing + // comment, the intention is to insert a line break after it in order to make + // shuffling around entries easier. const FormatToken *BeforeClosingBrace = nullptr; - if (Left.isOneOf(tok::l_brace, TT_ArrayInitializerLSquare) && + if ((Left.isOneOf(tok::l_brace, TT_ArrayInitializerLSquare) || + (Style.Language == FormatStyle::LK_JavaScript && + Left.is(tok::l_paren))) && Left.BlockKind != BK_Block && Left.MatchingParen) BeforeClosingBrace = Left.MatchingParen->Previous; else if (Right.MatchingParen && - Right.MatchingParen->isOneOf(tok::l_brace, - TT_ArrayInitializerLSquare)) + (Right.MatchingParen->isOneOf(tok::l_brace, + TT_ArrayInitializerLSquare) || + (Style.Language == FormatStyle::LK_JavaScript && + Right.MatchingParen->is(tok::l_paren)))) BeforeClosingBrace = &Left; if (BeforeClosingBrace && (BeforeClosingBrace->is(tok::comma) || BeforeClosingBrace->isTrailingComment())) diff --git a/unittests/Format/FormatTest.cpp b/unittests/Format/FormatTest.cpp index cec25440a55..076041406c8 100644 --- a/unittests/Format/FormatTest.cpp +++ b/unittests/Format/FormatTest.cpp @@ -2106,7 +2106,7 @@ TEST_F(FormatTest, LayoutStatementsAroundPreprocessorDirectives) { verifyIncompleteFormat("void f(\n" "#if A\n" - " );\n" + ");\n" "#else\n" "#endif"); } @@ -4475,7 +4475,7 @@ TEST_F(FormatTest, WrapsTemplateDeclarations) { EXPECT_EQ("static_cast<A< //\n" " B> *>(\n" "\n" - " );", + ");", format("static_cast<A<//\n" " B>*>(\n" "\n" @@ -6567,7 +6567,7 @@ TEST_F(FormatTest, BreaksStringLiteralsWithin_TMacro) { "#if !TEST\n" " _T(\"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXn\")\n" "#endif\n" - " );", + ");", format("f(\n" "#if !TEST\n" "_T(\"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXn\")\n" @@ -9639,7 +9639,7 @@ TEST_F(FormatTest, FormatsLambdas) { // Other corner cases. verifyFormat("void f() {\n" " bar([]() {} // Did not respect SpacesBeforeTrailingComments\n" - " );\n" + " );\n" "}"); // Lambdas created through weird macros. diff --git a/unittests/Format/FormatTestJS.cpp b/unittests/Format/FormatTestJS.cpp index c47d3ef55bc..b556e995d1e 100644 --- a/unittests/Format/FormatTestJS.cpp +++ b/unittests/Format/FormatTestJS.cpp @@ -550,6 +550,30 @@ TEST_F(FormatTestJS, AsyncFunctions) { "}\n"); } +TEST_F(FormatTestJS, FunctionParametersTrailingComma) { + verifyFormat("function trailingComma(\n" + " p1,\n" + " p2,\n" + " p3,\n" + ") {\n" + " a; //\n" + "}\n", + "function trailingComma(p1, p2, p3,) {\n" + " a; //\n" + "}\n"); + verifyFormat("trailingComma(\n" + " p1,\n" + " p2,\n" + " p3,\n" + ");\n", + "trailingComma(p1, p2, p3,);\n"); + verifyFormat("trailingComma(\n" + " p1 // hello\n" + ");\n", + "trailingComma(p1 // hello\n" + ");\n"); +} + TEST_F(FormatTestJS, ArrayLiterals) { verifyFormat("var aaaaa: List<SomeThing> =\n" " [new SomeThingAAAAAAAAAAAA(), new SomeThingBBBBBBBBB()];"); @@ -691,7 +715,7 @@ TEST_F(FormatTestJS, FunctionLiterals) { "})\n" " .doSomethingElse(\n" " // break\n" - " );"); + " );"); Style.ColumnLimit = 33; verifyFormat("f({a: function() { return 1; }});", Style); @@ -858,7 +882,7 @@ TEST_F(FormatTestJS, ArrowFunctions) { "})\n" " .doSomethingElse(\n" " // break\n" - " );"); + " );"); } TEST_F(FormatTestJS, ReturnStatements) { -- GitLab