Index: clang/docs/ClangFormatStyleOptions.rst =================================================================== --- clang/docs/ClangFormatStyleOptions.rst +++ clang/docs/ClangFormatStyleOptions.rst @@ -2717,6 +2717,33 @@ LoooooooooooooooooooooooooooooooooooooooongReturnType LoooooooooooooooooooooooooooooooongFunctionDeclaration(); +**InsertBraces** (``BraceInsertionStyle``) :versionbadge:`clang-format 14` + 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``. + + .. warning:: + + Setting ``InsertBraces`` to something other than `Never`, COULD + lead to incorrect code formatting due to incorrect decisions made due to + clang-formats lack of complete semantic information. + As such extra care should be taken to review code changes made by the use + of this option. + + Possible values: + + * ``BIS_Never`` (in configuration: ``Never``) + Do not insert braces. + + * ``BIS_Always`` (in configuration: ``Always``) + Always insert braces. + + * ``BIS_WrapLikely`` (in configuration: ``WrapLikely``) + Insert braces if the line is likely to wrap. + + + **InsertTrailingCommas** (``TrailingCommaStyle``) :versionbadge:`clang-format 12` If set to ``TCS_Wrapped`` will insert trailing commas in container literals (arrays and objects) that wrap across multiple lines. Index: clang/include/clang/Format/Format.h =================================================================== --- clang/include/clang/Format/Format.h +++ clang/include/clang/Format/Format.h @@ -982,6 +982,30 @@ /// \version 12 TrailingCommaStyle InsertTrailingCommas; + /// The style of inserting braces in control flow statements. + 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``. + /// \warning + /// Setting ``InsertBraces`` to something other than `Never`, COULD + /// lead to incorrect code formatting due to incorrect decisions made due to + /// clang-formats lack of complete semantic information. + /// As such extra care should be taken to review code changes made by the use + /// of this option. + /// \endwarning + /// \version 14 + 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 @@ -3666,7 +3690,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 && Index: clang/lib/Format/Format.cpp =================================================================== --- clang/lib/Format/Format.cpp +++ clang/lib/Format/Format.cpp @@ -214,6 +214,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); @@ -731,6 +739,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); @@ -1137,6 +1146,7 @@ LLVMStyle.IndentRequires = false; LLVMStyle.IndentWrappedFunctionNames = false; LLVMStyle.IndentWidth = 2; + LLVMStyle.InsertBraces = FormatStyle::BIS_Never; LLVMStyle.PPIndentWidth = -1; LLVMStyle.InsertTrailingCommas = FormatStyle::TCS_None; LLVMStyle.JavaScriptQuotes = FormatStyle::JSQS_Leave; @@ -1897,6 +1907,85 @@ 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(); + // The line is a comment. + if (!FirstTok) + continue; + // Get last token that is not a comment. + const FormatToken *LastTok = Line->Last; + if (LastTok->is(tok::comment)) + LastTok = LastTok->getPreviousNonComment(); + + // If this token starts a control flow stmt, mark it. + if (FirstTok->isOneOf(tok::kw_if, tok::kw_else, tok::kw_for, tok::kw_do, + tok::kw_while)) { + InsideCtrlFlowStmt = !LastTok->isOneOf(tok::l_brace, tok::semi); + continue; + } + + // If the previous line started 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, "{"))); + + // Mostly we can ignore putting a \n before the } and let the + // Formatter handle it, unless the last token is a // comment, Doing + // so avoids issues with macro continuation. And a line comment + // cannot be part of a continuation. + std::string ending = "}"; + if (Line->Last->is(tok::comment)) + ending = "\n}"; + cantFail(Result.add(tooling::Replacement( + Env.getSourceManager(), + Line->Last->Tok.getLocation().getLocWithOffset( + Line->Last->TokenText.size()), + 0, ending))); + } + } + InsideCtrlFlowStmt = false; + } + } + std::set Done; +}; + /// TrailingCommaInserter inserts trailing commas into container literals. /// E.g.: /// const x = [ @@ -2982,6 +3071,11 @@ }); } + if (Style.InsertBraces != FormatStyle::BIS_Never) + Passes.emplace_back([&](const Environment &Env) { + return BracesInserter(Env, Expanded).process(); + }); + if (Style.Language == FormatStyle::LK_JavaScript && Style.JavaScriptQuotes != FormatStyle::JSQS_Leave) Passes.emplace_back([&](const Environment &Env) { Index: clang/unittests/Format/FormatTest.cpp =================================================================== --- clang/unittests/Format/FormatTest.cpp +++ clang/unittests/Format/FormatTest.cpp @@ -72,7 +72,10 @@ EXPECT_EQ(Expected.str(), format(Expected, Style)) << "Expected code is not stable"; EXPECT_EQ(Expected.str(), format(Code, Style)); - if (Style.Language == FormatStyle::LK_Cpp) { + // clang::format::internal::reformat does not run any of the options that + // modify code for ObjC + if (Style.Language == FormatStyle::LK_Cpp && + Style.InsertBraces == FormatStyle::BIS_Never) { // Objective-C++ is a superset of C++, so everything checked for C++ // needs to be checked for Objective-C++ as well. FormatStyle ObjCStyle = Style; @@ -18758,6 +18761,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); @@ -22397,6 +22406,271 @@ EXPECT_EQ(Code, format(Code, Style)); } +TEST_F(FormatTest, InsertBraces) { + FormatStyle Style = getLLVMStyle(); + + StringRef IfSourceShort = "if (condition)\n" + " call_function(arg, arg);"; + verifyFormat(IfSourceShort); + + StringRef IfSourceLong = "if (condition)\n" + " call_function(arg, arg, arg, arg, arg, arg);"; + verifyFormat(IfSourceLong); + + StringRef IfElseSourceShort = "if (condition)\n" + " a_function(arg, arg);\n" + "else\n" + " another_function(arg, arg);"; + verifyFormat(IfElseSourceShort); + + StringRef IfElseSourceLong = + "if (condition)\n" + " a_function(arg, arg, arg, arg, arg, arg);\n" + "else\n" + " another_function(arg, arg, arg, arg, arg, arg);"; + verifyFormat(IfElseSourceLong); + + StringRef ForSourceShort = "for (auto val : container)\n" + " call_function(arg, arg);"; + verifyFormat(ForSourceShort); + + StringRef ForSourceLong = "for (auto val : container)\n" + " call_function(arg, arg, arg, arg, arg, arg);"; + verifyFormat(ForSourceLong); + + StringRef WhileShort = "while (condition)\n" + " call_function(arg, arg);"; + verifyFormat(WhileShort); + + StringRef WhileLong = "while (condition)\n" + " call_function(arg, arg, arg, arg, arg, arg);"; + verifyFormat(WhileLong); + + StringRef DoShort = "do\n" + " call_function(arg, arg);\n" + "while (condition);"; + verifyFormat(DoShort); + + StringRef DoLong = "do\n" + " call_function(arg, arg, arg, arg, arg, arg);\n" + "while (condition);"; + verifyFormat(DoLong); + + StringRef ChainedConditionals = "if (A)\n" + " if (B)\n" + " callAB();\n" + " else\n" + " callA();\n" + "else if (B)\n" + " callB();\n" + "else\n" + " call();"; + verifyFormat(ChainedConditionals); + + StringRef ChainedDoWhiles = "do\n" + " while (arg++)\n" + " do\n" + " while (arg++)\n" + " call_function(arg, arg);\n" + " while (condition);\n" + "while (condition);"; + verifyFormat(ChainedDoWhiles); + + StringRef IfWithMultilineComment = + "if /*condition*/ (condition) /*condition*/\n" + " you_do_you(); /*condition*/"; + verifyFormat(IfWithMultilineComment); + + StringRef IfSinglelineCommentOnConditional = "if (condition) // my test\n" + " you_do_you();"; + verifyFormat(IfSinglelineCommentOnConditional); + + Style.InsertBraces = FormatStyle::BIS_Always; + + verifyFormat("while (condition) {\n" + " call_function(arg, arg);\n" + "}", + WhileShort, Style); + + verifyFormat("while (condition) {\n" + " call_function(arg, arg, arg, arg, arg, arg);\n" + "}", + WhileLong, Style); + + verifyFormat("do {\n" + " call_function(arg, arg);\n" + "} while (condition);", + DoShort, Style); + + verifyFormat("do {\n" + " call_function(arg, arg, arg, arg, arg, arg);\n" + "} while (condition);", + DoLong, Style); + + verifyFormat("if (condition) {\n" + " call_function(arg, arg);\n" + "}", + IfSourceShort, Style); + + verifyFormat("if (condition) {\n" + " call_function(arg, arg);\n" + "}", + IfSourceShort, Style); + + verifyFormat("if (condition) {\n" + " call_function(arg, arg, arg, arg, arg, arg);\n" + "}", + IfSourceLong, Style); + + verifyFormat("if (condition) {\n" + " a_function(arg, arg);\n" + "} else {\n" + " another_function(arg, arg);\n" + "}", + IfElseSourceShort, Style); + + verifyFormat("if (condition) {\n" + " a_function(arg, arg, arg, arg, arg, arg);\n" + "} else {\n" + " another_function(arg, arg, arg, arg, arg, arg);\n" + "}", + IfElseSourceLong, Style); + + verifyFormat("for (auto val : container) {\n" + " call_function(arg, arg);\n" + "}", + ForSourceShort, Style); + + verifyFormat("for (auto val : container) {\n" + " call_function(arg, arg, arg, arg, arg, arg);\n" + "}", + ForSourceLong, Style); + + verifyFormat("if (A)\n" + " if (B) {\n" + " callAB();\n" + " } else {\n" + " callA();\n" + " }\n" + "else if (B) {\n" + " callB();\n" + "} else {\n" + " call();\n" + "}", + ChainedConditionals, Style); + + verifyFormat("do\n" + " while (arg++)\n" + " do\n" + " while (arg++) {\n" + " call_function(arg, arg);\n" + " }\n" + " while (condition);\n" + "while (condition);", + ChainedDoWhiles, Style); + + verifyFormat("if /*condition*/ (condition) /*condition*/\n" + "{\n" + " you_do_you(); /*condition*/\n" + "}", + IfWithMultilineComment, Style); + + verifyFormat("if (condition) // my test\n" + "{\n" + " you_do_you();\n" + "}", + IfSinglelineCommentOnConditional, Style); + + Style.InsertBraces = FormatStyle::BIS_WrapLikely; + Style.ColumnLimit = 35; + + verifyFormat(IfSourceShort, IfSourceShort, Style); + + verifyFormat("if (condition) {\n" + " call_function(arg, arg, arg, arg,\n" + " arg, arg);\n" + "}", + IfSourceLong, Style); + + verifyFormat(IfElseSourceShort, IfElseSourceShort, Style); + + verifyFormat("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" + "}", + IfElseSourceLong, Style); + + verifyFormat(ForSourceShort, ForSourceShort, Style); + + verifyFormat("for (auto val : container) {\n" + " call_function(arg, arg, arg, arg,\n" + " arg, arg);\n" + "}", + ForSourceLong, Style); + + format("while (foo) ", Style); +} + +TEST_F(FormatTest, InsertBracesMacro) { + auto Style = getLLVMStyleWithColumns(20); + + verifyFormat("#define FOO1(x) \\\n" + " if (x) \\\n" + " return true; \\\n" + " else \\\n" + " return false;", + Style); + + Style.InsertBraces = FormatStyle::BIS_Always; + + verifyFormat("#define FOO2(x) \\\n" + " if (x) { \\\n" + " return true; \\\n" + " } else { \\\n" + " return false; \\\n" + " }", + "#define FOO2(x) \\\n" + " if (x) \\\n" + " return true; \\\n" + " else \\\n" + " return false;", + Style); + + verifyFormat("#define FOO3(x) \\\n" + " if (x) { \\\n" + " return true; \\\n" + " } else { \\\n" + " return false; \\\n" + " }", + Style); +} + +TEST_F(FormatTest, InsertBracesComments) { + auto Style = getLLVMStyle(); + + verifyFormat("if (x)\n" + " return true;\n" + "else\n" + " return false;", + Style); + + Style.InsertBraces = FormatStyle::BIS_Always; + + verifyFormat("if (x) {\n" + " return true; // Comment\n" + "} else {\n" + " return false; // Comment\n" + "}", + "if (x)\n" + " return true; // Comment\n" + "else\n" + " return false; // Comment", + Style); +} + } // namespace } // namespace format } // namespace clang