src/go/printer/nodes.go GO 2,017 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 2,017.
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.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.