diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst --- a/clang/docs/ClangFormatStyleOptions.rst +++ b/clang/docs/ClangFormatStyleOptions.rst @@ -2228,6 +2228,44 @@ LoooooooooooooooooooooooooooooooooooooooongReturnType LoooooooooooooooooooooooooooooooongFunctionDeclaration(); +**InsertBraces** (``BraceInsertionStyle``) + The brace insertion style to use for control flow statements (if/else/for). + + Possible values: + + * ``BIS_Never`` (in configuration: ``Never``) + Does not insert braces. + + .. code-block:: c++ + + if (condition) + funtion_call(arg1, arg2); + + * ``BIS_Always`` (in configuration: ``Always``) + Always insert braces. + + .. code-block:: c++ + + if (condition) { + // ^ inserted + funtion_call(arg1, arg2); + } // < inserted + + * ``BIS_WrapLikely`` (in configuration: ``WrapLikely``) + Insert braces if wrapping is likely + + .. code-block:: c++ + + if (condition) { + short_line(arg1, arg2); + } + + if (condition) { + // ^ inserted + very_very_long_line( + arg1, arg2, arg3); + } // < inserted + **InsertTrailingCommas** (``TrailingCommaStyle``) If set to ``TCS_Wrapped`` will insert trailing commas in container literals (arrays and objects) that wrap across multiple lines. 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 @@ -648,6 +648,22 @@ /// \endcode TrailingCommaStyle InsertTrailingCommas; + /// The style of inserting braces in control flow statements (if/else/for). + enum BraceInsertionStyle : unsigned char { + /// Do not insert braces. + BIS_Never, + /// Always insert braces. + BIS_Always, + /// Insert braces if the line is likely to wrap. + BIS_WrapLikely, + }; + + /// If set to ``BIS_WrapLikely`` will insert braces if the line after the + /// control flow statement is likely to wrap. + /// If set to ``BIS_Always`` will always insert braces. + /// The default is the disabled value of ``BIS_Never``. + BraceInsertionStyle InsertBraces; + /// If ``false``, a function declaration's or function definition's /// parameters will either all be on the same line or will have one line each. /// \code @@ -2786,7 +2802,7 @@ IndentPPDirectives == R.IndentPPDirectives && IndentExternBlock == R.IndentExternBlock && IndentRequires == R.IndentRequires && IndentWidth == R.IndentWidth && - Language == R.Language && + InsertBraces == R.InsertBraces && Language == R.Language && IndentWrappedFunctionNames == R.IndentWrappedFunctionNames && JavaImportGroups == R.JavaImportGroups && JavaScriptQuotes == R.JavaScriptQuotes && 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 @@ -166,6 +166,14 @@ } }; +template <> struct ScalarEnumerationTraits { + static void enumeration(IO &IO, FormatStyle::BraceInsertionStyle &Value) { + IO.enumCase(Value, "Never", FormatStyle::BIS_Never); + IO.enumCase(Value, "Always", FormatStyle::BIS_Always); + IO.enumCase(Value, "WrapLikely", FormatStyle::BIS_WrapLikely); + } +}; + template <> struct ScalarEnumerationTraits { static void enumeration(IO &IO, FormatStyle::BinaryOperatorStyle &Value) { IO.enumCase(Value, "All", FormatStyle::BOS_All); @@ -565,6 +573,7 @@ IO.mapOptional("IndentWidth", Style.IndentWidth); IO.mapOptional("IndentWrappedFunctionNames", Style.IndentWrappedFunctionNames); + IO.mapOptional("InsertBraces", Style.InsertBraces); IO.mapOptional("InsertTrailingCommas", Style.InsertTrailingCommas); IO.mapOptional("JavaImportGroups", Style.JavaImportGroups); IO.mapOptional("JavaScriptQuotes", Style.JavaScriptQuotes); @@ -932,6 +941,7 @@ LLVMStyle.IndentRequires = false; LLVMStyle.IndentWrappedFunctionNames = false; LLVMStyle.IndentWidth = 2; + LLVMStyle.InsertBraces = FormatStyle::BIS_Never; LLVMStyle.InsertTrailingCommas = FormatStyle::TCS_None; LLVMStyle.JavaScriptQuotes = FormatStyle::JSQS_Leave; LLVMStyle.JavaScriptWrapImports = true; @@ -1645,6 +1655,72 @@ FormattingAttemptStatus *Status; }; +class BracesInserter : public TokenAnalyzer { +public: + BracesInserter(const Environment &Env, const FormatStyle &Style) + : TokenAnalyzer(Env, Style) {} + + std::pair + analyze(TokenAnnotator &Annotator, + SmallVectorImpl &AnnotatedLines, + FormatTokenLexer &Tokens) override { + tooling::Replacements Result; + if (AffectedRangeMgr.computeAffectedLines(AnnotatedLines)) + insertBraces(AnnotatedLines, Result); + return {Result, 0}; + } + +private: + void insertBraces(SmallVectorImpl &Lines, + tooling::Replacements &Result) { + bool InsideCtrlFlowStmt = false; + for (AnnotatedLine *Line : Lines) { + insertBraces(Line->Children, Result); + + // Get first token that is not a comment + const FormatToken *FirstTok = Line->First; + if (FirstTok->is(tok::comment)) + FirstTok = FirstTok->getNextNonComment(); + if (!FirstTok) + continue; + + // If this token starts a control flow stmt, mark it + // We ignore do/while for now as they have a more complex logic + if (FirstTok->isOneOf(tok::kw_if, tok::kw_else, tok::kw_for)) { + InsideCtrlFlowStmt = true; + continue; + } + + // if the previous line starte a ctrl flow block and this line does not + // start with curly + if (Line->Affected && !FirstTok->Finalized && InsideCtrlFlowStmt && + FirstTok->isNot(tok::l_brace)) { + const SourceLocation startBraceLoc = Line->First->Tok.getLocation(); + // in some cases clang-format will run the same transform twice which + // could lead to adding the curlies twice, skip if we already added one + // at this location + if (Done.count(startBraceLoc)) + break; + + if (Style.InsertBraces == + FormatStyle::BraceInsertionStyle::BIS_Always || + Line->Last->OriginalColumn > Style.ColumnLimit) { + Done.insert(startBraceLoc); + cantFail(Result.add(tooling::Replacement(Env.getSourceManager(), + startBraceLoc, 0, "{"))); + cantFail(Result.add(tooling::Replacement( + Env.getSourceManager(), + Line->Last->Tok.getLocation().getLocWithOffset( + Line->Last->TokenText.size()), + 0, "}"))); + } + } + InsideCtrlFlowStmt = false; + } + } + std::set Done; +}; + /// TrailingCommaInserter inserts trailing commas into container literals. /// E.g.: /// const x = [ @@ -2687,6 +2763,11 @@ Passes.emplace_back([&](const Environment &Env) { return UsingDeclarationsSorter(Env, Expanded).process(); }); + + if (Style.InsertBraces != FormatStyle::BIS_Never) + Passes.emplace_back([&](const Environment &Env) { + return BracesInserter(Env, Expanded).process(); + }); } if (Style.Language == FormatStyle::LK_JavaScript && 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 @@ -14749,6 +14749,12 @@ CHECK_PARSE("IndentExternBlock: false", IndentExternBlock, FormatStyle::IEBS_NoIndent); + Style.InsertBraces = FormatStyle::BIS_Never; + CHECK_PARSE("InsertBraces: Always", InsertBraces, FormatStyle::BIS_Always); + CHECK_PARSE("InsertBraces: WrapLikely", InsertBraces, + FormatStyle::BIS_WrapLikely); + CHECK_PARSE("InsertBraces: Never", InsertBraces, FormatStyle::BIS_Never); + Style.BitFieldColonSpacing = FormatStyle::BFCS_None; CHECK_PARSE("BitFieldColonSpacing: Both", BitFieldColonSpacing, FormatStyle::BFCS_Both); @@ -17890,6 +17896,105 @@ "}", format(Source, Style)); } + +TEST_F(FormatTest, InsertBraces) { + FormatStyle Style = getLLVMStyle(); + StringRef IfSourceShort = "if (condition)\n" + " call_function(arg, arg);"; + EXPECT_EQ(IfSourceShort, format(IfSourceShort, Style)); + + StringRef IfSourceLong = "if (condition)\n" + " call_function(arg, arg, arg, arg, arg, arg);"; + EXPECT_EQ(IfSourceLong, format(IfSourceLong, Style)); + + StringRef IfElseSourceShort = "if (condition)\n" + " a_function(arg, arg);\n" + "else\n" + " another_function(arg, arg);"; + EXPECT_EQ(IfElseSourceShort, format(IfElseSourceShort, Style)); + + StringRef IfElseSourceLong = + "if (condition)\n" + " a_function(arg, arg, arg, arg, arg, arg);\n" + "else\n" + " another_function(arg, arg, arg, arg, arg, arg);"; + EXPECT_EQ(IfElseSourceLong, format(IfElseSourceLong, Style)); + + StringRef ForSourceShort = "for (auto val : container)\n" + " call_function(arg, arg);"; + EXPECT_EQ(ForSourceShort, format(ForSourceShort, Style)); + + StringRef ForSourceLong = "for (auto val : container)\n" + " call_function(arg, arg, arg, arg, arg, arg);"; + EXPECT_EQ(ForSourceLong, format(ForSourceLong, Style)); + + Style.InsertBraces = FormatStyle::BIS_Always; + + EXPECT_EQ("if (condition) {\n" + " call_function(arg, arg);\n" + "}", + format(IfSourceShort, Style)); + + EXPECT_EQ("if (condition) {\n" + " call_function(arg, arg, arg, arg, arg, arg);\n" + "}", + format(IfSourceLong, Style)); + + EXPECT_EQ("if (condition) {\n" + " a_function(arg, arg);\n" + "} else {\n" + " another_function(arg, arg);\n" + "}", + format(IfElseSourceShort, Style)); + + EXPECT_EQ("if (condition) {\n" + " a_function(arg, arg, arg, arg, arg, arg);\n" + "} else {\n" + " another_function(arg, arg, arg, arg, arg, arg);\n" + "}", + format(IfElseSourceLong, Style)); + + EXPECT_EQ("for (auto val : container) {\n" + " call_function(arg, arg);\n" + "}", + format(ForSourceShort, Style)); + + EXPECT_EQ("for (auto val : container) {\n" + " call_function(arg, arg, arg, arg, arg, arg);\n" + "}", + format(ForSourceLong, Style)); + + Style.InsertBraces = FormatStyle::BIS_WrapLikely; + Style.ColumnLimit = 35; + + EXPECT_EQ(IfSourceShort, format(IfSourceShort, Style)); + + EXPECT_EQ("if (condition) {\n" + " call_function(arg, arg, arg, arg,\n" + " arg, arg);\n" + "}", + format(IfSourceLong, Style)); + + EXPECT_EQ(IfElseSourceShort, format(IfElseSourceShort, Style)); + + EXPECT_EQ("if (condition) {\n" + " a_function(arg, arg, arg, arg,\n" + " arg, arg);\n" + "} else {\n" + " another_function(arg, arg, arg,\n" + " arg, arg, arg);\n" + "}", + format(IfElseSourceLong, Style)); + + EXPECT_EQ(ForSourceShort, format(ForSourceShort, Style)); + + EXPECT_EQ("for (auto val : container) {\n" + " call_function(arg, arg, arg, arg,\n" + " arg, arg);\n" + "}", + format(ForSourceLong, Style)); +} + } // namespace } // namespace format } // namespace clang