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

llvm-svn: 303049
This commit is contained in:
Martin Probst 2017-05-15 11:15:29 +00:00
parent 4ab5193735
commit 2c1cdae2df
4 changed files with 52 additions and 12 deletions

View File

@ -674,6 +674,8 @@ unsigned ContinuationIndenter::getNewLineColumn(const LineState &State) {
return State.Stack[State.Stack.size() - 2].LastSpace; return State.Stack[State.Stack.size() - 2].LastSpace;
return State.FirstIndent; 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()) if (NextNonComment->is(TT_TemplateString) && NextNonComment->closesScope())
return State.Stack[State.Stack.size() - 2].LastSpace; return State.Stack[State.Stack.size() - 2].LastSpace;
if (Current.is(tok::identifier) && Current.Next && if (Current.is(tok::identifier) && Current.Next &&
@ -1038,13 +1040,20 @@ void ContinuationIndenter::moveStatePastScopeOpener(LineState &State,
NestedBlockIndent = Column; NestedBlockIndent = Column;
} }
bool EndsInComma =
Current.MatchingParen &&
Current.MatchingParen->getPreviousNonComment() &&
Current.MatchingParen->getPreviousNonComment()->is(tok::comma);
AvoidBinPacking = AvoidBinPacking =
(Style.Language == FormatStyle::LK_JavaScript && EndsInComma) ||
(State.Line->MustBeDeclaration && !Style.BinPackParameters) || (State.Line->MustBeDeclaration && !Style.BinPackParameters) ||
(!State.Line->MustBeDeclaration && !Style.BinPackArguments) || (!State.Line->MustBeDeclaration && !Style.BinPackArguments) ||
(Style.ExperimentalAutoDetectBinPacking && (Style.ExperimentalAutoDetectBinPacking &&
(Current.PackingKind == PPK_OnePerLine || (Current.PackingKind == PPK_OnePerLine ||
(!BinPackInconclusiveFunctions && (!BinPackInconclusiveFunctions &&
Current.PackingKind == PPK_Inconclusive))); Current.PackingKind == PPK_Inconclusive)));
if (Current.is(TT_ObjCMethodExpr) && Current.MatchingParen) { if (Current.is(TT_ObjCMethodExpr) && Current.MatchingParen) {
if (Style.ColumnLimit) { if (Style.ColumnLimit) {
// If this '[' opens an ObjC call, determine whether all parameters fit // 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. // Generally inherit NoLineBreak from the current scope to nested scope.
// However, don't do this for non-empty nested blocks, dict literals and // However, don't do this for non-empty nested blocks, dict literals and

View File

@ -2463,16 +2463,20 @@ bool TokenAnnotator::mustBreakBefore(const AnnotatedLine &Line,
return true; return true;
} }
// If the last token before a '}' is a comma or a trailing comment, the // If the last token before a '}', ']', or ')' is a comma or a trailing
// intention is to insert a line break after it in order to make shuffling // comment, the intention is to insert a line break after it in order to make
// around entries easier. // shuffling around entries easier.
const FormatToken *BeforeClosingBrace = nullptr; 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) Left.BlockKind != BK_Block && Left.MatchingParen)
BeforeClosingBrace = Left.MatchingParen->Previous; BeforeClosingBrace = Left.MatchingParen->Previous;
else if (Right.MatchingParen && else if (Right.MatchingParen &&
Right.MatchingParen->isOneOf(tok::l_brace, (Right.MatchingParen->isOneOf(tok::l_brace,
TT_ArrayInitializerLSquare)) TT_ArrayInitializerLSquare) ||
(Style.Language == FormatStyle::LK_JavaScript &&
Right.MatchingParen->is(tok::l_paren))))
BeforeClosingBrace = &Left; BeforeClosingBrace = &Left;
if (BeforeClosingBrace && (BeforeClosingBrace->is(tok::comma) || if (BeforeClosingBrace && (BeforeClosingBrace->is(tok::comma) ||
BeforeClosingBrace->isTrailingComment())) BeforeClosingBrace->isTrailingComment()))

View File

@ -2106,7 +2106,7 @@ TEST_F(FormatTest, LayoutStatementsAroundPreprocessorDirectives) {
verifyIncompleteFormat("void f(\n" verifyIncompleteFormat("void f(\n"
"#if A\n" "#if A\n"
" );\n" ");\n"
"#else\n" "#else\n"
"#endif"); "#endif");
} }
@ -4475,7 +4475,7 @@ TEST_F(FormatTest, WrapsTemplateDeclarations) {
EXPECT_EQ("static_cast<A< //\n" EXPECT_EQ("static_cast<A< //\n"
" B> *>(\n" " B> *>(\n"
"\n" "\n"
" );", ");",
format("static_cast<A<//\n" format("static_cast<A<//\n"
" B>*>(\n" " B>*>(\n"
"\n" "\n"
@ -6567,7 +6567,7 @@ TEST_F(FormatTest, BreaksStringLiteralsWithin_TMacro) {
"#if !TEST\n" "#if !TEST\n"
" _T(\"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXn\")\n" " _T(\"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXn\")\n"
"#endif\n" "#endif\n"
" );", ");",
format("f(\n" format("f(\n"
"#if !TEST\n" "#if !TEST\n"
"_T(\"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXn\")\n" "_T(\"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXn\")\n"

View File

@ -550,6 +550,30 @@ TEST_F(FormatTestJS, AsyncFunctions) {
"}\n"); "}\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) { TEST_F(FormatTestJS, ArrayLiterals) {
verifyFormat("var aaaaa: List<SomeThing> =\n" verifyFormat("var aaaaa: List<SomeThing> =\n"
" [new SomeThingAAAAAAAAAAAA(), new SomeThingBBBBBBBBB()];"); " [new SomeThingAAAAAAAAAAAA(), new SomeThingBBBBBBBBB()];");