printer

Imports

Imports #

"go/ast"
"go/doc/comment"
"strings"
"go/build/constraint"
"slices"
"text/tabwriter"
"go/ast"
"go/token"
"math"
"strconv"
"strings"
"unicode"
"unicode/utf8"
"fmt"
"go/ast"
"go/build/constraint"
"go/token"
"io"
"os"
"strings"
"sync"
"text/tabwriter"
"unicode"

Constants & Variables

RawFormat const #

const RawFormat Mode = *ast.BinaryExpr

SourcePos const #

const SourcePos

TabIndent const #

const TabIndent

UseSpaces const #

const UseSpaces

aNewline var #

var aNewline = *ast.CallExpr

blank const #

const blank = *ast.CallExpr

commaTerm const #

const commaTerm exprListMode = *ast.BinaryExpr

debug const #

const debug = false

filteredMsg const #

const filteredMsg = "contains filtered or unexported fields"

formfeed const #

const formfeed = *ast.CallExpr

funcParam const #

const funcParam paramMode = iota

funcTParam const #

const funcTParam

ignore const #

const ignore = *ast.CallExpr

inEscape const #

trimmer is implemented as a state machine. It can be in one of the following states:

const inEscape

inSpace const #

trimmer is implemented as a state machine. It can be in one of the following states:

const inSpace = iota

inText const #

trimmer is implemented as a state machine. It can be in one of the following states:

const inText

indent const #

const indent = *ast.CallExpr

infinity const #

const infinity = *ast.BinaryExpr

maxNewlines const #

const maxNewlines = 2

newline const #

const newline = *ast.CallExpr

noExtraBlank const #

const noExtraBlank pmode = *ast.BinaryExpr

noExtraLinebreak const #

const noExtraLinebreak

noIndent const #

const noIndent

normalizeNumbers const #

normalizeNumbers means to canonicalize number literal prefixes and exponents while printing. This value is known in and used by go/format and cmd/gofmt. It is currently more convenient and performant for those packages to apply number normalization during printing, rather than by modifying the AST in advance.

const normalizeNumbers Mode = *ast.BinaryExpr

printerPool var #

var printerPool = sync.Pool{...}

typeTParam const #

const typeTParam

unindent const #

const unindent = *ast.CallExpr

vtab const #

const vtab = *ast.CallExpr

Type Aliases

Mode type #

A Mode value is a set of flags (or 0). They control printing.

type Mode uint

exprListMode type #

type exprListMode uint

paramMode type #

type paramMode int

pmode type #

A pmode value represents the current printer mode.

type pmode int

whiteSpace type #

type whiteSpace byte

Structs

CommentedNode struct #

A CommentedNode bundles an AST node and corresponding comments. It may be provided as argument to any of the [Fprint] functions.

type CommentedNode struct {
Node any
Comments []*ast.CommentGroup
}

Config struct #

A Config node controls the output of Fprint.

type Config struct {
Mode Mode
Tabwidth int
Indent int
}

commentInfo struct #

type commentInfo struct {
cindex int
comment *ast.CommentGroup
commentOffset int
commentNewline bool
}

printer struct #

type printer struct {
Config
fset *token.FileSet
output []byte
indent int
level int
mode pmode
endAlignment bool
impliedSemi bool
lastTok token.Token
prevOpen token.Token
wsbuf []whiteSpace
goBuild []int
plusBuild []int
pos token.Position
out token.Position
last token.Position
linePtr *int
sourcePosErr error
comments []*ast.CommentGroup
useNodeComments bool
commentInfo
nodeSizes map[ast.Node]int
cachedPos token.Pos
cachedLine int
}

sizeCounter struct #

sizeCounter is an io.Writer which counts the number of bytes written, as well as whether a newline character was seen.

type sizeCounter struct {
hasNewline bool
size int
}

trimmer struct #

A trimmer is an io.Writer filter for stripping tabwriter.Escape characters, trailing blanks and tabs, and for converting formfeed and vtab characters into newlines and htabs (in case no tabwriter is used). Text bracketed by tabwriter.Escape characters is passed through unchanged.

type trimmer struct {
output io.Writer
state int
space []byte
}

Functions

Fprint function #

Fprint "pretty-prints" an AST node to output. It calls [Config.Fprint] with default settings. Note that gofmt uses tabs for indentation but spaces for alignment; use format.Node (package go/format) for output that matches gofmt.

func Fprint(output io.Writer, fset *token.FileSet, node any) error

Fprint method #

Fprint "pretty-prints" an AST node to output for a given configuration cfg. Position information is interpreted relative to the file set fset. The node type must be *[ast.File], *[CommentedNode], [][ast.Decl], [][ast.Stmt], or assignment-compatible to [ast.Expr], [ast.Decl], [ast.Spec], or [ast.Stmt].

func (cfg *Config) Fprint(output io.Writer, fset *token.FileSet, node any) error

Write method #

func (p *trimmer) Write(data []byte) (n int, err error)

Write method #

func (c *sizeCounter) Write(p []byte) (int, error)

allStars function #

allStars reports whether text is the interior of an old-style /* */ comment with a star at the start of each line.

func allStars(text string) bool

appendLines function #

appendLines is like append(x, y...) but it avoids creating doubled blank lines, which would not be gofmt-standard output. It assumes that only whole blocks of lines are being appended, not line fragments.

func appendLines(x []byte, y []byte) []byte

binaryExpr method #

Format the binary expression: decide the cutoff and then format. Let's call depth == 1 Normal mode, and depth > 1 Compact mode. (Algorithm suggestion by Russ Cox.) The precedences are: 5 * / % << >> & &^ 4 + - | ^ 3 == != < <= > >= 2 && 1 || The only decision is whether there will be spaces around levels 4 and 5. There are never spaces at level 6 (unary), and always spaces at levels 3 and below. To choose the cutoff, look at the whole expression but excluding primary expressions (function calls, parenthesized exprs), and apply these rules: 1. If there is a binary operator with a right side unary operand that would clash without a space, the cutoff must be (in order): /* 6 && 6 &^ 6 ++ 5 -- 5 (Comparison operators always have spaces around them.) 2. If there is a mix of level 5 and level 4 operators, then the cutoff is 5 (use spaces to distinguish precedence) in Normal mode and 4 (never use spaces) in Compact mode. 3. If there are no level 4 operators or no level 5 operators, then the cutoff is 6 (always use spaces) in Normal mode and 4 (never use spaces) in Compact mode.

func (p *printer) binaryExpr(x *ast.BinaryExpr, prec1 int, cutoff int, depth int)

block method #

block prints an *ast.BlockStmt; it always spans at least two lines.

func (p *printer) block(b *ast.BlockStmt, nindent int)

bodySize method #

bodySize is like nodeSize but it is specialized for *ast.BlockStmt's.

func (p *printer) bodySize(b *ast.BlockStmt, maxSize int) int

combinesWithName function #

combinesWithName reports whether a name followed by the expression x syntactically combines to another valid (value) expression. For instance using *T for x, "name *T" syntactically appears as the expression x*T. On the other hand, using P|Q or *P|~Q for x, "name P|Q" or "name *P|~Q" cannot be combined into a valid (value) expression.

func combinesWithName(x ast.Expr) bool

commentBefore method #

commentBefore reports whether the current comment group occurs before the next position in the source code and printing it does not introduce implicit semicolons.

func (p *printer) commentBefore(next token.Position) bool

commentSizeBefore method #

commentSizeBefore returns the estimated size of the comments on the same line before the next position.

func (p *printer) commentSizeBefore(next token.Position) int

commentTextAt method #

func (p *printer) commentTextAt(start int) string

commentsHaveNewline method #

commentsHaveNewline reports whether a list of comments belonging to an *ast.CommentGroup contains newlines. Because the position information may only be partially correct, we also have to read the comment text.

func (p *printer) commentsHaveNewline(list []*ast.Comment) bool

commonPrefix function #

commonPrefix returns the common prefix of a and b.

func commonPrefix(a string, b string) string

containsLinebreak method #

containsLinebreak reports whether the whitespace buffer contains any line breaks.

func (p *printer) containsLinebreak() bool

controlClause method #

func (p *printer) controlClause(isForStmt bool, init ast.Stmt, expr ast.Expr, post ast.Stmt)

cutoff function #

func cutoff(e *ast.BinaryExpr, depth int) int

decl method #

func (p *printer) decl(decl ast.Decl)

declList method #

func (p *printer) declList(list []ast.Decl)

declToken function #

func declToken(decl ast.Decl) (tok token.Token)

diffPrec function #

func diffPrec(expr ast.Expr, prec int) int

distanceFrom method #

distanceFrom returns the column difference between p.out (the current output position) and startOutCol. If the start position is on a different line from the current position (or either is unknown), the result is infinity.

func (p *printer) distanceFrom(startPos token.Pos, startOutCol int) int

expr method #

func (p *printer) expr(x ast.Expr)

expr0 method #

func (p *printer) expr0(x ast.Expr, depth int)

expr1 method #

func (p *printer) expr1(expr ast.Expr, prec1 int, depth int)

exprList method #

Print a list of expressions. If the list spans multiple source lines, the original line breaks are respected between expressions. TODO(gri) Consider rewriting this to be independent of []ast.Expr so that we can use the algorithm for any kind of list (e.g., pass list via a channel over which to range).

func (p *printer) exprList(prev0 token.Pos, list []ast.Expr, depth int, mode exprListMode, next0 token.Pos, isIncomplete bool)

fieldList method #

func (p *printer) fieldList(fields *ast.FieldList, isStruct bool, isIncomplete bool)

file method #

func (p *printer) file(src *ast.File)

fixGoBuildLines method #

func (p *printer) fixGoBuildLines()

flush method #

flush prints any pending comments and whitespace occurring textually before the position of the next token tok. The flush result indicates if a newline was written or if a formfeed was dropped from the whitespace buffer.

func (p *printer) flush(next token.Position, tok token.Token) (wroteNewline bool, droppedFF bool)

formatDocComment function #

formatDocComment reformats the doc comment list, returning the canonical formatting.

func formatDocComment(list []*ast.Comment) []*ast.Comment

fprint method #

fprint implements Fprint and takes a nodesSizes map for setting up the printer state.

func (cfg *Config) fprint(output io.Writer, fset *token.FileSet, node any, nodeSizes map[ast.Node]int) (err error)

free method #

func (p *printer) free()

funcBody method #

funcBody prints a function body following a function header of given headerSize. If the header's and block's size are "small enough" and the block is "simple enough", the block is printed on the current line, without line breaks, spaced from the header by sep. Otherwise the block's opening "{" is printed on the current line, followed by lines for the block's statements and its closing "}".

func (p *printer) funcBody(headerSize int, sep whiteSpace, b *ast.BlockStmt)

funcDecl method #

func (p *printer) funcDecl(d *ast.FuncDecl)

genDecl method #

func (p *printer) genDecl(d *ast.GenDecl)

getDoc function #

getDoc returns the ast.CommentGroup associated with n, if any.

func getDoc(n ast.Node) *ast.CommentGroup

getLastComment function #

func getLastComment(n ast.Node) *ast.CommentGroup

identList method #

If indent is set, a multi-line identifier list is indented after the first linebreak encountered.

func (p *printer) identList(list []*ast.Ident, indent bool)

identListSize function #

func identListSize(list []*ast.Ident, maxSize int) (size int)

indentList method #

indentList reports whether an expression list would look better if it were indented wholesale (starting with the very first element, rather than starting at the first line break).

func (p *printer) indentList(list []ast.Expr) bool

internalError method #

func (p *printer) internalError(msg ...any)

intersperseComments method #

intersperseComments consumes all comments that appear before the next token tok and prints it together with the buffered whitespace (i.e., the whitespace that needs to be written before the next token). A heuristic is used to mix the comments and whitespace. The intersperseComments result indicates if a newline was written or if a formfeed was dropped from the whitespace buffer.

func (p *printer) intersperseComments(next token.Position, tok token.Token) (wroteNewline bool, droppedFF bool)

isBinary function #

func isBinary(expr ast.Expr) bool

isBlank function #

Returns true if s contains only white space (only tabs and blanks can appear in the printer's context).

func isBlank(s string) bool

isDirective function #

isDirective reports whether c is a comment directive. See go.dev/issue/37974. This code is also in go/ast.

func isDirective(c string) bool

isNL function #

func isNL(b byte) bool

isOneLineFieldList method #

func (p *printer) isOneLineFieldList(list []*ast.Field) bool

isTypeElem function #

isTypeElem reports whether x is a (possibly parenthesized) type element expression. The result is false if x could be a type element OR an ordinary (value) expression.

func isTypeElem(x ast.Expr) bool

isTypeName function #

func isTypeName(x ast.Expr) bool

keepTypeColumn function #

The keepTypeColumn function determines if the type column of a series of consecutive const or var declarations must be kept, or if initialization values (V) can be placed in the type column (T) instead. The i'th entry in the result slice is true if the type column in spec[i] must be kept. For example, the declaration: const ( foobar int = 42 // comment x = 7 // comment foo bar = 991 ) leads to the type/values matrix below. A run of value columns (V) can be moved into the type column if there is no type for any of the values in that column (we only move entire columns so that they align properly). matrix formatted result matrix T V -> T V -> true there is a T and so the type - V - V true column must be kept - - - - false - V V - false V is moved into T column

func keepTypeColumn(specs []ast.Spec) []bool

lineAt method #

func (p *printer) lineAt(start int) []byte

lineFor method #

func (p *printer) lineFor(pos token.Pos) int

linebreak method #

Print as many newlines as necessary (but at least min newlines) to get to the current line. ws is printed before the first line break. If newSection is set, the first line break is printed as formfeed. Returns 0 if no line breaks were printed, returns 1 if there was exactly one newline printed, and returns a value > 1 if there was a formfeed or more than one newline printed. TODO(gri): linebreak may add too many lines if the next statement at "line" is preceded by comments because the computation of n assumes the current position before the comment and the target position after the comment. Thus, after interspersing such comments, the space taken up by them is not considered to reduce the number of linebreaks. At the moment there is no easy way to know about future (not yet interspersed) comments in this function.

func (p *printer) linebreak(line int, min int, ws whiteSpace, newSection bool) (nbreaks int)

linesFrom method #

linesFrom returns the number of output lines between the current output line and the line argument, ignoring any pending (not yet emitted) whitespace or comments. It is used to compute an accurate size (in number of lines) for a formatted construct.

func (p *printer) linesFrom(line int) int

mayCombine function #

func mayCombine(prev token.Token, next byte) (b bool)

newPrinter function #

func newPrinter(cfg *Config, fset *token.FileSet, nodeSizes map[ast.Node]int) *printer

nextComment method #

func (p *printer) nextComment()

nlimit function #

nlimit limits n to maxNewlines.

func nlimit(n int) int

nodeSize method #

nodeSize determines the size of n in chars after formatting. The result is <= maxSize if the node fits on one line with at most maxSize chars and the formatted output doesn't contain any control chars. Otherwise, the result is > maxSize.

func (p *printer) nodeSize(n ast.Node, maxSize int) (size int)

normalizedNumber function #

normalizedNumber rewrites base prefixes and exponents of numbers to use lower-case letters (0X123 to 0x123 and 1.2E3 to 1.2e3), and removes leading 0's from integer imaginary literals (0765i to 765i). It leaves hexadecimal digits alone. normalizedNumber doesn't modify the ast.BasicLit value lit points to. If lit is not a number or a number in canonical format already, lit is returned as is. Otherwise a new ast.BasicLit is created.

func normalizedNumber(lit *ast.BasicLit) *ast.BasicLit

numLines method #

numLines returns the number of lines spanned by node n in the original source.

func (p *printer) numLines(n ast.Node) int

parameters method #

func (p *printer) parameters(fields *ast.FieldList, mode paramMode)

posFor method #

func (p *printer) posFor(pos token.Pos) token.Position

possibleSelectorExpr method #

func (p *printer) possibleSelectorExpr(expr ast.Expr, prec1 int, depth int) bool

print method #

print prints a list of "items" (roughly corresponding to syntactic tokens, but also including whitespace and formatting information). It is the only print function that should be called directly from any of the AST printing functions in nodes.go. Whitespace is accumulated until a non-whitespace token appears. Any comments that need to appear before that token are printed first, taking into account the amount and structure of any pending white- space for best comment placement. Then, any leftover whitespace is printed, followed by the actual token.

func (p *printer) print(args ...any)

printNode method #

func (p *printer) printNode(node any) error

recordLine method #

recordLine records the output line number for the next non-whitespace token in *linePtr. It is used to compute an accurate line number for a formatted construct, independent of pending (not yet emitted) whitespace or comments.

func (p *printer) recordLine(linePtr *int)

reduceDepth function #

func reduceDepth(depth int) int

resetSpace method #

func (p *trimmer) resetSpace()

sanitizeImportPath function #

func sanitizeImportPath(lit *ast.BasicLit) *ast.BasicLit

selectorExpr method #

selectorExpr handles an *ast.SelectorExpr node and reports whether x spans multiple lines.

func (p *printer) selectorExpr(x *ast.SelectorExpr, depth int, isMethod bool) bool

setComment method #

setComment sets g as the next comment if g != nil and if node comments are enabled - this mode is used when printing source code fragments such as exports only. It assumes that there is no pending comment in p.comments and at most one pending comment in the p.comment cache.

func (p *printer) setComment(g *ast.CommentGroup)

setLineComment method #

func (p *printer) setLineComment(text string)

setPos method #

func (p *printer) setPos(pos token.Pos)

signature method #

func (p *printer) signature(sig *ast.FuncType)

spec method #

The parameter n is the number of specs in the group. If doIndent is set, multi-line identifier lists in the spec are indented when the first linebreak is encountered.

func (p *printer) spec(spec ast.Spec, n int, doIndent bool)

stmt method #

func (p *printer) stmt(stmt ast.Stmt, nextIsRBrace bool)

stmtList method #

Print the statement list indented, but without a newline after the last statement. Extra line breaks between statements in the source are respected but at most one empty line is printed between statements.

func (p *printer) stmtList(list []ast.Stmt, nindent int, nextIsRBrace bool)

stripCommonPrefix function #

stripCommonPrefix removes a common prefix from /*-style comment lines (unless no comment line is indented, all but the first line have some form of space prefix). The prefix is computed using heuristics such that is likely that the comment contents are nicely laid out after re-printing each line using the printer's current indentation.

func stripCommonPrefix(lines []string)

stripParens function #

func stripParens(x ast.Expr) ast.Expr

stripParensAlways function #

func stripParensAlways(x ast.Expr) ast.Expr

trimRight function #

trimRight returns s with trailing whitespace removed.

func trimRight(s string) string

valueSpec method #

func (p *printer) valueSpec(s *ast.ValueSpec, keepType bool)

walkBinary function #

func walkBinary(e *ast.BinaryExpr) (has4 bool, has5 bool, maxProblem int)

writeByte method #

writeByte writes ch n times to p.output and updates p.pos. Only used to write formatting (white space) characters.

func (p *printer) writeByte(ch byte, n int)

writeComment method #

func (p *printer) writeComment(comment *ast.Comment)

writeCommentPrefix method #

writeCommentPrefix writes the whitespace before a comment. If there is any pending whitespace, it consumes as much of it as is likely to help position the comment nicely. pos is the comment position, next the position of the item after all pending comments, prev is the previous comment in a group of comments (or nil), and tok is the next token.

func (p *printer) writeCommentPrefix(pos token.Position, next token.Position, prev *ast.Comment, tok token.Token)

writeCommentSuffix method #

writeCommentSuffix writes a line break after a comment if indicated and processes any leftover indentation information. If a line break is needed, the kind of break (newline vs formfeed) depends on the pending whitespace. The writeCommentSuffix result indicates if a newline was written or if a formfeed was dropped from the whitespace buffer.

func (p *printer) writeCommentSuffix(needsLinebreak bool) (wroteNewline bool, droppedFF bool)

writeIndent method #

writeIndent writes indentation.

func (p *printer) writeIndent()

writeLineDirective method #

writeLineDirective writes a //line directive if necessary.

func (p *printer) writeLineDirective(pos token.Position)

writeString method #

writeString writes the string s to p.output and updates p.pos, p.out, and p.last. If isLit is set, s is escaped w/ tabwriter.Escape characters to protect s from being interpreted by the tabwriter. Note: writeString is only used to write Go tokens, literals, and comments, all of which must be written literally. Thus, it is correct to always set isLit = true. However, setting it explicitly only when needed (i.e., when we don't know that s contains no tabs or line breaks) avoids processing extra escape characters and reduces run time of the printer benchmark by up to 10%.

func (p *printer) writeString(pos token.Position, s string, isLit bool)

writeWhitespace method #

writeWhitespace writes the first n whitespace entries.

func (p *printer) writeWhitespace(n int)

Generated with Arrow