1// Copyright 2009 The Go Authors. All rights reserved.2// Use of this source code is governed by a BSD-style3// license that can be found in the LICENSE file.45// This file implements printing of AST nodes; specifically6// expressions, statements, declarations, and files. It uses7// the print functionality implemented in printer.go.89package printer1011import (12 "go/ast"13 "go/token"14 "strconv"15 "strings"16 "unicode"17 "unicode/utf8"18)1920// Formatting issues:21// - better comment formatting for /*-style comments at the end of a line (e.g. a declaration)22// when the comment spans multiple lines; if such a comment is just two lines, formatting is23// not idempotent24// - formatting of expression lists25// - should use blank instead of tab to separate one-line function bodies from26// the function header unless there is a group of consecutive one-liners2728// ----------------------------------------------------------------------------29// Common AST nodes.3031// Print as many newlines as necessary (but at least min newlines) to get to32// the current line. ws is printed before the first line break. If newSection33// is set, the first line break is printed as formfeed. Returns 0 if no line34// breaks were printed, returns 1 if there was exactly one newline printed,35// and returns a value > 1 if there was a formfeed or more than one newline36// printed.37//38// TODO(gri): linebreak may add too many lines if the next statement at "line"39// is preceded by comments because the computation of n assumes40// the current position before the comment and the target position41// after the comment. Thus, after interspersing such comments, the42// space taken up by them is not considered to reduce the number of43// linebreaks. At the moment there is no easy way to know about44// future (not yet interspersed) comments in this function.45func (p *printer) linebreak(line, min int, ws whiteSpace, newSection bool) (nbreaks int) {46 n := max(nlimit(line-p.pos.Line), min)47 if n > 0 {48 p.print(ws)49 if newSection {50 p.print(formfeed)51 n--52 nbreaks = 253 }54 nbreaks += n55 for ; n > 0; n-- {56 p.print(newline)57 }58 }59 return60}6162// setComment sets g as the next comment if g != nil and if node comments63// are enabled - this mode is used when printing source code fragments such64// as exports only. It assumes that there is no pending comment in p.comments65// and at most one pending comment in the p.comment cache.66func (p *printer) setComment(g *ast.CommentGroup) {67 if g == nil || !p.useNodeComments {68 return69 }70 if p.comments == nil {71 // initialize p.comments lazily72 p.comments = make([]*ast.CommentGroup, 1)73 } else if p.cindex < len(p.comments) {74 // for some reason there are pending comments; this75 // should never happen - handle gracefully and flush76 // all comments up to g, ignore anything after that77 p.flush(p.posFor(g.List[0].Pos()), token.ILLEGAL)78 p.comments = p.comments[0:1]79 // in debug mode, report error80 p.internalError("setComment found pending comments")81 }82 p.comments[0] = g83 p.cindex = 084 // don't overwrite any pending comment in the p.comment cache85 // (there may be a pending comment when a line comment is86 // immediately followed by a lead comment with no other87 // tokens between)88 if p.commentOffset == infinity {89 p.nextComment() // get comment ready for use90 }91}9293type exprListMode uint9495const (96 commaTerm exprListMode = 1 << iota // list is optionally terminated by a comma97 noIndent // no extra indentation in multi-line lists98)99100// If indent is set, a multi-line identifier list is indented after the101// first linebreak encountered.102func (p *printer) identList(list []*ast.Ident, indent bool) {103 // convert into an expression list so we can re-use exprList formatting104 xlist := make([]ast.Expr, len(list))105 for i, x := range list {106 xlist[i] = x107 }108 var mode exprListMode109 if !indent {110 mode = noIndent111 }112 p.exprList(token.NoPos, xlist, 1, mode, token.NoPos, false)113}114115const filteredMsg = "contains filtered or unexported fields"116117// Print a list of expressions. If the list spans multiple118// source lines, the original line breaks are respected between119// expressions.120//121// TODO(gri) Consider rewriting this to be independent of []ast.Expr122// so that we can use the algorithm for any kind of list123//124// (e.g., pass list via a channel over which to range).125func (p *printer) exprList(prev0 token.Pos, list []ast.Expr, depth int, mode exprListMode, next0 token.Pos, isIncomplete bool) {126 if len(list) == 0 {127 if isIncomplete {128 prev := p.posFor(prev0)129 next := p.posFor(next0)130 if prev.IsValid() && prev.Line == next.Line {131 p.print("/* " + filteredMsg + " */")132 } else {133 p.print(newline)134 p.print(indent, "// "+filteredMsg, unindent, newline)135 }136 }137 return138 }139140 prev := p.posFor(prev0)141 next := p.posFor(next0)142 line := p.lineFor(list[0].Pos())143 endLine := p.lineFor(list[len(list)-1].End())144145 if prev.IsValid() && prev.Line == line && line == endLine {146 // all list entries on a single line147 for i, x := range list {148 if i > 0 {149 // use position of expression following the comma as150 // comma position for correct comment placement151 p.setPos(x.Pos())152 p.print(token.COMMA, blank)153 }154 p.expr0(x, depth)155 }156 if isIncomplete {157 p.print(token.COMMA, blank, "/* "+filteredMsg+" */")158 }159 return160 }161162 // list entries span multiple lines;163 // use source code positions to guide line breaks164165 // Don't add extra indentation if noIndent is set;166 // i.e., pretend that the first line is already indented.167 ws := ignore168 if mode&noIndent == 0 {169 ws = indent170 }171172 // The first linebreak is always a formfeed since this section must not173 // depend on any previous formatting.174 prevBreak := -1 // index of last expression that was followed by a linebreak175 if prev.IsValid() && prev.Line < line && p.linebreak(line, 0, ws, true) > 0 {176 ws = ignore177 prevBreak = 0178 }179180 // initialize expression/key size: a zero value indicates expr/key doesn't fit on a single line181 size := 0182183 // We use the ratio between the geometric mean of the previous key sizes and184 // the current size to determine if there should be a break in the alignment.185 // To compute the geometric mean we accumulate the log₂(size) values (log2sum)186 // and the number of sizes included (count).187 log2sum := 0.0188 count := 0189190 // print all list elements191 prevLine := prev.Line192 for i, x := range list {193 line = p.lineFor(x.Pos())194195 // Determine if the next linebreak, if any, needs to use formfeed:196 // in general, use the entire node size to make the decision; for197 // key:value expressions, use the key size.198 // TODO(gri) for a better result, should probably incorporate both199 // the key and the node size into the decision process200 useFF := true201202 // Determine element size: All bets are off if we don't have203 // position information for the previous and next token (likely204 // generated code - simply ignore the size in this case by setting205 // it to 0).206 prevSize := size207 const infinity = 1e6 // larger than any source line208 size = p.nodeSize(x, infinity)209 pair, isPair := x.(*ast.KeyValueExpr)210 if size <= infinity && prev.IsValid() && next.IsValid() {211 // x fits on a single line212 if isPair {213 size = p.nodeSize(pair.Key, infinity) // size <= infinity214 }215 } else {216 // size too large or we don't have good layout information217 size = 0218 }219220 // If the previous line and the current line had single-221 // line-expressions and the key sizes are small or the222 // ratio between the current key and the geometric mean223 // if the previous key sizes does not exceed a threshold,224 // align columns and do not use formfeed.225 if prevSize > 0 && size > 0 {226 const smallSize = 40227 if count == 0 || prevSize <= smallSize && size <= smallSize {228 useFF = false229 } else {230 const r = 2.5 // threshold231 geomean := exp2ish(log2sum / float64(count)) // count > 0232 ratio := float64(size) / geomean233 useFF = r*ratio <= 1 || r <= ratio234 }235 }236237 needsLinebreak := 0 < prevLine && prevLine < line238 if i > 0 {239 // Use position of expression following the comma as240 // comma position for correct comment placement, but241 // only if the expression is on the same line.242 if !needsLinebreak {243 p.setPos(x.Pos())244 }245 p.print(token.COMMA)246 needsBlank := true247 if needsLinebreak {248 // Lines are broken using newlines so comments remain aligned249 // unless useFF is set or there are multiple expressions on250 // the same line in which case formfeed is used.251 nbreaks := p.linebreak(line, 0, ws, useFF || prevBreak+1 < i)252 if nbreaks > 0 {253 ws = ignore254 prevBreak = i255 needsBlank = false // we got a line break instead256 }257 // If there was a new section or more than one new line258 // (which means that the tabwriter will implicitly break259 // the section), reset the geomean variables since we are260 // starting a new group of elements with the next element.261 if nbreaks > 1 {262 log2sum = 0263 count = 0264 }265 }266 if needsBlank {267 p.print(blank)268 }269 }270271 if len(list) > 1 && isPair && size > 0 && needsLinebreak {272 // We have a key:value expression that fits onto one line273 // and it's not on the same line as the prior expression:274 // Use a column for the key such that consecutive entries275 // can align if possible.276 // (needsLinebreak is set if we started a new line before)277 p.expr(pair.Key)278 p.setPos(pair.Colon)279 p.print(token.COLON, vtab)280 p.expr(pair.Value)281 } else {282 p.expr0(x, depth)283 }284285 if size > 0 {286 log2sum += log2ish(float64(size))287 count++288 }289290 prevLine = line291 }292293 if mode&commaTerm != 0 && next.IsValid() && p.pos.Line < next.Line {294 // Print a terminating comma if the next token is on a new line.295 p.print(token.COMMA)296 if isIncomplete {297 p.print(newline)298 p.print("// " + filteredMsg)299 }300 if ws == ignore && mode&noIndent == 0 {301 // unindent if we indented302 p.print(unindent)303 }304 p.print(formfeed) // terminating comma needs a line break to look good305 return306 }307308 if isIncomplete {309 p.print(token.COMMA, newline)310 p.print("// "+filteredMsg, newline)311 }312313 if ws == ignore && mode&noIndent == 0 {314 // unindent if we indented315 p.print(unindent)316 }317}318319type paramMode int320321const (322 funcParam paramMode = iota323 funcTParam324 typeTParam325)326327func (p *printer) parameters(fields *ast.FieldList, mode paramMode) {328 openTok, closeTok := token.LPAREN, token.RPAREN329 if mode != funcParam {330 openTok, closeTok = token.LBRACK, token.RBRACK331 }332 p.setPos(fields.Opening)333 p.print(openTok)334 if len(fields.List) > 0 {335 prevLine := p.lineFor(fields.Opening)336 ws := indent337 for i, par := range fields.List {338 // determine par begin and end line (may be different339 // if there are multiple parameter names for this par340 // or the type is on a separate line)341 parLineBeg := p.lineFor(par.Pos())342 parLineEnd := p.lineFor(par.End())343 // separating "," if needed344 needsLinebreak := 0 < prevLine && prevLine < parLineBeg345 if i > 0 {346 // use position of parameter following the comma as347 // comma position for correct comma placement, but348 // only if the next parameter is on the same line349 if !needsLinebreak {350 p.setPos(par.Pos())351 }352 p.print(token.COMMA)353 }354 // separator if needed (linebreak or blank)355 if needsLinebreak && p.linebreak(parLineBeg, 0, ws, true) > 0 {356 // break line if the opening "(" or previous parameter ended on a different line357 ws = ignore358 } else if i > 0 {359 p.print(blank)360 }361 // parameter names362 if len(par.Names) > 0 {363 // Very subtle: If we indented before (ws == ignore), identList364 // won't indent again. If we didn't (ws == indent), identList will365 // indent if the identList spans multiple lines, and it will outdent366 // again at the end (and still ws == indent). Thus, a subsequent indent367 // by a linebreak call after a type, or in the next multi-line identList368 // will do the right thing.369 p.identList(par.Names, ws == indent)370 p.print(blank)371 }372 // parameter type373 p.expr(stripParensAlways(par.Type))374 prevLine = parLineEnd375 }376377 // if the closing ")" is on a separate line from the last parameter,378 // print an additional "," and line break379 if closing := p.lineFor(fields.Closing); 0 < prevLine && prevLine < closing {380 p.print(token.COMMA)381 p.linebreak(closing, 0, ignore, true)382 } else if mode == typeTParam && fields.NumFields() == 1 && combinesWithName(stripParensAlways(fields.List[0].Type)) {383 // A type parameter list [P T] where the name P and the type expression T syntactically384 // combine to another valid (value) expression requires a trailing comma, as in [P *T,]385 // (or an enclosing interface as in [P interface(*T)]), so that the type parameter list386 // is not parsed as an array length [P*T].387 p.print(token.COMMA)388 }389390 // unindent if we indented391 if ws == ignore {392 p.print(unindent)393 }394 }395396 p.setPos(fields.Closing)397 p.print(closeTok)398}399400// combinesWithName reports whether a name followed by the expression x401// syntactically combines to another valid (value) expression. For instance402// using *T for x, "name *T" syntactically appears as the expression x*T.403// On the other hand, using P|Q or *P|~Q for x, "name P|Q" or "name *P|~Q"404// cannot be combined into a valid (value) expression.405func combinesWithName(x ast.Expr) bool {406 switch x := x.(type) {407 case *ast.StarExpr:408 // name *x.X combines to name*x.X if x.X is not a type element409 return !isTypeElem(x.X)410 case *ast.BinaryExpr:411 return combinesWithName(x.X) && !isTypeElem(x.Y)412 case *ast.ParenExpr:413 return !isTypeElem(x.X)414 }415 return false416}417418// isTypeElem reports whether x is a (possibly parenthesized) type element expression.419// The result is false if x could be a type element OR an ordinary (value) expression.420func isTypeElem(x ast.Expr) bool {421 switch x := x.(type) {422 case *ast.ArrayType, *ast.StructType, *ast.FuncType, *ast.InterfaceType, *ast.MapType, *ast.ChanType:423 return true424 case *ast.UnaryExpr:425 return x.Op == token.TILDE426 case *ast.BinaryExpr:427 return isTypeElem(x.X) || isTypeElem(x.Y)428 case *ast.ParenExpr:429 return isTypeElem(x.X)430 }431 return false432}433434func (p *printer) signature(sig *ast.FuncType) {435 if sig.TypeParams != nil {436 p.parameters(sig.TypeParams, funcTParam)437 }438 if sig.Params != nil {439 p.parameters(sig.Params, funcParam)440 } else {441 p.print(token.LPAREN, token.RPAREN)442 }443 res := sig.Results444 n := res.NumFields()445 if n > 0 {446 // res != nil447 p.print(blank)448 if n == 1 && res.List[0].Names == nil {449 // single anonymous res; no ()'s450 p.expr(stripParensAlways(res.List[0].Type))451 return452 }453 p.parameters(res, funcParam)454 }455}456457func identListSize(list []*ast.Ident, maxSize int) (size int) {458 for i, x := range list {459 if i > 0 {460 size += len(", ")461 }462 size += utf8.RuneCountInString(x.Name)463 if size >= maxSize {464 break465 }466 }467 return468}469470func (p *printer) isOneLineFieldList(list []*ast.Field) bool {471 if len(list) != 1 {472 return false // allow only one field473 }474 f := list[0]475 if f.Tag != nil || f.Comment != nil {476 return false // don't allow tags or comments477 }478 // only name(s) and type479 const maxSize = 30 // adjust as appropriate, this is an approximate value480 namesSize := identListSize(f.Names, maxSize)481 if namesSize > 0 {482 namesSize = 1 // blank between names and types483 }484 typeSize := p.nodeSize(f.Type, maxSize)485 return namesSize+typeSize <= maxSize486}487488func (p *printer) setLineComment(text string) {489 p.setComment(&ast.CommentGroup{List: []*ast.Comment{{Slash: token.NoPos, Text: text}}})490}491492func (p *printer) fieldList(fields *ast.FieldList, isStruct, isIncomplete bool) {493 lbrace := fields.Opening494 list := fields.List495 rbrace := fields.Closing496 hasComments := isIncomplete || p.commentBefore(p.posFor(rbrace))497 srcIsOneLine := lbrace.IsValid() && rbrace.IsValid() && p.lineFor(lbrace) == p.lineFor(rbrace)498499 if !hasComments && srcIsOneLine {500 // possibly a one-line struct/interface501 if len(list) == 0 {502 // no blank between keyword and {} in this case503 p.setPos(lbrace)504 p.print(token.LBRACE)505 p.setPos(rbrace)506 p.print(token.RBRACE)507 return508 } else if p.isOneLineFieldList(list) {509 // small enough - print on one line510 // (don't use identList and ignore source line breaks)511 p.setPos(lbrace)512 p.print(token.LBRACE, blank)513 f := list[0]514 if isStruct {515 for i, x := range f.Names {516 if i > 0 {517 // no comments so no need for comma position518 p.print(token.COMMA, blank)519 }520 p.expr(x)521 }522 if len(f.Names) > 0 {523 p.print(blank)524 }525 p.expr(f.Type)526 } else { // interface527 if len(f.Names) > 0 {528 name := f.Names[0] // method name529 p.expr(name)530 p.signature(f.Type.(*ast.FuncType)) // don't print "func"531 } else {532 // embedded interface533 p.expr(f.Type)534 }535 }536 p.print(blank)537 p.setPos(rbrace)538 p.print(token.RBRACE)539 return540 }541 }542 // hasComments || !srcIsOneLine543544 p.print(blank)545 p.setPos(lbrace)546 p.print(token.LBRACE, indent)547 if hasComments || len(list) > 0 {548 p.print(formfeed)549 }550551 if isStruct {552553 sep := vtab554 if len(list) == 1 {555 sep = blank556 }557 var line int558 for i, f := range list {559 if i > 0 {560 p.linebreak(p.lineFor(f.Pos()), 1, ignore, p.linesFrom(line) > 0)561 }562 extraTabs := 0563 p.setComment(f.Doc)564 p.recordLine(&line)565 if len(f.Names) > 0 {566 // named fields567 p.identList(f.Names, false)568 p.print(sep)569 p.expr(f.Type)570 extraTabs = 1571 } else {572 // anonymous field573 p.expr(f.Type)574 extraTabs = 2575 }576 if f.Tag != nil {577 if len(f.Names) > 0 && sep == vtab {578 p.print(sep)579 }580 p.print(sep)581 p.expr(f.Tag)582 extraTabs = 0583 }584 if f.Comment != nil {585 for ; extraTabs > 0; extraTabs-- {586 p.print(sep)587 }588 p.setComment(f.Comment)589 }590 }591 if isIncomplete {592 if len(list) > 0 {593 p.print(formfeed)594 }595 p.flush(p.posFor(rbrace), token.RBRACE) // make sure we don't lose the last line comment596 p.setLineComment("// " + filteredMsg)597 }598599 } else { // interface600601 var line int602 var prev *ast.Ident // previous "type" identifier603 for i, f := range list {604 var name *ast.Ident // first name, or nil605 if len(f.Names) > 0 {606 name = f.Names[0]607 }608 if i > 0 {609 // don't do a line break (min == 0) if we are printing a list of types610 // TODO(gri) this doesn't work quite right if the list of types is611 // spread across multiple lines612 min := 1613 if prev != nil && name == prev {614 min = 0615 }616 p.linebreak(p.lineFor(f.Pos()), min, ignore, p.linesFrom(line) > 0)617 }618 p.setComment(f.Doc)619 p.recordLine(&line)620 if name != nil {621 // method622 p.expr(name)623 p.signature(f.Type.(*ast.FuncType)) // don't print "func"624 prev = nil625 } else {626 // embedded interface627 p.expr(f.Type)628 prev = nil629 }630 p.setComment(f.Comment)631 }632 if isIncomplete {633 if len(list) > 0 {634 p.print(formfeed)635 }636 p.flush(p.posFor(rbrace), token.RBRACE) // make sure we don't lose the last line comment637 p.setLineComment("// contains filtered or unexported methods")638 }639640 }641 p.print(unindent, formfeed)642 p.setPos(rbrace)643 p.print(token.RBRACE)644}645646// ----------------------------------------------------------------------------647// Expressions648649func walkBinary(e *ast.BinaryExpr) (has4, has5 bool, maxProblem int) {650 switch e.Op.Precedence() {651 case 4:652 has4 = true653 case 5:654 has5 = true655 }656657 switch l := e.X.(type) {658 case *ast.BinaryExpr:659 if l.Op.Precedence() < e.Op.Precedence() {660 // parens will be inserted.661 // pretend this is an *ast.ParenExpr and do nothing.662 break663 }664 h4, h5, mp := walkBinary(l)665 has4 = has4 || h4666 has5 = has5 || h5667 maxProblem = max(maxProblem, mp)668 }669670 switch r := e.Y.(type) {671 case *ast.BinaryExpr:672 if r.Op.Precedence() <= e.Op.Precedence() {673 // parens will be inserted.674 // pretend this is an *ast.ParenExpr and do nothing.675 break676 }677 h4, h5, mp := walkBinary(r)678 has4 = has4 || h4679 has5 = has5 || h5680 maxProblem = max(maxProblem, mp)681682 case *ast.StarExpr:683 if e.Op == token.QUO { // `*/`684 maxProblem = 5685 }686687 case *ast.UnaryExpr:688 switch e.Op.String() + r.Op.String() {689 case "/*", "&&", "&^":690 maxProblem = 5691 case "++", "--":692 maxProblem = max(maxProblem, 4)693 }694 }695 return696}697698func cutoff(e *ast.BinaryExpr, depth int) int {699 has4, has5, maxProblem := walkBinary(e)700 if maxProblem > 0 {701 return maxProblem + 1702 }703 if has4 && has5 {704 if depth == 1 {705 return 5706 }707 return 4708 }709 if depth == 1 {710 return 6711 }712 return 4713}714715func diffPrec(expr ast.Expr, prec int) int {716 x, ok := expr.(*ast.BinaryExpr)717 if !ok || prec != x.Op.Precedence() {718 return 1719 }720 return 0721}722723func reduceDepth(depth int) int {724 depth--725 if depth < 1 {726 depth = 1727 }728 return depth729}730731// Format the binary expression: decide the cutoff and then format.732// Let's call depth == 1 Normal mode, and depth > 1 Compact mode.733// (Algorithm suggestion by Russ Cox.)734//735// The precedences are:736//737// 5 * / % << >> & &^738// 4 + - | ^739// 3 == != < <= > >=740// 2 &&741// 1 ||742//743// The only decision is whether there will be spaces around levels 4 and 5.744// There are never spaces at level 6 (unary), and always spaces at levels 3 and below.745//746// To choose the cutoff, look at the whole expression but excluding primary747// expressions (function calls, parenthesized exprs), and apply these rules:748//749// 1. If there is a binary operator with a right side unary operand750// that would clash without a space, the cutoff must be (in order):751//752// /* 6753// && 6754// &^ 6755// ++ 5756// -- 5757//758// (Comparison operators always have spaces around them.)759//760// 2. If there is a mix of level 5 and level 4 operators, then the cutoff761// is 5 (use spaces to distinguish precedence) in Normal mode762// and 4 (never use spaces) in Compact mode.763//764// 3. If there are no level 4 operators or no level 5 operators, then the765// cutoff is 6 (always use spaces) in Normal mode766// and 4 (never use spaces) in Compact mode.767func (p *printer) binaryExpr(x *ast.BinaryExpr, prec1, cutoff, depth int) {768 prec := x.Op.Precedence()769 if prec < prec1 {770 // parenthesis needed771 // Note: The parser inserts an ast.ParenExpr node; thus this case772 // can only occur if the AST is created in a different way.773 p.print(token.LPAREN)774 p.expr0(x, reduceDepth(depth)) // parentheses undo one level of depth775 p.print(token.RPAREN)776 return777 }778779 printBlank := prec < cutoff780781 ws := indent782 p.expr1(x.X, prec, depth+diffPrec(x.X, prec))783 if printBlank {784 p.print(blank)785 }786 xline := p.pos.Line // before the operator (it may be on the next line!)787 yline := p.lineFor(x.Y.Pos())788 p.setPos(x.OpPos)789 p.print(x.Op)790 if xline != yline && xline > 0 && yline > 0 {791 // at least one line break, but respect an extra empty line792 // in the source793 if p.linebreak(yline, 1, ws, true) > 0 {794 ws = ignore795 printBlank = false // no blank after line break796 }797 }798 if printBlank {799 p.print(blank)800 }801 p.expr1(x.Y, prec+1, depth+1)802 if ws == ignore {803 p.print(unindent)804 }805}806807func isBinary(expr ast.Expr) bool {808 _, ok := expr.(*ast.BinaryExpr)809 return ok810}811812func (p *printer) expr1(expr ast.Expr, prec1, depth int) {813 p.setPos(expr.Pos())814815 switch x := expr.(type) {816 case *ast.BadExpr:817 p.print("BadExpr")818819 case *ast.Ident:820 p.print(x)821822 case *ast.BinaryExpr:823 if depth < 1 {824 p.internalError("depth < 1:", depth)825 depth = 1826 }827 p.binaryExpr(x, prec1, cutoff(x, depth), depth)828829 case *ast.KeyValueExpr:830 p.expr(x.Key)831 p.setPos(x.Colon)832 p.print(token.COLON, blank)833 p.expr(x.Value)834835 case *ast.StarExpr:836 const prec = token.UnaryPrec837 if prec < prec1 {838 // parenthesis needed839 p.print(token.LPAREN)840 p.print(token.MUL)841 p.expr(x.X)842 p.print(token.RPAREN)843 } else {844 // no parenthesis needed845 p.print(token.MUL)846 p.expr(x.X)847 }848849 case *ast.UnaryExpr:850 const prec = token.UnaryPrec851 if prec < prec1 {852 // parenthesis needed853 p.print(token.LPAREN)854 p.expr(x)855 p.print(token.RPAREN)856 } else {857 // no parenthesis needed858 p.print(x.Op)859 if x.Op == token.RANGE {860 // TODO(gri) Remove this code if it cannot be reached.861 p.print(blank)862 }863 p.expr1(x.X, prec, depth)864 }865866 case *ast.BasicLit:867 if p.Config.Mode&normalizeNumbers != 0 {868 x = normalizedNumber(x)869 }870 p.print(x)871872 case *ast.FuncLit:873 p.setPos(x.Type.Pos())874 p.print(token.FUNC)875 // See the comment in funcDecl about how the header size is computed.876 startCol := p.out.Column - len("func")877 p.signature(x.Type)878 p.funcBody(p.distanceFrom(x.Type.Pos(), startCol), blank, x.Body)879880 case *ast.ParenExpr:881 if _, hasParens := x.X.(*ast.ParenExpr); hasParens {882 // don't print parentheses around an already parenthesized expression883 // TODO(gri) consider making this more general and incorporate precedence levels884 p.expr0(x.X, depth)885 } else {886 p.print(token.LPAREN)887 p.expr0(x.X, reduceDepth(depth)) // parentheses undo one level of depth888 p.setPos(x.Rparen)889 p.print(token.RPAREN)890 }891892 case *ast.SelectorExpr:893 p.selectorExpr(x, depth, false)894895 case *ast.TypeAssertExpr:896 p.expr1(x.X, token.HighestPrec, depth)897 p.print(token.PERIOD)898 p.setPos(x.Lparen)899 p.print(token.LPAREN)900 if x.Type != nil {901 p.expr(x.Type)902 } else {903 p.print(token.TYPE)904 }905 p.setPos(x.Rparen)906 p.print(token.RPAREN)907908 case *ast.IndexExpr:909 // TODO(gri): should treat[] like parentheses and undo one level of depth910 p.expr1(x.X, token.HighestPrec, 1)911 p.setPos(x.Lbrack)912 p.print(token.LBRACK)913 p.expr0(x.Index, depth+1)914 p.setPos(x.Rbrack)915 p.print(token.RBRACK)916917 case *ast.IndexListExpr:918 // TODO(gri): as for IndexExpr, should treat [] like parentheses and undo919 // one level of depth920 p.expr1(x.X, token.HighestPrec, 1)921 p.setPos(x.Lbrack)922 p.print(token.LBRACK)923 p.exprList(x.Lbrack, x.Indices, depth+1, commaTerm, x.Rbrack, false)924 p.setPos(x.Rbrack)925 p.print(token.RBRACK)926927 case *ast.SliceExpr:928 // TODO(gri): should treat[] like parentheses and undo one level of depth929 p.expr1(x.X, token.HighestPrec, 1)930 p.setPos(x.Lbrack)931 p.print(token.LBRACK)932 indices := []ast.Expr{x.Low, x.High}933 if x.Max != nil {934 indices = append(indices, x.Max)935 }936 // determine if we need extra blanks around ':'937 var needsBlanks bool938 if depth <= 1 {939 var indexCount int940 var hasBinaries bool941 for _, x := range indices {942 if x != nil {943 indexCount++944 if isBinary(x) {945 hasBinaries = true946 }947 }948 }949 if indexCount > 1 && hasBinaries {950 needsBlanks = true951 }952 }953 for i, x := range indices {954 if i > 0 {955 if indices[i-1] != nil && needsBlanks {956 p.print(blank)957 }958 p.print(token.COLON)959 if x != nil && needsBlanks {960 p.print(blank)961 }962 }963 if x != nil {964 p.expr0(x, depth+1)965 }966 }967 p.setPos(x.Rbrack)968 p.print(token.RBRACK)969970 case *ast.CallExpr:971 if len(x.Args) > 1 {972 depth++973 }974975 // Conversions to literal function types or <-chan976 // types require parentheses around the type.977 paren := false978 switch t := x.Fun.(type) {979 case *ast.FuncType:980 paren = true981 case *ast.ChanType:982 paren = t.Dir == ast.RECV983 }984 if paren {985 p.print(token.LPAREN)986 }987 wasIndented := p.possibleSelectorExpr(x.Fun, token.HighestPrec, depth)988 if paren {989 p.print(token.RPAREN)990 }991992 p.setPos(x.Lparen)993 p.print(token.LPAREN)994 if x.Ellipsis.IsValid() {995 p.exprList(x.Lparen, x.Args, depth, 0, x.Ellipsis, false)996 p.setPos(x.Ellipsis)997 p.print(token.ELLIPSIS)998 if x.Rparen.IsValid() && p.lineFor(x.Ellipsis) < p.lineFor(x.Rparen) {999 p.print(token.COMMA, formfeed)1000 }1001 } else {1002 p.exprList(x.Lparen, x.Args, depth, commaTerm, x.Rparen, false)1003 }1004 p.setPos(x.Rparen)1005 p.print(token.RPAREN)1006 if wasIndented {1007 p.print(unindent)1008 }10091010 case *ast.CompositeLit:1011 // composite literal elements that are composite literals themselves may have the type omitted1012 if x.Type != nil {1013 p.expr1(x.Type, token.HighestPrec, depth)1014 }1015 p.level++1016 p.setPos(x.Lbrace)1017 p.print(token.LBRACE)1018 p.exprList(x.Lbrace, x.Elts, 1, commaTerm, x.Rbrace, x.Incomplete)1019 // do not insert extra line break following a /*-style comment1020 // before the closing '}' as it might break the code if there1021 // is no trailing ','1022 mode := noExtraLinebreak1023 // do not insert extra blank following a /*-style comment1024 // before the closing '}' unless the literal is empty1025 if len(x.Elts) > 0 {1026 mode |= noExtraBlank1027 }1028 // need the initial indent to print lone comments with1029 // the proper level of indentation1030 p.print(indent, unindent, mode)1031 p.setPos(x.Rbrace)1032 p.print(token.RBRACE, mode)1033 p.level--10341035 case *ast.Ellipsis:1036 p.print(token.ELLIPSIS)1037 if x.Elt != nil {1038 p.expr(x.Elt)1039 }10401041 case *ast.ArrayType:1042 p.print(token.LBRACK)1043 if x.Len != nil {1044 p.expr(x.Len)1045 }1046 p.print(token.RBRACK)1047 p.expr(x.Elt)10481049 case *ast.StructType:1050 p.print(token.STRUCT)1051 p.fieldList(x.Fields, true, x.Incomplete)10521053 case *ast.FuncType:1054 p.print(token.FUNC)1055 p.signature(x)10561057 case *ast.InterfaceType:1058 p.print(token.INTERFACE)1059 p.fieldList(x.Methods, false, x.Incomplete)10601061 case *ast.MapType:1062 p.print(token.MAP, token.LBRACK)1063 p.expr(x.Key)1064 p.print(token.RBRACK)1065 p.expr(x.Value)10661067 case *ast.ChanType:1068 switch x.Dir {1069 case ast.SEND | ast.RECV:1070 p.print(token.CHAN)1071 case ast.RECV:1072 p.print(token.ARROW, token.CHAN) // x.Arrow and x.Pos() are the same1073 case ast.SEND:1074 p.print(token.CHAN)1075 p.setPos(x.Arrow)1076 p.print(token.ARROW)1077 }1078 p.print(blank)1079 p.expr(x.Value)10801081 default:1082 panic("unreachable")1083 }1084}10851086// normalizedNumber rewrites base prefixes and exponents1087// of numbers to use lower-case letters (0X123 to 0x123 and 1.2E3 to 1.2e3),1088// and removes leading 0's from integer imaginary literals (0765i to 765i).1089// It leaves hexadecimal digits alone.1090//1091// normalizedNumber doesn't modify the ast.BasicLit value lit points to.1092// If lit is not a number or a number in canonical format already,1093// lit is returned as is. Otherwise a new ast.BasicLit is created.1094func normalizedNumber(lit *ast.BasicLit) *ast.BasicLit {1095 if lit.Kind != token.INT && lit.Kind != token.FLOAT && lit.Kind != token.IMAG {1096 return lit // not a number - nothing to do1097 }1098 if len(lit.Value) < 2 {1099 return lit // only one digit (common case) - nothing to do1100 }1101 // len(lit.Value) >= 211021103 // We ignore lit.Kind because for lit.Kind == token.IMAG the literal may be an integer1104 // or floating-point value, decimal or not. Instead, just consider the literal pattern.1105 x := lit.Value1106 switch x[:2] {1107 default:1108 // 0-prefix octal, decimal int, or float (possibly with 'i' suffix)1109 if i := strings.LastIndexByte(x, 'E'); i >= 0 {1110 x = x[:i] + "e" + x[i+1:]1111 break1112 }1113 // remove leading 0's from integer (but not floating-point) imaginary literals1114 if x[len(x)-1] == 'i' && !strings.ContainsAny(x, ".e") {1115 x = strings.TrimLeft(x, "0_")1116 if x == "i" {1117 x = "0i"1118 }1119 }1120 case "0X":1121 x = "0x" + x[2:]1122 // possibly a hexadecimal float1123 if i := strings.LastIndexByte(x, 'P'); i >= 0 {1124 x = x[:i] + "p" + x[i+1:]1125 }1126 case "0x":1127 // possibly a hexadecimal float1128 i := strings.LastIndexByte(x, 'P')1129 if i == -1 {1130 return lit // nothing to do1131 }1132 x = x[:i] + "p" + x[i+1:]1133 case "0O":1134 x = "0o" + x[2:]1135 case "0o":1136 return lit // nothing to do1137 case "0B":1138 x = "0b" + x[2:]1139 case "0b":1140 return lit // nothing to do1141 }11421143 return &ast.BasicLit{ValuePos: lit.ValuePos, Kind: lit.Kind, Value: x}1144}11451146func (p *printer) possibleSelectorExpr(expr ast.Expr, prec1, depth int) bool {1147 if x, ok := expr.(*ast.SelectorExpr); ok {1148 return p.selectorExpr(x, depth, true)1149 }1150 p.expr1(expr, prec1, depth)1151 return false1152}11531154// selectorExpr handles an *ast.SelectorExpr node and reports whether x spans1155// multiple lines.1156func (p *printer) selectorExpr(x *ast.SelectorExpr, depth int, isMethod bool) bool {1157 p.expr1(x.X, token.HighestPrec, depth)1158 p.print(token.PERIOD)1159 if line := p.lineFor(x.Sel.Pos()); p.pos.IsValid() && p.pos.Line < line {1160 p.print(indent, newline)1161 p.setPos(x.Sel.Pos())1162 p.print(x.Sel)1163 if !isMethod {1164 p.print(unindent)1165 }1166 return true1167 }1168 p.setPos(x.Sel.Pos())1169 p.print(x.Sel)1170 return false1171}11721173func (p *printer) expr0(x ast.Expr, depth int) {1174 p.expr1(x, token.LowestPrec, depth)1175}11761177func (p *printer) expr(x ast.Expr) {1178 const depth = 11179 p.expr1(x, token.LowestPrec, depth)1180}11811182// ----------------------------------------------------------------------------1183// Statements11841185// Print the statement list indented, but without a newline after the last statement.1186// Extra line breaks between statements in the source are respected but at most one1187// empty line is printed between statements.1188func (p *printer) stmtList(list []ast.Stmt, nindent int, nextIsRBrace bool) {1189 if nindent > 0 {1190 p.print(indent)1191 }1192 var line int1193 i := 01194 for _, s := range list {1195 // ignore empty statements (was issue 3466)1196 if _, isEmpty := s.(*ast.EmptyStmt); !isEmpty {1197 // nindent == 0 only for lists of switch/select case clauses;1198 // in those cases each clause is a new section1199 if len(p.output) > 0 {1200 // only print line break if we are not at the beginning of the output1201 // (i.e., we are not printing only a partial program)1202 p.linebreak(p.lineFor(s.Pos()), 1, ignore, i == 0 || nindent == 0 || p.linesFrom(line) > 0)1203 }1204 p.recordLine(&line)1205 p.stmt(s, nextIsRBrace && i == len(list)-1)1206 // labeled statements put labels on a separate line, but here1207 // we only care about the start line of the actual statement1208 // without label - correct line for each label1209 for t := s; ; {1210 lt, _ := t.(*ast.LabeledStmt)1211 if lt == nil {1212 break1213 }1214 line++1215 t = lt.Stmt1216 }1217 i++1218 }1219 }1220 if nindent > 0 {1221 p.print(unindent)1222 }1223}12241225// block prints an *ast.BlockStmt; it always spans at least two lines.1226func (p *printer) block(b *ast.BlockStmt, nindent int) {1227 p.setPos(b.Lbrace)1228 p.print(token.LBRACE)1229 p.stmtList(b.List, nindent, true)1230 p.linebreak(p.lineFor(b.Rbrace), 1, ignore, true)1231 p.setPos(b.Rbrace)1232 p.print(token.RBRACE)1233}12341235func isTypeName(x ast.Expr) bool {1236 switch t := x.(type) {1237 case *ast.Ident:1238 return true1239 case *ast.SelectorExpr:1240 return isTypeName(t.X)1241 }1242 return false1243}12441245func stripParens(x ast.Expr) ast.Expr {1246 if px, strip := x.(*ast.ParenExpr); strip {1247 // parentheses must not be stripped if there are any1248 // unparenthesized composite literals starting with1249 // a type name1250 ast.Inspect(px.X, func(node ast.Node) bool {1251 switch x := node.(type) {1252 case *ast.ParenExpr:1253 // parentheses protect enclosed composite literals1254 return false1255 case *ast.CompositeLit:1256 if isTypeName(x.Type) {1257 strip = false // do not strip parentheses1258 }1259 return false1260 }1261 // in all other cases, keep inspecting1262 return true1263 })1264 if strip {1265 return stripParens(px.X)1266 }1267 }1268 return x1269}12701271func stripParensAlways(x ast.Expr) ast.Expr {1272 if x, ok := x.(*ast.ParenExpr); ok {1273 return stripParensAlways(x.X)1274 }1275 return x1276}12771278func (p *printer) controlClause(isForStmt bool, init ast.Stmt, expr ast.Expr, post ast.Stmt) {1279 p.print(blank)1280 needsBlank := false1281 if init == nil && post == nil {1282 // no semicolons required1283 if expr != nil {1284 p.expr(stripParens(expr))1285 needsBlank = true1286 }1287 } else {1288 // all semicolons required1289 // (they are not separators, print them explicitly)1290 if init != nil {1291 p.stmt(init, false)1292 }1293 p.print(token.SEMICOLON, blank)1294 if expr != nil {1295 p.expr(stripParens(expr))1296 needsBlank = true1297 }1298 if isForStmt {1299 p.print(token.SEMICOLON, blank)1300 needsBlank = false1301 if post != nil {1302 p.stmt(post, false)1303 needsBlank = true1304 }1305 }1306 }1307 if needsBlank {1308 p.print(blank)1309 }1310}13111312// isCompositeLitLike reports whether x is a composite literal or an expression1313// whose core is a composite literal (e.g. &T{...}), ignoring parentheses.1314func isCompositeLitLike(x ast.Expr) bool {1315 switch x := stripParensAlways(x).(type) {1316 case *ast.CompositeLit:1317 return true1318 case *ast.UnaryExpr:1319 _, ok := stripParensAlways(x.X).(*ast.CompositeLit)1320 return x.Op == token.AND && ok1321 }1322 return false1323}13241325// indentList reports whether an expression list would look better if it1326// were indented wholesale (starting with the very first element, rather1327// than starting at the first line break).1328// Currently this function is only used to improve formatting of return1329// statements.1330func (p *printer) indentList(list []ast.Expr) bool {1331 // Heuristic: indentList reports whether there are more than one multi-1332 // line element (such as a complex expression, but excluding composite1333 // literals) in the list, or if there is any element that is not starting1334 // on the same line as the previous one ends.1335 if len(list) >= 2 {1336 var b = p.lineFor(list[0].Pos())1337 var e = p.lineFor(list[len(list)-1].End())1338 if 0 < b && b < e {1339 // list spans multiple lines1340 n := 0 // multi-line element count1341 line := b1342 for _, x := range list {1343 xb := p.lineFor(x.Pos())1344 xe := p.lineFor(x.End())1345 if line < xb {1346 // x is not starting on the same1347 // line as the previous one ended1348 return true1349 }1350 if xb < xe && !isCompositeLitLike(x) {1351 // x is a multi-line element but not a composite literal1352 // (composite literals have their own field indentation1353 // already, see go.dev/issue/7195)1354 n++1355 }1356 line = xe1357 }1358 return n > 11359 }1360 }1361 return false1362}13631364func (p *printer) stmt(stmt ast.Stmt, nextIsRBrace bool) {1365 p.setPos(stmt.Pos())13661367 switch s := stmt.(type) {1368 case *ast.BadStmt:1369 p.print("BadStmt")13701371 case *ast.DeclStmt:1372 p.decl(s.Decl)13731374 case *ast.EmptyStmt:1375 // nothing to do13761377 case *ast.LabeledStmt:1378 // a "correcting" unindent immediately following a line break1379 // is applied before the line break if there is no comment1380 // between (see writeWhitespace)1381 p.print(unindent)1382 p.expr(s.Label)1383 p.setPos(s.Colon)1384 p.print(token.COLON, indent)1385 if e, isEmpty := s.Stmt.(*ast.EmptyStmt); isEmpty {1386 if !nextIsRBrace {1387 p.print(newline)1388 p.setPos(e.Pos())1389 p.print(token.SEMICOLON)1390 break1391 }1392 } else {1393 p.linebreak(p.lineFor(s.Stmt.Pos()), 1, ignore, true)1394 }1395 p.stmt(s.Stmt, nextIsRBrace)13961397 case *ast.ExprStmt:1398 const depth = 11399 p.expr0(s.X, depth)14001401 case *ast.SendStmt:1402 const depth = 11403 p.expr0(s.Chan, depth)1404 p.print(blank)1405 p.setPos(s.Arrow)1406 p.print(token.ARROW, blank)1407 p.expr0(s.Value, depth)14081409 case *ast.IncDecStmt:1410 const depth = 11411 p.expr0(s.X, depth+1)1412 p.setPos(s.TokPos)1413 p.print(s.Tok)14141415 case *ast.AssignStmt:1416 var depth = 11417 if len(s.Lhs) > 1 && len(s.Rhs) > 1 {1418 depth++1419 }1420 p.exprList(s.Pos(), s.Lhs, depth, 0, s.TokPos, false)1421 p.print(blank)1422 p.setPos(s.TokPos)1423 p.print(s.Tok, blank)1424 p.exprList(s.TokPos, s.Rhs, depth, 0, token.NoPos, false)14251426 case *ast.GoStmt:1427 p.print(token.GO, blank)1428 p.expr(s.Call)14291430 case *ast.DeferStmt:1431 p.print(token.DEFER, blank)1432 p.expr(s.Call)14331434 case *ast.ReturnStmt:1435 p.print(token.RETURN)1436 if s.Results != nil {1437 p.print(blank)1438 // Use indentList heuristic to make corner cases look1439 // better (issue 1207). A more systematic approach would1440 // always indent, but this would cause significant1441 // reformatting of the code base and not necessarily1442 // lead to more nicely formatted code in general.1443 if p.indentList(s.Results) {1444 p.print(indent)1445 // Use NoPos so that a newline never goes before1446 // the results (see issue #32854).1447 p.exprList(token.NoPos, s.Results, 1, noIndent, token.NoPos, false)1448 p.print(unindent)1449 } else {1450 p.exprList(token.NoPos, s.Results, 1, 0, token.NoPos, false)1451 }1452 }14531454 case *ast.BranchStmt:1455 p.print(s.Tok)1456 if s.Label != nil {1457 p.print(blank)1458 p.expr(s.Label)1459 }14601461 case *ast.BlockStmt:1462 p.block(s, 1)14631464 case *ast.IfStmt:1465 p.print(token.IF)1466 p.controlClause(false, s.Init, s.Cond, nil)1467 p.block(s.Body, 1)1468 if s.Else != nil {1469 p.print(blank, token.ELSE, blank)1470 switch s.Else.(type) {1471 case *ast.BlockStmt, *ast.IfStmt:1472 p.stmt(s.Else, nextIsRBrace)1473 default:1474 // This can only happen with an incorrectly1475 // constructed AST. Permit it but print so1476 // that it can be parsed without errors.1477 p.print(token.LBRACE, indent, formfeed)1478 p.stmt(s.Else, true)1479 p.print(unindent, formfeed, token.RBRACE)1480 }1481 }14821483 case *ast.CaseClause:1484 if s.List != nil {1485 p.print(token.CASE, blank)1486 p.exprList(s.Pos(), s.List, 1, 0, s.Colon, false)1487 } else {1488 p.print(token.DEFAULT)1489 }1490 p.setPos(s.Colon)1491 p.print(token.COLON)1492 p.stmtList(s.Body, 1, nextIsRBrace)14931494 case *ast.SwitchStmt:1495 p.print(token.SWITCH)1496 p.controlClause(false, s.Init, s.Tag, nil)1497 p.block(s.Body, 0)14981499 case *ast.TypeSwitchStmt:1500 p.print(token.SWITCH)1501 if s.Init != nil {1502 p.print(blank)1503 p.stmt(s.Init, false)1504 p.print(token.SEMICOLON)1505 }1506 p.print(blank)1507 p.stmt(s.Assign, false)1508 p.print(blank)1509 p.block(s.Body, 0)15101511 case *ast.CommClause:1512 if s.Comm != nil {1513 p.print(token.CASE, blank)1514 p.stmt(s.Comm, false)1515 } else {1516 p.print(token.DEFAULT)1517 }1518 p.setPos(s.Colon)1519 p.print(token.COLON)1520 p.stmtList(s.Body, 1, nextIsRBrace)15211522 case *ast.SelectStmt:1523 p.print(token.SELECT, blank)1524 body := s.Body1525 if len(body.List) == 0 && !p.commentBefore(p.posFor(body.Rbrace)) {1526 // print empty select statement w/o comments on one line1527 p.setPos(body.Lbrace)1528 p.print(token.LBRACE)1529 p.setPos(body.Rbrace)1530 p.print(token.RBRACE)1531 } else {1532 p.block(body, 0)1533 }15341535 case *ast.ForStmt:1536 p.print(token.FOR)1537 p.controlClause(true, s.Init, s.Cond, s.Post)1538 p.block(s.Body, 1)15391540 case *ast.RangeStmt:1541 p.print(token.FOR, blank)1542 if s.Key != nil {1543 p.expr(s.Key)1544 if s.Value != nil {1545 // use position of value following the comma as1546 // comma position for correct comment placement1547 p.setPos(s.Value.Pos())1548 p.print(token.COMMA, blank)1549 p.expr(s.Value)1550 }1551 p.print(blank)1552 p.setPos(s.TokPos)1553 p.print(s.Tok, blank)1554 }1555 p.print(token.RANGE, blank)1556 p.expr(stripParens(s.X))1557 p.print(blank)1558 p.block(s.Body, 1)15591560 default:1561 panic("unreachable")1562 }1563}15641565// ----------------------------------------------------------------------------1566// Declarations15671568// The keepTypeColumn function determines if the type column of a series of1569// consecutive const or var declarations must be kept, or if initialization1570// values (V) can be placed in the type column (T) instead. The i'th entry1571// in the result slice is true if the type column in spec[i] must be kept.1572//1573// For example, the declaration:1574//1575// const (1576// foobar int = 42 // comment1577// x = 7 // comment1578// foo1579// bar = 9911580// )1581//1582// leads to the type/values matrix below. A run of value columns (V) can1583// be moved into the type column if there is no type for any of the values1584// in that column (we only move entire columns so that they align properly).1585//1586// matrix formatted result1587// matrix1588// T V -> T V -> true there is a T and so the type1589// - V - V true column must be kept1590// - - - - false1591// - V V - false V is moved into T column1592func keepTypeColumn(specs []ast.Spec) []bool {1593 m := make([]bool, len(specs))15941595 populate := func(i, j int, keepType bool) {1596 if keepType {1597 for ; i < j; i++ {1598 m[i] = true1599 }1600 }1601 }16021603 i0 := -1 // if i0 >= 0 we are in a run and i0 is the start of the run1604 var keepType bool1605 for i, s := range specs {1606 t := s.(*ast.ValueSpec)1607 if t.Values != nil {1608 if i0 < 0 {1609 // start of a run of ValueSpecs with non-nil Values1610 i0 = i1611 keepType = false1612 }1613 } else {1614 if i0 >= 0 {1615 // end of a run1616 populate(i0, i, keepType)1617 i0 = -11618 }1619 }1620 if t.Type != nil {1621 keepType = true1622 }1623 }1624 if i0 >= 0 {1625 // end of a run1626 populate(i0, len(specs), keepType)1627 }16281629 return m1630}16311632func (p *printer) valueSpec(s *ast.ValueSpec, keepType bool) {1633 p.setComment(s.Doc)1634 p.identList(s.Names, false) // always present1635 extraTabs := 31636 if s.Type != nil || keepType {1637 p.print(vtab)1638 extraTabs--1639 }1640 if s.Type != nil {1641 p.expr(s.Type)1642 }1643 if s.Values != nil {1644 p.print(vtab, token.ASSIGN, blank)1645 p.exprList(token.NoPos, s.Values, 1, 0, token.NoPos, false)1646 extraTabs--1647 }1648 if s.Comment != nil {1649 for ; extraTabs > 0; extraTabs-- {1650 p.print(vtab)1651 }1652 p.setComment(s.Comment)1653 }1654}16551656func sanitizeImportPath(lit *ast.BasicLit) *ast.BasicLit {1657 // Note: An unmodified AST generated by go/parser will already1658 // contain a backward- or double-quoted path string that does1659 // not contain any invalid characters, and most of the work1660 // here is not needed. However, a modified or generated AST1661 // may possibly contain non-canonical paths. Do the work in1662 // all cases since it's not too hard and not speed-critical.16631664 // if we don't have a proper string, be conservative and return whatever we have1665 if lit.Kind != token.STRING {1666 return lit1667 }1668 s, err := strconv.Unquote(lit.Value)1669 if err != nil {1670 return lit1671 }16721673 // if the string is an invalid path, return whatever we have1674 //1675 // spec: "Implementation restriction: A compiler may restrict1676 // ImportPaths to non-empty strings using only characters belonging1677 // to Unicode's L, M, N, P, and S general categories (the Graphic1678 // characters without spaces) and may also exclude the characters1679 // !"#$%&'()*,:;<=>?[\]^`{|} and the Unicode replacement character1680 // U+FFFD."1681 if s == "" {1682 return lit1683 }1684 const illegalChars = `!"#$%&'()*,:;<=>?[\]^{|}` + "`\uFFFD"1685 for _, r := range s {1686 if !unicode.IsGraphic(r) || unicode.IsSpace(r) || strings.ContainsRune(illegalChars, r) {1687 return lit1688 }1689 }16901691 // otherwise, return the double-quoted path1692 s = strconv.Quote(s)1693 if s == lit.Value {1694 return lit // nothing wrong with lit1695 }1696 return &ast.BasicLit{ValuePos: lit.ValuePos, Kind: token.STRING, Value: s}1697}16981699// The parameter n is the number of specs in the group. If doIndent is set,1700// multi-line identifier lists in the spec are indented when the first1701// linebreak is encountered.1702func (p *printer) spec(spec ast.Spec, n int, doIndent bool) {1703 switch s := spec.(type) {1704 case *ast.ImportSpec:1705 p.setComment(s.Doc)1706 if s.Name != nil {1707 p.expr(s.Name)1708 p.print(blank)1709 }1710 p.expr(sanitizeImportPath(s.Path))1711 p.setComment(s.Comment)1712 p.setPos(s.EndPos)17131714 case *ast.ValueSpec:1715 if n != 1 {1716 p.internalError("expected n = 1; got", n)1717 }1718 p.setComment(s.Doc)1719 p.identList(s.Names, doIndent) // always present1720 if s.Type != nil {1721 p.print(blank)1722 p.expr(s.Type)1723 }1724 if s.Values != nil {1725 p.print(blank, token.ASSIGN, blank)1726 p.exprList(token.NoPos, s.Values, 1, 0, token.NoPos, false)1727 }1728 p.setComment(s.Comment)17291730 case *ast.TypeSpec:1731 p.setComment(s.Doc)1732 p.expr(s.Name)1733 if s.TypeParams != nil {1734 p.parameters(s.TypeParams, typeTParam)1735 }1736 if n == 1 {1737 p.print(blank)1738 } else {1739 p.print(vtab)1740 }1741 if s.Assign.IsValid() {1742 p.print(token.ASSIGN, blank)1743 }1744 p.expr(s.Type)1745 p.setComment(s.Comment)17461747 default:1748 panic("unreachable")1749 }1750}17511752func (p *printer) genDecl(d *ast.GenDecl) {1753 p.setComment(d.Doc)1754 p.setPos(d.Pos())1755 p.print(d.Tok, blank)17561757 if d.Lparen.IsValid() || len(d.Specs) != 1 {1758 // group of parenthesized declarations1759 p.setPos(d.Lparen)1760 p.print(token.LPAREN)1761 if n := len(d.Specs); n > 0 {1762 p.print(indent, formfeed)1763 if n > 1 && (d.Tok == token.CONST || d.Tok == token.VAR) {1764 // two or more grouped const/var declarations:1765 // determine if the type column must be kept1766 keepType := keepTypeColumn(d.Specs)1767 var line int1768 for i, s := range d.Specs {1769 if i > 0 {1770 p.linebreak(p.lineFor(s.Pos()), 1, ignore, p.linesFrom(line) > 0)1771 }1772 p.recordLine(&line)1773 p.valueSpec(s.(*ast.ValueSpec), keepType[i])1774 }1775 } else {1776 var line int1777 for i, s := range d.Specs {1778 if i > 0 {1779 p.linebreak(p.lineFor(s.Pos()), 1, ignore, p.linesFrom(line) > 0)1780 }1781 p.recordLine(&line)1782 p.spec(s, n, false)1783 }1784 }1785 p.print(unindent, formfeed)1786 }1787 p.setPos(d.Rparen)1788 p.print(token.RPAREN)17891790 } else if len(d.Specs) > 0 {1791 // single declaration1792 p.spec(d.Specs[0], 1, true)1793 }1794}17951796// sizeCounter is an io.Writer which counts the number of bytes written,1797// as well as whether a newline character was seen.1798type sizeCounter struct {1799 hasNewline bool1800 size int1801}18021803func (c *sizeCounter) Write(p []byte) (int, error) {1804 if !c.hasNewline {1805 for _, b := range p {1806 if b == '\n' || b == '\f' {1807 c.hasNewline = true1808 break1809 }1810 }1811 }1812 c.size += len(p)1813 return len(p), nil1814}18151816// nodeSize determines the size of n in chars after formatting.1817// The result is <= maxSize if the node fits on one line with at1818// most maxSize chars and the formatted output doesn't contain1819// any control chars. Otherwise, the result is > maxSize.1820func (p *printer) nodeSize(n ast.Node, maxSize int) (size int) {1821 // nodeSize invokes the printer, which may invoke nodeSize1822 // recursively. For deep composite literal nests, this can1823 // lead to an exponential algorithm. Remember previous1824 // results to prune the recursion (was issue 1628).1825 if size, found := p.nodeSizes[n]; found {1826 return size1827 }18281829 size = maxSize + 1 // assume n doesn't fit1830 p.nodeSizes[n] = size18311832 // nodeSize computation must be independent of particular1833 // style so that we always get the same decision; print1834 // in RawFormat1835 cfg := Config{Mode: RawFormat}1836 var counter sizeCounter1837 if err := cfg.fprint(&counter, p.fset, n, p.nodeSizes); err != nil {1838 return1839 }1840 if counter.size <= maxSize && !counter.hasNewline {1841 // n fits in a single line1842 size = counter.size1843 p.nodeSizes[n] = size1844 }1845 return1846}18471848// numLines returns the number of lines spanned by node n in the original source.1849func (p *printer) numLines(n ast.Node) int {1850 if from := n.Pos(); from.IsValid() {1851 if to := n.End(); to.IsValid() {1852 return p.lineFor(to) - p.lineFor(from) + 11853 }1854 }1855 return infinity1856}18571858// bodySize is like nodeSize but it is specialized for *ast.BlockStmt's.1859func (p *printer) bodySize(b *ast.BlockStmt, maxSize int) int {1860 pos1 := b.Pos()1861 pos2 := b.Rbrace1862 if pos1.IsValid() && pos2.IsValid() && p.lineFor(pos1) != p.lineFor(pos2) {1863 // opening and closing brace are on different lines - don't make it a one-liner1864 return maxSize + 11865 }1866 if len(b.List) > 5 {1867 // too many statements - don't make it a one-liner1868 return maxSize + 11869 }1870 // otherwise, estimate body size1871 bodySize := p.commentSizeBefore(p.posFor(pos2))1872 for i, s := range b.List {1873 if bodySize > maxSize {1874 break // no need to continue1875 }1876 if i > 0 {1877 bodySize += 2 // space for a semicolon and blank1878 }1879 bodySize += p.nodeSize(s, maxSize)1880 }1881 return bodySize1882}18831884// funcBody prints a function body following a function header of given headerSize.1885// If the header's and block's size are "small enough" and the block is "simple enough",1886// the block is printed on the current line, without line breaks, spaced from the header1887// by sep. Otherwise the block's opening "{" is printed on the current line, followed by1888// lines for the block's statements and its closing "}".1889func (p *printer) funcBody(headerSize int, sep whiteSpace, b *ast.BlockStmt) {1890 if b == nil {1891 return1892 }18931894 // save/restore composite literal nesting level1895 defer func(level int) {1896 p.level = level1897 }(p.level)1898 p.level = 018991900 const maxSize = 1001901 if headerSize+p.bodySize(b, maxSize) <= maxSize {1902 p.print(sep)1903 p.setPos(b.Lbrace)1904 p.print(token.LBRACE)1905 if len(b.List) > 0 {1906 p.print(blank)1907 for i, s := range b.List {1908 if i > 0 {1909 p.print(token.SEMICOLON, blank)1910 }1911 p.stmt(s, i == len(b.List)-1)1912 }1913 p.print(blank)1914 }1915 p.print(noExtraLinebreak)1916 p.setPos(b.Rbrace)1917 p.print(token.RBRACE, noExtraLinebreak)1918 return1919 }19201921 if sep != ignore {1922 p.print(blank) // always use blank1923 }1924 p.block(b, 1)1925}19261927// distanceFrom returns the column difference between p.out (the current output1928// position) and startOutCol. If the start position is on a different line from1929// the current position (or either is unknown), the result is infinity.1930func (p *printer) distanceFrom(startPos token.Pos, startOutCol int) int {1931 if startPos.IsValid() && p.pos.IsValid() && p.posFor(startPos).Line == p.pos.Line {1932 return p.out.Column - startOutCol1933 }1934 return infinity1935}19361937func (p *printer) funcDecl(d *ast.FuncDecl) {1938 p.setComment(d.Doc)1939 p.setPos(d.Pos())1940 p.print(token.FUNC, blank)1941 // We have to save startCol only after emitting FUNC; otherwise it can be on a1942 // different line (all whitespace preceding the FUNC is emitted only when the1943 // FUNC is emitted).1944 startCol := p.out.Column - len("func ")1945 if d.Recv != nil {1946 p.parameters(d.Recv, funcParam) // method: print receiver1947 p.print(blank)1948 }1949 p.expr(d.Name)1950 p.signature(d.Type)1951 p.funcBody(p.distanceFrom(d.Pos(), startCol), vtab, d.Body)1952}19531954func (p *printer) decl(decl ast.Decl) {1955 switch d := decl.(type) {1956 case *ast.BadDecl:1957 p.setPos(d.Pos())1958 p.print("BadDecl")1959 case *ast.GenDecl:1960 p.genDecl(d)1961 case *ast.FuncDecl:1962 p.funcDecl(d)1963 default:1964 panic("unreachable")1965 }1966}19671968// ----------------------------------------------------------------------------1969// Files19701971func declToken(decl ast.Decl) (tok token.Token) {1972 tok = token.ILLEGAL1973 switch d := decl.(type) {1974 case *ast.GenDecl:1975 tok = d.Tok1976 case *ast.FuncDecl:1977 tok = token.FUNC1978 }1979 return1980}19811982func (p *printer) declList(list []ast.Decl) {1983 tok := token.ILLEGAL1984 for _, d := range list {1985 prev := tok1986 tok = declToken(d)1987 // If the declaration token changed (e.g., from CONST to TYPE)1988 // or the next declaration has documentation associated with it,1989 // print an empty line between top-level declarations.1990 // (because p.linebreak is called with the position of d, which1991 // is past any documentation, the minimum requirement is satisfied1992 // even w/o the extra getDoc(d) nil-check - leave it in case the1993 // linebreak logic improves - there's already a TODO).1994 if len(p.output) > 0 {1995 // only print line break if we are not at the beginning of the output1996 // (i.e., we are not printing only a partial program)1997 min := 11998 if prev != tok || getDoc(d) != nil {1999 min = 22000 }
Findings
✓ No findings reported for this file.