// writeString writes the string s to p.output and updates p.pos, p.out, // and p.last. If isLit is set, s is escaped w/ tabwriter.Escape characters // to protect s from being interpreted by the tabwriter. // // Note: writeString is only used to write Go tokens, literals, and // comments, all of which must be written literally. Thus, it is correct // to always set isLit = true. However, setting it explicitly only when // needed (i.e., when we don't know that s contains no tabs or line breaks) // avoids processing extra escape characters and reduces run time of the // printer benchmark by up to 10%. // func (p *printer) writeString(pos token.Position, s string, isLit bool) { if p.out.Column == 1 { p.atLineBegin(pos) } if pos.IsValid() { // update p.pos (if pos is invalid, continue with existing p.pos) // Note: Must do this after handling line beginnings because // atLineBegin updates p.pos if there's indentation, but p.pos // is the position of s. p.pos = pos } if isLit { // Protect s such that is passes through the tabwriter // unchanged. Note that valid Go programs cannot contain // tabwriter.Escape bytes since they do not appear in legal // UTF-8 sequences. p.output = append(p.output, tabwriter.Escape) } if debug { p.output = append(p.output, fmt.Sprintf("/*%s*/", pos)...) // do not update p.pos! } p.output = append(p.output, s...) // update positions nlines := 0 var li int // index of last newline; valid if nlines > 0 for i := 0; i < len(s); i++ { // Go tokens cannot contain '\f' - no need to look for it if s[i] == '\n' { nlines++ li = i } } p.pos.Offset += len(s) if nlines > 0 { p.pos.Line += nlines p.out.Line += nlines c := len(s) - li p.pos.Column = c p.out.Column = c } else { p.pos.Column += len(s) p.out.Column += len(s) } if isLit { p.output = append(p.output, tabwriter.Escape) } p.last = p.pos }
// atLineBegin emits a //line comment if necessary and prints indentation. func (p *printer) atLineBegin(pos token.Position) { // write a //line comment if necessary if p.Config.Mode&SourcePos != 0 && pos.IsValid() && (p.out.Line != pos.Line || p.out.Filename != pos.Filename) { p.output = append(p.output, tabwriter.Escape) // protect '\n' in //line from tabwriter interpretation p.output = append(p.output, fmt.Sprintf("//line %s:%d\n", pos.Filename, pos.Line)...) p.output = append(p.output, tabwriter.Escape) // p.out must match the //line comment p.out.Filename = pos.Filename p.out.Line = pos.Line } // write indentation // use "hard" htabs - indentation columns // must not be discarded by the tabwriter n := p.Config.Indent + p.indent // include base indentation for i := 0; i < n; i++ { p.output = append(p.output, '\t') } // update positions p.pos.Offset += n p.pos.Column += n p.out.Column += n }
// writeCommentPrefix writes the whitespace before a comment. // If there is any pending whitespace, it consumes as much of // it as is likely to help position the comment nicely. // pos is the comment position, next the position of the item // after all pending comments, prev is the previous comment in // a group of comments (or nil), and tok is the next token. // func (p *printer) writeCommentPrefix(pos, next token.Position, prev, comment *ast.Comment, tok token.Token) { if len(p.output) == 0 { // the comment is the first item to be printed - don't write any whitespace return } if pos.IsValid() && pos.Filename != p.last.Filename { // comment in a different file - separate with newlines p.writeByte('\f', maxNewlines) return } if pos.Line == p.last.Line && (prev == nil || prev.Text[1] != '/') { // comment on the same line as last item: // separate with at least one separator hasSep := false if prev == nil { // first comment of a comment group j := 0 for i, ch := range p.wsbuf { switch ch { case blank: // ignore any blanks before a comment p.wsbuf[i] = ignore continue case vtab: // respect existing tabs - important // for proper formatting of commented structs hasSep = true continue case indent: // apply pending indentation continue } j = i break } p.writeWhitespace(j) } // make sure there is at least one separator if !hasSep { sep := byte('\t') if pos.Line == next.Line { // next item is on the same line as the comment // (which must be a /*-style comment): separate // with a blank instead of a tab sep = ' ' } p.writeByte(sep, 1) } } else { // comment on a different line: // separate with at least one line break droppedLinebreak := false j := 0 for i, ch := range p.wsbuf { switch ch { case blank, vtab: // ignore any horizontal whitespace before line breaks p.wsbuf[i] = ignore continue case indent: // apply pending indentation continue case unindent: // if this is not the last unindent, apply it // as it is (likely) belonging to the last // construct (e.g., a multi-line expression list) // and is not part of closing a block if i+1 < len(p.wsbuf) && p.wsbuf[i+1] == unindent { continue } // if the next token is not a closing }, apply the unindent // if it appears that the comment is aligned with the // token; otherwise assume the unindent is part of a // closing block and stop (this scenario appears with // comments before a case label where the comments // apply to the next case instead of the current one) if tok != token.RBRACE && pos.Column == next.Column { continue } case newline, formfeed: p.wsbuf[i] = ignore droppedLinebreak = prev == nil // record only if first comment of a group } j = i break } p.writeWhitespace(j) // determine number of linebreaks before the comment n := 0 if pos.IsValid() && p.last.IsValid() { n = pos.Line - p.last.Line if n < 0 { // should never happen n = 0 } } // at the package scope level only (p.indent == 0), // add an extra newline if we dropped one before: // this preserves a blank line before documentation // comments at the package scope level (issue 2570) if p.indent == 0 && droppedLinebreak { n++ } // make sure there is at least one line break // if the previous comment was a line comment if n == 0 && prev != nil && prev.Text[1] == '/' { n = 1 } if n > 0 { // use formfeeds to break columns before a comment; // this is analogous to using formfeeds to separate // individual lines of /*-style comments p.writeByte('\f', nlimit(n)) } } }