@@ -12,7 +12,6 @@ import (
12
12
"go/token"
13
13
"io"
14
14
"os"
15
- "path/filepath"
16
15
"strconv"
17
16
"strings"
18
17
"text/tabwriter"
@@ -52,11 +51,12 @@ type printer struct {
52
51
fset * token.FileSet
53
52
54
53
// Current state
55
- output bytes.Buffer // raw printer result
56
- indent int // current indentation
57
- mode pmode // current printer mode
58
- lastTok token.Token // the last token printed (token.ILLEGAL if it's whitespace)
59
- wsbuf []whiteSpace // delayed white space
54
+ output bytes.Buffer // raw printer result
55
+ indent int // current indentation
56
+ mode pmode // current printer mode
57
+ impliedSemi bool // if set, a linebreak implies a semicolon
58
+ lastTok token.Token // the last token printed (token.ILLEGAL if it's whitespace)
59
+ wsbuf []whiteSpace // delayed white space
60
60
61
61
// The (possibly estimated) position in the generated output;
62
62
// in AST space (i.e., pos is set whenever a token position is
@@ -73,6 +73,11 @@ type printer struct {
73
73
cindex int // current comment index
74
74
useNodeComments bool // if not set, ignore lead and line comments of nodes
75
75
76
+ // Information about p.comments[p.cindex]; set up by nextComment.
77
+ comment * ast.CommentGroup // = p.comments[p.cindex]; or nil
78
+ commentOffset int // = p.posFor(p.comments[p.cindex].List[0].Pos()).Offset; or infinity
79
+ commentNewline bool // true if the comment group contains newlines
80
+
76
81
// Cache of already computed node sizes.
77
82
nodeSizes map [ast.Node ]int
78
83
@@ -89,6 +94,42 @@ func (p *printer) init(cfg *Config, fset *token.FileSet, nodeSizes map[ast.Node]
89
94
p .cachedPos = - 1
90
95
}
91
96
97
+ // commentsHaveNewline reports whether a list of comments belonging to
98
+ // an *ast.CommentGroup contains newlines. Because the position information
99
+ // may only be partially correct, we also have to read the comment text.
100
+ func (p * printer ) commentsHaveNewline (list []* ast.Comment ) bool {
101
+ // len(list) > 0
102
+ line := p .lineFor (list [0 ].Pos ())
103
+ for i , c := range list {
104
+ if i > 0 && p .lineFor (list [i ].Pos ()) != line {
105
+ // not all comments on the same line
106
+ return true
107
+ }
108
+ if t := c .Text ; len (t ) >= 2 && (t [1 ] == '/' || strings .Contains (t , "\n " )) {
109
+ return true
110
+ }
111
+ }
112
+ _ = line
113
+ return false
114
+ }
115
+
116
+ func (p * printer ) nextComment () {
117
+ for p .cindex < len (p .comments ) {
118
+ c := p .comments [p .cindex ]
119
+ p .cindex ++
120
+ if list := c .List ; len (list ) > 0 {
121
+ p .comment = c
122
+ p .commentOffset = p .posFor (list [0 ].Pos ()).Offset
123
+ p .commentNewline = p .commentsHaveNewline (list )
124
+ return
125
+ }
126
+ // we should not reach here (correct ASTs don't have empty
127
+ // ast.CommentGroup nodes), but be conservative and try again
128
+ }
129
+ // no more comments
130
+ p .commentOffset = infinity
131
+ }
132
+
92
133
func (p * printer ) internalError (msg ... interface {}) {
93
134
if debug {
94
135
fmt .Print (p .pos .String () + ": " )
@@ -204,8 +245,7 @@ func (p *printer) writeItem(pos token.Position, data string, isLit bool) {
204
245
}
205
246
if debug {
206
247
// do not update p.pos - use write0
207
- _ , filename := filepath .Split (pos .Filename )
208
- fmt .Fprintf (& p .output , "[%s:%d:%d]" , filename , pos .Line , pos .Column )
248
+ fmt .Fprintf (& p .output , "/*%s*/" , pos )
209
249
}
210
250
p .writeString (data , isLit )
211
251
p .last = p .pos
@@ -618,12 +658,13 @@ func (p *printer) writeCommentSuffix(needsLinebreak bool) (wroteNewline, dropped
618
658
//
619
659
func (p * printer ) intersperseComments (next token.Position , tok token.Token ) (wroteNewline , droppedFF bool ) {
620
660
var last * ast.Comment
621
- for ; p .commentBefore (next ); p . cindex ++ {
622
- for _ , c := range p .comments [ p . cindex ] .List {
661
+ for p .commentBefore (next ) {
662
+ for _ , c := range p .comment .List {
623
663
p .writeCommentPrefix (p .posFor (c .Pos ()), next , last , c , tok .IsKeyword ())
624
664
p .writeComment (c )
625
665
last = c
626
666
}
667
+ p .nextComment ()
627
668
}
628
669
629
670
if last != nil {
@@ -735,22 +776,24 @@ func mayCombine(prev token.Token, next byte) (b bool) {
735
776
// printed, followed by the actual token.
736
777
//
737
778
func (p * printer ) print (args ... interface {}) {
738
- for _ , f := range args {
739
- next := p . pos // estimated position of next item
740
- data := ""
741
- isLit := false
742
- var tok token. Token
779
+ for _ , arg := range args {
780
+ // information about the current arg
781
+ var data string
782
+ var isLit bool
783
+ var impliedSemi bool // value for p.impliedSemi after this arg
743
784
744
- switch x := f .(type ) {
785
+ switch x := arg .(type ) {
745
786
case pmode :
746
787
// toggle printer mode
747
788
p .mode ^= x
789
+ continue
790
+
748
791
case whiteSpace :
749
792
if x == ignore {
750
793
// don't add ignore's to the buffer; they
751
794
// may screw up "correcting" unindents (see
752
795
// LabeledStmt)
753
- break
796
+ continue
754
797
}
755
798
i := len (p .wsbuf )
756
799
if i == cap (p .wsbuf ) {
@@ -762,13 +805,27 @@ func (p *printer) print(args ...interface{}) {
762
805
}
763
806
p .wsbuf = p .wsbuf [0 : i + 1 ]
764
807
p .wsbuf [i ] = x
808
+ if x == newline || x == formfeed {
809
+ // newlines affect the current state (p.impliedSemi)
810
+ // and not the state after printing arg (impliedSemi)
811
+ // because comments can be interspersed before the arg
812
+ // in this case
813
+ p .impliedSemi = false
814
+ }
815
+ p .lastTok = token .ILLEGAL
816
+ continue
817
+
765
818
case * ast.Ident :
766
819
data = x .Name
767
- tok = token .IDENT
820
+ impliedSemi = true
821
+ p .lastTok = token .IDENT
822
+
768
823
case * ast.BasicLit :
769
824
data = x .Value
770
825
isLit = true
771
- tok = x .Kind
826
+ impliedSemi = true
827
+ p .lastTok = x .Kind
828
+
772
829
case token.Token :
773
830
s := x .String ()
774
831
if mayCombine (p .lastTok , s [0 ]) {
@@ -785,30 +842,40 @@ func (p *printer) print(args ...interface{}) {
785
842
p .wsbuf [0 ] = ' '
786
843
}
787
844
data = s
788
- tok = x
845
+ // some keywords followed by a newline imply a semicolon
846
+ switch x {
847
+ case token .BREAK , token .CONTINUE , token .FALLTHROUGH , token .RETURN ,
848
+ token .INC , token .DEC , token .RPAREN , token .RBRACK , token .RBRACE :
849
+ impliedSemi = true
850
+ }
851
+ p .lastTok = x
852
+
789
853
case token.Pos :
790
854
if x .IsValid () {
791
- next = p .posFor (x ) // accurate position of next item
855
+ p . pos = p .posFor (x ) // accurate position of next item
792
856
}
793
- tok = p .lastTok
857
+ continue
858
+
794
859
case string :
795
860
// incorrect AST - print error message
796
861
data = x
797
862
isLit = true
798
- tok = token .STRING
863
+ impliedSemi = true
864
+ p .lastTok = token .STRING
865
+
799
866
default :
800
- fmt .Fprintf (os .Stderr , "print: unsupported argument %v (%T)\n " , f , f )
867
+ fmt .Fprintf (os .Stderr , "print: unsupported argument %v (%T)\n " , arg , arg )
801
868
panic ("go/printer type" )
802
869
}
803
- p .lastTok = tok
804
- p .pos = next
870
+ // data != ""
805
871
806
- if data != "" {
807
- wroteNewline , droppedFF := p .flush (next , tok )
872
+ next := p . pos // estimated/accurate position of next item
873
+ wroteNewline , droppedFF := p .flush (next , p . lastTok )
808
874
809
- // intersperse extra newlines if present in the source
810
- // (don't do this in flush as it will cause extra newlines
811
- // at the end of a file)
875
+ // intersperse extra newlines if present in the source and
876
+ // if they don't cause extra semicolons (don't do this in
877
+ // flush as it will cause extra newlines at the end of a file)
878
+ if ! p .impliedSemi {
812
879
n := nlimit (next .Line - p .pos .Line )
813
880
// don't exceed maxNewlines if we already wrote one
814
881
if wroteNewline && n == maxNewlines {
@@ -820,22 +887,25 @@ func (p *printer) print(args ...interface{}) {
820
887
ch = '\f' // use formfeed since we dropped one before
821
888
}
822
889
p .writeByteN (ch , n )
890
+ impliedSemi = false
823
891
}
824
-
825
- p .writeItem (next , data , isLit )
826
892
}
893
+
894
+ p .writeItem (next , data , isLit )
895
+ p .impliedSemi = impliedSemi
827
896
}
828
897
}
829
898
830
- // commentBefore returns true iff the current comment occurs
831
- // before the next position in the source code.
899
+ // commentBefore returns true iff the current comment group occurs
900
+ // before the next position in the source code and printing it does
901
+ // not introduce implicit semicolons.
832
902
//
833
- func (p * printer ) commentBefore (next token.Position ) bool {
834
- return p .cindex < len ( p . comments ) && p . posFor ( p . comments [ p . cindex ]. List [ 0 ]. Pos ()). Offset < next . Offset
903
+ func (p * printer ) commentBefore (next token.Position ) ( result bool ) {
904
+ return p .commentOffset < next . Offset && ( ! p . impliedSemi || ! p . commentNewline )
835
905
}
836
906
837
- // Flush prints any pending comments and whitespace occurring textually
838
- // before the position of the next token tok. The Flush result indicates
907
+ // flush prints any pending comments and whitespace occurring textually
908
+ // before the position of the next token tok. The flush result indicates
839
909
// if a newline was written or if a formfeed was dropped from the whitespace
840
910
// buffer.
841
911
//
@@ -915,6 +985,9 @@ func (p *printer) printNode(node interface{}) error {
915
985
// if there are no comments, use node comments
916
986
p .useNodeComments = p .comments == nil
917
987
988
+ // get comments ready for use
989
+ p .nextComment ()
990
+
918
991
// format node
919
992
switch n := node .(type ) {
920
993
case ast.Expr :
@@ -1068,6 +1141,8 @@ func (cfg *Config) fprint(output io.Writer, fset *token.FileSet, node interface{
1068
1141
if err = p .printNode (node ); err != nil {
1069
1142
return
1070
1143
}
1144
+ // print outstanding comments
1145
+ p .impliedSemi = false // EOF acts like a newline
1071
1146
p .flush (token.Position {Offset : infinity , Line : infinity }, token .EOF )
1072
1147
1073
1148
// redirect output through a trimmer to eliminate trailing whitespace
0 commit comments