Index: docs/ClangFormatStyleOptions.rst =================================================================== --- docs/ClangFormatStyleOptions.rst +++ docs/ClangFormatStyleOptions.rst @@ -994,9 +994,9 @@ .. code-block:: c++ - Constructor() - : initializer1(), - initializer2() + Constructor() + : initializer1(), + initializer2() * ``BCIS_BeforeComma`` (in configuration: ``BeforeComma``) Break constructor initializers before the colon and commas, and align @@ -1004,18 +1004,18 @@ .. code-block:: c++ - Constructor() - : initializer1() - , initializer2() + Constructor() + : initializer1() + , initializer2() * ``BCIS_AfterColon`` (in configuration: ``AfterColon``) Break constructor initializers after the colon and commas. .. code-block:: c++ - Constructor() : - initializer1(), - initializer2() + Constructor() : + initializer1(), + initializer2() @@ -1201,7 +1201,8 @@ * ``IBS_Regroup`` (in configuration: ``Regroup``) Merge multiple ``#include`` blocks together and sort as one. - Then split into groups based on category priority. See ``IncludeCategories``. + Then split into groups based on category priority. See + ``IncludeCategories``. .. code-block:: c++ @@ -1583,6 +1584,8 @@ A raw string with a matching delimiter will be reformatted assuming the specified language based on a predefined style given by 'BasedOnStyle'. If 'BasedOnStyle' is not found, the formatting is based on llvm style. + If it exists, the last canonical delimiter for a given language will be + used to update other delimiters for that language. To configure this in the .clang-format file, use: @@ -1592,9 +1595,11 @@ - Delimiter: 'pb' Language: TextProto BasedOnStyle: llvm + Canonical: false - Delimiter: 'proto' Language: TextProto BasedOnStyle: google + Canonical: true **ReflowComments** (``bool``) If ``true``, clang-format will attempt to re-flow comments. Index: include/clang/Format/Format.h =================================================================== --- include/clang/Format/Format.h +++ include/clang/Format/Format.h @@ -1371,9 +1371,16 @@ /// If not specified, the raw string format is based on the style that this /// format is based on. std::string BasedOnStyle; + /// \brief If true, this delimiter is canonical for this language. + /// The canonical delimiter for a given language is the last delimiter + /// declared canonical in the sequence of raw string formats, if it exists. + /// If a canonical raw string format exists for a language, instances of + /// other raw string delimiters for that language will have their delimiter + /// updated to the canonical one. + bool Canonical; bool operator==(const RawStringFormat &Other) const { return Delimiter == Other.Delimiter && Language == Other.Language && - BasedOnStyle == Other.BasedOnStyle; + BasedOnStyle == Other.BasedOnStyle && Canonical == Other.Canonical; } }; @@ -1383,6 +1390,8 @@ /// A raw string with a matching delimiter will be reformatted assuming the /// specified language based on a predefined style given by 'BasedOnStyle'. /// If 'BasedOnStyle' is not found, the formatting is based on llvm style. + /// If it exists, the last canonical delimiter for a given language will be + /// used to update other delimiters for that language. /// /// To configure this in the .clang-format file, use: /// \code{.yaml} @@ -1390,9 +1399,11 @@ /// - Delimiter: 'pb' /// Language: TextProto /// BasedOnStyle: llvm + /// Canonical: false /// - Delimiter: 'proto' /// Language: TextProto /// BasedOnStyle: google + /// Canonical: true /// \endcode std::vector RawStringFormats; Index: lib/Format/ContinuationIndenter.cpp =================================================================== --- lib/Format/ContinuationIndenter.cpp +++ lib/Format/ContinuationIndenter.cpp @@ -102,6 +102,16 @@ return Delimiter; } +static llvm::Optional +getCanonicalRawStringDelimiter(const FormatStyle &Style, + FormatStyle::LanguageKind Language) { + for (const auto &Format : llvm::reverse(Style.RawStringFormats)) { + if (Format.Language == Language && Format.Canonical) + return StringRef(Format.Delimiter); + } + return None; +} + RawStringFormatStyleManager::RawStringFormatStyleManager( const FormatStyle &CodeStyle) { for (const auto &RawStringFormat : CodeStyle.RawStringFormats) { @@ -711,8 +721,8 @@ // about removing empty lines on closing blocks. Special case them here. MaxEmptyLinesToKeep = 1; } - unsigned Newlines = std::max( - 1u, std::min(Current.NewlinesBefore, MaxEmptyLinesToKeep)); + unsigned Newlines = + std::max(1u, std::min(Current.NewlinesBefore, MaxEmptyLinesToKeep)); bool ContinuePPDirective = State.Line->InPPDirective && State.Line->Type != LT_ImportStatement; Whitespaces.replaceWhitespace(Current, Newlines, State.Column, State.Column, @@ -1034,8 +1044,7 @@ State.Column += Current.ColumnWidth; State.NextToken = State.NextToken->Next; - unsigned Penalty = - handleEndOfLine(Current, State, DryRun, AllowBreak); + unsigned Penalty = handleEndOfLine(Current, State, DryRun, AllowBreak); if (Current.Role) Current.Role->formatFromToken(State, this, DryRun); @@ -1291,14 +1300,25 @@ const FormatToken &Current, LineState &State, const FormatStyle &RawStringStyle, bool DryRun) { unsigned StartColumn = State.Column - Current.ColumnWidth; - auto Delimiter = *getRawStringDelimiter(Current.TokenText); + StringRef OldDelimiter = *getRawStringDelimiter(Current.TokenText); + auto CanonicalDelimiter = + getCanonicalRawStringDelimiter(Style, RawStringStyle.Language); + StringRef NewDelimiter = + CanonicalDelimiter ? *CanonicalDelimiter : OldDelimiter; + bool UpdateDelimiter = + CanonicalDelimiter && *CanonicalDelimiter != OldDelimiter; + // The text of a raw string is between the leading 'R"delimiter(' and the // trailing 'delimiter)"'. - unsigned PrefixSize = 3 + Delimiter.size(); - unsigned SuffixSize = 2 + Delimiter.size(); + unsigned OldPrefixSize = 3 + OldDelimiter.size(); + unsigned NewPrefixSize = + UpdateDelimiter ? (3 + CanonicalDelimiter->size()) : OldPrefixSize; + unsigned OldSuffixSize = 2 + OldDelimiter.size(); + unsigned NewSuffixSize = + UpdateDelimiter ? (2 + CanonicalDelimiter->size()) : OldSuffixSize; - // The first start column is the column the raw text starts. - unsigned FirstStartColumn = StartColumn + PrefixSize; + // The first start column is the column the raw text starts after formatting. + unsigned FirstStartColumn = StartColumn + NewPrefixSize; // The next start column is the intended indentation a line break inside // the raw string at level 0. It is determined by the following rules: @@ -1309,7 +1329,7 @@ // These rules have the advantage that the formatted content both does not // violate the rectangle rule and visually flows within the surrounding // source. - bool ContentStartsOnNewline = Current.TokenText[PrefixSize] == '\n'; + bool ContentStartsOnNewline = Current.TokenText[OldPrefixSize] == '\n'; unsigned NextStartColumn = ContentStartsOnNewline ? State.Stack.back().Indent + Style.IndentWidth : FirstStartColumn; @@ -1323,11 +1343,11 @@ // - if the raw string prefix does not start on a newline, it is the current // indent. unsigned LastStartColumn = Current.NewlinesBefore - ? FirstStartColumn - PrefixSize + ? FirstStartColumn - NewPrefixSize : State.Stack.back().Indent; std::string RawText = - Current.TokenText.substr(PrefixSize).drop_back(SuffixSize); + Current.TokenText.substr(OldPrefixSize).drop_back(OldSuffixSize); std::pair Fixes = internal::reformat( RawStringStyle, RawText, {tooling::Range(0, RawText.size())}, @@ -1341,8 +1361,33 @@ return 0; } if (!DryRun) { + if (UpdateDelimiter) { + // In 'R"delimiter(...', the delimiter starts 2 characters after the start + // of the token. + SourceLocation PrefixDelimiterStart = + Current.Tok.getLocation().getLocWithOffset(2); + auto PrefixErr = Whitespaces.addReplacement(tooling::Replacement( + SourceMgr, PrefixDelimiterStart, OldDelimiter.size(), NewDelimiter)); + if (PrefixErr) { + llvm::errs() + << "Failed to update the prefix delimiter of a raw string: " + << llvm::toString(std::move(PrefixErr)) << "\n"; + } + // In 'R"delimiter(...)delimiter"', the suffix delimiter starts at + // position length - 1 - |delimiter|. + SourceLocation SuffixDelimiterStart = + Current.Tok.getLocation().getLocWithOffset(Current.TokenText.size() - + 1 - OldDelimiter.size()); + auto SuffixErr = Whitespaces.addReplacement(tooling::Replacement( + SourceMgr, SuffixDelimiterStart, OldDelimiter.size(), NewDelimiter)); + if (SuffixErr) { + llvm::errs() + << "Failed to update the suffix delimiter of a raw string: " + << llvm::toString(std::move(SuffixErr)) << "\n"; + } + } SourceLocation OriginLoc = - Current.Tok.getLocation().getLocWithOffset(PrefixSize); + Current.Tok.getLocation().getLocWithOffset(OldPrefixSize); for (const tooling::Replacement &Fix : Fixes.first) { auto Err = Whitespaces.addReplacement(tooling::Replacement( SourceMgr, OriginLoc.getLocWithOffset(Fix.getOffset()), @@ -1355,7 +1400,7 @@ } unsigned RawLastLineEndColumn = getLastLineEndColumn( *NewCode, FirstStartColumn, Style.TabWidth, Encoding); - State.Column = RawLastLineEndColumn + SuffixSize; + State.Column = RawLastLineEndColumn + NewSuffixSize; return Fixes.second; } @@ -1443,16 +1488,16 @@ return RawStringStyle; } -std::unique_ptr ContinuationIndenter::createBreakableToken( - const FormatToken &Current, LineState &State, bool AllowBreak) { +std::unique_ptr +ContinuationIndenter::createBreakableToken(const FormatToken &Current, + LineState &State, bool AllowBreak) { unsigned StartColumn = State.Column - Current.ColumnWidth; if (Current.isStringLiteral()) { // FIXME: String literal breaking is currently disabled for Java and JS, as // it requires strings to be merged using "+" which we don't support. if (Style.Language == FormatStyle::LK_Java || Style.Language == FormatStyle::LK_JavaScript || - !Style.BreakStringLiterals || - !AllowBreak) + !Style.BreakStringLiterals || !AllowBreak) return nullptr; // Don't break string literals inside preprocessor directives (except for @@ -1638,7 +1683,8 @@ Token->getLengthAfterCompression(ToNextSplitColumns, Split); DEBUG(llvm::dbgs() << " ContentStartColumn: " << ContentStartColumn << "\n"); - DEBUG(llvm::dbgs() << " ToNextSplit: " << ToNextSplitColumns << "\n"); + DEBUG(llvm::dbgs() << " ToNextSplit: " << ToNextSplitColumns + << "\n"); // If the whitespace compression makes us fit, continue on the current // line. bool ContinueOnLine = Index: lib/Format/Format.cpp =================================================================== --- lib/Format/Format.cpp +++ lib/Format/Format.cpp @@ -641,7 +641,8 @@ LLVMStyle.SpacesBeforeTrailingComments = 1; LLVMStyle.Standard = FormatStyle::LS_Cpp11; LLVMStyle.UseTab = FormatStyle::UT_Never; - LLVMStyle.RawStringFormats = {{"pb", FormatStyle::LK_TextProto, "google"}}; + LLVMStyle.RawStringFormats = { + {"pb", FormatStyle::LK_TextProto, "google", /*Canonical=*/false}}; LLVMStyle.ReflowComments = true; LLVMStyle.SpacesInParentheses = false; LLVMStyle.SpacesInSquareBrackets = false; @@ -697,6 +698,14 @@ GoogleStyle.PointerAlignment = FormatStyle::PAS_Left; GoogleStyle.SpacesBeforeTrailingComments = 2; GoogleStyle.Standard = FormatStyle::LS_Auto; + GoogleStyle.RawStringFormats = { + {"pb", FormatStyle::LK_TextProto, "google", /*Canonical=*/false}, + {"PB", FormatStyle::LK_TextProto, "google", /*Canonical=*/false}, + {"PROTO", FormatStyle::LK_TextProto, "google", /*Canonical=*/false}, + {"proto", FormatStyle::LK_TextProto, "google", /*Canonical=*/false}, + {"TEXTPROTO", FormatStyle::LK_TextProto, "google", /*Canonical=*/false}, + {"textproto", FormatStyle::LK_TextProto, "google", /*Canonical=*/true}, + }; GoogleStyle.PenaltyReturnTypeOnItsOwnLine = 200; GoogleStyle.PenaltyBreakBeforeFirstCallParameter = 1; Index: unittests/Format/FormatTestRawStrings.cpp =================================================================== --- unittests/Format/FormatTestRawStrings.cpp +++ unittests/Format/FormatTestRawStrings.cpp @@ -67,21 +67,24 @@ Style.ColumnLimit = ColumnLimit; Style.RawStringFormats = {{/*Delimiter=*/"pb", /*Kind=*/FormatStyle::LK_TextProto, - /*BasedOnStyle=*/"google"}}; + /*BasedOnStyle=*/"google", + /*Canonical=*/false}}; return Style; } FormatStyle getRawStringLLVMCppStyleBasedOn(std::string BasedOnStyle) { FormatStyle Style = getLLVMStyle(); Style.RawStringFormats = {{/*Delimiter=*/"cpp", - /*Kind=*/FormatStyle::LK_Cpp, BasedOnStyle}}; + /*Kind=*/FormatStyle::LK_Cpp, BasedOnStyle, + /*Canonical=*/false}}; return Style; } FormatStyle getRawStringGoogleCppStyleBasedOn(std::string BasedOnStyle) { FormatStyle Style = getGoogleStyle(FormatStyle::LK_Cpp); Style.RawStringFormats = {{/*Delimiter=*/"cpp", - /*Kind=*/FormatStyle::LK_Cpp, BasedOnStyle}}; + /*Kind=*/FormatStyle::LK_Cpp, BasedOnStyle, + /*Canonical=*/false}}; return Style; } @@ -125,9 +128,9 @@ FormatStyle MixedStyle = getLLVMStyle(); MixedStyle.RawStringFormats = { {/*Delimiter=*/"cpp", /*Kind=*/FormatStyle::LK_Cpp, - /*BasedOnStyle=*/"llvm"}, + /*BasedOnStyle=*/"llvm", /*Canonical=*/false}, {/*Delimiter=*/"CPP", /*Kind=*/FormatStyle::LK_Cpp, - /*BasedOnStyle=*/"google"}}; + /*BasedOnStyle=*/"google", /*Canonical=*/false}}; // Format the 'cpp' raw string with '*' on the right. // Format the 'CPP' raw string with '*' on the left. @@ -210,9 +213,9 @@ P p = TP(R"pb(item_1 <1> item_2: <2> item_3 {})pb");)test", - format(R"test( + format(R"test( P p = TP(R"pb(item_1<1> item_2:<2> item_3{ })pb");)test", - getRawStringPbStyleWithColumns(40))); + getRawStringPbStyleWithColumns(40))); expect_eq( R"test( @@ -515,7 +518,6 @@ format(R"test( ASSERT_TRUE(ParseFromString(R"pb(item_1: 1 item_2: 2)pb"), ptr);)test", getRawStringPbStyleWithColumns(40))); - } TEST_F(FormatTestRawStrings, RawStringsInOperands) { @@ -642,7 +644,6 @@ auto S=(count<3)?R"pb(item_1:1)pb":R"pb(item_2:2,item_3:3)pb"; )test", getRawStringPbStyleWithColumns(40))); - } TEST_F(FormatTestRawStrings, PrefixAndSuffixAlignment) { @@ -728,6 +729,54 @@ getRawStringPbStyleWithColumns(20))); } +TEST_F(FormatTestRawStrings, UpdatesToCanonicalDelimiters) { + FormatStyle Style = getRawStringPbStyleWithColumns(25); + Style.RawStringFormats.push_back({/*Delimiter=*/"proto", + /*Kind=*/FormatStyle::LK_TextProto, + /*BasedOnStyle=*/"google", + /*Canonical=*/true}); + expect_eq(R"test(a = R"proto(key: value)proto";)test", + format(R"test(a = R"pb(key:value)pb";)test", Style)); + expect_eq(R"test(a = R"proto(key: value)proto";)test", + format(R"test(a = R"proto(key:value)proto";)test", Style)); + expect_eq(R"test( +f(a, + R"proto(item { + key: value + key: value + })proto"); +)test", + format(R"test( +f(a, + R"pb(item { + key: value + key: value + })pb"); +)test", + Style)); + // The last canonical delimiter for a language is the canonical delimiter for + // that language. + Style.RawStringFormats.push_back({/*Delimiter=*/"cproto", + /*Kind=*/FormatStyle::LK_TextProto, + /*BasedOnStyle=*/"google", + /*Canonical=*/true}); + expect_eq(R"test( +f(a, + R"cproto(item { + key: value + key: value + })cproto"); +)test", + format(R"test( +f(a, + R"proto(item { + key: value + key: value + })proto"); +)test", + Style)); +} + } // end namespace } // end namespace format } // end namespace clang