1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
|
// Copyright 2013 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 buildtag defines an Analyzer that checks build tags.
package buildtag
import (
"bytes"
"fmt"
"go/ast"
"strings"
"unicode"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
)
var Analyzer = &analysis.Analyzer{
Name: "buildtag",
Doc: "check that +build tags are well-formed and correctly located",
Run: runBuildTag,
}
func runBuildTag(pass *analysis.Pass) (interface{}, error) {
for _, f := range pass.Files {
checkGoFile(pass, f)
}
for _, name := range pass.OtherFiles {
if err := checkOtherFile(pass, name); err != nil {
return nil, err
}
}
return nil, nil
}
func checkGoFile(pass *analysis.Pass, f *ast.File) {
pastCutoff := false
for _, group := range f.Comments {
// A +build comment is ignored after or adjoining the package declaration.
if group.End()+1 >= f.Package {
pastCutoff = true
}
// "+build" is ignored within or after a /*...*/ comment.
if !strings.HasPrefix(group.List[0].Text, "//") {
pastCutoff = true
continue
}
// Check each line of a //-comment.
for _, c := range group.List {
if !strings.Contains(c.Text, "+build") {
continue
}
if err := checkLine(c.Text, pastCutoff); err != nil {
pass.Reportf(c.Pos(), "%s", err)
}
}
}
}
func checkOtherFile(pass *analysis.Pass, filename string) error {
content, tf, err := analysisutil.ReadFile(pass.Fset, filename)
if err != nil {
return err
}
// We must look at the raw lines, as build tags may appear in non-Go
// files such as assembly files.
lines := bytes.SplitAfter(content, nl)
// Determine cutpoint where +build comments are no longer valid.
// They are valid in leading // comments in the file followed by
// a blank line.
//
// This must be done as a separate pass because of the
// requirement that the comment be followed by a blank line.
var cutoff int
for i, line := range lines {
line = bytes.TrimSpace(line)
if !bytes.HasPrefix(line, slashSlash) {
if len(line) > 0 {
break
}
cutoff = i
}
}
for i, line := range lines {
line = bytes.TrimSpace(line)
if !bytes.HasPrefix(line, slashSlash) {
continue
}
if !bytes.Contains(line, []byte("+build")) {
continue
}
if err := checkLine(string(line), i >= cutoff); err != nil {
pass.Reportf(analysisutil.LineStart(tf, i+1), "%s", err)
continue
}
}
return nil
}
// checkLine checks a line that starts with "//" and contains "+build".
func checkLine(line string, pastCutoff bool) error {
line = strings.TrimPrefix(line, "//")
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "+build") {
fields := strings.Fields(line)
if fields[0] != "+build" {
// Comment is something like +buildasdf not +build.
return fmt.Errorf("possible malformed +build comment")
}
if pastCutoff {
return fmt.Errorf("+build comment must appear before package clause and be followed by a blank line")
}
if err := checkArguments(fields); err != nil {
return err
}
} else {
// Comment with +build but not at beginning.
if !pastCutoff {
return fmt.Errorf("possible malformed +build comment")
}
}
return nil
}
func checkArguments(fields []string) error {
// The original version of this checker in vet could examine
// files with malformed build tags that would cause the file to
// be always ignored by "go build". However, drivers for the new
// analysis API will analyze only the files selected to form a
// package, so these checks will never fire.
// TODO(adonovan): rethink this.
for _, arg := range fields[1:] {
for _, elem := range strings.Split(arg, ",") {
if strings.HasPrefix(elem, "!!") {
return fmt.Errorf("invalid double negative in build constraint: %s", arg)
}
elem = strings.TrimPrefix(elem, "!")
for _, c := range elem {
if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' {
return fmt.Errorf("invalid non-alphanumeric build constraint: %s", arg)
}
}
}
}
return nil
}
var (
nl = []byte("\n")
slashSlash = []byte("//")
)
|