// package PkgName; DeclList[0], DeclList[1], ...
type File struct {
- Pragma Pragma
- PkgName *Name
- DeclList []Decl
- EOF Pos
+ Pragma Pragma
+ PkgName *Name
+ DeclList []Decl
+ EOF Pos
+ GoVersion string
node
}
import (
"fmt"
+ "go/build/constraint"
"io"
"strconv"
"strings"
pragh PragmaHandler
scanner
- base *PosBase // current position base
- first error // first error encountered
- errcnt int // number of errors encountered
- pragma Pragma // pragmas
+ base *PosBase // current position base
+ first error // first error encountered
+ errcnt int // number of errors encountered
+ pragma Pragma // pragmas
+ goVersion string // Go version from //go:build line
+ top bool // in top of file (before package clause)
fnest int // function nesting level (for error handling)
xnest int // expression nesting level (for complit ambiguity resolution)
indent []byte // tracing support
}
func (p *parser) init(file *PosBase, r io.Reader, errh ErrorHandler, pragh PragmaHandler, mode Mode) {
+ p.top = true
p.file = file
p.errh = errh
p.mode = mode
}
// go: directive (but be conservative and test)
- if pragh != nil && strings.HasPrefix(text, "go:") {
- p.pragma = pragh(p.posAt(line, col+2), p.scanner.blank, text, p.pragma) // +2 to skip over // or /*
+ if strings.HasPrefix(text, "go:") {
+ if p.top && strings.HasPrefix(msg, "//go:build") {
+ if x, err := constraint.Parse(msg); err == nil {
+ p.goVersion = constraint.GoVersion(x)
+ }
+ }
+ if pragh != nil {
+ p.pragma = pragh(p.posAt(line, col+2), p.scanner.blank, text, p.pragma) // +2 to skip over // or /*
+ }
}
},
directives,
f.pos = p.pos()
// PackageClause
+ f.GoVersion = p.goVersion
+ p.top = false
if !p.got(_Package) {
p.syntaxError("package statement must be first")
return nil
< go/token
< go/scanner
< go/ast
- < go/internal/typeparams
- < go/parser;
+ < go/internal/typeparams;
FMT
< go/build/constraint, go/doc/comment;
- go/build/constraint, go/doc/comment, go/parser, text/tabwriter
+ go/internal/typeparams, go/build/constraint
+ < go/parser;
+
+ go/doc/comment, go/parser, text/tabwriter
< go/printer
< go/format;
import (
"fmt"
"go/ast"
+ "go/build/constraint"
"go/internal/typeparams"
"go/scanner"
"go/token"
+ "strings"
)
// The parser structure holds the parser's internal state.
comments []*ast.CommentGroup
leadComment *ast.CommentGroup // last lead comment
lineComment *ast.CommentGroup // last line comment
+ top bool // in top of file (before package clause)
+ goVersion string // minimum Go version found in //go:build comment
// Next token
pos token.Pos // token position
func (p *parser) init(fset *token.FileSet, filename string, src []byte, mode Mode) {
p.file = fset.AddFile(filename, -1, len(src))
- var m scanner.Mode
- if mode&ParseComments != 0 {
- m = scanner.ScanComments
- }
eh := func(pos token.Position, msg string) { p.errors.Add(pos, msg) }
- p.scanner.Init(p.file, src, eh, m)
+ p.scanner.Init(p.file, src, eh, scanner.ScanComments)
+ p.top = true
p.mode = mode
p.trace = mode&Trace != 0 // for convenience (p.trace is used frequently)
p.next()
}
}
- p.pos, p.tok, p.lit = p.scanner.Scan()
+ for {
+ p.pos, p.tok, p.lit = p.scanner.Scan()
+ if p.tok == token.COMMENT {
+ if p.top && strings.HasPrefix(p.lit, "//go:build") {
+ if x, err := constraint.Parse(p.lit); err == nil {
+ p.goVersion = constraint.GoVersion(x)
+ }
+ }
+ if p.mode&ParseComments == 0 {
+ continue
+ }
+ } else {
+ // Found a non-comment; top of file is over.
+ p.top = false
+ }
+ break
+ }
}
// Consume a comment and return it and the line on which it ends.
FileEnd: token.Pos(p.file.Base() + p.file.Size()),
Imports: p.imports,
Comments: p.comments,
+ GoVersion: p.goVersion,
}
var declErr func(token.Pos, string)
if p.mode&DeclarationErrors != 0 {
}
}
}
+
+func TestGoVersion(t *testing.T) {
+ fset := token.NewFileSet()
+ pkgs, err := ParseDir(fset, "./testdata/goversion", nil, 0)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ for _, p := range pkgs {
+ want := strings.ReplaceAll(p.Name, "_", ".")
+ if want == "none" {
+ want = ""
+ }
+ for _, f := range p.Files {
+ if f.GoVersion != want {
+ t.Errorf("%s: GoVersion = %q, want %q", fset.Position(f.Pos()), f.GoVersion, want)
+ }
+ }
+ }
+}
--- /dev/null
+//go:build windows
+
+package none
--- /dev/null
+//go:build linux && go1.2
+
+package go1_2
--- /dev/null
+//go:build linux && go1.2 || windows
+
+package none
--- /dev/null
+// copyright notice
+
+//go:build (linux && go1.2) || (windows && go1.1)
+
+package go1_1
--- /dev/null
+//go:build linux && go1.2 && go1.4
+
+package go1_4
--- /dev/null
+//go:build go1
+
+package go1