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.
4
5// Package printer implements printing of AST nodes.
6package printer
7
8import (
9	"fmt"
10	"go/ast"
11	"go/build/constraint"
12	"go/token"
13	"io"
14	"os"
15	"strings"
16	"sync"
17	"text/tabwriter"
18	"unicode"
19)
20
21const (
22	maxNewlines = 2     // max. number of newlines between source text
23	debug       = false // enable for debugging
24	infinity    = 1 << 30
25)
26
27type whiteSpace byte
28
29const (
30	ignore   = whiteSpace(0)
31	blank    = whiteSpace(' ')
32	vtab     = whiteSpace('\v')
33	newline  = whiteSpace('\n')
34	formfeed = whiteSpace('\f')
35	indent   = whiteSpace('>')
36	unindent = whiteSpace('<')
37)
38
39// A pmode value represents the current printer mode.
40type pmode int
41
42const (
43	noExtraBlank     pmode = 1 << iota // disables extra blank after /*-style comment
44	noExtraLinebreak                   // disables extra line break after /*-style comment
45)
46
47type commentInfo struct {
48	cindex         int               // current comment index
49	comment        *ast.CommentGroup // = printer.comments[cindex]; or nil
50	commentOffset  int               // = printer.posFor(printer.comments[cindex].List[0].Pos()).Offset; or infinity
51	commentNewline bool              // true if the comment group contains newlines
52}
53
54type printer struct {
55	// Configuration (does not change after initialization)
56	Config
57	fset *token.FileSet
58
59	// Current state
60	output       []byte       // raw printer result
61	indent       int          // current indentation
62	level        int          // level == 0: outside composite literal; level > 0: inside composite literal
63	mode         pmode        // current printer mode
64	endAlignment bool         // if set, terminate alignment immediately
65	impliedSemi  bool         // if set, a linebreak implies a semicolon
66	lastTok      token.Token  // last token printed (token.ILLEGAL if it's whitespace)
67	prevOpen     token.Token  // previous non-brace "open" token (, [, or token.ILLEGAL
68	wsbuf        []whiteSpace // delayed white space
69	goBuild      []int        // start index of all //go:build comments in output
70	plusBuild    []int        // start index of all // +build comments in output
71
72	// Positions
73	// The out position differs from the pos position when the result
74	// formatting differs from the source formatting (in the amount of
75	// white space). If there's a difference and SourcePos is set in
76	// ConfigMode, //line directives are used in the output to restore
77	// original source positions for a reader.
78	pos          token.Position // current position in AST (source) space
79	out          token.Position // current position in output space
80	last         token.Position // value of pos after calling writeString
81	linePtr      *int           // if set, record out.Line for the next token in *linePtr
82	sourcePosErr error          // if non-nil, the first error emitting a //line directive
83
84	// The list of all source comments, in order of appearance.
85	comments        []*ast.CommentGroup // may be nil
86	useNodeComments bool                // if not set, ignore lead and line comments of nodes
87
88	// Information about p.comments[p.cindex]; set up by nextComment.
89	commentInfo
90
91	// Cache of already computed node sizes.
92	nodeSizes map[ast.Node]int
93
94	// Cache of most recently computed line position.
95	cachedPos  token.Pos
96	cachedLine int // line corresponding to cachedPos
97}
98
99func (p *printer) internalError(msg ...any) {
100	if debug {
101		fmt.Print(p.pos.String() + ": ")
102		fmt.Println(msg...)
103		panic("go/printer")
104	}
105}
106
107// commentsHaveNewline reports whether a list of comments belonging to
108// an *ast.CommentGroup contains newlines. Because the position information
109// may only be partially correct, we also have to read the comment text.
110func (p *printer) commentsHaveNewline(list []*ast.Comment) bool {
111	// len(list) > 0
112	line := p.lineFor(list[0].Pos())
113	for i, c := range list {
114		if i > 0 && p.lineFor(list[i].Pos()) != line {
115			// not all comments on the same line
116			return true
117		}
118		if t := c.Text; len(t) >= 2 && (t[1] == '/' || strings.Contains(t, "\n")) {
119			return true
120		}
121	}
122	_ = line
123	return false
124}
125
126func (p *printer) nextComment() {
127	for p.cindex < len(p.comments) {
128		c := p.comments[p.cindex]
129		p.cindex++
130		if list := c.List; len(list) > 0 {
131			p.comment = c
132			p.commentOffset = p.posFor(list[0].Pos()).Offset
133			p.commentNewline = p.commentsHaveNewline(list)
134			return
135		}
136		// we should not reach here (correct ASTs don't have empty
137		// ast.CommentGroup nodes), but be conservative and try again
138	}
139	// no more comments
140	p.commentOffset = infinity
141}
142
143// commentBefore reports whether the current comment group occurs
144// before the next position in the source code and printing it does
145// not introduce implicit semicolons.
146func (p *printer) commentBefore(next token.Position) bool {
147	return p.commentOffset < next.Offset && (!p.impliedSemi || !p.commentNewline)
148}
149
150// commentSizeBefore returns the estimated size of the
151// comments on the same line before the next position.
152func (p *printer) commentSizeBefore(next token.Position) int {
153	// save/restore current p.commentInfo (p.nextComment() modifies it)
154	defer func(info commentInfo) {
155		p.commentInfo = info
156	}(p.commentInfo)
157
158	size := 0
159	for p.commentBefore(next) {
160		for _, c := range p.comment.List {
161			size += len(c.Text)
162		}
163		p.nextComment()
164	}
165	return size
166}
167
168// recordLine records the output line number for the next non-whitespace
169// token in *linePtr. It is used to compute an accurate line number for a
170// formatted construct, independent of pending (not yet emitted) whitespace
171// or comments.
172func (p *printer) recordLine(linePtr *int) {
173	p.linePtr = linePtr
174}
175
176// linesFrom returns the number of output lines between the current
177// output line and the line argument, ignoring any pending (not yet
178// emitted) whitespace or comments. It is used to compute an accurate
179// size (in number of lines) for a formatted construct.
180func (p *printer) linesFrom(line int) int {
181	return p.out.Line - line
182}
183
184func (p *printer) posFor(pos token.Pos) token.Position {
185	// not used frequently enough to cache entire token.Position
186	return p.fset.PositionFor(pos, false /* absolute position */)
187}
188
189func (p *printer) lineFor(pos token.Pos) int {
190	if pos != p.cachedPos {
191		p.cachedPos = pos
192		p.cachedLine = p.fset.PositionFor(pos, false /* absolute position */).Line
193	}
194	return p.cachedLine
195}
196
197// writeLineDirective writes a //line directive if necessary.
198func (p *printer) writeLineDirective(pos token.Position) {
199	if pos.IsValid() && (p.out.Line != pos.Line || p.out.Filename != pos.Filename) {
200		if strings.ContainsAny(pos.Filename, "\r\n") {
201			if p.sourcePosErr == nil {
202				p.sourcePosErr = fmt.Errorf("go/printer: source filename contains unexpected newline character: %q", pos.Filename)
203			}
204			return
205		}
206
207		p.output = append(p.output, tabwriter.Escape) // protect '\n' in //line from tabwriter interpretation
208		p.output = append(p.output, fmt.Sprintf("//line %s:%d\n", pos.Filename, pos.Line)...)
209		p.output = append(p.output, tabwriter.Escape)
210		// p.out must match the //line directive
211		p.out.Filename = pos.Filename
212		p.out.Line = pos.Line
213	}
214}
215
216// writeIndent writes indentation.
217func (p *printer) writeIndent() {
218	// use "hard" htabs - indentation columns
219	// must not be discarded by the tabwriter
220	n := p.Config.Indent + p.indent // include base indentation
221	for i := 0; i < n; i++ {
222		p.output = append(p.output, '\t')
223	}
224
225	// update positions
226	p.pos.Offset += n
227	p.pos.Column += n
228	p.out.Column += n
229}
230
231// writeByte writes ch n times to p.output and updates p.pos.
232// Only used to write formatting (white space) characters.
233func (p *printer) writeByte(ch byte, n int) {
234	if p.endAlignment {
235		// Ignore any alignment control character;
236		// and at the end of the line, break with
237		// a formfeed to indicate termination of
238		// existing columns.
239		switch ch {
240		case '\t', '\v':
241			ch = ' '
242		case '\n', '\f':
243			ch = '\f'
244			p.endAlignment = false
245		}
246	}
247
248	if p.out.Column == 1 {
249		// no need to write line directives before white space
250		p.writeIndent()
251	}
252
253	for i := 0; i < n; i++ {
254		p.output = append(p.output, ch)
255	}
256
257	// update positions
258	p.pos.Offset += n
259	if ch == '\n' || ch == '\f' {
260		p.pos.Line += n
261		p.out.Line += n
262		p.pos.Column = 1
263		p.out.Column = 1
264		return
265	}
266	p.pos.Column += n
267	p.out.Column += n
268}
269
270// writeString writes the string s to p.output and updates p.pos, p.out,
271// and p.last. If isLit is set, s is escaped w/ tabwriter.Escape characters
272// to protect s from being interpreted by the tabwriter.
273//
274// Note: writeString is only used to write Go tokens, literals, and
275// comments, all of which must be written literally. Thus, it is correct
276// to always set isLit = true. However, setting it explicitly only when
277// needed (i.e., when we don't know that s contains no tabs or line breaks)
278// avoids processing extra escape characters and reduces run time of the
279// printer benchmark by up to 10%.
280func (p *printer) writeString(pos token.Position, s string, isLit bool) {
281	if p.out.Column == 1 {
282		if p.Config.Mode&SourcePos != 0 {
283			p.writeLineDirective(pos)
284		}
285		p.writeIndent()
286	}
287
288	if pos.IsValid() {
289		// update p.pos (if pos is invalid, continue with existing p.pos)
290		// Note: Must do this after handling line beginnings because
291		// writeIndent updates p.pos if there's indentation, but p.pos
292		// is the position of s.
293		p.pos = pos
294	}
295
296	if isLit {
297		// Protect s such that is passes through the tabwriter
298		// unchanged. Note that valid Go programs cannot contain
299		// tabwriter.Escape bytes since they do not appear in legal
300		// UTF-8 sequences.
301		p.output = append(p.output, tabwriter.Escape)
302	}
303
304	if debug {
305		p.output = append(p.output, fmt.Sprintf("/*%s*/", pos)...) // do not update p.pos!
306	}
307	p.output = append(p.output, s...)
308
309	// update positions
310	nlines := 0
311	var li int // index of last newline; valid if nlines > 0
312	for i := 0; i < len(s); i++ {
313		// Raw string literals may contain any character except back quote (`).
314		if ch := s[i]; ch == '\n' || ch == '\f' {
315			// account for line break
316			nlines++
317			li = i
318			// A line break inside a literal will break whatever column
319			// formatting is in place; ignore any further alignment through
320			// the end of the line.
321			p.endAlignment = true
322		}
323	}
324	p.pos.Offset += len(s)
325	if nlines > 0 {
326		p.pos.Line += nlines
327		p.out.Line += nlines
328		c := len(s) - li
329		p.pos.Column = c
330		p.out.Column = c
331	} else {
332		p.pos.Column += len(s)
333		p.out.Column += len(s)
334	}
335
336	if isLit {
337		p.output = append(p.output, tabwriter.Escape)
338	}
339
340	p.last = p.pos
341}
342
343// writeCommentPrefix writes the whitespace before a comment.
344// If there is any pending whitespace, it consumes as much of
345// it as is likely to help position the comment nicely.
346// pos is the comment position, next the position of the item
347// after all pending comments, prev is the previous comment in
348// a group of comments (or nil), and tok is the next token.
349func (p *printer) writeCommentPrefix(pos, next token.Position, prev *ast.Comment, tok token.Token) {
350	if len(p.output) == 0 {
351		// the comment is the first item to be printed - don't write any whitespace
352		return
353	}
354
355	if pos.IsValid() && pos.Filename != p.last.Filename {
356		// comment in a different file - separate with newlines
357		p.writeByte('\f', maxNewlines)
358		return
359	}
360
361	if pos.Line == p.last.Line && (prev == nil || prev.Text[1] != '/') {
362		// comment on the same line as last item:
363		// separate with at least one separator
364		hasSep := false
365		if prev == nil {
366			// first comment of a comment group
367			j := 0
368			for i, ch := range p.wsbuf {
369				switch ch {
370				case blank:
371					// ignore any blanks before a comment
372					p.wsbuf[i] = ignore
373					continue
374				case vtab:
375					// respect existing tabs - important
376					// for proper formatting of commented structs
377					hasSep = true
378					continue
379				case indent:
380					// apply pending indentation
381					continue
382				}
383				j = i
384				break
385			}
386			p.writeWhitespace(j)
387		}
388		// make sure there is at least one separator
389		if !hasSep {
390			sep := byte('\t')
391			if pos.Line == next.Line {
392				// next item is on the same line as the comment
393				// (which must be a /*-style comment): separate
394				// with a blank instead of a tab
395				sep = ' '
396			}
397			p.writeByte(sep, 1)
398		}
399
400	} else {
401		// comment on a different line:
402		// separate with at least one line break
403		droppedLinebreak := false
404		j := 0
405		for i, ch := range p.wsbuf {
406			switch ch {
407			case blank, vtab:
408				// ignore any horizontal whitespace before line breaks
409				p.wsbuf[i] = ignore
410				continue
411			case indent:
412				// apply pending indentation
413				continue
414			case unindent:
415				// if this is not the last unindent, apply it
416				// as it is (likely) belonging to the last
417				// construct (e.g., a multi-line expression list)
418				// and is not part of closing a block
419				if i+1 < len(p.wsbuf) && p.wsbuf[i+1] == unindent {
420					continue
421				}
422				// if the next token is not a closing }, apply the unindent
423				// if it appears that the comment is aligned with the
424				// token; otherwise assume the unindent is part of a
425				// closing block and stop (this scenario appears with
426				// comments before a case label where the comments
427				// apply to the next case instead of the current one)
428				if tok != token.RBRACE && pos.Column == next.Column {
429					continue
430				}
431			case newline, formfeed:
432				p.wsbuf[i] = ignore
433				droppedLinebreak = prev == nil // record only if first comment of a group
434			}
435			j = i
436			break
437		}
438		p.writeWhitespace(j)
439
440		// determine number of linebreaks before the comment
441		n := 0
442		if pos.IsValid() && p.last.IsValid() {
443			n = pos.Line - p.last.Line
444			if n < 0 { // should never happen
445				n = 0
446			}
447		}
448
449		// at the package scope level only (p.indent == 0),
450		// add an extra newline if we dropped one before:
451		// this preserves a blank line before documentation
452		// comments at the package scope level (issue 2570)
453		if p.indent == 0 && droppedLinebreak {
454			n++
455		}
456
457		// make sure there is at least one line break
458		// if the previous comment was a line comment
459		if n == 0 && prev != nil && prev.Text[1] == '/' {
460			n = 1
461		}
462
463		if n > 0 {
464			// use formfeeds to break columns before a comment;
465			// this is analogous to using formfeeds to separate
466			// individual lines of /*-style comments
467			p.writeByte('\f', nlimit(n))
468		}
469	}
470}
471
472// Returns true if s contains only white space
473// (only tabs and blanks can appear in the printer's context).
474func isBlank(s string) bool {
475	for i := 0; i < len(s); i++ {
476		if s[i] > ' ' {
477			return false
478		}
479	}
480	return true
481}
482
483// commonPrefix returns the common prefix of a and b.
484func commonPrefix(a, b string) string {
485	i := 0
486	for i < len(a) && i < len(b) && a[i] == b[i] && (a[i] <= ' ' || a[i] == '*') {
487		i++
488	}
489	return a[0:i]
490}
491
492// trimRight returns s with trailing whitespace removed.
493func trimRight(s string) string {
494	return strings.TrimRightFunc(s, unicode.IsSpace)
495}
496
497// stripCommonPrefix removes a common prefix from /*-style comment lines (unless no
498// comment line is indented, all but the first line have some form of space prefix).
499// The prefix is computed using heuristics such that is likely that the comment
500// contents are nicely laid out after re-printing each line using the printer's
501// current indentation.
502func stripCommonPrefix(lines []string) {
503	if len(lines) <= 1 {
504		return // at most one line - nothing to do
505	}
506	// len(lines) > 1
507
508	// The heuristic in this function tries to handle a few
509	// common patterns of /*-style comments: Comments where
510	// the opening /* and closing */ are aligned and the
511	// rest of the comment text is aligned and indented with
512	// blanks or tabs, cases with a vertical "line of stars"
513	// on the left, and cases where the closing */ is on the
514	// same line as the last comment text.
515
516	// Compute maximum common white prefix of all but the first,
517	// last, and blank lines, and replace blank lines with empty
518	// lines (the first line starts with /* and has no prefix).
519	// In cases where only the first and last lines are not blank,
520	// such as two-line comments, or comments where all inner lines
521	// are blank, consider the last line for the prefix computation
522	// since otherwise the prefix would be empty.
523	//
524	// Note that the first and last line are never empty (they
525	// contain the opening /* and closing */ respectively) and
526	// thus they can be ignored by the blank line check.
527	prefix := ""
528	prefixSet := false
529	if len(lines) > 2 {
530		for i, line := range lines[1 : len(lines)-1] {
531			if isBlank(line) {
532				lines[1+i] = "" // range starts with lines[1]
533			} else {
534				if !prefixSet {
535					prefix = line
536					prefixSet = true
537				}
538				prefix = commonPrefix(prefix, line)
539			}
540
541		}
542	}
543	// If we don't have a prefix yet, consider the last line.
544	if !prefixSet {
545		line := lines[len(lines)-1]
546		prefix = commonPrefix(line, line)
547	}
548
549	/*
550	 * Check for vertical "line of stars" and correct prefix accordingly.
551	 */
552	lineOfStars := false
553	if p, _, ok := strings.Cut(prefix, "*"); ok {
554		// remove trailing blank from prefix so stars remain aligned
555		prefix = strings.TrimSuffix(p, " ")
556		lineOfStars = true
557	} else {
558		// No line of stars present.
559		// Determine the white space on the first line after the /*
560		// and before the beginning of the comment text, assume two
561		// blanks instead of the /* unless the first character after
562		// the /* is a tab. If the first comment line is empty but
563		// for the opening /*, assume up to 3 blanks or a tab. This
564		// whitespace may be found as suffix in the common prefix.
565		first := lines[0]
566		if isBlank(first[2:]) {
567			// no comment text on the first line:
568			// reduce prefix by up to 3 blanks or a tab
569			// if present - this keeps comment text indented
570			// relative to the /* and */'s if it was indented
571			// in the first place
572			i := len(prefix)
573			for n := 0; n < 3 && i > 0 && prefix[i-1] == ' '; n++ {
574				i--
575			}
576			if i == len(prefix) && i > 0 && prefix[i-1] == '\t' {
577				i--
578			}
579			prefix = prefix[0:i]
580		} else {
581			// comment text on the first line
582			suffix := make([]byte, len(first))
583			n := 2 // start after opening /*
584			for n < len(first) && first[n] <= ' ' {
585				suffix[n] = first[n]
586				n++
587			}
588			if n > 2 && suffix[2] == '\t' {
589				// assume the '\t' compensates for the /*
590				suffix = suffix[2:n]
591			} else {
592				// otherwise assume two blanks
593				suffix[0], suffix[1] = ' ', ' '
594				suffix = suffix[0:n]
595			}
596			// Shorten the computed common prefix by the length of
597			// suffix, if it is found as suffix of the prefix.
598			prefix = strings.TrimSuffix(prefix, string(suffix))
599		}
600	}
601
602	// Handle last line: If it only contains a closing */, align it
603	// with the opening /*, otherwise align the text with the other
604	// lines.
605	last := lines[len(lines)-1]
606	closing := "*/"
607	before, _, _ := strings.Cut(last, closing) // closing always present
608	if isBlank(before) {
609		// last line only contains closing */
610		if lineOfStars {
611			closing = " */" // add blank to align final star
612		}
613		lines[len(lines)-1] = prefix + closing
614	} else {
615		// last line contains more comment text - assume
616		// it is aligned like the other lines and include
617		// in prefix computation
618		prefix = commonPrefix(prefix, last)
619	}
620
621	// Remove the common prefix from all but the first and empty lines.
622	for i, line := range lines {
623		if i > 0 && line != "" {
624			lines[i] = line[len(prefix):]
625		}
626	}
627}
628
629func (p *printer) writeComment(comment *ast.Comment) {
630	text := comment.Text
631	pos := p.posFor(comment.Pos())
632
633	const linePrefix = "//line "
634	if strings.HasPrefix(text, linePrefix) && (!pos.IsValid() || pos.Column == 1) {
635		// Possibly a //-style line directive.
636		// Suspend indentation temporarily to keep line directive valid.
637		defer func(indent int) { p.indent = indent }(p.indent)
638		p.indent = 0
639	}
640
641	// shortcut common case of //-style comments
642	if text[1] == '/' {
643		if constraint.IsGoBuild(text) {
644			p.goBuild = append(p.goBuild, len(p.output))
645		} else if constraint.IsPlusBuild(text) {
646			p.plusBuild = append(p.plusBuild, len(p.output))
647		}
648		p.writeString(pos, trimRight(text), true)
649		return
650	}
651
652	// for /*-style comments, print line by line and let the
653	// write function take care of the proper indentation
654	lines := strings.Split(text, "\n")
655
656	// The comment started in the first column but is going
657	// to be indented. For an idempotent result, add indentation
658	// to all lines such that they look like they were indented
659	// before - this will make sure the common prefix computation
660	// is the same independent of how many times formatting is
661	// applied (was issue 1835).
662	if pos.IsValid() && pos.Column == 1 && p.indent > 0 {
663		for i, line := range lines[1:] {
664			lines[1+i] = "   " + line
665		}
666	}
667
668	stripCommonPrefix(lines)
669
670	// write comment lines, separated by formfeed,
671	// without a line break after the last line
672	for i, line := range lines {
673		if i > 0 {
674			p.writeByte('\f', 1)
675			pos = p.pos
676		}
677		if len(line) > 0 {
678			p.writeString(pos, trimRight(line), true)
679		}
680	}
681}
682
683// writeCommentSuffix writes a line break after a comment if indicated
684// and processes any leftover indentation information. If a line break
685// is needed, the kind of break (newline vs formfeed) depends on the
686// pending whitespace. The writeCommentSuffix result indicates if a
687// newline was written or if a formfeed was dropped from the whitespace
688// buffer.
689func (p *printer) writeCommentSuffix(needsLinebreak bool) (wroteNewline, droppedFF bool) {
690	for i, ch := range p.wsbuf {
691		switch ch {
692		case blank, vtab:
693			// ignore trailing whitespace
694			p.wsbuf[i] = ignore
695		case indent, unindent:
696			// don't lose indentation information
697		case newline, formfeed:
698			// if we need a line break, keep exactly one
699			// but remember if we dropped any formfeeds
700			if needsLinebreak {
701				needsLinebreak = false
702				wroteNewline = true
703			} else {
704				if ch == formfeed {
705					droppedFF = true
706				}
707				p.wsbuf[i] = ignore
708			}
709		}
710	}
711	p.writeWhitespace(len(p.wsbuf))
712
713	// make sure we have a line break
714	if needsLinebreak {
715		p.writeByte('\n', 1)
716		wroteNewline = true
717	}
718
719	return
720}
721
722// containsLinebreak reports whether the whitespace buffer contains any line breaks.
723func (p *printer) containsLinebreak() bool {
724	for _, ch := range p.wsbuf {
725		if ch == newline || ch == formfeed {
726			return true
727		}
728	}
729	return false
730}
731
732// intersperseComments consumes all comments that appear before the next token
733// tok and prints it together with the buffered whitespace (i.e., the whitespace
734// that needs to be written before the next token). A heuristic is used to mix
735// the comments and whitespace. The intersperseComments result indicates if a
736// newline was written or if a formfeed was dropped from the whitespace buffer.
737func (p *printer) intersperseComments(next token.Position, tok token.Token) (wroteNewline, droppedFF bool) {
738	var last *ast.Comment
739	for p.commentBefore(next) {
740		list := p.comment.List
741		changed := false
742		if p.lastTok != token.IMPORT && // do not rewrite cgo's import "C" comments
743			p.posFor(p.comment.Pos()).Column == 1 &&
744			p.posFor(p.comment.End()+1) == next {
745			// Unindented comment abutting next token position:
746			// a top-level doc comment.
747			list = formatDocComment(list)
748			changed = true
749
750			if len(p.comment.List) > 0 && len(list) == 0 {
751				// The doc comment was removed entirely.
752				// Keep preceding whitespace.
753				p.writeCommentPrefix(p.posFor(p.comment.Pos()), next, last, tok)
754				// Change print state to continue at next.
755				p.pos = next
756				p.last = next
757				// There can't be any more comments.
758				p.nextComment()
759				return p.writeCommentSuffix(false)
760			}
761		}
762		for _, c := range list {
763			p.writeCommentPrefix(p.posFor(c.Pos()), next, last, tok)
764			p.writeComment(c)
765			last = c
766		}
767		// In case list was rewritten, change print state to where
768		// the original list would have ended.
769		if len(p.comment.List) > 0 && changed {
770			last = p.comment.List[len(p.comment.List)-1]
771			p.pos = p.posFor(last.End())
772			p.last = p.pos
773		}
774		p.nextComment()
775	}
776
777	if last != nil {
778		// If the last comment is a /*-style comment and the next item
779		// follows on the same line but is not a comma, and not a "closing"
780		// token immediately following its corresponding "opening" token,
781		// add an extra separator unless explicitly disabled. Use a blank
782		// as separator unless we have pending linebreaks, they are not
783		// disabled, and we are outside a composite literal, in which case
784		// we want a linebreak (issue 15137).
785		// TODO(gri) This has become overly complicated. We should be able
786		// to track whether we're inside an expression or statement and
787		// use that information to decide more directly.
788		needsLinebreak := false
789		if p.mode&noExtraBlank == 0 &&
790			last.Text[1] == '*' && p.lineFor(last.Pos()) == next.Line &&
791			tok != token.COMMA &&
792			(tok != token.RPAREN || p.prevOpen == token.LPAREN) &&
793			(tok != token.RBRACK || p.prevOpen == token.LBRACK) {
794			if p.containsLinebreak() && p.mode&noExtraLinebreak == 0 && p.level == 0 {
795				needsLinebreak = true
796			} else {
797				p.writeByte(' ', 1)
798			}
799		}
800		// Ensure that there is a line break after a //-style comment,
801		// before EOF, and before a closing '}' unless explicitly disabled.
802		if last.Text[1] == '/' ||
803			tok == token.EOF ||
804			tok == token.RBRACE && p.mode&noExtraLinebreak == 0 {
805			needsLinebreak = true
806		}
807		return p.writeCommentSuffix(needsLinebreak)
808	}
809
810	// no comment was written - we should never reach here since
811	// intersperseComments should not be called in that case
812	p.internalError("intersperseComments called without pending comments")
813	return
814}
815
816// writeWhitespace writes the first n whitespace entries.
817func (p *printer) writeWhitespace(n int) {
818	// write entries
819	for i := 0; i < n; i++ {
820		switch ch := p.wsbuf[i]; ch {
821		case ignore:
822			// ignore!
823		case indent:
824			p.indent++
825		case unindent:
826			p.indent--
827			if p.indent < 0 {
828				p.internalError("negative indentation:", p.indent)
829				p.indent = 0
830			}
831		case newline, formfeed:
832			// A line break immediately followed by a "correcting"
833			// unindent is swapped with the unindent - this permits
834			// proper label positioning. If a comment is between
835			// the line break and the label, the unindent is not
836			// part of the comment whitespace prefix and the comment
837			// will be positioned correctly indented.
838			if i+1 < n && p.wsbuf[i+1] == unindent {
839				// Use a formfeed to terminate the current section.
840				// Otherwise, a long label name on the next line leading
841				// to a wide column may increase the indentation column
842				// of lines before the label; effectively leading to wrong
843				// indentation.
844				p.wsbuf[i], p.wsbuf[i+1] = unindent, formfeed
845				i-- // do it again
846				continue
847			}
848			fallthrough
849		default:
850			p.writeByte(byte(ch), 1)
851		}
852	}
853
854	// shift remaining entries down
855	l := copy(p.wsbuf, p.wsbuf[n:])
856	p.wsbuf = p.wsbuf[:l]
857}
858
859// ----------------------------------------------------------------------------
860// Printing interface
861
862// nlimit limits n to maxNewlines.
863func nlimit(n int) int {
864	return min(n, maxNewlines)
865}
866
867func mayCombine(prev token.Token, next byte) (b bool) {
868	switch prev {
869	case token.INT:
870		b = next == '.' // 1.
871	case token.ADD:
872		b = next == '+' // ++
873	case token.SUB:
874		b = next == '-' // --
875	case token.QUO:
876		b = next == '*' // /*
877	case token.LSS:
878		b = next == '-' || next == '<' // <- or <<
879	case token.AND:
880		b = next == '&' || next == '^' // && or &^
881	}
882	return
883}
884
885func (p *printer) setPos(pos token.Pos) {
886	if pos.IsValid() {
887		p.pos = p.posFor(pos) // accurate position of next item
888	}
889}
890
891// print prints a list of "items" (roughly corresponding to syntactic
892// tokens, but also including whitespace and formatting information).
893// It is the only print function that should be called directly from
894// any of the AST printing functions in nodes.go.
895//
896// Whitespace is accumulated until a non-whitespace token appears. Any
897// comments that need to appear before that token are printed first,
898// taking into account the amount and structure of any pending white-
899// space for best comment placement. Then, any leftover whitespace is
900// printed, followed by the actual token.
901func (p *printer) print(args ...any) {
902	for _, arg := range args {
903		// information about the current arg
904		var data string
905		var isLit bool
906		var impliedSemi bool // value for p.impliedSemi after this arg
907
908		// record previous opening token, if any
909		switch p.lastTok {
910		case token.ILLEGAL:
911			// ignore (white space)
912		case token.LPAREN, token.LBRACK:
913			p.prevOpen = p.lastTok
914		default:
915			// other tokens followed any opening token
916			p.prevOpen = token.ILLEGAL
917		}
918
919		switch x := arg.(type) {
920		case pmode:
921			// toggle printer mode
922			p.mode ^= x
923			continue
924
925		case whiteSpace:
926			if x == ignore {
927				// don't add ignore's to the buffer; they
928				// may screw up "correcting" unindents (see
929				// LabeledStmt)
930				continue
931			}
932			i := len(p.wsbuf)
933			if i == cap(p.wsbuf) {
934				// Whitespace sequences are very short so this should
935				// never happen. Handle gracefully (but possibly with
936				// bad comment placement) if it does happen.
937				p.writeWhitespace(i)
938				i = 0
939			}
940			p.wsbuf = p.wsbuf[0 : i+1]
941			p.wsbuf[i] = x
942			if x == newline || x == formfeed {
943				// newlines affect the current state (p.impliedSemi)
944				// and not the state after printing arg (impliedSemi)
945				// because comments can be interspersed before the arg
946				// in this case
947				p.impliedSemi = false
948			}
949			p.lastTok = token.ILLEGAL
950			continue
951
952		case *ast.Ident:
953			data = x.Name
954			impliedSemi = true
955			p.lastTok = token.IDENT
956
957		case *ast.BasicLit:
958			data = x.Value
959			isLit = true
960			impliedSemi = true
961			p.lastTok = x.Kind
962
963		case token.Token:
964			s := x.String()
965			if mayCombine(p.lastTok, s[0]) {
966				// the previous and the current token must be
967				// separated by a blank otherwise they combine
968				// into a different incorrect token sequence
969				// (except for token.INT followed by a '.' this
970				// should never happen because it is taken care
971				// of via binary expression formatting)
972				if len(p.wsbuf) != 0 {
973					p.internalError("whitespace buffer not empty")
974				}
975				p.wsbuf = p.wsbuf[0:1]
976				p.wsbuf[0] = ' '
977			}
978			data = s
979			// some keywords followed by a newline imply a semicolon
980			switch x {
981			case token.BREAK, token.CONTINUE, token.FALLTHROUGH, token.RETURN,
982				token.INC, token.DEC, token.RPAREN, token.RBRACK, token.RBRACE:
983				impliedSemi = true
984			}
985			p.lastTok = x
986
987		case string:
988			// incorrect AST - print error message
989			data = x
990			isLit = true
991			impliedSemi = true
992			p.lastTok = token.STRING
993
994		default:
995			fmt.Fprintf(os.Stderr, "print: unsupported argument %v (%T)\n", arg, arg)
996			panic("go/printer type")
997		}
998		// data != ""
999
1000		next := p.pos // estimated/accurate position of next item
1001		wroteNewline, droppedFF := p.flush(next, p.lastTok)
1002
1003		// intersperse extra newlines if present in the source and
1004		// if they don't cause extra semicolons (don't do this in
1005		// flush as it will cause extra newlines at the end of a file)
1006		if !p.impliedSemi {
1007			n := nlimit(next.Line - p.pos.Line)
1008			// don't exceed maxNewlines if we already wrote one
1009			if wroteNewline && n == maxNewlines {
1010				n = maxNewlines - 1
1011			}
1012			if n > 0 {
1013				ch := byte('\n')
1014				if droppedFF {
1015					ch = '\f' // use formfeed since we dropped one before
1016				}
1017				p.writeByte(ch, n)
1018				impliedSemi = false
1019			}
1020		}
1021
1022		// the next token starts now - record its line number if requested
1023		if p.linePtr != nil {
1024			*p.linePtr = p.out.Line
1025			p.linePtr = nil
1026		}
1027
1028		p.writeString(next, data, isLit)
1029		p.impliedSemi = impliedSemi
1030	}
1031}
1032
1033// flush prints any pending comments and whitespace occurring textually
1034// before the position of the next token tok. The flush result indicates
1035// if a newline was written or if a formfeed was dropped from the whitespace
1036// buffer.
1037func (p *printer) flush(next token.Position, tok token.Token) (wroteNewline, droppedFF bool) {
1038	if p.commentBefore(next) {
1039		// if there are comments before the next item, intersperse them
1040		wroteNewline, droppedFF = p.intersperseComments(next, tok)
1041	} else {
1042		// otherwise, write any leftover whitespace
1043		p.writeWhitespace(len(p.wsbuf))
1044	}
1045	return
1046}
1047
1048// getDoc returns the ast.CommentGroup associated with n, if any.
1049func getDoc(n ast.Node) *ast.CommentGroup {
1050	switch n := n.(type) {
1051	case *ast.Field:
1052		return n.Doc
1053	case *ast.ImportSpec:
1054		return n.Doc
1055	case *ast.ValueSpec:
1056		return n.Doc
1057	case *ast.TypeSpec:
1058		return n.Doc
1059	case *ast.GenDecl:
1060		return n.Doc
1061	case *ast.FuncDecl:
1062		return n.Doc
1063	case *ast.File:
1064		return n.Doc
1065	}
1066	return nil
1067}
1068
1069func getLastComment(n ast.Node) *ast.CommentGroup {
1070	switch n := n.(type) {
1071	case *ast.Field:
1072		return n.Comment
1073	case *ast.ImportSpec:
1074		return n.Comment
1075	case *ast.ValueSpec:
1076		return n.Comment
1077	case *ast.TypeSpec:
1078		return n.Comment
1079	case *ast.GenDecl:
1080		if len(n.Specs) > 0 {
1081			return getLastComment(n.Specs[len(n.Specs)-1])
1082		}
1083	case *ast.File:
1084		if len(n.Comments) > 0 {
1085			return n.Comments[len(n.Comments)-1]
1086		}
1087	}
1088	return nil
1089}
1090
1091func (p *printer) printNode(node any) error {
1092	// unpack *CommentedNode, if any
1093	var comments []*ast.CommentGroup
1094	if cnode, ok := node.(*CommentedNode); ok {
1095		node = cnode.Node
1096		comments = cnode.Comments
1097	}
1098
1099	if comments != nil {
1100		// commented node - restrict comment list to relevant range
1101		n, ok := node.(ast.Node)
1102		if !ok {
1103			goto unsupported
1104		}
1105		beg := n.Pos()
1106		end := n.End()
1107		// if the node has associated documentation,
1108		// include that commentgroup in the range
1109		// (the comment list is sorted in the order
1110		// of the comment appearance in the source code)
1111		if doc := getDoc(n); doc != nil {
1112			beg = doc.Pos()
1113		}
1114		if com := getLastComment(n); com != nil {
1115			if e := com.End(); e > end {
1116				end = e
1117			}
1118		}
1119		// token.Pos values are global offsets, we can
1120		// compare them directly
1121		i := 0
1122		for i < len(comments) && comments[i].End() < beg {
1123			i++
1124		}
1125		j := i
1126		for j < len(comments) && comments[j].Pos() < end {
1127			j++
1128		}
1129		if i < j {
1130			p.comments = comments[i:j]
1131		}
1132	} else if n, ok := node.(*ast.File); ok {
1133		// use ast.File comments, if any
1134		p.comments = n.Comments
1135	}
1136
1137	// if there are no comments, use node comments
1138	p.useNodeComments = p.comments == nil
1139
1140	// get comments ready for use
1141	p.nextComment()
1142
1143	p.print(pmode(0))
1144
1145	// format node
1146	switch n := node.(type) {
1147	case ast.Expr:
1148		p.expr(n)
1149	case ast.Stmt:
1150		// A labeled statement will un-indent to position the label.
1151		// Set p.indent to 1 so we don't get indent "underflow".
1152		if _, ok := n.(*ast.LabeledStmt); ok {
1153			p.indent = 1
1154		}
1155		p.stmt(n, false)
1156	case ast.Decl:
1157		p.decl(n)
1158	case ast.Spec:
1159		p.spec(n, 1, false)
1160	case []ast.Stmt:
1161		// A labeled statement will un-indent to position the label.
1162		// Set p.indent to 1 so we don't get indent "underflow".
1163		for _, s := range n {
1164			if _, ok := s.(*ast.LabeledStmt); ok {
1165				p.indent = 1
1166			}
1167		}
1168		p.stmtList(n, 0, false)
1169	case []ast.Decl:
1170		p.declList(n)
1171	case *ast.File:
1172		p.file(n)
1173	default:
1174		goto unsupported
1175	}
1176
1177	return p.sourcePosErr
1178
1179unsupported:
1180	return fmt.Errorf("go/printer: unsupported node type %T", node)
1181}
1182
1183// ----------------------------------------------------------------------------
1184// Trimmer
1185
1186// A trimmer is an io.Writer filter for stripping tabwriter.Escape
1187// characters, trailing blanks and tabs, and for converting formfeed
1188// and vtab characters into newlines and htabs (in case no tabwriter
1189// is used). Text bracketed by tabwriter.Escape characters is passed
1190// through unchanged.
1191type trimmer struct {
1192	output io.Writer
1193	state  int
1194	space  []byte
1195}
1196
1197// trimmer is implemented as a state machine.
1198// It can be in one of the following states:
1199const (
1200	inSpace  = iota // inside space
1201	inEscape        // inside text bracketed by tabwriter.Escapes
1202	inText          // inside text
1203)
1204
1205func (p *trimmer) resetSpace() {
1206	p.state = inSpace
1207	p.space = p.space[0:0]
1208}
1209
1210// Design note: It is tempting to eliminate extra blanks occurring in
1211//              whitespace in this function as it could simplify some
1212//              of the blanks logic in the node printing functions.
1213//              However, this would mess up any formatting done by
1214//              the tabwriter.
1215
1216var aNewline = []byte("\n")
1217
1218func (p *trimmer) Write(data []byte) (n int, err error) {
1219	// invariants:
1220	// p.state == inSpace:
1221	//	p.space is unwritten
1222	// p.state == inEscape, inText:
1223	//	data[m:n] is unwritten
1224	m := 0
1225	var b byte
1226	for n, b = range data {
1227		if b == '\v' {
1228			b = '\t' // convert to htab
1229		}
1230		switch p.state {
1231		case inSpace:
1232			switch b {
1233			case '\t', ' ':
1234				p.space = append(p.space, b)
1235			case '\n', '\f':
1236				p.resetSpace() // discard trailing space
1237				_, err = p.output.Write(aNewline)
1238			case tabwriter.Escape:
1239				_, err = p.output.Write(p.space)
1240				p.state = inEscape
1241				m = n + 1 // +1: skip tabwriter.Escape
1242			default:
1243				_, err = p.output.Write(p.space)
1244				p.state = inText
1245				m = n
1246			}
1247		case inEscape:
1248			if b == tabwriter.Escape {
1249				_, err = p.output.Write(data[m:n])
1250				p.resetSpace()
1251			}
1252		case inText:
1253			switch b {
1254			case '\t', ' ':
1255				_, err = p.output.Write(data[m:n])
1256				p.resetSpace()
1257				p.space = append(p.space, b)
1258			case '\n', '\f':
1259				_, err = p.output.Write(data[m:n])
1260				p.resetSpace()
1261				if err == nil {
1262					_, err = p.output.Write(aNewline)
1263				}
1264			case tabwriter.Escape:
1265				_, err = p.output.Write(data[m:n])
1266				p.state = inEscape
1267				m = n + 1 // +1: skip tabwriter.Escape
1268			}
1269		default:
1270			panic("unreachable")
1271		}
1272		if err != nil {
1273			return
1274		}
1275	}
1276	n = len(data)
1277
1278	switch p.state {
1279	case inEscape, inText:
1280		_, err = p.output.Write(data[m:n])
1281		p.resetSpace()
1282	}
1283
1284	return
1285}
1286
1287// ----------------------------------------------------------------------------
1288// Public interface
1289
1290// A Mode value is a set of flags (or 0). They control printing.
1291type Mode uint
1292
1293const (
1294	RawFormat Mode = 1 << iota // do not use a tabwriter; if set, UseSpaces is ignored
1295	TabIndent                  // use tabs for indentation independent of UseSpaces
1296	UseSpaces                  // use spaces instead of tabs for alignment
1297	SourcePos                  // emit //line directives to preserve original source positions
1298)
1299
1300// The mode below is not included in printer's public API because
1301// editing code text is deemed out of scope. Because this mode is
1302// unexported, it's also possible to modify or remove it based on
1303// the evolving needs of go/format and cmd/gofmt without breaking
1304// users. See discussion in CL 240683.
1305const (
1306	// normalizeNumbers means to canonicalize number
1307	// literal prefixes and exponents while printing.
1308	//
1309	// This value is known in and used by go/format and cmd/gofmt.
1310	// It is currently more convenient and performant for those
1311	// packages to apply number normalization during printing,
1312	// rather than by modifying the AST in advance.
1313	normalizeNumbers Mode = 1 << 30
1314)
1315
1316// A Config node controls the output of Fprint.
1317type Config struct {
1318	Mode     Mode // default: 0
1319	Tabwidth int  // default: 8
1320	Indent   int  // default: 0 (all code is indented at least by this much)
1321}
1322
1323var printerPool = sync.Pool{
1324	New: func() any {
1325		return &printer{
1326			// Whitespace sequences are short.
1327			wsbuf: make([]whiteSpace, 0, 16),
1328			// We start the printer with a 16K output buffer, which is currently
1329			// larger than about 80% of Go files in the standard library.
1330			output: make([]byte, 0, 16<<10),
1331		}
1332	},
1333}
1334
1335func newPrinter(cfg *Config, fset *token.FileSet, nodeSizes map[ast.Node]int) *printer {
1336	p := printerPool.Get().(*printer)
1337	*p = printer{
1338		Config:    *cfg,
1339		fset:      fset,
1340		pos:       token.Position{Line: 1, Column: 1},
1341		out:       token.Position{Line: 1, Column: 1},
1342		wsbuf:     p.wsbuf[:0],
1343		nodeSizes: nodeSizes,
1344		cachedPos: -1,
1345		output:    p.output[:0],
1346	}
1347	return p
1348}
1349
1350func (p *printer) free() {
1351	// Hard limit on buffer size; see https://golang.org/issue/23199.
1352	if cap(p.output) > 64<<10 {
1353		return
1354	}
1355
1356	printerPool.Put(p)
1357}
1358
1359// fprint implements Fprint and takes a nodesSizes map for setting up the printer state.
1360func (cfg *Config) fprint(output io.Writer, fset *token.FileSet, node any, nodeSizes map[ast.Node]int) (err error) {
1361	// print node
1362	p := newPrinter(cfg, fset, nodeSizes)
1363	defer p.free()
1364	if err = p.printNode(node); err != nil {
1365		return
1366	}
1367	// print outstanding comments
1368	p.impliedSemi = false // EOF acts like a newline
1369	p.flush(token.Position{Offset: infinity, Line: infinity}, token.EOF)
1370
1371	// output is buffered in p.output now.
1372	// fix //go:build and // +build comments if needed.
1373	p.fixGoBuildLines()
1374
1375	// redirect output through a trimmer to eliminate trailing whitespace
1376	// (Input to a tabwriter must be untrimmed since trailing tabs provide
1377	// formatting information. The tabwriter could provide trimming
1378	// functionality but no tabwriter is used when RawFormat is set.)
1379	output = &trimmer{output: output}
1380
1381	// redirect output through a tabwriter if necessary
1382	if cfg.Mode&RawFormat == 0 {
1383		minwidth := cfg.Tabwidth
1384
1385		padchar := byte('\t')
1386		if cfg.Mode&UseSpaces != 0 {
1387			padchar = ' '
1388		}
1389
1390		twmode := tabwriter.DiscardEmptyColumns
1391		if cfg.Mode&TabIndent != 0 {
1392			minwidth = 0
1393			twmode |= tabwriter.TabIndent
1394		}
1395
1396		output = tabwriter.NewWriter(output, minwidth, cfg.Tabwidth, 1, padchar, twmode)
1397	}
1398
1399	// write printer result via tabwriter/trimmer to output
1400	if _, err = output.Write(p.output); err != nil {
1401		return
1402	}
1403
1404	// flush tabwriter, if any
1405	if tw, _ := output.(*tabwriter.Writer); tw != nil {
1406		err = tw.Flush()
1407	}
1408
1409	return
1410}
1411
1412// A CommentedNode bundles an AST node and corresponding comments.
1413// It may be provided as argument to any of the [Fprint] functions.
1414type CommentedNode struct {
1415	Node     any // *ast.File, or ast.Expr, ast.Decl, ast.Spec, or ast.Stmt
1416	Comments []*ast.CommentGroup
1417}
1418
1419// Fprint "pretty-prints" an AST node to output for a given configuration cfg.
1420// Position information is interpreted relative to the file set fset.
1421// The node type must be *[ast.File], *[CommentedNode], [][ast.Decl], [][ast.Stmt],
1422// or assignment-compatible to [ast.Expr], [ast.Decl], [ast.Spec], or [ast.Stmt].
1423func (cfg *Config) Fprint(output io.Writer, fset *token.FileSet, node any) error {
1424	return cfg.fprint(output, fset, node, make(map[ast.Node]int))
1425}
1426
1427// Fprint "pretty-prints" an AST node to output.
1428// It calls [Config.Fprint] with default settings.
1429// Note that gofmt uses tabs for indentation but spaces for alignment;
1430// use format.Node (package go/format) for output that matches gofmt.
1431func Fprint(output io.Writer, fset *token.FileSet, node any) error {
1432	return (&Config{Tabwidth: 8}).Fprint(output, fset, node)
1433}
1434