[clang-format] Support <>-style proto message fields

Summary:
This patch adds support for <>-style proto message fields inside proto options.
Previously these were wrongly treated as binary operators and as such were
working only by chance for a limited number of cases.

Reviewers: djasper

Reviewed By: djasper

Subscribers: cfe-commits, klimek

Differential Revision: https://reviews.llvm.org/D34621

llvm-svn: 306406
This commit is contained in:
Krasimir Georgiev 2017-06-27 13:43:07 +00:00
parent 4155c8f1f3
commit ff747be4d5
6 changed files with 188 additions and 15 deletions

View File

@ -56,6 +56,8 @@ static bool startsNextParameter(const FormatToken &Current,
if (Current.is(TT_CtorInitializerComma) &&
Style.BreakConstructorInitializers == FormatStyle::BCIS_BeforeComma)
return true;
if (Style.Language == FormatStyle::LK_Proto && Current.is(TT_SelectorName))
return true;
return Previous.is(tok::comma) && !Current.isTrailingComment() &&
((Previous.isNot(TT_CtorInitializerComma) ||
Style.BreakConstructorInitializers !=
@ -499,6 +501,13 @@ void ContinuationIndenter::addTokenOnCurrentLine(LineState &State, bool DryRun,
}
}
static bool lessOpensProtoMessageField(const FormatToken &LessTok,
const LineState &State) {
assert(LessTok.is(tok::less));
return LessTok.NestingLevel > 0 ||
(LessTok.Previous && LessTok.Previous->is(tok::equal));
}
unsigned ContinuationIndenter::addTokenOnNewLine(LineState &State,
bool DryRun) {
FormatToken &Current = *State.NextToken;
@ -641,6 +650,9 @@ unsigned ContinuationIndenter::addTokenOnNewLine(LineState &State,
// before the corresponding } or ].
if (PreviousNonComment &&
(PreviousNonComment->isOneOf(tok::l_brace, TT_ArrayInitializerLSquare) ||
(Style.Language == FormatStyle::LK_Proto &&
PreviousNonComment->is(tok::less) &&
lessOpensProtoMessageField(*PreviousNonComment, State)) ||
(PreviousNonComment->is(TT_TemplateString) &&
PreviousNonComment->opensScope())))
State.Stack.back().BreakBeforeClosingBrace = true;
@ -682,7 +694,9 @@ unsigned ContinuationIndenter::getNewLineColumn(const LineState &State) {
if (NextNonComment->is(tok::l_brace) && NextNonComment->BlockKind == BK_Block)
return Current.NestingLevel == 0 ? State.FirstIndent
: State.Stack.back().Indent;
if (Current.isOneOf(tok::r_brace, tok::r_square) && State.Stack.size() > 1) {
if ((Current.isOneOf(tok::r_brace, tok::r_square) ||
(Current.is(tok::greater) && Style.Language == FormatStyle::LK_Proto)) &&
State.Stack.size() > 1) {
if (Current.closesBlockOrBlockTypeList(Style))
return State.Stack[State.Stack.size() - 2].NestedBlockIndent;
if (Current.MatchingParen &&
@ -1035,7 +1049,9 @@ void ContinuationIndenter::moveStatePastScopeOpener(LineState &State,
bool BreakBeforeParameter = false;
unsigned NestedBlockIndent = std::max(State.Stack.back().StartOfFunctionCall,
State.Stack.back().NestedBlockIndent);
if (Current.isOneOf(tok::l_brace, TT_ArrayInitializerLSquare)) {
if (Current.isOneOf(tok::l_brace, TT_ArrayInitializerLSquare) ||
(Style.Language == FormatStyle::LK_Proto && Current.is(tok::less) &&
lessOpensProtoMessageField(Current, State))) {
if (Current.opensBlockOrBlockTypeList(Style)) {
NewIndent = Style.IndentWidth +
std::min(State.Column, State.Stack.back().NestedBlockIndent);

View File

@ -465,7 +465,8 @@ struct FormatToken {
return is(TT_ArrayInitializerLSquare) ||
(is(tok::l_brace) &&
(BlockKind == BK_Block || is(TT_DictLiteral) ||
(!Style.Cpp11BracedListStyle && NestingLevel == 0)));
(!Style.Cpp11BracedListStyle && NestingLevel == 0))) ||
(is(tok::less) && Style.Language == FormatStyle::LK_Proto);
}
/// \brief Same as opensBlockOrBlockTypeList, but for the closing token.

View File

@ -89,7 +89,8 @@ private:
continue;
}
if (CurrentToken->isOneOf(tok::r_paren, tok::r_square, tok::r_brace) ||
(CurrentToken->isOneOf(tok::colon, tok::question) && InExprContext))
(CurrentToken->isOneOf(tok::colon, tok::question) && InExprContext &&
Style.Language != FormatStyle::LK_Proto))
return false;
// If a && or || is found and interpreted as a binary operator, this set
// of angles is likely part of something like "a < b && c > d". If the
@ -103,6 +104,14 @@ private:
!Line.startsWith(tok::kw_template))
return false;
updateParameterCount(Left, CurrentToken);
if (Style.Language == FormatStyle::LK_Proto) {
if (FormatToken *Previous = CurrentToken->getPreviousNonComment()) {
if (CurrentToken->is(tok::colon) ||
(CurrentToken->isOneOf(tok::l_brace, tok::less) &&
Previous->isNot(tok::colon)))
Previous->Type = TT_SelectorName;
}
}
if (!consumeToken())
return false;
}
@ -440,7 +449,7 @@ private:
if (CurrentToken->isOneOf(tok::r_paren, tok::r_square))
return false;
updateParameterCount(Left, CurrentToken);
if (CurrentToken->isOneOf(tok::colon, tok::l_brace)) {
if (CurrentToken->isOneOf(tok::colon, tok::l_brace, tok::less)) {
FormatToken *Previous = CurrentToken->getPreviousNonComment();
if (((CurrentToken->is(tok::colon) &&
(!Contexts.back().ColonIsDictLiteral || !Style.isCpp())) ||
@ -2549,10 +2558,16 @@ bool TokenAnnotator::mustBreakBefore(const AnnotatedLine &Line,
// deliberate choice and might have aligned the contents of the string
// literal accordingly. Thus, we try keep existing line breaks.
return Right.NewlinesBefore > 0;
if (Right.Previous->is(tok::l_brace) && Right.NestingLevel == 1 &&
Style.Language == FormatStyle::LK_Proto)
// Don't put enums onto single lines in protocol buffers.
if ((Right.Previous->is(tok::l_brace) ||
(Right.Previous->is(tok::less) &&
Right.Previous->Previous &&
Right.Previous->Previous->is(tok::equal))
) &&
Right.NestingLevel == 1 && Style.Language == FormatStyle::LK_Proto) {
// Don't put enums or option definitions onto single lines in protocol
// buffers.
return true;
}
if (Right.is(TT_InlineASMBrace))
return Right.HasUnescapedNewline;
if (isAllmanBrace(Left) || isAllmanBrace(Right))

View File

@ -1176,9 +1176,11 @@ void UnwrappedLineParser::parseStructuralElement() {
}
nextToken();
if (FormatTok->Tok.is(tok::l_brace)) {
if (FormatTok->Tok.is(tok::l_brace))
parseBracedList();
}
else if (Style.Language == FormatStyle::LK_Proto &&
FormatTok->Tok.is(tok::less))
parseBracedList(/*ClosingBraceKind=*/tok::greater);
break;
case tok::l_square:
parseSquare();
@ -1346,7 +1348,8 @@ bool UnwrappedLineParser::tryToParseBracedList() {
return true;
}
bool UnwrappedLineParser::parseBracedList(bool ContinueOnSemicolons) {
bool UnwrappedLineParser::parseBracedList(bool ContinueOnSemicolons,
tok::TokenKind ClosingBraceKind) {
bool HasError = false;
nextToken();
@ -1375,6 +1378,10 @@ bool UnwrappedLineParser::parseBracedList(bool ContinueOnSemicolons) {
parseChildBlock();
}
}
if (FormatTok->Tok.getKind() == ClosingBraceKind) {
nextToken();
return !HasError;
}
switch (FormatTok->Tok.getKind()) {
case tok::caret:
nextToken();
@ -1401,9 +1408,6 @@ bool UnwrappedLineParser::parseBracedList(bool ContinueOnSemicolons) {
FormatTok->BlockKind = BK_BracedInit;
parseBracedList();
break;
case tok::r_brace:
nextToken();
return !HasError;
case tok::semi:
// JavaScript (or more precisely TypeScript) can have semicolons in braced
// lists (in so-called TypeMemberLists). Thus, the semicolon cannot be

View File

@ -93,7 +93,8 @@ private:
void readTokenWithJavaScriptASI();
void parseStructuralElement();
bool tryToParseBracedList();
bool parseBracedList(bool ContinueOnSemicolons = false);
bool parseBracedList(bool ContinueOnSemicolons = false,
tok::TokenKind ClosingBraceKind = tok::r_brace);
void parseParens();
void parseSquare();
void parseIfThenElse();

View File

@ -207,6 +207,142 @@ TEST_F(FormatTestProto, FormatsOptions) {
" field_c: \"OK\",\n"
" msg_field: <field_d: 123>\n"
"};");
verifyFormat("option (MyProto.options) = {\n"
" msg_field: <>\n"
" field_c: \"OK\",\n"
" msg_field: <field_d: 123>\n"
" field_e: OK\n"
" msg_field: <field_d: 12>\n"
"};");
verifyFormat("option (MyProto.options) = <\n"
" field_a: OK\n"
" field_b: \"OK\"\n"
" field_c: 1\n"
" field_d: 12.5\n"
" field_e: OK\n"
">;");
verifyFormat("option (MyProto.options) = <\n"
" field_a: OK,\n"
" field_b: \"OK\",\n"
" field_c: 1,\n"
" field_d: 12.5,\n"
" field_e: OK,\n"
">;");
verifyFormat("option (MyProto.options) = <\n"
" field_a: \"OK\"\n"
" msg_field: {field_b: OK}\n"
" field_g: OK\n"
" field_g: OK\n"
" field_g: OK\n"
">;");
verifyFormat("option (MyProto.options) = <\n"
" field_a: \"OK\"\n"
" msg_field<\n"
" field_b: OK\n"
" field_c: OK\n"
" field_d: OK\n"
" field_e: OK\n"
" field_f: OK\n"
" >\n"
" field_g: OK\n"
">;");
verifyFormat("option (MyProto.options) = <\n"
" field_a: \"OK\"\n"
" msg_field<\n"
" field_b: OK,\n"
" field_c: OK,\n"
" field_d: OK,\n"
" field_e: OK,\n"
" field_f: OK\n"
" >\n"
" field_g: OK\n"
">;");
verifyFormat("option (MyProto.options) = <\n"
" field_a: \"OK\"\n"
" msg_field: <\n"
" field_b: OK\n"
" field_c: OK\n"
" field_d: OK\n"
" field_e: OK\n"
" field_f: OK\n"
" >\n"
" field_g: OK\n"
">;");
verifyFormat("option (MyProto.options) = <\n"
" field_a: \"OK\"\n"
" msg_field: {\n"
" field_b: OK\n"
" field_c: OK\n"
" field_d: OK\n"
" field_e: OK\n"
" field_f: OK\n"
" }\n"
" field_g: OK\n"
">;");
verifyFormat("option (MyProto.options) = <\n"
" field_a: \"OK\"\n"
" msg_field{\n"
" field_b: OK\n"
" field_c: OK\n"
" field_d: OK\n"
" field_e: OK\n"
" field_f: OK\n"
" }\n"
" field_g: OK\n"
">;");
verifyFormat("option (MyProto.options) = {\n"
" field_a: \"OK\"\n"
" msg_field<\n"
" field_b: OK\n"
" field_c: OK\n"
" field_d: OK\n"
" field_e: OK\n"
" field_f: OK\n"
" >\n"
" field_g: OK\n"
"};");
verifyFormat("option (MyProto.options) = {\n"
" field_a: \"OK\"\n"
" msg_field: <\n"
" field_b: OK\n"
" field_c: OK\n"
" field_d: OK\n"
" field_e: OK\n"
" field_f: OK\n"
" >\n"
" field_g: OK\n"
"};");
verifyFormat("option (MyProto.options) = <\n"
" field_a: \"OK\"\n"
" msg_field{\n"
" field_b: OK\n"
" field_c: OK\n"
" field_d: OK\n"
" msg_field<\n"
" field_A: 1\n"
" field_B: 2\n"
" field_C: 3\n"
" field_D: 4\n"
" field_E: 5\n"
" >\n"
" msg_field<field_A: 1 field_B: 2 field_C: 3 field_D: 4>\n"
" field_e: OK\n"
" field_f: OK\n"
" }\n"
" field_g: OK\n"
">;");
}
TEST_F(FormatTestProto, FormatsService) {