diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst --- a/clang/docs/ClangFormatStyleOptions.rst +++ b/clang/docs/ClangFormatStyleOptions.rst @@ -846,14 +846,85 @@ -**AlignTrailingComments** (``Boolean``) :versionbadge:`clang-format 3.7` - If ``true``, aligns trailing comments. +**AlignTrailingComments** (``TrailingCommentsAlignmentStyle``) :versionbadge:`clang-format 3.7` + Control of trailing comments. - .. code-block:: c++ + NOTE: As of clang-format 16 this option is not a bool but can be set + to the options. Conventional bool options still can be parsed as before. + + + .. code-block:: yaml + + # Example of usage: + AlignTrailingComments: + Kind: Always + OverEmptyLines: 2 + + Nested configuration flags: + + Alignment options + + * ``TrailingCommentsAlignmentKinds Kind`` + Specifies the way to align trailing comments + + Possible values: + + * ``TCAS_Leave`` (in configuration: ``Leave``) + Leave trailing comments as they are. + + .. code-block:: c++ + + int a; // comment + int ab; // comment + + int abc; // comment + int abcd; // comment + + * ``TCAS_Always`` (in configuration: ``Always``) + Align trailing comments. + + .. code-block:: c++ + + int a; // comment + int ab; // comment + + int abc; // comment + int abcd; // comment + + * ``TCAS_Never`` (in configuration: ``Never``) + Don't align trailing comments but other formatter applies. + + .. code-block:: c++ + + int a; // comment + int ab; // comment + + int abc; // comment + int abcd; // comment + + + * ``unsigned OverEmptyLines`` How many empty lines to apply alignment + With ``MaxEmptyLinesToKeep`` is 2 and ``OverEmptyLines`` is 2, + + .. code-block:: c++ + + int a; // all these + + int ab; // comments are + + + int abcdef; // aligned + And with ``MaxEmptyLinesToKeep`` is 2 and ``OverEmptyLines`` is 1, + + .. code-block:: c++ + + int a; // these are + + int ab; // aligned + + + int abcdef; // but this isn't - true: false: - int a; // My comment a vs. int a; // My comment a - int b = 2; // comment b int b = 2; // comment about b **AllowAllArgumentsOnNextLine** (``Boolean``) :versionbadge:`clang-format 9` If a function call or braced initializer list doesn't fit on a @@ -3567,7 +3638,7 @@ .. code-block:: yaml - QualifierOrder: ['inline', 'static', 'type', 'const'] + QualifierOrder: ['inline', 'static' , 'type', 'const'] .. code-block:: c++ 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 @@ -369,14 +369,83 @@ /// \version 3.5 OperandAlignmentStyle AlignOperands; - /// If ``true``, aligns trailing comments. - /// \code - /// true: false: - /// int a; // My comment a vs. int a; // My comment a - /// int b = 2; // comment b int b = 2; // comment about b + /// Enums for AlignTrailingComments + enum TrailingCommentsAlignmentKinds : int8_t { + /// Leave trailing comments as they are. + /// \code + /// int a; // comment + /// int ab; // comment + /// + /// int abc; // comment + /// int abcd; // comment + /// \endcode + TCAS_Leave, + /// Align trailing comments. + /// \code + /// int a; // comment + /// int ab; // comment + /// + /// int abc; // comment + /// int abcd; // comment + /// \endcode + TCAS_Always, + /// Don't align trailing comments but other formatter applies. + /// \code + /// int a; // comment + /// int ab; // comment + /// + /// int abc; // comment + /// int abcd; // comment + /// \endcode + TCAS_Never, + }; + + /// Alignment options + struct TrailingCommentsAlignmentStyle { + /// Specifies the way to align trailing comments + TrailingCommentsAlignmentKinds Kind; + /// How many empty lines to apply alignment + /// With ``MaxEmptyLinesToKeep`` is 2 and ``OverEmptyLines`` is 2, + /// \code + /// int a; // all these + /// + /// int ab; // comments are + /// + /// + /// int abcdef; // aligned + /// \endcode + /// And with ``MaxEmptyLinesToKeep`` is 2 and ``OverEmptyLines`` is 1, + /// \code + /// int a; // these are + /// + /// int ab; // aligned + /// + /// + /// int abcdef; // but this isn't + /// \endcode + unsigned OverEmptyLines; + + bool operator==(const TrailingCommentsAlignmentStyle &R) const { + return Kind == R.Kind && OverEmptyLines == R.OverEmptyLines; + } + bool operator!=(const TrailingCommentsAlignmentStyle &R) const { + return !(*this == R); + } + }; + + /// Control of trailing comments. + /// + /// NOTE: As of clang-format 16 this option is not a bool but can be set + /// to the options. Conventional bool options still can be parsed as before. + /// + /// \code{.yaml} + /// # Example of usage: + /// AlignTrailingComments: + /// Kind: Always + /// OverEmptyLines: 2 /// \endcode /// \version 3.7 - bool AlignTrailingComments; + TrailingCommentsAlignmentStyle AlignTrailingComments; /// \brief If a function call or braced initializer list doesn't fit on a /// line, allow putting all arguments onto the next line, even if 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 @@ -204,6 +204,47 @@ } }; +template <> +struct ScalarEnumerationTraits { + static void enumeration(IO &IO, + FormatStyle::TrailingCommentsAlignmentKinds &Value) { + IO.enumCase(Value, "Leave", FormatStyle::TCAS_Leave); + IO.enumCase(Value, "Always", FormatStyle::TCAS_Always); + IO.enumCase(Value, "Never", FormatStyle::TCAS_Never); + } +}; + +template <> struct MappingTraits { + static void enumInput(IO &IO, + FormatStyle::TrailingCommentsAlignmentStyle &Value) { + IO.enumCase(Value, "Leave", + FormatStyle::TrailingCommentsAlignmentStyle( + {FormatStyle::TCAS_Leave, 1})); + + IO.enumCase(Value, "Always", + FormatStyle::TrailingCommentsAlignmentStyle( + {FormatStyle::TCAS_Always, 1})); + + IO.enumCase(Value, "Never", + FormatStyle::TrailingCommentsAlignmentStyle( + {FormatStyle::TCAS_Never, 1})); + + // For backwards compatibility + IO.enumCase(Value, "true", + FormatStyle::TrailingCommentsAlignmentStyle( + {FormatStyle::TCAS_Always, 1})); + IO.enumCase(Value, "false", + FormatStyle::TrailingCommentsAlignmentStyle( + {FormatStyle::TCAS_Never, 1})); + } + + static void mapping(IO &IO, + FormatStyle::TrailingCommentsAlignmentStyle &Value) { + IO.mapOptional("Kind", Value.Kind); + IO.mapOptional("OverEmptyLines", Value.OverEmptyLines); + } +}; + template <> struct ScalarEnumerationTraits { static void enumeration(IO &IO, @@ -1181,7 +1222,6 @@ LLVMStyle.AlignAfterOpenBracket = FormatStyle::BAS_Align; LLVMStyle.AlignArrayOfStructures = FormatStyle::AIAS_None; LLVMStyle.AlignOperands = FormatStyle::OAS_Align; - LLVMStyle.AlignTrailingComments = true; LLVMStyle.AlignConsecutiveAssignments = {}; LLVMStyle.AlignConsecutiveAssignments.Enabled = false; LLVMStyle.AlignConsecutiveAssignments.AcrossEmptyLines = false; @@ -1191,6 +1231,9 @@ LLVMStyle.AlignConsecutiveBitFields = {}; LLVMStyle.AlignConsecutiveDeclarations = {}; LLVMStyle.AlignConsecutiveMacros = {}; + LLVMStyle.AlignTrailingComments = {}; + LLVMStyle.AlignTrailingComments.Kind = FormatStyle::TCAS_Always; + LLVMStyle.AlignTrailingComments.OverEmptyLines = 0; LLVMStyle.AllowAllArgumentsOnNextLine = true; LLVMStyle.AllowAllParametersOfDeclarationOnNextLine = true; LLVMStyle.AllowShortEnumsOnASingleLine = true; @@ -1447,7 +1490,8 @@ if (Language == FormatStyle::LK_Java) { GoogleStyle.AlignAfterOpenBracket = FormatStyle::BAS_DontAlign; GoogleStyle.AlignOperands = FormatStyle::OAS_DontAlign; - GoogleStyle.AlignTrailingComments = false; + GoogleStyle.AlignTrailingComments = {}; + GoogleStyle.AlignTrailingComments.Kind = FormatStyle::TCAS_Never; GoogleStyle.AllowShortFunctionsOnASingleLine = FormatStyle::SFS_Empty; GoogleStyle.AllowShortIfStatementsOnASingleLine = FormatStyle::SIS_Never; GoogleStyle.AlwaysBreakBeforeMultilineStrings = false; @@ -1595,7 +1639,8 @@ Style.AccessModifierOffset = -4; Style.AlignAfterOpenBracket = FormatStyle::BAS_DontAlign; Style.AlignOperands = FormatStyle::OAS_DontAlign; - Style.AlignTrailingComments = false; + Style.AlignTrailingComments = {}; + Style.AlignTrailingComments.Kind = FormatStyle::TCAS_Never; Style.AllowShortBlocksOnASingleLine = FormatStyle::SBS_Empty; Style.BreakBeforeBinaryOperators = FormatStyle::BOS_All; Style.BreakBeforeBraces = FormatStyle::BS_WebKit; diff --git a/clang/lib/Format/WhitespaceManager.cpp b/clang/lib/Format/WhitespaceManager.cpp --- a/clang/lib/Format/WhitespaceManager.cpp +++ b/clang/lib/Format/WhitespaceManager.cpp @@ -927,6 +927,11 @@ unsigned StartOfSequence = 0; bool BreakBeforeNext = false; unsigned Newlines = 0; + unsigned int NewLineThreshold = 1; + if (Style.AlignTrailingComments.Kind == FormatStyle::TCAS_Always) { + NewLineThreshold = Style.AlignTrailingComments.OverEmptyLines + 1; + } + for (unsigned i = 0, e = Changes.size(); i != e; ++i) { if (Changes[i].StartOfBlockComment) continue; @@ -934,6 +939,21 @@ if (!Changes[i].IsTrailingComment) continue; + if (Style.AlignTrailingComments.Kind == FormatStyle::TCAS_Leave) { + auto OriginalSpaces = + Changes[i].OriginalWhitespaceRange.getEnd().getRawEncoding() - + Changes[i].OriginalWhitespaceRange.getBegin().getRawEncoding(); + unsigned RestoredLineLength = Changes[i].StartOfTokenColumn + + Changes[i].TokenLength + OriginalSpaces; + // If leaving comments makes the line exceed the column limit, give up to + // leave the comments. + if (RestoredLineLength >= Style.ColumnLimit && Style.ColumnLimit != 0) { + break; + } + Changes[i].Spaces = OriginalSpaces; + continue; + } + unsigned ChangeMinColumn = Changes[i].StartOfTokenColumn; unsigned ChangeMaxColumn; @@ -957,7 +977,7 @@ Changes[i - 1].Tok->is(tok::r_brace) && Changes[i - 1].StartOfTokenColumn == 0; bool WasAlignedWithStartOfNextLine = false; - if (Changes[i].NewlinesBefore == 1) { // A comment on its own line. + if (Changes[i].NewlinesBefore >= 1) { // A comment on its own line. unsigned CommentColumn = SourceMgr.getSpellingColumnNumber( Changes[i].OriginalWhitespaceRange.getEnd()); for (unsigned j = i + 1; j != e; ++j) { @@ -974,12 +994,13 @@ break; } } - if (!Style.AlignTrailingComments || FollowsRBraceInColumn0) { + if (Style.AlignTrailingComments.Kind == FormatStyle::TCAS_Never || + FollowsRBraceInColumn0) { alignTrailingComments(StartOfSequence, i, MinColumn); MinColumn = ChangeMinColumn; MaxColumn = ChangeMinColumn; StartOfSequence = i; - } else if (BreakBeforeNext || Newlines > 1 || + } else if (BreakBeforeNext || Newlines > NewLineThreshold || (ChangeMinColumn > MaxColumn || ChangeMaxColumn < MinColumn) || // Break the comment sequence if the previous line did not end // in a trailing comment. 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 @@ -20038,10 +20038,14 @@ EXPECT_EQ(0, parseConfiguration(TEXT, &Style).value()); \ EXPECT_EQ(VALUE, Style.FIELD) << "Unexpected value after parsing!" +#define CHECK_PARSE_NESTED_VALUE(TEXT, STRUCT, FIELD, VALUE) \ + EXPECT_NE(VALUE, Style.STRUCT.FIELD) << "Initial value already the same!"; \ + EXPECT_EQ(0, parseConfiguration(#STRUCT ":\n " TEXT, &Style).value()); \ + EXPECT_EQ(VALUE, Style.STRUCT.FIELD) << "Unexpected value after parsing!"; + TEST_F(FormatTest, ParsesConfigurationBools) { FormatStyle Style = {}; Style.Language = FormatStyle::LK_Cpp; - CHECK_PARSE_BOOL(AlignTrailingComments); CHECK_PARSE_BOOL(AllowAllArgumentsOnNextLine); CHECK_PARSE_BOOL(AllowAllParametersOfDeclarationOnNextLine); CHECK_PARSE_BOOL(AllowShortCaseLabelsOnASingleLine); @@ -20365,6 +20369,31 @@ FormatStyle::OAS_DontAlign); CHECK_PARSE("AlignOperands: true", AlignOperands, FormatStyle::OAS_Align); + CHECK_PARSE("AlignTrailingComments: Leave", AlignTrailingComments, + FormatStyle::TrailingCommentsAlignmentStyle( + {FormatStyle::TCAS_Leave, 1})); + CHECK_PARSE("AlignTrailingComments: Always", AlignTrailingComments, + FormatStyle::TrailingCommentsAlignmentStyle( + {FormatStyle::TCAS_Always, 1})); + CHECK_PARSE("AlignTrailingComments: Never", AlignTrailingComments, + FormatStyle::TrailingCommentsAlignmentStyle( + {FormatStyle::TCAS_Never, 1})); + // For backwards compatibility + CHECK_PARSE("AlignTrailingComments: true", AlignTrailingComments, + FormatStyle::TrailingCommentsAlignmentStyle( + {FormatStyle::TCAS_Always, 1})); + CHECK_PARSE("AlignTrailingComments: false", AlignTrailingComments, + FormatStyle::TrailingCommentsAlignmentStyle( + {FormatStyle::TCAS_Never, 1})); + CHECK_PARSE_NESTED_VALUE("Kind: Always", AlignTrailingComments, Kind, + FormatStyle::TCAS_Always); + CHECK_PARSE_NESTED_VALUE("Kind: Never", AlignTrailingComments, Kind, + FormatStyle::TCAS_Never); + CHECK_PARSE_NESTED_VALUE("Kind: Leave", AlignTrailingComments, Kind, + FormatStyle::TCAS_Leave); + CHECK_PARSE_NESTED_VALUE("OverEmptyLines: 1234", AlignTrailingComments, + OverEmptyLines, 1234u); + Style.UseTab = FormatStyle::UT_ForIndentation; CHECK_PARSE("UseTab: Never", UseTab, FormatStyle::UT_Never); CHECK_PARSE("UseTab: ForIndentation", UseTab, FormatStyle::UT_ForIndentation); diff --git a/clang/unittests/Format/FormatTestComments.cpp b/clang/unittests/Format/FormatTestComments.cpp --- a/clang/unittests/Format/FormatTestComments.cpp +++ b/clang/unittests/Format/FormatTestComments.cpp @@ -2858,6 +2858,213 @@ "int a; //\n"); } +TEST_F(FormatTestComments, AlignTrailingCommentsAcrossEmptyLines) { + FormatStyle Style = getLLVMStyle(); + Style.AlignTrailingComments.Kind = FormatStyle::TCAS_Always; + Style.AlignTrailingComments.OverEmptyLines = 1; + verifyFormat("#include \"a.h\" // simple\n" + "\n" + "#include \"aa.h\" // example case\n", + Style); + + verifyFormat("#include \"a.h\" // align across\n" + "\n" + "#include \"aa.h\" // two empty lines\n" + "\n" + "#include \"aaa.h\" // in a row\n", + Style); + + verifyFormat("#include \"a.h\" // align\n" + "#include \"aa.h\" // comment\n" + "#include \"aaa.h\" // blocks\n" + "\n" + "#include \"aaaa.h\" // across\n" + "#include \"aaaaa.h\" // one\n" + "#include \"aaaaaa.h\" // empty line\n", + Style); + + verifyFormat("#include \"a.h\" // align trailing comments\n" + "#include \"a.h\"\n" + "#include \"aa.h\" // across a line without comment\n", + Style); + + verifyFormat("#include \"a.h\" // align across\n" + "#include \"a.h\"\n" + "#include \"aa.h\" // two lines without comment\n" + "#include \"a.h\"\n" + "#include \"aaa.h\" // in a row\n", + Style); + + verifyFormat("#include \"a.h\" // align\n" + "#include \"aa.h\" // comment\n" + "#include \"aaa.h\" // blocks\n" + "#include \"a.h\"\n" + "#include \"aaaa.h\" // across\n" + "#include \"aaaaa.h\" // a line without\n" + "#include \"aaaaaa.h\" // comment\n", + Style); + + // Start of testing OverEmptyLines + Style.MaxEmptyLinesToKeep = 3; + Style.AlignTrailingComments.OverEmptyLines = 2; + // Cannot use verifyFormat here + // test::messUp removes all new lines which changes the logic + EXPECT_EQ("#include \"a.h\" // comment\n" + "\n" + "\n" + "\n" + "#include \"ab.h\" // comment\n" + "\n" + "\n" + "#include \"abcdefg.h\" // comment\n", + format("#include \"a.h\" // comment\n" + "\n" + "\n" + "\n" + "#include \"ab.h\" // comment\n" + "\n" + "\n" + "#include \"abcdefg.h\" // comment\n", + Style)); + + Style.MaxEmptyLinesToKeep = 1; + Style.AlignTrailingComments.OverEmptyLines = 1; + // End of testing OverEmptyLines + + Style.ColumnLimit = 15; + EXPECT_EQ("int ab; // line\n" + "int a; // long\n" + " // long\n" + "\n" + " // long", + format("int ab; // line\n" + "int a; // long long\n" + "\n" + "// long", + Style)); + + Style.ColumnLimit = 15; + EXPECT_EQ("int ab; // line\n" + "\n" + "int a; // long\n" + " // long\n", + format("int ab; // line\n" + "\n" + "int a; // long long\n", + Style)); + + Style.ColumnLimit = 30; + EXPECT_EQ("int foo = 12345; // comment\n" + "int bar =\n" + " 1234; // This is a very\n" + " // long comment\n" + " // which is wrapped\n" + " // arround.\n" + "\n" + "int x = 2; // Is this still\n" + " // aligned?\n", + format("int foo = 12345; // comment\n" + "int bar = 1234; // This is a very long comment\n" + " // which is wrapped arround.\n" + "\n" + "int x = 2; // Is this still aligned?\n", + Style)); + + Style.ColumnLimit = 35; + EXPECT_EQ("int foo = 12345; // comment\n" + "int bar =\n" + " 1234; // This is a very long\n" + " // comment which is\n" + " // wrapped arround.\n" + "\n" + "int x =\n" + " 2; // Is this still aligned?\n", + format("int foo = 12345; // comment\n" + "int bar = 1234; // This is a very long comment\n" + " // which is wrapped arround.\n" + "\n" + "int x = 2; // Is this still aligned?\n", + Style)); + + Style.ColumnLimit = 40; + EXPECT_EQ("int foo = 12345; // comment\n" + "int bar =\n" + " 1234; // This is a very long comment\n" + " // which is wrapped arround.\n" + "\n" + "int x = 2; // Is this still aligned?\n", + format("int foo = 12345; // comment\n" + "int bar = 1234; // This is a very long comment\n" + " // which is wrapped arround.\n" + "\n" + "int x = 2; // Is this still aligned?\n", + Style)); + + Style.ColumnLimit = 45; + EXPECT_EQ("int foo = 12345; // comment\n" + "int bar =\n" + " 1234; // This is a very long comment\n" + " // which is wrapped arround.\n" + "\n" + "int x = 2; // Is this still aligned?\n", + format("int foo = 12345; // comment\n" + "int bar = 1234; // This is a very long comment\n" + " // which is wrapped arround.\n" + "\n" + "int x = 2; // Is this still aligned?\n", + Style)); + + Style.ColumnLimit = 80; + EXPECT_EQ("int a; // line about a\n" + "\n" + "// line about b\n" + "long b;", + format("int a; // line about a\n" + "\n" + " // line about b\n" + " long b;", + Style)); + + Style.ColumnLimit = 80; + EXPECT_EQ("int a; // line about a\n" + "\n" + "// line 1 about b\n" + "// line 2 about b\n" + "long b;", + format("int a; // line about a\n" + "\n" + " // line 1 about b\n" + " // line 2 about b\n" + " long b;", + Style)); +} + +TEST_F(FormatTestComments, AlignTrailingCommentsLeave) { + FormatStyle Style = getLLVMStyle(); + Style.AlignTrailingComments.Kind = FormatStyle::TCAS_Leave; + EXPECT_EQ("int a;// do not touch\n" + "int b; // any comments\n" + "int c; // comment\n" + "int d; // comment\n", + format("int a;// do not touch\n" + "int b; // any comments\n" + "int c; // comment\n" + "int d; // comment\n", + Style)); + + // Just format comments normally when leaving exceeds the column limit + Style.ColumnLimit = 35; + EXPECT_EQ("int foo = 12345; // comment\n" + "int bar =\n" + " 1234; // This is a very long\n" + " // comment which is\n" + " // wrapped arround.\n", + format("int foo = 12345; // comment\n" + "int bar = 1234; // This is a very long comment\n" + " // which is wrapped arround.\n", + Style)); +} + TEST_F(FormatTestComments, AlignsBlockCommentDecorations) { EXPECT_EQ("/*\n" " */",