1 // Copyright 2009 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
5 // Package printer implements printing of AST nodes.
21 maxNewlines
= 2 // max. number of newlines between source text
22 debug
= false // enable for debugging
29 ignore
= whiteSpace(0)
30 blank
= whiteSpace(' ')
31 vtab
= whiteSpace('\v')
32 newline
= whiteSpace('\n')
33 formfeed
= whiteSpace('\f')
34 indent
= whiteSpace('>')
35 unindent
= whiteSpace('<')
38 // A pmode value represents the current printer mode.
42 noExtraBlank pmode
= 1 << iota // disables extra blank after /*-style comment
43 noExtraLinebreak
// disables extra line break after /*-style comment
46 type commentInfo
struct {
47 cindex
int // current comment index
48 comment
*ast
.CommentGroup
// = printer.comments[cindex]; or nil
49 commentOffset
int // = printer.posFor(printer.comments[cindex].List[0].Pos()).Offset; or infinity
50 commentNewline
bool // true if the comment group contains newlines
54 // Configuration (does not change after initialization)
59 output
[]byte // raw printer result
60 indent
int // current indentation
61 level
int // level == 0: outside composite literal; level > 0: inside composite literal
62 mode pmode
// current printer mode
63 endAlignment
bool // if set, terminate alignment immediately
64 impliedSemi
bool // if set, a linebreak implies a semicolon
65 lastTok token
.Token
// last token printed (token.ILLEGAL if it's whitespace)
66 prevOpen token
.Token
// previous non-brace "open" token (, [, or token.ILLEGAL
67 wsbuf
[]whiteSpace
// delayed white space
68 goBuild
[]int // start index of all //go:build comments in output
69 plusBuild
[]int // start index of all // +build comments in output
72 // The out position differs from the pos position when the result
73 // formatting differs from the source formatting (in the amount of
74 // white space). If there's a difference and SourcePos is set in
75 // ConfigMode, //line directives are used in the output to restore
76 // original source positions for a reader.
77 pos token
.Position
// current position in AST (source) space
78 out token
.Position
// current position in output space
79 last token
.Position
// value of pos after calling writeString
80 linePtr
*int // if set, record out.Line for the next token in *linePtr
82 // The list of all source comments, in order of appearance.
83 comments
[]*ast
.CommentGroup
// may be nil
84 useNodeComments
bool // if not set, ignore lead and line comments of nodes
86 // Information about p.comments[p.cindex]; set up by nextComment.
89 // Cache of already computed node sizes.
90 nodeSizes
map[ast
.Node
]int
92 // Cache of most recently computed line position.
94 cachedLine
int // line corresponding to cachedPos
97 func (p
*printer
) init(cfg
*Config
, fset
*token
.FileSet
, nodeSizes
map[ast
.Node
]int) {
100 p
.pos
= token
.Position
{Line
: 1, Column
: 1}
101 p
.out
= token
.Position
{Line
: 1, Column
: 1}
102 p
.wsbuf
= make([]whiteSpace
, 0, 16) // whitespace sequences are short
103 p
.nodeSizes
= nodeSizes
107 func (p
*printer
) internalError(msg
...any
) {
109 fmt
.Print(p
.pos
.String() + ": ")
115 // commentsHaveNewline reports whether a list of comments belonging to
116 // an *ast.CommentGroup contains newlines. Because the position information
117 // may only be partially correct, we also have to read the comment text.
118 func (p
*printer
) commentsHaveNewline(list
[]*ast
.Comment
) bool {
120 line
:= p
.lineFor(list
[0].Pos())
121 for i
, c
:= range list
{
122 if i
> 0 && p
.lineFor(list
[i
].Pos()) != line
{
123 // not all comments on the same line
126 if t
:= c
.Text
; len(t
) >= 2 && (t
[1] == '/' || strings
.Contains(t
, "\n")) {
134 func (p
*printer
) nextComment() {
135 for p
.cindex
< len(p
.comments
) {
136 c
:= p
.comments
[p
.cindex
]
138 if list
:= c
.List
; len(list
) > 0 {
140 p
.commentOffset
= p
.posFor(list
[0].Pos()).Offset
141 p
.commentNewline
= p
.commentsHaveNewline(list
)
144 // we should not reach here (correct ASTs don't have empty
145 // ast.CommentGroup nodes), but be conservative and try again
148 p
.commentOffset
= infinity
151 // commentBefore reports whether the current comment group occurs
152 // before the next position in the source code and printing it does
153 // not introduce implicit semicolons.
155 func (p
*printer
) commentBefore(next token
.Position
) bool {
156 return p
.commentOffset
< next
.Offset
&& (!p
.impliedSemi ||
!p
.commentNewline
)
159 // commentSizeBefore returns the estimated size of the
160 // comments on the same line before the next position.
162 func (p
*printer
) commentSizeBefore(next token
.Position
) int {
163 // save/restore current p.commentInfo (p.nextComment() modifies it)
164 defer func(info commentInfo
) {
169 for p
.commentBefore(next
) {
170 for _
, c
:= range p
.comment
.List
{
178 // recordLine records the output line number for the next non-whitespace
179 // token in *linePtr. It is used to compute an accurate line number for a
180 // formatted construct, independent of pending (not yet emitted) whitespace
183 func (p
*printer
) recordLine(linePtr
*int) {
187 // linesFrom returns the number of output lines between the current
188 // output line and the line argument, ignoring any pending (not yet
189 // emitted) whitespace or comments. It is used to compute an accurate
190 // size (in number of lines) for a formatted construct.
192 func (p
*printer
) linesFrom(line
int) int {
193 return p
.out
.Line
- line
196 func (p
*printer
) posFor(pos token
.Pos
) token
.Position
{
197 // not used frequently enough to cache entire token.Position
198 return p
.fset
.PositionFor(pos
, false /* absolute position */)
201 func (p
*printer
) lineFor(pos token
.Pos
) int {
202 if pos
!= p
.cachedPos
{
204 p
.cachedLine
= p
.fset
.PositionFor(pos
, false /* absolute position */).Line
209 // writeLineDirective writes a //line directive if necessary.
210 func (p
*printer
) writeLineDirective(pos token
.Position
) {
211 if pos
.IsValid() && (p
.out
.Line
!= pos
.Line || p
.out
.Filename
!= pos
.Filename
) {
212 p
.output
= append(p
.output
, tabwriter
.Escape
) // protect '\n' in //line from tabwriter interpretation
213 p
.output
= append(p
.output
, fmt
.Sprintf("//line %s:%d\n", pos
.Filename
, pos
.Line
)...)
214 p
.output
= append(p
.output
, tabwriter
.Escape
)
215 // p.out must match the //line directive
216 p
.out
.Filename
= pos
.Filename
217 p
.out
.Line
= pos
.Line
221 // writeIndent writes indentation.
222 func (p
*printer
) writeIndent() {
223 // use "hard" htabs - indentation columns
224 // must not be discarded by the tabwriter
225 n
:= p
.Config
.Indent
+ p
.indent
// include base indentation
226 for i
:= 0; i
< n
; i
++ {
227 p
.output
= append(p
.output
, '\t')
236 // writeByte writes ch n times to p.output and updates p.pos.
237 // Only used to write formatting (white space) characters.
238 func (p
*printer
) writeByte(ch
byte, n
int) {
240 // Ignore any alignment control character;
241 // and at the end of the line, break with
242 // a formfeed to indicate termination of
249 p
.endAlignment
= false
253 if p
.out
.Column
== 1 {
254 // no need to write line directives before white space
258 for i
:= 0; i
< n
; i
++ {
259 p
.output
= append(p
.output
, ch
)
264 if ch
== '\n' || ch
== '\f' {
275 // writeString writes the string s to p.output and updates p.pos, p.out,
276 // and p.last. If isLit is set, s is escaped w/ tabwriter.Escape characters
277 // to protect s from being interpreted by the tabwriter.
279 // Note: writeString is only used to write Go tokens, literals, and
280 // comments, all of which must be written literally. Thus, it is correct
281 // to always set isLit = true. However, setting it explicitly only when
282 // needed (i.e., when we don't know that s contains no tabs or line breaks)
283 // avoids processing extra escape characters and reduces run time of the
284 // printer benchmark by up to 10%.
286 func (p
*printer
) writeString(pos token
.Position
, s
string, isLit
bool) {
287 if p
.out
.Column
== 1 {
288 if p
.Config
.Mode
&SourcePos
!= 0 {
289 p
.writeLineDirective(pos
)
295 // update p.pos (if pos is invalid, continue with existing p.pos)
296 // Note: Must do this after handling line beginnings because
297 // writeIndent updates p.pos if there's indentation, but p.pos
298 // is the position of s.
303 // Protect s such that is passes through the tabwriter
304 // unchanged. Note that valid Go programs cannot contain
305 // tabwriter.Escape bytes since they do not appear in legal
307 p
.output
= append(p
.output
, tabwriter
.Escape
)
311 p
.output
= append(p
.output
, fmt
.Sprintf("/*%s*/", pos
)...) // do not update p.pos!
313 p
.output
= append(p
.output
, s
...)
317 var li
int // index of last newline; valid if nlines > 0
318 for i
:= 0; i
< len(s
); i
++ {
319 // Raw string literals may contain any character except back quote (`).
320 if ch
:= s
[i
]; ch
== '\n' || ch
== '\f' {
321 // account for line break
324 // A line break inside a literal will break whatever column
325 // formatting is in place; ignore any further alignment through
326 // the end of the line.
327 p
.endAlignment
= true
330 p
.pos
.Offset
+= len(s
)
338 p
.pos
.Column
+= len(s
)
339 p
.out
.Column
+= len(s
)
343 p
.output
= append(p
.output
, tabwriter
.Escape
)
349 // writeCommentPrefix writes the whitespace before a comment.
350 // If there is any pending whitespace, it consumes as much of
351 // it as is likely to help position the comment nicely.
352 // pos is the comment position, next the position of the item
353 // after all pending comments, prev is the previous comment in
354 // a group of comments (or nil), and tok is the next token.
356 func (p
*printer
) writeCommentPrefix(pos
, next token
.Position
, prev
*ast
.Comment
, tok token
.Token
) {
357 if len(p
.output
) == 0 {
358 // the comment is the first item to be printed - don't write any whitespace
362 if pos
.IsValid() && pos
.Filename
!= p
.last
.Filename
{
363 // comment in a different file - separate with newlines
364 p
.writeByte('\f', maxNewlines
)
368 if pos
.Line
== p
.last
.Line
&& (prev
== nil || prev
.Text
[1] != '/') {
369 // comment on the same line as last item:
370 // separate with at least one separator
373 // first comment of a comment group
375 for i
, ch
:= range p
.wsbuf
{
378 // ignore any blanks before a comment
382 // respect existing tabs - important
383 // for proper formatting of commented structs
387 // apply pending indentation
395 // make sure there is at least one separator
398 if pos
.Line
== next
.Line
{
399 // next item is on the same line as the comment
400 // (which must be a /*-style comment): separate
401 // with a blank instead of a tab
408 // comment on a different line:
409 // separate with at least one line break
410 droppedLinebreak
:= false
412 for i
, ch
:= range p
.wsbuf
{
415 // ignore any horizontal whitespace before line breaks
419 // apply pending indentation
422 // if this is not the last unindent, apply it
423 // as it is (likely) belonging to the last
424 // construct (e.g., a multi-line expression list)
425 // and is not part of closing a block
426 if i
+1 < len(p
.wsbuf
) && p
.wsbuf
[i
+1] == unindent
{
429 // if the next token is not a closing }, apply the unindent
430 // if it appears that the comment is aligned with the
431 // token; otherwise assume the unindent is part of a
432 // closing block and stop (this scenario appears with
433 // comments before a case label where the comments
434 // apply to the next case instead of the current one)
435 if tok
!= token
.RBRACE
&& pos
.Column
== next
.Column
{
438 case newline
, formfeed
:
440 droppedLinebreak
= prev
== nil // record only if first comment of a group
447 // determine number of linebreaks before the comment
449 if pos
.IsValid() && p
.last
.IsValid() {
450 n
= pos
.Line
- p
.last
.Line
451 if n
< 0 { // should never happen
456 // at the package scope level only (p.indent == 0),
457 // add an extra newline if we dropped one before:
458 // this preserves a blank line before documentation
459 // comments at the package scope level (issue 2570)
460 if p
.indent
== 0 && droppedLinebreak
{
464 // make sure there is at least one line break
465 // if the previous comment was a line comment
466 if n
== 0 && prev
!= nil && prev
.Text
[1] == '/' {
471 // use formfeeds to break columns before a comment;
472 // this is analogous to using formfeeds to separate
473 // individual lines of /*-style comments
474 p
.writeByte('\f', nlimit(n
))
479 // Returns true if s contains only white space
480 // (only tabs and blanks can appear in the printer's context).
482 func isBlank(s
string) bool {
483 for i
:= 0; i
< len(s
); i
++ {
491 // commonPrefix returns the common prefix of a and b.
492 func commonPrefix(a
, b
string) string {
494 for i
< len(a
) && i
< len(b
) && a
[i
] == b
[i
] && (a
[i
] <= ' ' || a
[i
] == '*') {
500 // trimRight returns s with trailing whitespace removed.
501 func trimRight(s
string) string {
502 return strings
.TrimRightFunc(s
, unicode
.IsSpace
)
505 // stripCommonPrefix removes a common prefix from /*-style comment lines (unless no
506 // comment line is indented, all but the first line have some form of space prefix).
507 // The prefix is computed using heuristics such that is likely that the comment
508 // contents are nicely laid out after re-printing each line using the printer's
509 // current indentation.
511 func stripCommonPrefix(lines
[]string) {
513 return // at most one line - nothing to do
517 // The heuristic in this function tries to handle a few
518 // common patterns of /*-style comments: Comments where
519 // the opening /* and closing */ are aligned and the
520 // rest of the comment text is aligned and indented with
521 // blanks or tabs, cases with a vertical "line of stars"
522 // on the left, and cases where the closing */ is on the
523 // same line as the last comment text.
525 // Compute maximum common white prefix of all but the first,
526 // last, and blank lines, and replace blank lines with empty
527 // lines (the first line starts with /* and has no prefix).
528 // In cases where only the first and last lines are not blank,
529 // such as two-line comments, or comments where all inner lines
530 // are blank, consider the last line for the prefix computation
531 // since otherwise the prefix would be empty.
533 // Note that the first and last line are never empty (they
534 // contain the opening /* and closing */ respectively) and
535 // thus they can be ignored by the blank line check.
539 for i
, line
:= range lines
[1 : len(lines
)-1] {
541 lines
[1+i
] = "" // range starts with lines[1]
547 prefix
= commonPrefix(prefix
, line
)
552 // If we don't have a prefix yet, consider the last line.
554 line
:= lines
[len(lines
)-1]
555 prefix
= commonPrefix(line
, line
)
559 * Check for vertical "line of stars" and correct prefix accordingly.
562 if p
, _
, ok
:= strings
.Cut(prefix
, "*"); ok
{
563 // remove trailing blank from prefix so stars remain aligned
564 prefix
= strings
.TrimSuffix(p
, " ")
567 // No line of stars present.
568 // Determine the white space on the first line after the /*
569 // and before the beginning of the comment text, assume two
570 // blanks instead of the /* unless the first character after
571 // the /* is a tab. If the first comment line is empty but
572 // for the opening /*, assume up to 3 blanks or a tab. This
573 // whitespace may be found as suffix in the common prefix.
575 if isBlank(first
[2:]) {
576 // no comment text on the first line:
577 // reduce prefix by up to 3 blanks or a tab
578 // if present - this keeps comment text indented
579 // relative to the /* and */'s if it was indented
580 // in the first place
582 for n
:= 0; n
< 3 && i
> 0 && prefix
[i
-1] == ' '; n
++ {
585 if i
== len(prefix
) && i
> 0 && prefix
[i
-1] == '\t' {
590 // comment text on the first line
591 suffix
:= make([]byte, len(first
))
592 n
:= 2 // start after opening /*
593 for n
< len(first
) && first
[n
] <= ' ' {
597 if n
> 2 && suffix
[2] == '\t' {
598 // assume the '\t' compensates for the /*
601 // otherwise assume two blanks
602 suffix
[0], suffix
[1] = ' ', ' '
605 // Shorten the computed common prefix by the length of
606 // suffix, if it is found as suffix of the prefix.
607 prefix
= strings
.TrimSuffix(prefix
, string(suffix
))
611 // Handle last line: If it only contains a closing */, align it
612 // with the opening /*, otherwise align the text with the other
614 last
:= lines
[len(lines
)-1]
616 before
, _
, _
:= strings
.Cut(last
, closing
) // closing always present
618 // last line only contains closing */
620 closing
= " */" // add blank to align final star
622 lines
[len(lines
)-1] = prefix
+ closing
624 // last line contains more comment text - assume
625 // it is aligned like the other lines and include
626 // in prefix computation
627 prefix
= commonPrefix(prefix
, last
)
630 // Remove the common prefix from all but the first and empty lines.
631 for i
, line
:= range lines
{
632 if i
> 0 && line
!= "" {
633 lines
[i
] = line
[len(prefix
):]
638 func (p
*printer
) writeComment(comment
*ast
.Comment
) {
640 pos
:= p
.posFor(comment
.Pos())
642 const linePrefix
= "//line "
643 if strings
.HasPrefix(text
, linePrefix
) && (!pos
.IsValid() || pos
.Column
== 1) {
644 // Possibly a //-style line directive.
645 // Suspend indentation temporarily to keep line directive valid.
646 defer func(indent
int) { p
.indent
= indent
}(p
.indent
)
650 // shortcut common case of //-style comments
652 if constraint
.IsGoBuild(text
) {
653 p
.goBuild
= append(p
.goBuild
, len(p
.output
))
654 } else if constraint
.IsPlusBuild(text
) {
655 p
.plusBuild
= append(p
.plusBuild
, len(p
.output
))
657 p
.writeString(pos
, trimRight(text
), true)
661 // for /*-style comments, print line by line and let the
662 // write function take care of the proper indentation
663 lines
:= strings
.Split(text
, "\n")
665 // The comment started in the first column but is going
666 // to be indented. For an idempotent result, add indentation
667 // to all lines such that they look like they were indented
668 // before - this will make sure the common prefix computation
669 // is the same independent of how many times formatting is
670 // applied (was issue 1835).
671 if pos
.IsValid() && pos
.Column
== 1 && p
.indent
> 0 {
672 for i
, line
:= range lines
[1:] {
673 lines
[1+i
] = " " + line
677 stripCommonPrefix(lines
)
679 // write comment lines, separated by formfeed,
680 // without a line break after the last line
681 for i
, line
:= range lines
{
687 p
.writeString(pos
, trimRight(line
), true)
692 // writeCommentSuffix writes a line break after a comment if indicated
693 // and processes any leftover indentation information. If a line break
694 // is needed, the kind of break (newline vs formfeed) depends on the
695 // pending whitespace. The writeCommentSuffix result indicates if a
696 // newline was written or if a formfeed was dropped from the whitespace
699 func (p
*printer
) writeCommentSuffix(needsLinebreak
bool) (wroteNewline
, droppedFF
bool) {
700 for i
, ch
:= range p
.wsbuf
{
703 // ignore trailing whitespace
705 case indent
, unindent
:
706 // don't lose indentation information
707 case newline
, formfeed
:
708 // if we need a line break, keep exactly one
709 // but remember if we dropped any formfeeds
711 needsLinebreak
= false
721 p
.writeWhitespace(len(p
.wsbuf
))
723 // make sure we have a line break
732 // containsLinebreak reports whether the whitespace buffer contains any line breaks.
733 func (p
*printer
) containsLinebreak() bool {
734 for _
, ch
:= range p
.wsbuf
{
735 if ch
== newline || ch
== formfeed
{
742 // intersperseComments consumes all comments that appear before the next token
743 // tok and prints it together with the buffered whitespace (i.e., the whitespace
744 // that needs to be written before the next token). A heuristic is used to mix
745 // the comments and whitespace. The intersperseComments result indicates if a
746 // newline was written or if a formfeed was dropped from the whitespace buffer.
748 func (p
*printer
) intersperseComments(next token
.Position
, tok token
.Token
) (wroteNewline
, droppedFF
bool) {
749 var last
*ast
.Comment
750 for p
.commentBefore(next
) {
751 for _
, c
:= range p
.comment
.List
{
752 p
.writeCommentPrefix(p
.posFor(c
.Pos()), next
, last
, tok
)
760 // If the last comment is a /*-style comment and the next item
761 // follows on the same line but is not a comma, and not a "closing"
762 // token immediately following its corresponding "opening" token,
763 // add an extra separator unless explicitly disabled. Use a blank
764 // as separator unless we have pending linebreaks, they are not
765 // disabled, and we are outside a composite literal, in which case
766 // we want a linebreak (issue 15137).
767 // TODO(gri) This has become overly complicated. We should be able
768 // to track whether we're inside an expression or statement and
769 // use that information to decide more directly.
770 needsLinebreak
:= false
771 if p
.mode
&noExtraBlank
== 0 &&
772 last
.Text
[1] == '*' && p
.lineFor(last
.Pos()) == next
.Line
&&
773 tok
!= token
.COMMA
&&
774 (tok
!= token
.RPAREN || p
.prevOpen
== token
.LPAREN
) &&
775 (tok
!= token
.RBRACK || p
.prevOpen
== token
.LBRACK
) {
776 if p
.containsLinebreak() && p
.mode
&noExtraLinebreak
== 0 && p
.level
== 0 {
777 needsLinebreak
= true
782 // Ensure that there is a line break after a //-style comment,
783 // before EOF, and before a closing '}' unless explicitly disabled.
784 if last
.Text
[1] == '/' ||
786 tok
== token
.RBRACE
&& p
.mode
&noExtraLinebreak
== 0 {
787 needsLinebreak
= true
789 return p
.writeCommentSuffix(needsLinebreak
)
792 // no comment was written - we should never reach here since
793 // intersperseComments should not be called in that case
794 p
.internalError("intersperseComments called without pending comments")
798 // whiteWhitespace writes the first n whitespace entries.
799 func (p
*printer
) writeWhitespace(n
int) {
801 for i
:= 0; i
< n
; i
++ {
802 switch ch
:= p
.wsbuf
[i
]; ch
{
810 p
.internalError("negative indentation:", p
.indent
)
813 case newline
, formfeed
:
814 // A line break immediately followed by a "correcting"
815 // unindent is swapped with the unindent - this permits
816 // proper label positioning. If a comment is between
817 // the line break and the label, the unindent is not
818 // part of the comment whitespace prefix and the comment
819 // will be positioned correctly indented.
820 if i
+1 < n
&& p
.wsbuf
[i
+1] == unindent
{
821 // Use a formfeed to terminate the current section.
822 // Otherwise, a long label name on the next line leading
823 // to a wide column may increase the indentation column
824 // of lines before the label; effectively leading to wrong
826 p
.wsbuf
[i
], p
.wsbuf
[i
+1] = unindent
, formfeed
832 p
.writeByte(byte(ch
), 1)
836 // shift remaining entries down
837 l
:= copy(p
.wsbuf
, p
.wsbuf
[n
:])
838 p
.wsbuf
= p
.wsbuf
[:l
]
841 // ----------------------------------------------------------------------------
842 // Printing interface
844 // nlimit limits n to maxNewlines.
845 func nlimit(n
int) int {
852 func mayCombine(prev token
.Token
, next
byte) (b
bool) {
855 b
= next
== '.' // 1.
857 b
= next
== '+' // ++
859 b
= next
== '-' // --
861 b
= next
== '*' // /*
863 b
= next
== '-' || next
== '<' // <- or <<
865 b
= next
== '&' || next
== '^' // && or &^
870 // print prints a list of "items" (roughly corresponding to syntactic
871 // tokens, but also including whitespace and formatting information).
872 // It is the only print function that should be called directly from
873 // any of the AST printing functions in nodes.go.
875 // Whitespace is accumulated until a non-whitespace token appears. Any
876 // comments that need to appear before that token are printed first,
877 // taking into account the amount and structure of any pending white-
878 // space for best comment placement. Then, any leftover whitespace is
879 // printed, followed by the actual token.
881 func (p
*printer
) print(args
...any
) {
882 for _
, arg
:= range args
{
883 // information about the current arg
886 var impliedSemi
bool // value for p.impliedSemi after this arg
888 // record previous opening token, if any
891 // ignore (white space)
892 case token
.LPAREN
, token
.LBRACK
:
893 p
.prevOpen
= p
.lastTok
895 // other tokens followed any opening token
896 p
.prevOpen
= token
.ILLEGAL
899 switch x
:= arg
.(type) {
901 // toggle printer mode
907 // don't add ignore's to the buffer; they
908 // may screw up "correcting" unindents (see
913 if i
== cap(p
.wsbuf
) {
914 // Whitespace sequences are very short so this should
915 // never happen. Handle gracefully (but possibly with
916 // bad comment placement) if it does happen.
920 p
.wsbuf
= p
.wsbuf
[0 : i
+1]
922 if x
== newline || x
== formfeed
{
923 // newlines affect the current state (p.impliedSemi)
924 // and not the state after printing arg (impliedSemi)
925 // because comments can be interspersed before the arg
927 p
.impliedSemi
= false
929 p
.lastTok
= token
.ILLEGAL
935 p
.lastTok
= token
.IDENT
945 if mayCombine(p
.lastTok
, s
[0]) {
946 // the previous and the current token must be
947 // separated by a blank otherwise they combine
948 // into a different incorrect token sequence
949 // (except for token.INT followed by a '.' this
950 // should never happen because it is taken care
951 // of via binary expression formatting)
952 if len(p
.wsbuf
) != 0 {
953 p
.internalError("whitespace buffer not empty")
955 p
.wsbuf
= p
.wsbuf
[0:1]
959 // some keywords followed by a newline imply a semicolon
961 case token
.BREAK
, token
.CONTINUE
, token
.FALLTHROUGH
, token
.RETURN
,
962 token
.INC
, token
.DEC
, token
.RPAREN
, token
.RBRACK
, token
.RBRACE
:
969 p
.pos
= p
.posFor(x
) // accurate position of next item
974 // incorrect AST - print error message
978 p
.lastTok
= token
.STRING
981 fmt
.Fprintf(os
.Stderr
, "print: unsupported argument %v (%T)\n", arg
, arg
)
982 panic("go/printer type")
986 next
:= p
.pos
// estimated/accurate position of next item
987 wroteNewline
, droppedFF
:= p
.flush(next
, p
.lastTok
)
989 // intersperse extra newlines if present in the source and
990 // if they don't cause extra semicolons (don't do this in
991 // flush as it will cause extra newlines at the end of a file)
993 n
:= nlimit(next
.Line
- p
.pos
.Line
)
994 // don't exceed maxNewlines if we already wrote one
995 if wroteNewline
&& n
== maxNewlines
{
1001 ch
= '\f' // use formfeed since we dropped one before
1008 // the next token starts now - record its line number if requested
1009 if p
.linePtr
!= nil {
1010 *p
.linePtr
= p
.out
.Line
1014 p
.writeString(next
, data
, isLit
)
1015 p
.impliedSemi
= impliedSemi
1019 // flush prints any pending comments and whitespace occurring textually
1020 // before the position of the next token tok. The flush result indicates
1021 // if a newline was written or if a formfeed was dropped from the whitespace
1024 func (p
*printer
) flush(next token
.Position
, tok token
.Token
) (wroteNewline
, droppedFF
bool) {
1025 if p
.commentBefore(next
) {
1026 // if there are comments before the next item, intersperse them
1027 wroteNewline
, droppedFF
= p
.intersperseComments(next
, tok
)
1029 // otherwise, write any leftover whitespace
1030 p
.writeWhitespace(len(p
.wsbuf
))
1035 // getNode returns the ast.CommentGroup associated with n, if any.
1036 func getDoc(n ast
.Node
) *ast
.CommentGroup
{
1037 switch n
:= n
.(type) {
1040 case *ast
.ImportSpec
:
1042 case *ast
.ValueSpec
:
1056 func getLastComment(n ast
.Node
) *ast
.CommentGroup
{
1057 switch n
:= n
.(type) {
1060 case *ast
.ImportSpec
:
1062 case *ast
.ValueSpec
:
1067 if len(n
.Specs
) > 0 {
1068 return getLastComment(n
.Specs
[len(n
.Specs
)-1])
1071 if len(n
.Comments
) > 0 {
1072 return n
.Comments
[len(n
.Comments
)-1]
1078 func (p
*printer
) printNode(node any
) error
{
1079 // unpack *CommentedNode, if any
1080 var comments
[]*ast
.CommentGroup
1081 if cnode
, ok
:= node
.(*CommentedNode
); ok
{
1083 comments
= cnode
.Comments
1086 if comments
!= nil {
1087 // commented node - restrict comment list to relevant range
1088 n
, ok
:= node
.(ast
.Node
)
1094 // if the node has associated documentation,
1095 // include that commentgroup in the range
1096 // (the comment list is sorted in the order
1097 // of the comment appearance in the source code)
1098 if doc
:= getDoc(n
); doc
!= nil {
1101 if com
:= getLastComment(n
); com
!= nil {
1102 if e
:= com
.End(); e
> end
{
1106 // token.Pos values are global offsets, we can
1107 // compare them directly
1109 for i
< len(comments
) && comments
[i
].End() < beg
{
1113 for j
< len(comments
) && comments
[j
].Pos() < end
{
1117 p
.comments
= comments
[i
:j
]
1119 } else if n
, ok
:= node
.(*ast
.File
); ok
{
1120 // use ast.File comments, if any
1121 p
.comments
= n
.Comments
1124 // if there are no comments, use node comments
1125 p
.useNodeComments
= p
.comments
== nil
1127 // get comments ready for use
1133 switch n
:= node
.(type) {
1137 // A labeled statement will un-indent to position the label.
1138 // Set p.indent to 1 so we don't get indent "underflow".
1139 if _
, ok
:= n
.(*ast
.LabeledStmt
); ok
{
1148 // A labeled statement will un-indent to position the label.
1149 // Set p.indent to 1 so we don't get indent "underflow".
1150 for _
, s
:= range n
{
1151 if _
, ok
:= s
.(*ast
.LabeledStmt
); ok
{
1155 p
.stmtList(n
, 0, false)
1167 return fmt
.Errorf("go/printer: unsupported node type %T", node
)
1170 // ----------------------------------------------------------------------------
1173 // A trimmer is an io.Writer filter for stripping tabwriter.Escape
1174 // characters, trailing blanks and tabs, and for converting formfeed
1175 // and vtab characters into newlines and htabs (in case no tabwriter
1176 // is used). Text bracketed by tabwriter.Escape characters is passed
1177 // through unchanged.
1179 type trimmer
struct {
1185 // trimmer is implemented as a state machine.
1186 // It can be in one of the following states:
1188 inSpace
= iota // inside space
1189 inEscape
// inside text bracketed by tabwriter.Escapes
1190 inText
// inside text
1193 func (p
*trimmer
) resetSpace() {
1195 p
.space
= p
.space
[0:0]
1198 // Design note: It is tempting to eliminate extra blanks occurring in
1199 // whitespace in this function as it could simplify some
1200 // of the blanks logic in the node printing functions.
1201 // However, this would mess up any formatting done by
1204 var aNewline
= []byte("\n")
1206 func (p
*trimmer
) Write(data
[]byte) (n
int, err error
) {
1208 // p.state == inSpace:
1209 // p.space is unwritten
1210 // p.state == inEscape, inText:
1211 // data[m:n] is unwritten
1214 for n
, b
= range data
{
1216 b
= '\t' // convert to htab
1222 p
.space
= append(p
.space
, b
)
1224 p
.resetSpace() // discard trailing space
1225 _
, err
= p
.output
.Write(aNewline
)
1226 case tabwriter
.Escape
:
1227 _
, err
= p
.output
.Write(p
.space
)
1229 m
= n
+ 1 // +1: skip tabwriter.Escape
1231 _
, err
= p
.output
.Write(p
.space
)
1236 if b
== tabwriter
.Escape
{
1237 _
, err
= p
.output
.Write(data
[m
:n
])
1243 _
, err
= p
.output
.Write(data
[m
:n
])
1245 p
.space
= append(p
.space
, b
)
1247 _
, err
= p
.output
.Write(data
[m
:n
])
1250 _
, err
= p
.output
.Write(aNewline
)
1252 case tabwriter
.Escape
:
1253 _
, err
= p
.output
.Write(data
[m
:n
])
1255 m
= n
+ 1 // +1: skip tabwriter.Escape
1258 panic("unreachable")
1267 case inEscape
, inText
:
1268 _
, err
= p
.output
.Write(data
[m
:n
])
1275 // ----------------------------------------------------------------------------
1278 // A Mode value is a set of flags (or 0). They control printing.
1282 RawFormat Mode
= 1 << iota // do not use a tabwriter; if set, UseSpaces is ignored
1283 TabIndent
// use tabs for indentation independent of UseSpaces
1284 UseSpaces
// use spaces instead of tabs for alignment
1285 SourcePos
// emit //line directives to preserve original source positions
1288 // The mode below is not included in printer's public API because
1289 // editing code text is deemed out of scope. Because this mode is
1290 // unexported, it's also possible to modify or remove it based on
1291 // the evolving needs of go/format and cmd/gofmt without breaking
1292 // users. See discussion in CL 240683.
1294 // normalizeNumbers means to canonicalize number
1295 // literal prefixes and exponents while printing.
1297 // This value is known in and used by go/format and cmd/gofmt.
1298 // It is currently more convenient and performant for those
1299 // packages to apply number normalization during printing,
1300 // rather than by modifying the AST in advance.
1301 normalizeNumbers Mode
= 1 << 30
1304 // A Config node controls the output of Fprint.
1305 type Config
struct {
1306 Mode Mode
// default: 0
1307 Tabwidth
int // default: 8
1308 Indent
int // default: 0 (all code is indented at least by this much)
1311 // fprint implements Fprint and takes a nodesSizes map for setting up the printer state.
1312 func (cfg
*Config
) fprint(output io
.Writer
, fset
*token
.FileSet
, node any
, nodeSizes
map[ast
.Node
]int) (err error
) {
1315 p
.init(cfg
, fset
, nodeSizes
)
1316 if err
= p
.printNode(node
); err
!= nil {
1319 // print outstanding comments
1320 p
.impliedSemi
= false // EOF acts like a newline
1321 p
.flush(token
.Position
{Offset
: infinity
, Line
: infinity
}, token
.EOF
)
1323 // output is buffered in p.output now.
1324 // fix //go:build and // +build comments if needed.
1327 // redirect output through a trimmer to eliminate trailing whitespace
1328 // (Input to a tabwriter must be untrimmed since trailing tabs provide
1329 // formatting information. The tabwriter could provide trimming
1330 // functionality but no tabwriter is used when RawFormat is set.)
1331 output
= &trimmer
{output
: output
}
1333 // redirect output through a tabwriter if necessary
1334 if cfg
.Mode
&RawFormat
== 0 {
1335 minwidth
:= cfg
.Tabwidth
1337 padchar
:= byte('\t')
1338 if cfg
.Mode
&UseSpaces
!= 0 {
1342 twmode
:= tabwriter
.DiscardEmptyColumns
1343 if cfg
.Mode
&TabIndent
!= 0 {
1345 twmode |
= tabwriter
.TabIndent
1348 output
= tabwriter
.NewWriter(output
, minwidth
, cfg
.Tabwidth
, 1, padchar
, twmode
)
1351 // write printer result via tabwriter/trimmer to output
1352 if _
, err
= output
.Write(p
.output
); err
!= nil {
1356 // flush tabwriter, if any
1357 if tw
, _
:= output
.(*tabwriter
.Writer
); tw
!= nil {
1364 // A CommentedNode bundles an AST node and corresponding comments.
1365 // It may be provided as argument to any of the Fprint functions.
1367 type CommentedNode
struct {
1368 Node any
// *ast.File, or ast.Expr, ast.Decl, ast.Spec, or ast.Stmt
1369 Comments
[]*ast
.CommentGroup
1372 // Fprint "pretty-prints" an AST node to output for a given configuration cfg.
1373 // Position information is interpreted relative to the file set fset.
1374 // The node type must be *ast.File, *CommentedNode, []ast.Decl, []ast.Stmt,
1375 // or assignment-compatible to ast.Expr, ast.Decl, ast.Spec, or ast.Stmt.
1377 func (cfg
*Config
) Fprint(output io
.Writer
, fset
*token
.FileSet
, node any
) error
{
1378 return cfg
.fprint(output
, fset
, node
, make(map[ast
.Node
]int))
1381 // Fprint "pretty-prints" an AST node to output.
1382 // It calls Config.Fprint with default settings.
1383 // Note that gofmt uses tabs for indentation but spaces for alignment;
1384 // use format.Node (package go/format) for output that matches gofmt.
1386 func Fprint(output io
.Writer
, fset
*token
.FileSet
, node any
) error
{
1387 return (&Config
{Tabwidth
: 8}).Fprint(output
, fset
, node
)