| // Copyright 2020 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package printer |
| |
| import ( |
| "go/build/constraint" |
| "sort" |
| "text/tabwriter" |
| ) |
| |
| func (p *printer) fixGoBuildLines() { |
| if len(p.goBuild)+len(p.plusBuild) == 0 { |
| return |
| } |
| |
| // Find latest possible placement of //go:build and // +build comments. |
| // That's just after the last blank line before we find a non-comment. |
| // (We'll add another blank line after our comment block.) |
| // When we start dropping // +build comments, we can skip over /* */ comments too. |
| // Note that we are processing tabwriter input, so every comment |
| // begins and ends with a tabwriter.Escape byte. |
| // And some newlines have turned into \f bytes. |
| insert := 0 |
| for pos := 0; ; { |
| // Skip leading space at beginning of line. |
| blank := true |
| for pos < len(p.output) && (p.output[pos] == ' ' || p.output[pos] == '\t') { |
| pos++ |
| } |
| // Skip over // comment if any. |
| if pos+3 < len(p.output) && p.output[pos] == tabwriter.Escape && p.output[pos+1] == '/' && p.output[pos+2] == '/' { |
| blank = false |
| for pos < len(p.output) && !isNL(p.output[pos]) { |
| pos++ |
| } |
| } |
| // Skip over \n at end of line. |
| if pos >= len(p.output) || !isNL(p.output[pos]) { |
| break |
| } |
| pos++ |
| |
| if blank { |
| insert = pos |
| } |
| } |
| |
| // If there is a //go:build comment before the place we identified, |
| // use that point instead. (Earlier in the file is always fine.) |
| if len(p.goBuild) > 0 && p.goBuild[0] < insert { |
| insert = p.goBuild[0] |
| } else if len(p.plusBuild) > 0 && p.plusBuild[0] < insert { |
| insert = p.plusBuild[0] |
| } |
| |
| var x constraint.Expr |
| switch len(p.goBuild) { |
| case 0: |
| // Synthesize //go:build expression from // +build lines. |
| for _, pos := range p.plusBuild { |
| y, err := constraint.Parse(p.commentTextAt(pos)) |
| if err != nil { |
| x = nil |
| break |
| } |
| if x == nil { |
| x = y |
| } else { |
| x = &constraint.AndExpr{X: x, Y: y} |
| } |
| } |
| case 1: |
| // Parse //go:build expression. |
| x, _ = constraint.Parse(p.commentTextAt(p.goBuild[0])) |
| } |
| |
| var block []byte |
| if x == nil { |
| // Don't have a valid //go:build expression to treat as truth. |
| // Bring all the lines together but leave them alone. |
| // Note that these are already tabwriter-escaped. |
| for _, pos := range p.goBuild { |
| block = append(block, p.lineAt(pos)...) |
| } |
| for _, pos := range p.plusBuild { |
| block = append(block, p.lineAt(pos)...) |
| } |
| } else { |
| block = append(block, tabwriter.Escape) |
| block = append(block, "//go:build "...) |
| block = append(block, x.String()...) |
| block = append(block, tabwriter.Escape, '\n') |
| if len(p.plusBuild) > 0 { |
| lines, err := constraint.PlusBuildLines(x) |
| if err != nil { |
| lines = []string{"// +build error: " + err.Error()} |
| } |
| for _, line := range lines { |
| block = append(block, tabwriter.Escape) |
| block = append(block, line...) |
| block = append(block, tabwriter.Escape, '\n') |
| } |
| } |
| } |
| block = append(block, '\n') |
| |
| // Build sorted list of lines to delete from remainder of output. |
| toDelete := append(p.goBuild, p.plusBuild...) |
| sort.Ints(toDelete) |
| |
| // Collect output after insertion point, with lines deleted, into after. |
| var after []byte |
| start := insert |
| for _, end := range toDelete { |
| if end < start { |
| continue |
| } |
| after = appendLines(after, p.output[start:end]) |
| start = end + len(p.lineAt(end)) |
| } |
| after = appendLines(after, p.output[start:]) |
| if n := len(after); n >= 2 && isNL(after[n-1]) && isNL(after[n-2]) { |
| after = after[:n-1] |
| } |
| |
| p.output = p.output[:insert] |
| p.output = append(p.output, block...) |
| p.output = append(p.output, after...) |
| } |
| |
| // appendLines is like append(x, y...) |
| // but it avoids creating doubled blank lines, |
| // which would not be gofmt-standard output. |
| // It assumes that only whole blocks of lines are being appended, |
| // not line fragments. |
| func appendLines(x, y []byte) []byte { |
| if len(y) > 0 && isNL(y[0]) && // y starts in blank line |
| (len(x) == 0 || len(x) >= 2 && isNL(x[len(x)-1]) && isNL(x[len(x)-2])) { // x is empty or ends in blank line |
| y = y[1:] // delete y's leading blank line |
| } |
| return append(x, y...) |
| } |
| |
| func (p *printer) lineAt(start int) []byte { |
| pos := start |
| for pos < len(p.output) && !isNL(p.output[pos]) { |
| pos++ |
| } |
| if pos < len(p.output) { |
| pos++ |
| } |
| return p.output[start:pos] |
| } |
| |
| func (p *printer) commentTextAt(start int) string { |
| if start < len(p.output) && p.output[start] == tabwriter.Escape { |
| start++ |
| } |
| pos := start |
| for pos < len(p.output) && p.output[pos] != tabwriter.Escape && !isNL(p.output[pos]) { |
| pos++ |
| } |
| return string(p.output[start:pos]) |
| } |
| |
| func isNL(b byte) bool { |
| return b == '\n' || b == '\f' |
| } |