diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst --- a/clang/docs/ClangFormatStyleOptions.rst +++ b/clang/docs/ClangFormatStyleOptions.rst @@ -1960,6 +1960,23 @@ +**BreakBeforeClosingParen** (``CloseBracketAlignmentStyle``) + If ``true``, closing parentheses will be placed on a new line. + + This option is only used if there was also a newline after the + beginning left paren. + + .. code-block:: c++ + + true: + someLongFunction( + argument1, argument2 + ) + + false: + someLongFunction( + argument1, argument2) + **BreakBeforeConceptDeclarations** (``bool``) If ``true``, concept will be placed on a new line. diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -188,6 +188,8 @@ - Option ``AllowShortEnumsOnASingleLine: false`` has been improved, it now correctly places the opening brace according to ``BraceWrapping.AfterEnum``. +- Option ``BreakBeforeClosingParen`` has been added. If ``true``, closing + parentheses will be placed on a new line. libclang -------- diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h --- a/clang/include/clang/Format/Format.h +++ b/clang/include/clang/Format/Format.h @@ -1727,6 +1727,23 @@ /// \endcode BraceWrappingFlags BraceWrapping; + /// If ``true``, closing parentheses will be placed on a new line. + /// + /// This option is used only if there also was a newline after the + /// beginning left paren. + /// + /// \code + /// true: + /// someLongFunction( + /// argument1, argument2 + /// ) + /// + /// false: + /// someLongFunction( + /// argument1, argument2) + /// \endcode + bool BreakBeforeClosingParen; + /// If ``true``, concept will be placed on a new line. /// \code /// true: diff --git a/clang/lib/Format/ContinuationIndenter.h b/clang/lib/Format/ContinuationIndenter.h --- a/clang/lib/Format/ContinuationIndenter.h +++ b/clang/lib/Format/ContinuationIndenter.h @@ -203,15 +203,15 @@ bool AvoidBinPacking, bool NoLineBreak) : Tok(Tok), Indent(Indent), LastSpace(LastSpace), NestedBlockIndent(Indent), IsAligned(false), - BreakBeforeClosingBrace(false), AvoidBinPacking(AvoidBinPacking), - BreakBeforeParameter(false), NoLineBreak(NoLineBreak), - NoLineBreakInOperand(false), LastOperatorWrapped(true), - ContainsLineBreak(false), ContainsUnwrappedBuilder(false), - AlignColons(true), ObjCSelectorNameFound(false), - HasMultipleNestedBlocks(false), NestedBlockInlined(false), - IsInsideObjCArrayLiteral(false), IsCSharpGenericTypeConstraint(false), - IsChainedConditional(false), IsWrappedConditional(false), - UnindentOperator(false) {} + BreakBeforeClosingBrace(false), BreakBeforeClosingParen(false), + AvoidBinPacking(AvoidBinPacking), BreakBeforeParameter(false), + NoLineBreak(NoLineBreak), NoLineBreakInOperand(false), + LastOperatorWrapped(true), ContainsLineBreak(false), + ContainsUnwrappedBuilder(false), AlignColons(true), + ObjCSelectorNameFound(false), HasMultipleNestedBlocks(false), + NestedBlockInlined(false), IsInsideObjCArrayLiteral(false), + IsCSharpGenericTypeConstraint(false), IsChainedConditional(false), + IsWrappedConditional(false), UnindentOperator(false) {} /// \brief The token opening this parenthesis level, or nullptr if this level /// is opened by fake parenthesis. @@ -277,6 +277,13 @@ /// was a newline after the beginning left brace. bool BreakBeforeClosingBrace : 1; + /// Whether a newline needs to be inserted before the block's closing + /// paren. + /// + /// We only want to insert a newline before the closing paren if there also + /// was a newline after the beginning left paren. + bool BreakBeforeClosingParen : 1; + /// Avoid bin packing, i.e. multiple parameters/elements on multiple /// lines, in this context. bool AvoidBinPacking : 1; @@ -362,6 +369,8 @@ return IsAligned; if (BreakBeforeClosingBrace != Other.BreakBeforeClosingBrace) return BreakBeforeClosingBrace; + if (BreakBeforeClosingParen != Other.BreakBeforeClosingParen) + return BreakBeforeClosingParen; if (QuestionColumn != Other.QuestionColumn) return QuestionColumn < Other.QuestionColumn; if (AvoidBinPacking != Other.AvoidBinPacking) diff --git a/clang/lib/Format/ContinuationIndenter.cpp b/clang/lib/Format/ContinuationIndenter.cpp --- a/clang/lib/Format/ContinuationIndenter.cpp +++ b/clang/lib/Format/ContinuationIndenter.cpp @@ -341,6 +341,8 @@ if (State.Stack.back().BreakBeforeClosingBrace && Current.closesBlockOrBlockTypeList(Style)) return true; + if (State.Stack.back().BreakBeforeClosingParen && Current.is(tok::r_paren)) + return true; if (Previous.is(tok::semi) && State.LineContainsContinuedForLoopSection) return true; if (Style.Language == FormatStyle::LK_ObjC && @@ -944,6 +946,10 @@ opensProtoMessageField(*PreviousNonComment, Style))) State.Stack.back().BreakBeforeClosingBrace = true; + if (PreviousNonComment && PreviousNonComment->is(tok::l_paren)) { + State.Stack.back().BreakBeforeClosingParen = Style.BreakBeforeClosingParen; + } + if (State.Stack.back().AvoidBinPacking) { // If we are breaking after '(', '{', '<', or this is the break after a ':' // to start a member initializater list in a constructor, this should not @@ -1038,6 +1044,9 @@ (!Current.Next || Current.Next->isOneOf(tok::semi, tok::kw_const, tok::l_brace))) return State.Stack[State.Stack.size() - 2].LastSpace; + if (Style.BreakBeforeClosingParen && 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 && diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp --- a/clang/lib/Format/Format.cpp +++ b/clang/lib/Format/Format.cpp @@ -607,6 +607,7 @@ IO.mapOptional("BraceWrapping", Style.BraceWrapping); IO.mapOptional("BreakBeforeBinaryOperators", Style.BreakBeforeBinaryOperators); + IO.mapOptional("BreakBeforeClosingParen", Style.BreakBeforeClosingParen); IO.mapOptional("BreakBeforeConceptDeclarations", Style.BreakBeforeConceptDeclarations); IO.mapOptional("BreakBeforeBraces", Style.BreakBeforeBraces); @@ -1046,6 +1047,7 @@ LLVMStyle.BinPackArguments = true; LLVMStyle.BinPackParameters = true; LLVMStyle.BreakBeforeBinaryOperators = FormatStyle::BOS_None; + LLVMStyle.BreakBeforeClosingParen = false; LLVMStyle.BreakBeforeConceptDeclarations = true; LLVMStyle.BreakBeforeTernaryOperators = true; LLVMStyle.BreakBeforeBraces = FormatStyle::BS_Attach; diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp --- a/clang/lib/Format/TokenAnnotator.cpp +++ b/clang/lib/Format/TokenAnnotator.cpp @@ -4188,7 +4188,9 @@ if (Right.is(TT_ImplicitStringLiteral)) return false; - if (Right.is(tok::r_paren) || Right.is(TT_TemplateCloser)) + if (Right.is(tok::r_paren)) + return Style.BreakBeforeClosingParen; + if (Right.is(TT_TemplateCloser)) return false; if (Right.is(tok::r_square) && Right.MatchingParen && Right.MatchingParen->is(TT_LambdaLSquare)) diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp --- a/clang/unittests/Format/FormatTest.cpp +++ b/clang/unittests/Format/FormatTest.cpp @@ -18242,6 +18242,7 @@ CHECK_PARSE_BOOL(BinPackArguments); CHECK_PARSE_BOOL(BinPackParameters); CHECK_PARSE_BOOL(BreakAfterJavaFieldAnnotations); + CHECK_PARSE_BOOL(BreakBeforeClosingParen); CHECK_PARSE_BOOL(BreakBeforeConceptDeclarations); CHECK_PARSE_BOOL(BreakBeforeTernaryOperators); CHECK_PARSE_BOOL(BreakStringLiterals); @@ -22294,6 +22295,162 @@ "}"; EXPECT_EQ(Code, format(Code, Style)); } + +TEST_F(FormatTest, BreakBeforeClosingParen) { + auto Style = getLLVMStyle(); + + StringRef Short = "functionCall(paramA, paramB, paramC);\n" + "void functionDecl(int a, int b, int c);"; + + StringRef Medium = "functionCall(paramA, paramB, paramC, paramD, paramE, " + "paramF, paramG, paramH, paramI);\n" + "void functionDecl(int argumentA, int argumentB, int " + "argumentC, int argumentD, int argumentE);"; + + verifyFormat(Short, Style); + + StringRef NoBreak = "functionCall(paramA, paramB, paramC, paramD, paramE, " + "paramF, paramG, paramH,\n" + " paramI);\n" + "void functionDecl(int argumentA, int argumentB, int " + "argumentC, int argumentD,\n" + " int argumentE);"; + + verifyFormat(NoBreak, Medium, Style); + verifyFormat(NoBreak, + "functionCall(\n" + " paramA,\n" + " paramB,\n" + " paramC,\n" + " paramD,\n" + " paramE,\n" + " paramF,\n" + " paramG,\n" + " paramH,\n" + " paramI\n" + ");\n" + "void functionDecl(\n" + " int argumentA,\n" + " int argumentB,\n" + " int argumentC,\n" + " int argumentD,\n" + " int argumentE\n" + ");", + Style); + + verifyFormat("outerFunctionCall(nestedFunctionCall(argument1),\n" + " nestedLongFunctionCall(argument1, " + "argument2, argument3,\n" + " argument4, " + "argument5));", + Style); + + Style.BreakBeforeClosingParen = true; + + verifyFormat(Short, Style); + verifyFormat(NoBreak, Medium, Style); + + Style.AllowAllArgumentsOnNextLine = false; + Style.AllowAllConstructorInitializersOnNextLine = false; + Style.AllowAllParametersOfDeclarationOnNextLine = false; + + verifyFormat(Short, Style); + verifyFormat(NoBreak, Medium, Style); + + Style.AlignAfterOpenBracket = FormatStyle::BAS_AlwaysBreak; + + verifyFormat(Short, Style); + verifyFormat( + "functionCall(\n" + " paramA, paramB, paramC, paramD, paramE, paramF, paramG, paramH, " + "paramI\n" + ");\n" + "void functionDecl(\n" + " int argumentA, int argumentB, int argumentC, int argumentD, int " + "argumentE\n" + ");", + Medium, Style); + + Style.BinPackArguments = false; + Style.BinPackParameters = false; + + verifyFormat(Short, Style); + + verifyFormat("functionCall(\n" + " paramA,\n" + " paramB,\n" + " paramC,\n" + " paramD,\n" + " paramE,\n" + " paramF,\n" + " paramG,\n" + " paramH,\n" + " paramI\n" + ");\n" + "void functionDecl(\n" + " int argumentA,\n" + " int argumentB,\n" + " int argumentC,\n" + " int argumentD,\n" + " int argumentE\n" + ");", + Medium, Style); + + verifyFormat("outerFunctionCall(\n" + " nestedFunctionCall(argument1),\n" + " nestedLongFunctionCall(\n" + " argument1,\n" + " argument2,\n" + " argument3,\n" + " argument4,\n" + " argument5\n" + " )\n" + ");", + Style); + + verifyFormat("int a = (int)b;", Style); + verifyFormat("int a = (int)b;", + "int a = (\n" + " int\n" + ") b;", + Style); + + verifyFormat("return (true);", Style); + verifyFormat("return (true);", + "return (\n" + " true\n" + ");", + Style); + + verifyFormat("void foo();", Style); + verifyFormat("void foo();", + "void foo(\n" + ");", + Style); + + verifyFormat("void foo() {}", Style); + verifyFormat("void foo() {}", + "void foo(\n" + ") {\n" + "}", + Style); + + verifyFormat("auto string = std::string();", Style); + verifyFormat("auto string = std::string();", + "auto string = std::string(\n" + ");", + Style); + + verifyFormat("void (*functionPointer)() = nullptr;", Style); + verifyFormat("void (*functionPointer)() = nullptr;", + "void (\n" + " *functionPointer\n" + ")\n" + "(\n" + ") = nullptr;", + Style); +} + } // namespace } // namespace format } // namespace clang