Source file src/go/printer/gobuild.go

     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  	"sort"
    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 = []string{"// +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  	sort.Ints(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) string {
   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 string(p.output[start:pos])
   166  }
   167  
   168  func isNL(b byte) bool {
   169  	return b == '\n' || b == '\f'
   170  }
   171  

View as plain text