Skip to content

Commit bfcc2e6

Browse files
authored
Fix extraneous whitespace before first header node (#4043)
1 parent 6d91d81 commit bfcc2e6

File tree

6 files changed

+106
-41
lines changed

6 files changed

+106
-41
lines changed

private/buf/bufformat/formatter.go

Lines changed: 77 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -252,15 +252,22 @@ func (f *formatter) writeFileHeader() {
252252
// There aren't any header values, so we can return early.
253253
return
254254
}
255+
// We use a sentinel value to track which node was written first (since a proto file
256+
// without a syntax is valid). This is then used to ensure that we do not preserve
257+
// unnecessary leading whitespace for the first node written.
258+
first := true
255259
editionNode := f.fileNode.Edition
256260
if editionNode != nil {
257261
f.writeEdition(editionNode)
262+
first = false
258263
}
259264
if syntaxNode := f.fileNode.Syntax; syntaxNode != nil && editionNode == nil {
260265
f.writeSyntax(syntaxNode)
266+
first = false
261267
}
262268
if packageNode != nil {
263-
f.writePackage(packageNode)
269+
f.writePackage(packageNode, first)
270+
first = false
264271
}
265272
sort.Slice(importNodes, func(i, j int) bool {
266273
iName := importNodes[i].Name.AsString()
@@ -297,7 +304,8 @@ func (f *formatter) writeFileHeader() {
297304
continue
298305
}
299306

300-
f.writeImport(importNode, i > 0)
307+
f.writeImport(importNode, i > 0, first)
308+
first = false
301309
}
302310
sort.Slice(optionNodes, func(i, j int) bool {
303311
// The default options (e.g. cc_enable_arenas) should always
@@ -320,7 +328,8 @@ func (f *formatter) writeFileHeader() {
320328
if i == 0 && f.previousNode != nil && !f.leadingCommentsContainBlankLine(optionNode) {
321329
f.P("")
322330
}
323-
f.writeFileOption(optionNode, i > 0)
331+
f.writeFileOption(optionNode, i > 0, first)
332+
first = false
324333
}
325334
}
326335

@@ -349,7 +358,10 @@ func (f *formatter) writeFileTypes() {
349358
//
350359
// syntax = "proto3";
351360
func (f *formatter) writeSyntax(syntaxNode *ast.SyntaxNode) {
352-
f.writeStart(syntaxNode.Keyword)
361+
// If this is the first node, we want to ignore leading whitespace, unless there are
362+
// leading comments.
363+
info := f.nodeInfo(syntaxNode)
364+
f.writeStart(syntaxNode.Keyword, info.LeadingComments().Len() == 0)
353365
f.Space()
354366
f.writeInline(syntaxNode.Equals)
355367
f.Space()
@@ -363,7 +375,10 @@ func (f *formatter) writeSyntax(syntaxNode *ast.SyntaxNode) {
363375
//
364376
// edition = "2023";
365377
func (f *formatter) writeEdition(editionNode *ast.EditionNode) {
366-
f.writeStart(editionNode.Keyword)
378+
// If this is the first node, we want to ignore leading whitespace, unless there are
379+
// leading comments.
380+
info := f.nodeInfo(editionNode)
381+
f.writeStart(editionNode.Keyword, info.LeadingComments().Len() == 0)
367382
f.Space()
368383
f.writeInline(editionNode.Equals)
369384
f.Space()
@@ -376,8 +391,11 @@ func (f *formatter) writeEdition(editionNode *ast.EditionNode) {
376391
// For example,
377392
//
378393
// package acme.weather.v1;
379-
func (f *formatter) writePackage(packageNode *ast.PackageNode) {
380-
f.writeStart(packageNode.Keyword)
394+
func (f *formatter) writePackage(packageNode *ast.PackageNode, first bool) {
395+
// If this is the first node, we want to ignore leading whitespace, unless there are
396+
// leading comments.
397+
info := f.nodeInfo(packageNode)
398+
f.writeStart(packageNode.Keyword, first && info.LeadingComments().Len() == 0)
381399
f.Space()
382400
f.writeInline(packageNode.Name)
383401
f.writeLineEnd(packageNode.Semicolon)
@@ -388,8 +406,10 @@ func (f *formatter) writePackage(packageNode *ast.PackageNode) {
388406
// For example,
389407
//
390408
// import "google/protobuf/descriptor.proto";
391-
func (f *formatter) writeImport(importNode *ast.ImportNode, forceCompact bool) {
392-
f.writeStartMaybeCompact(importNode.Keyword, forceCompact)
409+
func (f *formatter) writeImport(importNode *ast.ImportNode, forceCompact, first bool) {
410+
// If this is the first node, we want to ignore leading whitespace, unless there are
411+
// leading comments.
412+
f.writeStartMaybeCompact(importNode.Keyword, forceCompact, first && !f.importHasComment(importNode))
393413
f.Space()
394414
// We don't want to write the "public" and "weak" nodes
395415
// if they aren't defined. One could be set, but never both.
@@ -408,8 +428,11 @@ func (f *formatter) writeImport(importNode *ast.ImportNode, forceCompact bool) {
408428
// writeFileOption writes a file option. This function is slightly
409429
// different than f.writeOption because file options are sorted at
410430
// the top of the file, and leading comments are adjusted accordingly.
411-
func (f *formatter) writeFileOption(optionNode *ast.OptionNode, forceCompact bool) {
412-
f.writeStartMaybeCompact(optionNode.Keyword, forceCompact)
431+
func (f *formatter) writeFileOption(optionNode *ast.OptionNode, forceCompact, first bool) {
432+
// If this is the first node, we want to ignore leading whitespace, unless there are
433+
// leading comments.
434+
info := f.nodeInfo(optionNode)
435+
f.writeStartMaybeCompact(optionNode.Keyword, forceCompact, first && info.LeadingComments().Len() == 0)
413436
f.Space()
414437
f.writeNode(optionNode.Name)
415438
f.Space()
@@ -481,11 +504,11 @@ func (f *formatter) writeLastCompactOption(optionNode *ast.OptionNode) {
481504
func (f *formatter) writeOptionPrefix(optionNode *ast.OptionNode) {
482505
if optionNode.Keyword != nil {
483506
// Compact options don't have the keyword.
484-
f.writeStart(optionNode.Keyword)
507+
f.writeStart(optionNode.Keyword, false)
485508
f.Space()
486509
f.writeNode(optionNode.Name)
487510
} else {
488-
f.writeStart(optionNode.Name)
511+
f.writeStart(optionNode.Name, false)
489512
}
490513
f.Space()
491514
f.writeInline(optionNode.Equals)
@@ -562,7 +585,7 @@ func (f *formatter) writeMessage(messageNode *ast.MessageNode) {
562585
}
563586
}
564587
}
565-
f.writeStart(messageNode.Keyword)
588+
f.writeStart(messageNode.Keyword, false)
566589
f.Space()
567590
f.writeInline(messageNode.Name)
568591
f.Space()
@@ -791,14 +814,14 @@ func (f *formatter) writeMessageFieldPrefix(messageFieldNode *ast.MessageFieldNo
791814
// normally formatted in-line (i.e. as option name components).
792815
fieldReferenceNode := messageFieldNode.Name
793816
if fieldReferenceNode.Open != nil {
794-
f.writeStart(fieldReferenceNode.Open)
817+
f.writeStart(fieldReferenceNode.Open, false)
795818
if fieldReferenceNode.URLPrefix != nil {
796819
f.writeInline(fieldReferenceNode.URLPrefix)
797820
f.writeInline(fieldReferenceNode.Slash)
798821
}
799822
f.writeInline(fieldReferenceNode.Name)
800823
} else {
801-
f.writeStart(fieldReferenceNode.Name)
824+
f.writeStart(fieldReferenceNode.Name, false)
802825
}
803826
if fieldReferenceNode.Close != nil {
804827
f.writeInline(fieldReferenceNode.Close)
@@ -833,7 +856,7 @@ func (f *formatter) writeEnum(enumNode *ast.EnumNode) {
833856
}
834857
}
835858
}
836-
f.writeStart(enumNode.Keyword)
859+
f.writeStart(enumNode.Keyword, false)
837860
f.Space()
838861
f.writeInline(enumNode.Name)
839862
f.Space()
@@ -853,7 +876,7 @@ func (f *formatter) writeEnum(enumNode *ast.EnumNode) {
853876
// deprecated = true
854877
// ];
855878
func (f *formatter) writeEnumValue(enumValueNode *ast.EnumValueNode) {
856-
f.writeStart(enumValueNode.Name)
879+
f.writeStart(enumValueNode.Name, false)
857880
f.Space()
858881
f.writeInline(enumValueNode.Equals)
859882
f.Space()
@@ -879,7 +902,7 @@ func (f *formatter) writeField(fieldNode *ast.FieldNode) {
879902
// a label might not be defined, but it has the leading comments attached
880903
// to it.
881904
if fieldNode.Label.KeywordNode != nil {
882-
f.writeStart(fieldNode.Label)
905+
f.writeStart(fieldNode.Label, false)
883906
f.Space()
884907
f.writeInline(fieldNode.FldType)
885908
} else {
@@ -888,7 +911,7 @@ func (f *formatter) writeField(fieldNode *ast.FieldNode) {
888911
if compoundIdentNode, ok := fieldNode.FldType.(*ast.CompoundIdentNode); ok {
889912
f.writeCompoundIdentForFieldName(compoundIdentNode)
890913
} else {
891-
f.writeStart(fieldNode.FldType)
914+
f.writeStart(fieldNode.FldType, false)
892915
}
893916
}
894917
f.Space()
@@ -926,7 +949,7 @@ func (f *formatter) writeMapField(mapFieldNode *ast.MapFieldNode) {
926949

927950
// writeMapType writes a map type (e.g. 'map<string, string>').
928951
func (f *formatter) writeMapType(mapTypeNode *ast.MapTypeNode) {
929-
f.writeStart(mapTypeNode.Keyword)
952+
f.writeStart(mapTypeNode.Keyword, false)
930953
f.writeInline(mapTypeNode.OpenAngle)
931954
f.writeInline(mapTypeNode.KeyType)
932955
f.writeInline(mapTypeNode.Comma)
@@ -962,7 +985,7 @@ func (f *formatter) writeExtend(extendNode *ast.ExtendNode) {
962985
}
963986
}
964987
}
965-
f.writeStart(extendNode.Keyword)
988+
f.writeStart(extendNode.Keyword, false)
966989
f.Space()
967990
f.writeInline(extendNode.Extendee)
968991
f.Space()
@@ -990,7 +1013,7 @@ func (f *formatter) writeService(serviceNode *ast.ServiceNode) {
9901013
}
9911014
}
9921015
}
993-
f.writeStart(serviceNode.Keyword)
1016+
f.writeStart(serviceNode.Keyword, false)
9941017
f.Space()
9951018
f.writeInline(serviceNode.Name)
9961019
f.Space()
@@ -1018,7 +1041,7 @@ func (f *formatter) writeRPC(rpcNode *ast.RPCNode) {
10181041
}
10191042
}
10201043
}
1021-
f.writeStart(rpcNode.Keyword)
1044+
f.writeStart(rpcNode.Keyword, false)
10221045
f.Space()
10231046
f.writeInline(rpcNode.Name)
10241047
f.writeInline(rpcNode.Input)
@@ -1073,7 +1096,7 @@ func (f *formatter) writeOneOf(oneOfNode *ast.OneofNode) {
10731096
}
10741097
}
10751098
}
1076-
f.writeStart(oneOfNode.Keyword)
1099+
f.writeStart(oneOfNode.Keyword, false)
10771100
f.Space()
10781101
f.writeInline(oneOfNode.Name)
10791102
f.Space()
@@ -1108,13 +1131,13 @@ func (f *formatter) writeGroup(groupNode *ast.GroupNode) {
11081131
// a label might not be defined, but it has the leading comments attached
11091132
// to it.
11101133
if groupNode.Label.KeywordNode != nil {
1111-
f.writeStart(groupNode.Label)
1134+
f.writeStart(groupNode.Label, false)
11121135
f.Space()
11131136
f.writeInline(groupNode.Keyword)
11141137
} else {
11151138
// If a label was not written, the multiline comments will be
11161139
// attached to the keyword.
1117-
f.writeStart(groupNode.Keyword)
1140+
f.writeStart(groupNode.Keyword, false)
11181141
}
11191142
f.Space()
11201143
f.writeInline(groupNode.Name)
@@ -1142,7 +1165,7 @@ func (f *formatter) writeGroup(groupNode *ast.GroupNode) {
11421165
// deprecated = true
11431166
// ];
11441167
func (f *formatter) writeExtensionRange(extensionRangeNode *ast.ExtensionRangeNode) {
1145-
f.writeStart(extensionRangeNode.Keyword)
1168+
f.writeStart(extensionRangeNode.Keyword, false)
11461169
f.Space()
11471170
for i := range extensionRangeNode.Ranges {
11481171
if i > 0 {
@@ -1165,7 +1188,7 @@ func (f *formatter) writeExtensionRange(extensionRangeNode *ast.ExtensionRangeNo
11651188
//
11661189
// reserved 5-10, 100 to max;
11671190
func (f *formatter) writeReserved(reservedNode *ast.ReservedNode) {
1168-
f.writeStart(reservedNode.Keyword)
1191+
f.writeStart(reservedNode.Keyword, false)
11691192
// Either names or ranges will be set, but never both.
11701193
elements := make([]ast.Node, 0, len(reservedNode.Names)+len(reservedNode.Ranges))
11711194
switch {
@@ -1345,7 +1368,7 @@ func (f *formatter) writeArrayLiteral(arrayLiteralNode *ast.ArrayLiteralNode) {
13451368
f.writeLineElement(arrayLiteralNode.Elements[i])
13461369
return
13471370
}
1348-
f.writeStart(arrayLiteralNode.Elements[i])
1371+
f.writeStart(arrayLiteralNode.Elements[i], false)
13491372
f.writeLineEnd(arrayLiteralNode.Commas[i])
13501373
}
13511374
}
@@ -1515,11 +1538,11 @@ func (f *formatter) writeCompoundIdent(compoundIdentNode *ast.CompoundIdentNode)
15151538
// }
15161539
func (f *formatter) writeCompoundIdentForFieldName(compoundIdentNode *ast.CompoundIdentNode) {
15171540
if compoundIdentNode.LeadingDot != nil {
1518-
f.writeStart(compoundIdentNode.LeadingDot)
1541+
f.writeStart(compoundIdentNode.LeadingDot, false)
15191542
}
15201543
for i := range compoundIdentNode.Components {
15211544
if i == 0 && compoundIdentNode.LeadingDot == nil {
1522-
f.writeStart(compoundIdentNode.Components[i])
1545+
f.writeStart(compoundIdentNode.Components[i], false)
15231546
continue
15241547
}
15251548
if i > 0 {
@@ -1560,7 +1583,7 @@ func (f *formatter) writeCompoundStringLiteral(
15601583
for i, child := range compoundStringLiteralNode.Children() {
15611584
if hasTrailingPunctuation && i == len(compoundStringLiteralNode.Children())-1 {
15621585
// inline because there may be a subsequent comma or punctuation from enclosing element
1563-
f.writeStart(child)
1586+
f.writeStart(child, false)
15641587
break
15651588
}
15661589
f.writeLineElement(child)
@@ -1599,7 +1622,7 @@ func (f *formatter) writeCompoundStringLiteralForArray(
15991622
) {
16001623
for i, child := range compoundStringLiteralNode.Children() {
16011624
if !lastElement && i == len(compoundStringLiteralNode.Children())-1 {
1602-
f.writeStart(child)
1625+
f.writeStart(child, false)
16031626
return
16041627
}
16051628
f.writeLineElement(child)
@@ -1626,7 +1649,7 @@ func (f *formatter) writeSignedFloatLiteralForArray(
16261649
signedFloatLiteralNode *ast.SignedFloatLiteralNode,
16271650
lastElement bool,
16281651
) {
1629-
f.writeStart(signedFloatLiteralNode.Sign)
1652+
f.writeStart(signedFloatLiteralNode.Sign, false)
16301653
if lastElement {
16311654
f.writeLineEnd(signedFloatLiteralNode.Float)
16321655
return
@@ -1671,7 +1694,7 @@ func (f *formatter) writeNegativeIntLiteralForArray(
16711694
negativeIntLiteralNode *ast.NegativeIntLiteralNode,
16721695
lastElement bool,
16731696
) {
1674-
f.writeStart(negativeIntLiteralNode.Minus)
1697+
f.writeStart(negativeIntLiteralNode.Minus, false)
16751698
if lastElement {
16761699
f.writeLineEnd(negativeIntLiteralNode.Uint)
16771700
return
@@ -1734,7 +1757,9 @@ func (f *formatter) writeNode(node ast.Node) {
17341757
case *ast.IdentNode:
17351758
f.writeIdent(element)
17361759
case *ast.ImportNode:
1737-
f.writeImport(element, false)
1760+
// Make no assumptions on node ordering, set first == false to always preserve leading
1761+
// whitespace.
1762+
f.writeImport(element, false, false)
17381763
case *ast.KeywordNode:
17391764
f.writeKeyword(element)
17401765
case *ast.MapFieldNode:
@@ -1756,7 +1781,9 @@ func (f *formatter) writeNode(node ast.Node) {
17561781
case *ast.OptionNameNode:
17571782
f.writeOptionName(element)
17581783
case *ast.PackageNode:
1759-
f.writePackage(element)
1784+
// Make no assumptions on node ordering, set first == false to always preserve leading
1785+
// whitespace.
1786+
f.writePackage(element, false)
17601787
case *ast.RangeNode:
17611788
f.writeRange(element)
17621789
case *ast.ReservedNode:
@@ -1818,17 +1845,26 @@ func (f *formatter) writeNode(node ast.Node) {
18181845
// Note that this is one of the most complex component of the formatter - it
18191846
// controls how each node should be separated from one another and preserves
18201847
// newlines in the original source.
1821-
func (f *formatter) writeStart(node ast.Node) {
1822-
f.writeStartMaybeCompact(node, false)
1848+
func (f *formatter) writeStart(node ast.Node, ignoreLeadingWhitespace bool) {
1849+
f.writeStartMaybeCompact(node, false, ignoreLeadingWhitespace)
18231850
}
18241851

1825-
func (f *formatter) writeStartMaybeCompact(node ast.Node, forceCompact bool) {
1852+
func (f *formatter) writeStartMaybeCompact(
1853+
node ast.Node,
1854+
forceCompact bool,
1855+
ignoreLeadingWhitespace bool,
1856+
) {
18261857
defer f.SetPreviousNode(node)
18271858
info := f.nodeInfo(node)
18281859
var (
18291860
nodeNewlineCount = newlineCount(info.LeadingWhitespace())
18301861
compact = forceCompact || isOpenBrace(f.previousNode)
18311862
)
1863+
if ignoreLeadingWhitespace {
1864+
// If we are asked to ignore leading whitespace, then we forcibly set nodeNewlineCount
1865+
// to 0, effectively ignoring the leading whitespace line count.
1866+
nodeNewlineCount = 0
1867+
}
18321868
if length := info.LeadingComments().Len(); length > 0 {
18331869
// If leading comments are defined, the whitespace we care about
18341870
// is attached to the first comment.

private/buf/bufformat/formatter_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ func TestFormatter(t *testing.T) {
3434
testFormatEditions(t)
3535
testFormatProto2(t)
3636
testFormatProto3(t)
37+
testFormatHeader(t)
3738
}
3839

3940
func testFormatCustomOptions(t *testing.T) {
@@ -68,6 +69,10 @@ func testFormatProto3(t *testing.T) {
6869
testFormatNoDiff(t, "testdata/proto3/service/v1")
6970
}
7071

72+
func testFormatHeader(t *testing.T) {
73+
testFormatNoDiff(t, "testdata/header")
74+
}
75+
7176
func testFormatNoDiff(t *testing.T, path string) {
7277
t.Run(path, func(t *testing.T) {
7378
ctx := context.Background()
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import "google/protobuf/any.proto";
2+
import "google/protobuf/descriptor.proto";
3+
import "google/protobuf/timestamp.proto";
4+
5+
option java_package = "com.foo.bar";

0 commit comments

Comments
 (0)