gobuild.mx raw

   1  // Copyright 2020 The Go Authors. All rights reserved.
   2  // Use of this source code is governed by a BSD-style
   3  // license that can be found in the LICENSE file.
   4  
   5  package printer
   6  
   7  import (
   8  	"go/build/constraint"
   9  	"slices"
  10  	"text/tabwriter"
  11  )
  12  
  13  func (p *printer) fixGoBuildLines() {
  14  	if len(p.goBuild)+len(p.plusBuild) == 0 {
  15  		return
  16  	}
  17  
  18  	// Find latest possible placement of //go:build and // +build comments.
  19  	// That's just after the last blank line before we find a non-comment.
  20  	// (We'll add another blank line after our comment block.)
  21  	// When we start dropping // +build comments, we can skip over /* */ comments too.
  22  	// Note that we are processing tabwriter input, so every comment
  23  	// begins and ends with a tabwriter.Escape byte.
  24  	// And some newlines have turned into \f bytes.
  25  	insert := 0
  26  	for pos := 0; ; {
  27  		// Skip leading space at beginning of line.
  28  		blank := true
  29  		for pos < len(p.output) && (p.output[pos] == ' ' || p.output[pos] == '\t') {
  30  			pos++
  31  		}
  32  		// Skip over // comment if any.
  33  		if pos+3 < len(p.output) && p.output[pos] == tabwriter.Escape && p.output[pos+1] == '/' && p.output[pos+2] == '/' {
  34  			blank = false
  35  			for pos < len(p.output) && !isNL(p.output[pos]) {
  36  				pos++
  37  			}
  38  		}
  39  		// Skip over \n at end of line.
  40  		if pos >= len(p.output) || !isNL(p.output[pos]) {
  41  			break
  42  		}
  43  		pos++
  44  
  45  		if blank {
  46  			insert = pos
  47  		}
  48  	}
  49  
  50  	// If there is a //go:build comment before the place we identified,
  51  	// use that point instead. (Earlier in the file is always fine.)
  52  	if len(p.goBuild) > 0 && p.goBuild[0] < insert {
  53  		insert = p.goBuild[0]
  54  	} else if len(p.plusBuild) > 0 && p.plusBuild[0] < insert {
  55  		insert = p.plusBuild[0]
  56  	}
  57  
  58  	var x constraint.Expr
  59  	switch len(p.goBuild) {
  60  	case 0:
  61  		// Synthesize //go:build expression from // +build lines.
  62  		for _, pos := range p.plusBuild {
  63  			y, err := constraint.Parse(p.commentTextAt(pos))
  64  			if err != nil {
  65  				x = nil
  66  				break
  67  			}
  68  			if x == nil {
  69  				x = y
  70  			} else {
  71  				x = &constraint.AndExpr{X: x, Y: y}
  72  			}
  73  		}
  74  	case 1:
  75  		// Parse //go:build expression.
  76  		x, _ = constraint.Parse(p.commentTextAt(p.goBuild[0]))
  77  	}
  78  
  79  	var block []byte
  80  	if x == nil {
  81  		// Don't have a valid //go:build expression to treat as truth.
  82  		// Bring all the lines together but leave them alone.
  83  		// Note that these are already tabwriter-escaped.
  84  		for _, pos := range p.goBuild {
  85  			block = append(block, p.lineAt(pos)...)
  86  		}
  87  		for _, pos := range p.plusBuild {
  88  			block = append(block, p.lineAt(pos)...)
  89  		}
  90  	} else {
  91  		block = append(block, tabwriter.Escape)
  92  		block = append(block, "//go:build "...)
  93  		block = append(block, x.String()...)
  94  		block = append(block, tabwriter.Escape, '\n')
  95  		if len(p.plusBuild) > 0 {
  96  			lines, err := constraint.PlusBuildLines(x)
  97  			if err != nil {
  98  				lines = [][]byte{"// +build error: " + err.Error()}
  99  			}
 100  			for _, line := range lines {
 101  				block = append(block, tabwriter.Escape)
 102  				block = append(block, line...)
 103  				block = append(block, tabwriter.Escape, '\n')
 104  			}
 105  		}
 106  	}
 107  	block = append(block, '\n')
 108  
 109  	// Build sorted list of lines to delete from remainder of output.
 110  	toDelete := append(p.goBuild, p.plusBuild...)
 111  	slices.Sort(toDelete)
 112  
 113  	// Collect output after insertion point, with lines deleted, into after.
 114  	var after []byte
 115  	start := insert
 116  	for _, end := range toDelete {
 117  		if end < start {
 118  			continue
 119  		}
 120  		after = appendLines(after, p.output[start:end])
 121  		start = end + len(p.lineAt(end))
 122  	}
 123  	after = appendLines(after, p.output[start:])
 124  	if n := len(after); n >= 2 && isNL(after[n-1]) && isNL(after[n-2]) {
 125  		after = after[:n-1]
 126  	}
 127  
 128  	p.output = p.output[:insert]
 129  	p.output = append(p.output, block...)
 130  	p.output = append(p.output, after...)
 131  }
 132  
 133  // appendLines is like append(x, y...)
 134  // but it avoids creating doubled blank lines,
 135  // which would not be gofmt-standard output.
 136  // It assumes that only whole blocks of lines are being appended,
 137  // not line fragments.
 138  func appendLines(x, y []byte) []byte {
 139  	if len(y) > 0 && isNL(y[0]) && // y starts in blank line
 140  		(len(x) == 0 || len(x) >= 2 && isNL(x[len(x)-1]) && isNL(x[len(x)-2])) { // x is empty or ends in blank line
 141  		y = y[1:] // delete y's leading blank line
 142  	}
 143  	return append(x, y...)
 144  }
 145  
 146  func (p *printer) lineAt(start int) []byte {
 147  	pos := start
 148  	for pos < len(p.output) && !isNL(p.output[pos]) {
 149  		pos++
 150  	}
 151  	if pos < len(p.output) {
 152  		pos++
 153  	}
 154  	return p.output[start:pos]
 155  }
 156  
 157  func (p *printer) commentTextAt(start int) []byte {
 158  	if start < len(p.output) && p.output[start] == tabwriter.Escape {
 159  		start++
 160  	}
 161  	pos := start
 162  	for pos < len(p.output) && p.output[pos] != tabwriter.Escape && !isNL(p.output[pos]) {
 163  		pos++
 164  	}
 165  	return []byte(p.output[start:pos])
 166  }
 167  
 168  func isNL(b byte) bool {
 169  	return b == '\n' || b == '\f'
 170  }
 171