Index: include/clang/Format/Format.h =================================================================== --- include/clang/Format/Format.h +++ include/clang/Format/Format.h @@ -18,6 +18,8 @@ #include "clang/Basic/LangOptions.h" #include "clang/Tooling/Core/Replacement.h" #include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/DenseMap.h" +#include #include namespace clang { @@ -1211,7 +1213,9 @@ LK_TableGen, /// Should be used for Protocol Buffer messages in text format /// (https://developers.google.com/protocol-buffers/). - LK_TextProto + LK_TextProto, + /// Do not use. Keep at last position. + LK_End, }; bool isCpp() const { return Language == LK_Cpp || Language == LK_ObjC; } @@ -1363,17 +1367,16 @@ /// See documentation of ``RawStringFormats``. struct RawStringFormat { - /// \brief The delimiter that this raw string format matches. - std::string Delimiter; /// \brief The language of this raw string. LanguageKind Language; - /// \brief The style name on which this raw string format is based on. - /// If not specified, the raw string format is based on the style that this - /// format is based on. - std::string BasedOnStyle; + std::vector Delimiters; + std::vector EnclosingFunctionNames; + std::string CanonicalDelimiter; + bool operator==(const RawStringFormat &Other) const { - return Delimiter == Other.Delimiter && Language == Other.Language && - BasedOnStyle == Other.BasedOnStyle; + return Language == Other.Language && Delimiters == Other.Delimiters && + EnclosingFunctionNames == Other.EnclosingFunctionNames && + CanonicalDelimiter == Other.CanonicalDelimiter; } }; @@ -1685,8 +1688,10 @@ Standard == R.Standard && TabWidth == R.TabWidth && UseTab == R.UseTab; } -}; + std::array, FormatStyle::LK_End> + AdditionalLanguageStyles; +}; /// \brief Returns a format style complying with the LLVM coding standards: /// http://llvm.org/docs/CodingStandards.html. FormatStyle getLLVMStyle(); Index: lib/Format/ContinuationIndenter.h =================================================================== --- lib/Format/ContinuationIndenter.h +++ lib/Format/ContinuationIndenter.h @@ -37,11 +37,11 @@ class WhitespaceManager; struct RawStringFormatStyleManager { - llvm::StringMap DelimiterStyle; + llvm::StringMap DelimiterStyle; RawStringFormatStyleManager(const FormatStyle &CodeStyle); - llvm::Optional get(StringRef Delimiter) const; + const FormatStyle *getDelimiterStyle(StringRef Delimiter) const; }; class ContinuationIndenter { Index: lib/Format/ContinuationIndenter.cpp =================================================================== --- lib/Format/ContinuationIndenter.cpp +++ lib/Format/ContinuationIndenter.cpp @@ -23,6 +23,11 @@ #define DEBUG_TYPE "format-indenter" +#define DBG(A) DEBUG({ \ + llvm::dbgs() << __LINE__ << ":" << __func__ << ":" \ + << #A << " = " << A << "\n"; \ +}); + namespace clang { namespace format { @@ -102,25 +107,32 @@ return Delimiter; } +static StringRef +getCanonicalRawStringDelimiter(const FormatStyle &Style, + FormatStyle::LanguageKind Language) { + for (const auto &Format : llvm::reverse(Style.RawStringFormats)) { + if (Format.Language == Language) + return StringRef(Format.CanonicalDelimiter); + } + return ""; +} + RawStringFormatStyleManager::RawStringFormatStyleManager( const FormatStyle &CodeStyle) { for (const auto &RawStringFormat : CodeStyle.RawStringFormats) { - FormatStyle Style; - if (!getPredefinedStyle(RawStringFormat.BasedOnStyle, - RawStringFormat.Language, &Style)) { - Style = getLLVMStyle(); - Style.Language = RawStringFormat.Language; + for (StringRef Delimiter : RawStringFormat.Delimiters) { + DelimiterStyle.insert( + {Delimiter, + CodeStyle.AdditionalLanguageStyles[RawStringFormat.Language].get()}); } - Style.ColumnLimit = CodeStyle.ColumnLimit; - DelimiterStyle.insert({RawStringFormat.Delimiter, Style}); } } -llvm::Optional -RawStringFormatStyleManager::get(StringRef Delimiter) const { +const FormatStyle * +RawStringFormatStyleManager::getDelimiterStyle(StringRef Delimiter) const { auto It = DelimiterStyle.find(Delimiter); if (It == DelimiterStyle.end()) - return None; + return nullptr; return It->second; } @@ -1291,14 +1303,22 @@ 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); + StringRef NewDelimiter = + getCanonicalRawStringDelimiter(Style, RawStringStyle.Language); + if (NewDelimiter.empty() || OldDelimiter.empty()) + NewDelimiter = OldDelimiter; + bool UpdateDelimiter = (NewDelimiter != 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 = 3 + NewDelimiter.size(); + unsigned OldSuffixSize = 2 + OldDelimiter.size(); + unsigned NewSuffixSize = 2 + NewDelimiter.size(); // The first start column is the column the raw text starts. - unsigned FirstStartColumn = StartColumn + PrefixSize; + 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; } @@ -1383,6 +1428,7 @@ // that can be reformatted. auto RawStringStyle = getRawStringStyle(Current, State); if (RawStringStyle) { + DBG(RawStringStyle->PointerAlignment); Penalty = reformatRawStringLiteral(Current, State, *RawStringStyle, DryRun); } else if (Current.IsMultiline && Current.isNot(TT_BlockComment)) { // Don't break multi-line tokens other than block comments and raw string @@ -1436,10 +1482,13 @@ auto Delimiter = getRawStringDelimiter(Current.TokenText); if (!Delimiter) return None; - auto RawStringStyle = RawStringFormats.get(*Delimiter); - if (!RawStringStyle) + DBG(*Delimiter); + auto DelimiterStyle = RawStringFormats.getDelimiterStyle(*Delimiter); + if (!DelimiterStyle) return None; - RawStringStyle->ColumnLimit = getColumnLimit(State); + DBG(DelimiterStyle->PointerAlignment); + FormatStyle RawStringStyle = *DelimiterStyle; + RawStringStyle.ColumnLimit = getColumnLimit(State); return RawStringStyle; } Index: lib/Format/Format.cpp =================================================================== --- lib/Format/Format.cpp +++ lib/Format/Format.cpp @@ -455,9 +455,10 @@ template <> struct MappingTraits { static void mapping(IO &IO, FormatStyle::RawStringFormat &Format) { - IO.mapOptional("Delimiter", Format.Delimiter); IO.mapOptional("Language", Format.Language); - IO.mapOptional("BasedOnStyle", Format.BasedOnStyle); + IO.mapOptional("Delimiters", Format.Delimiters); + IO.mapOptional("EnclosingFunctionNames", Format.EnclosingFunctionNames); + IO.mapOptional("CanonicalDelimiter", Format.CanonicalDelimiter); } }; @@ -641,7 +642,10 @@ LLVMStyle.SpacesBeforeTrailingComments = 1; LLVMStyle.Standard = FormatStyle::LS_Cpp11; LLVMStyle.UseTab = FormatStyle::UT_Never; - LLVMStyle.RawStringFormats = {{"pb", FormatStyle::LK_TextProto, "google"}}; + LLVMStyle.RawStringFormats = { + {FormatStyle::LK_TextProto, /*Delimiters=*/{"pb", "PB"}, + /*EnclosingFunctionNames=*/{}, /*CanonicalDelimiter=*/""}, + }; LLVMStyle.ReflowComments = true; LLVMStyle.SpacesInParentheses = false; LLVMStyle.SpacesInSquareBrackets = false; @@ -829,6 +833,8 @@ return NoStyle; } +/// \brief Gets a predefined style for the specified language by name. + bool getPredefinedStyle(StringRef Name, FormatStyle::LanguageKind Language, FormatStyle *Style) { if (Name.equals_lower("llvm")) { @@ -888,14 +894,31 @@ // Look for a suitable configuration starting from the end, so we can // find the configuration for the specific language first, and the default // configuration (which can only be at slot 0) after it. + bool LanguageFound = false; for (int i = Styles.size() - 1; i >= 0; --i) { - if (Styles[i].Language == Language || - Styles[i].Language == FormatStyle::LK_None) { + if (!LanguageFound && (Styles[i].Language == Language || + Styles[i].Language == FormatStyle::LK_None)) { *Style = Styles[i]; Style->Language = Language; - return make_error_code(ParseError::Success); + LanguageFound = true; + } + if (Styles[i].Language == FormatStyle::LK_None) { + for (unsigned AdditionalLanguage = 0; + AdditionalLanguage < FormatStyle::LK_End; ++AdditionalLanguage) { + FormatStyle AdditionalLanguageStyle = Styles[i]; + AdditionalLanguageStyle.Language = + static_cast(AdditionalLanguage); + if (!Style->AdditionalLanguageStyles[AdditionalLanguage]) + Style->AdditionalLanguageStyles[AdditionalLanguage].reset( + new FormatStyle(AdditionalLanguageStyle)); + } + continue; } + Style->AdditionalLanguageStyles[Styles[i].Language].reset( + new FormatStyle(Styles[i])); } + if (LanguageFound) + return make_error_code(ParseError::Success); return make_error_code(ParseError::Unsuitable); } Index: unittests/Format/FormatTest.cpp =================================================================== --- unittests/Format/FormatTest.cpp +++ unittests/Format/FormatTest.cpp @@ -10402,16 +10402,27 @@ Style.RawStringFormats.clear(); std::vector ExpectedRawStringFormats = { - {"pb", FormatStyle::LK_TextProto, "llvm"}, - {"cpp", FormatStyle::LK_Cpp, "google"}}; + {FormatStyle::LK_TextProto, + {"pb", "proto"}, + {"PARSE_TEXT_PROTO"}, + "textproto"}, + {FormatStyle::LK_Cpp, {"cc", "cpp"}, {"C_CODEBLOCK", "CPPEVAL"}, ""}}; CHECK_PARSE("RawStringFormats:\n" - " - Delimiter: 'pb'\n" - " Language: TextProto\n" - " BasedOnStyle: llvm\n" - " - Delimiter: 'cpp'\n" - " Language: Cpp\n" - " BasedOnStyle: google", + " - Language: TextProto\n" + " Delimiters:\n" + " - 'pb'\n" + " - 'proto'\n" + " EnclosingFunctionNames:\n" + " - 'PARSE_TEXT_PROTO'\n" + " CanonicalDelimiter: 'textproto'\n" + " - Language: Cpp\n" + " Delimiters:\n" + " - 'cc'\n" + " - 'cpp'\n" + " EnclosingFunctionNames:\n" + " - 'C_CODEBLOCK'\n" + " - 'CPPEVAL'", RawStringFormats, ExpectedRawStringFormats); } Index: unittests/Format/FormatTestRawStrings.cpp =================================================================== --- unittests/Format/FormatTestRawStrings.cpp +++ unittests/Format/FormatTestRawStrings.cpp @@ -9,6 +9,8 @@ #include "clang/Format/Format.h" +#include + #include "../Tooling/ReplacementTest.h" #include "FormatTestUtils.h" @@ -19,6 +21,12 @@ #define DEBUG_TYPE "format-test" +#define DBG(A) \ + DEBUG({ \ + llvm::dbgs() << __LINE__ << ":" << __func__ << ":" << #A << " = " << A \ + << "\n"; \ + }); + using clang::tooling::ReplacementTest; using clang::tooling::toReplacements; @@ -65,23 +73,40 @@ FormatStyle getRawStringPbStyleWithColumns(unsigned ColumnLimit) { FormatStyle Style = getLLVMStyle(); Style.ColumnLimit = ColumnLimit; - Style.RawStringFormats = {{/*Delimiter=*/"pb", - /*Kind=*/FormatStyle::LK_TextProto, - /*BasedOnStyle=*/"google"}}; + Style.AdditionalLanguageStyles[FormatStyle::LK_TextProto] = + std::make_shared( + getGoogleStyle(FormatStyle::LK_TextProto)); + Style.RawStringFormats = {{/*Language=*/FormatStyle::LK_TextProto, + /*Delimiters=*/{"pb"}, + /*EnclosingFunctionNames=*/{}, + /*CanonicalDelimiter=*/""}}; return Style; } - FormatStyle getRawStringLLVMCppStyleBasedOn(std::string BasedOnStyle) { + FormatStyle getRawStringLLVMCppStyleBasedOn(std::string Name) { FormatStyle Style = getLLVMStyle(); - Style.RawStringFormats = {{/*Delimiter=*/"cpp", - /*Kind=*/FormatStyle::LK_Cpp, BasedOnStyle}}; + FormatStyle BasedOnStyle = getLLVMStyle(); + getPredefinedStyle(Name, FormatStyle::LK_Cpp, &BasedOnStyle); + Style.AdditionalLanguageStyles[FormatStyle::LK_Cpp] = + std::make_shared(BasedOnStyle); + Style.RawStringFormats = {{/*Language=*/FormatStyle::LK_Cpp, + /*Delimiters=*/{"cpp"}, + /*EnclosingFunctionNames=*/{}, + /*CanonicalDelimiter=*/""}}; + DBG(BasedOnStyle.PointerAlignment); return Style; } - FormatStyle getRawStringGoogleCppStyleBasedOn(std::string BasedOnStyle) { + FormatStyle getRawStringGoogleCppStyleBasedOn(std::string Name) { FormatStyle Style = getGoogleStyle(FormatStyle::LK_Cpp); - Style.RawStringFormats = {{/*Delimiter=*/"cpp", - /*Kind=*/FormatStyle::LK_Cpp, BasedOnStyle}}; + FormatStyle BasedOnStyle = getLLVMStyle(); + getPredefinedStyle(Name, FormatStyle::LK_Cpp, &BasedOnStyle); + Style.AdditionalLanguageStyles[FormatStyle::LK_Cpp] = + std::make_shared(BasedOnStyle); + Style.RawStringFormats = {{/*Language=*/FormatStyle::LK_Cpp, + /*Delimiters=*/{"cpp"}, + /*EnclosingFunctionNames=*/{}, + /*CanonicalDelimiter=*/""}}; return Style; } @@ -96,17 +121,19 @@ // llvm style puts '*' on the right. // google style puts '*' on the left. - // Use the llvm style if the raw string style has no BasedOnStyle. - expect_eq(R"test(int *i = R"cpp(int *p = nullptr;)cpp")test", - format(R"test(int * i = R"cpp(int * p = nullptr;)cpp")test", - getRawStringLLVMCppStyleBasedOn(""))); - - // Use the google style if the raw string style has BasedOnStyle=google. + // Use llvm style outside and the google style inside if the raw string style + // is based on google. expect_eq(R"test(int *i = R"cpp(int* p = nullptr;)cpp")test", format(R"test(int * i = R"cpp(int * p = nullptr;)cpp")test", getRawStringLLVMCppStyleBasedOn("google"))); - // Use the llvm style if the raw string style has no BasedOnStyle=llvm. + // Use llvm style if the raw string style has no BasedOnStyle. + expect_eq(R"test(int *i = R"cpp(int *p = nullptr;)cpp")test", + format(R"test(int * i = R"cpp(int * p = nullptr;)cpp")test", + getRawStringLLVMCppStyleBasedOn(""))); + + // Use google style outside and the llvm style inside if the raw string style + // is based on llvm. expect_eq(R"test(int* i = R"cpp(int *p = nullptr;)cpp")test", format(R"test(int * i = R"cpp(int * p = nullptr;)cpp")test", getRawStringGoogleCppStyleBasedOn("llvm"))); @@ -121,29 +148,6 @@ s = R"PB(item:1)PB"; t = R"pb(item:1)pb";)test", getRawStringPbStyleWithColumns(40))); - - FormatStyle MixedStyle = getLLVMStyle(); - MixedStyle.RawStringFormats = { - {/*Delimiter=*/"cpp", /*Kind=*/FormatStyle::LK_Cpp, - /*BasedOnStyle=*/"llvm"}, - {/*Delimiter=*/"CPP", /*Kind=*/FormatStyle::LK_Cpp, - /*BasedOnStyle=*/"google"}}; - - // Format the 'cpp' raw string with '*' on the right. - // Format the 'CPP' raw string with '*' on the left. - // Do not format the 'Cpp' raw string. - // Do not format non-raw strings. - expect_eq(R"test( -a = R"cpp(int *i = 0;)cpp"; -b = R"CPP(int* j = 0;)CPP"; -c = R"Cpp(int * k = 0;)Cpp"; -d = R"cpp(int * k = 0;)Cpp";)test", - format(R"test( -a = R"cpp(int * i = 0;)cpp"; -b = R"CPP(int * j = 0;)CPP"; -c = R"Cpp(int * k = 0;)Cpp"; -d = R"cpp(int * k = 0;)Cpp";)test", - MixedStyle)); } TEST_F(FormatTestRawStrings, ReformatsShortRawStringsOnSingleLine) { @@ -210,9 +214,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 +519,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 +645,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 +730,13 @@ getRawStringPbStyleWithColumns(20))); } +TEST_F(FormatTestRawStrings, UpdatesToCanonicalDelimiters) { + FormatStyle Style = getRawStringPbStyleWithColumns(25); + Style.RawStringFormats[0].CanonicalDelimiter = "proto"; + expect_eq(R"test(a = R"proto(key: value)proto";)test", + format(R"test(a = R"pb(key:value)pb";)test", Style)); +} + } // end namespace } // end namespace format } // end namespace clang