golang buildtag 代码
文件路径:/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/buildtag/buildtag.go
/ 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.
/go:build go1.16
/ +build go1.16
/ Package buildtag defines an Analyzer that checks build tags.
package buildtag
import (
"go/ast"
"go/build/constraint"
"go/parser"
"go/token"
"strings"
"unicode"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
)
const Doc = "check that +build tags are well-formed and correctly located"
var Analyzer = &analysis.Analyzer{
Name: "buildtag",
Doc: Doc,
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
}
}
for _, name := range pass.IgnoredFiles {
if strings.HasSuffix(name, ".go") {
f, err := parser.ParseFile(pass.Fset, name, nil, parser.ParseComments)
if err != nil {
/ Not valid Go source code - not our job to diagnose, so ignore.
return nil, nil
}
checkGoFile(pass, f)
} else {
if err := checkOtherFile(pass, name); err != nil {
return nil, err
}
}
}
return nil, nil
}
func checkGoFile(pass *analysis.Pass, f *ast.File) {
var check checker
check.init(pass)
defer check.finish()
for _, group := range f.Comments {
/ A +build comment is ignored after or adjoining the package declaration.
if group.End()+1 >= f.Package {
check.plusBuildOK = false
}
/ A /go:build comment is ignored after the package declaration
/ (but adjoining it is OK, in contrast to +build comments).
if group.Pos() >= f.Package {
check.goBuildOK = false
}
/ Check each line of a /-comment.
for _, c := range group.List {
/ "+build" is ignored within or after a /*...*/ comment.
if !strings.HasPrefix(c.Text, "/") {
check.plusBuildOK = false
}
check.comment(c.Slash, c.Text)
}
}
}
func checkOtherFile(pass *analysis.Pass, filename string) error {
var check checker
check.init(pass)
defer check.finish()
/ We cannot use the Go parser, since this may not be a Go source file.
/ Read the raw bytes instead.
content, tf, err := analysisutil.ReadFile(pass.Fset, filename)
if err != nil {
return err
}
check.file(token.Pos(tf.Base()), string(content))
return nil
}
type checker struct {
pass *analysis.Pass
plusBuildOK bool / "+build" lines still OK
goBuildOK bool / "go:build" lines still OK
crossCheck bool / cross-check go:build and +build lines when done reading file
inStar bool / currently in a /* */ comment
goBuildPos token.Pos / position of first go:build line found
plusBuildPos token.Pos / position of first "+build" line found
goBuild constraint.Expr / go:build constraint found
plusBuild constraint.Expr / AND of +build constraints found
}
func (check *checker) init(pass *analysis.Pass) {
check.pass = pass
check.goBuildOK = true
check.plusBuildOK = true
check.crossCheck = true
}
func (check *checker) file(pos token.Pos, text string) {
/ 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 plusBuildCutoff int
fullText := text
for text != "" {
i := strings.Index(text, "\n")
if i < 0 {
i = len(text)
} else {
i++
}
offset := len(fullText) - len(text)
line := text[:i]
text = text[i:]
line = strings.TrimSpace(line)
if !strings.HasPrefix(line, "/") && line != "" {
break
}
if line == "" {
plusBuildCutoff = offset
}
}
/ Process each line.
/ Must stop once we hit goBuildOK == false
text = fullText
check.inStar = false
for text != "" {
i := strings.Index(text, "\n")
if i < 0 {
i = len(text)
} else {
i++
}
offset := len(fullText) - len(text)
line := text[:i]
text = text[i:]
check.plusBuildOK = offset < plusBuildCutoff
if strings.HasPrefix(line, "/") {
check.comment(pos+token.Pos(offset), line)
continue
}
/ Keep looking for the point at which /go:build comments
/ stop being allowed. Skip over, cut out any /* */ comments.
for {
line = strings.TrimSpace(line)
if check.inStar {
i := strings.Index(line, "*/")
if i < 0 {
line = ""
break
}
line = line[i+len("*/"):]
check.inStar = false
continue
}
if strings.HasPrefix(line, "/*") {
check.inStar = true
line = line[len("/*"):]
continue
}
break
}
if line != "" {
/ Found non-comment non-blank line.
/ Ends space for valid /go:build comments,
/ but also ends the fraction of the file we can
/ reliably parse. From this point on we might
/ incorrectly flag "comments" inside multiline
/ string constants or anything else (this might
/ not even be a Go program). So stop.
break
}
}
}
func (check *checker) comment(pos token.Pos, text string) {
if strings.HasPrefix(text, "/") {
if strings.Contains(text, "+build") {
check.plusBuildLine(pos, text)
}
if strings.Contains(text, "/go:build") {
check.goBuildLine(pos, text)
}
}
if strings.HasPrefix(text, "/*") {
if i := strings.Index(text, "\n"); i >= 0 {
/ multiline /* */ comment - process interior lines
check.inStar = true
i++
pos += token.Pos(i)
text = text[i:]
for text != "" {
i := strings.Index(text, "\n")
if i < 0 {
i = len(text)
} else {
i++
}
line := text[:i]
if strings.HasPrefix(line, "/") {
check.comment(pos, line)
}
pos += token.Pos(i)
text = text[i:]
}
check.inStar = false
}
}
}
func (check *checker) goBuildLine(pos token.Pos, line string) {
if !constraint.IsGoBuild(line) {
if !strings.HasPrefix(line, "/go:build") && constraint.IsGoBuild("/"+strings.TrimSpace(line[len("/"):])) {
check.pass.Reportf(pos, "malformed /go:build line (space between / and go:build)")
}
return
}
if !check.goBuildOK || check.inStar {
check.pass.Reportf(pos, "misplaced /go:build comment")
check.crossCheck = false
return
}
if check.goBuildPos == token.NoPos {
check.goBuildPos = pos
} else {
check.pass.Reportf(pos, "unexpected extra /go:build line")
check.crossCheck = false
}
/ testing hack: stop at / ERROR
if i := strings.Index(line, " / ERROR "); i >= 0 {
line = line[:i]
}
x, err := constraint.Parse(line)
if err != nil {
check.pass.Reportf(pos, "%v", err)
check.crossCheck = false
return
}
if check.goBuild == nil {
check.goBuild = x
}
}
func (check *checker) plusBuildLine(pos token.Pos, line string) {
line = strings.TrimSpace(line)
if !constraint.IsPlusBuild(line) {
/ Comment with +build but not at beginning.
/ Only report early in file.
if check.plusBuildOK && !strings.HasPrefix(line, "/ want") {
check.pass.Reportf(pos, "possible malformed +build comment")
}
return
}
if !check.plusBuildOK { / inStar implies !plusBuildOK
check.pass.Reportf(pos, "misplaced +build comment")
check.crossCheck = false
}
if check.plusBuildPos == token.NoPos {
check.plusBuildPos = pos
}
/ testing hack: stop at / ERROR
if i := strings.Index(line, " / ERROR "); i >= 0 {
line = line[:i]
}
fields := strings.Fields(line[len("/"):])
/ IsPlusBuildConstraint check above implies fields[0] == "+build"
for _, arg := range fields[1:] {
for _, elem := range strings.Split(arg, ",") {
if strings.HasPrefix(elem, "!!") {
check.pass.Reportf(pos, "invalid double negative in build constraint: %s", arg)
check.crossCheck = false
continue
}
elem = strings.TrimPrefix(elem, "!")
for _, c := range elem {
if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' {
check.pass.Reportf(pos, "invalid non-alphanumeric build constraint: %s", arg)
check.crossCheck = false
break
}
}
}
}
if check.crossCheck {
y, err := constraint.Parse(line)
if err != nil {
/ Should never happen - constraint.Parse never rejects a / +build line.
/ Also, we just checked the syntax above.
/ Even so, report.
check.pass.Reportf(pos, "%v", err)
check.crossCheck = false
return
}
if check.plusBuild == nil {
check.plusBuild = y
} else {
check.plusBuild = &constraint.AndExpr{X: check.plusBuild, Y: y}
}
}
}
func (check *checker) finish() {
if !check.crossCheck || check.plusBuildPos == token.NoPos || check.goBuildPos == token.NoPos {
return
}
/ Have both /go:build and / +build,
/ with no errors found (crossCheck still true).
/ Check they match.
var want constraint.Expr
lines, err := constraint.PlusBuildLines(check.goBuild)
if err != nil {
check.pass.Reportf(check.goBuildPos, "%v", err)
return
}
for _, line := range lines {
y, err := constraint.Parse(line)
if err != nil {
/ Definitely should not happen, but not the user's fault.
/ Do not report.
return
}
if want == nil {
want = y
} else {
want = &constraint.AndExpr{X: want, Y: y}
}
}
if want.String() != check.plusBuild.String() {
check.pass.Reportf(check.plusBuildPos, "+build lines do not match /go:build condition")
return
}
}