2017-12-23 04:30:39 +03:00
|
|
|
// Copyright 2018 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.
|
|
|
|
|
|
|
|
// This file caches information about which standard library types, methods,
|
|
|
|
// and functions appeared in what version of Go
|
|
|
|
|
|
|
|
package godoc
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"go/build"
|
|
|
|
"log"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2021-02-03 07:38:24 +03:00
|
|
|
"sort"
|
|
|
|
"strconv"
|
2017-12-23 04:30:39 +03:00
|
|
|
"strings"
|
|
|
|
"unicode"
|
|
|
|
)
|
|
|
|
|
|
|
|
// apiVersions is a map of packages to information about those packages'
|
|
|
|
// symbols and when they were added to Go.
|
|
|
|
//
|
|
|
|
// Only things added after Go1 are tracked. Version strings are of the
|
|
|
|
// form "1.1", "1.2", etc.
|
|
|
|
type apiVersions map[string]pkgAPIVersions // keyed by Go package ("net/http")
|
|
|
|
|
|
|
|
// pkgAPIVersions contains information about which version of Go added
|
|
|
|
// certain package symbols.
|
|
|
|
//
|
|
|
|
// Only things added after Go1 are tracked. Version strings are of the
|
|
|
|
// form "1.1", "1.2", etc.
|
|
|
|
type pkgAPIVersions struct {
|
|
|
|
typeSince map[string]string // "Server" -> "1.7"
|
2018-07-18 00:23:18 +03:00
|
|
|
methodSince map[string]map[string]string // "*Server" ->"Shutdown"->1.8
|
2017-12-23 04:30:39 +03:00
|
|
|
funcSince map[string]string // "NewServer" -> "1.7"
|
2018-07-18 00:23:18 +03:00
|
|
|
fieldSince map[string]map[string]string // "ClientTrace" -> "Got1xxResponse" -> "1.11"
|
2017-12-23 04:30:39 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// sinceVersionFunc returns a string (such as "1.7") specifying which Go
|
|
|
|
// version introduced a symbol, unless it was introduced in Go1, in
|
|
|
|
// which case it returns the empty string.
|
|
|
|
//
|
|
|
|
// The kind is one of "type", "method", or "func".
|
|
|
|
//
|
|
|
|
// The receiver is only used for "methods" and specifies the receiver type,
|
|
|
|
// such as "*Server".
|
|
|
|
//
|
|
|
|
// The name is the symbol name ("Server") and the pkg is the package
|
|
|
|
// ("net/http").
|
|
|
|
func (v apiVersions) sinceVersionFunc(kind, receiver, name, pkg string) string {
|
|
|
|
pv := v[pkg]
|
|
|
|
switch kind {
|
|
|
|
case "func":
|
|
|
|
return pv.funcSince[name]
|
|
|
|
case "type":
|
|
|
|
return pv.typeSince[name]
|
|
|
|
case "method":
|
|
|
|
return pv.methodSince[receiver][name]
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
// versionedRow represents an API feature, a parsed line of a
|
|
|
|
// $GOROOT/api/go.*txt file.
|
|
|
|
type versionedRow struct {
|
2018-07-18 00:23:18 +03:00
|
|
|
pkg string // "net/http"
|
|
|
|
kind string // "type", "func", "method", "field" TODO: "const", "var"
|
|
|
|
recv string // for methods, the receiver type ("Server", "*Server")
|
|
|
|
name string // name of type, (struct) field, func, method
|
|
|
|
structName string // for struct fields, the outer struct name
|
2017-12-23 04:30:39 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// versionParser parses $GOROOT/api/go*.txt files and stores them in in its rows field.
|
|
|
|
type versionParser struct {
|
|
|
|
res apiVersions // initialized lazily
|
|
|
|
}
|
|
|
|
|
2021-02-03 07:38:24 +03:00
|
|
|
// parseFile parses the named $GOROOT/api/goVERSION.txt file.
|
|
|
|
//
|
|
|
|
// For each row, it updates the corresponding entry in
|
|
|
|
// vp.res to VERSION, overwriting any previous value.
|
|
|
|
// As a special case, if goVERSION is "go1", it deletes
|
|
|
|
// from the map instead.
|
2017-12-23 04:30:39 +03:00
|
|
|
func (vp *versionParser) parseFile(name string) error {
|
|
|
|
f, err := os.Open(name)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
|
2021-02-03 07:38:24 +03:00
|
|
|
base := filepath.Base(name)
|
|
|
|
ver := strings.TrimPrefix(strings.TrimSuffix(base, ".txt"), "go")
|
|
|
|
|
2017-12-23 04:30:39 +03:00
|
|
|
sc := bufio.NewScanner(f)
|
|
|
|
for sc.Scan() {
|
|
|
|
row, ok := parseRow(sc.Text())
|
|
|
|
if !ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if vp.res == nil {
|
|
|
|
vp.res = make(apiVersions)
|
|
|
|
}
|
|
|
|
pkgi, ok := vp.res[row.pkg]
|
|
|
|
if !ok {
|
|
|
|
pkgi = pkgAPIVersions{
|
|
|
|
typeSince: make(map[string]string),
|
|
|
|
methodSince: make(map[string]map[string]string),
|
|
|
|
funcSince: make(map[string]string),
|
2018-07-18 00:23:18 +03:00
|
|
|
fieldSince: make(map[string]map[string]string),
|
2017-12-23 04:30:39 +03:00
|
|
|
}
|
|
|
|
vp.res[row.pkg] = pkgi
|
|
|
|
}
|
|
|
|
switch row.kind {
|
|
|
|
case "func":
|
2021-02-03 07:38:24 +03:00
|
|
|
if ver == "1" {
|
|
|
|
delete(pkgi.funcSince, row.name)
|
|
|
|
break
|
|
|
|
}
|
2017-12-23 04:30:39 +03:00
|
|
|
pkgi.funcSince[row.name] = ver
|
|
|
|
case "type":
|
2021-02-03 07:38:24 +03:00
|
|
|
if ver == "1" {
|
|
|
|
delete(pkgi.typeSince, row.name)
|
|
|
|
break
|
|
|
|
}
|
2017-12-23 04:30:39 +03:00
|
|
|
pkgi.typeSince[row.name] = ver
|
|
|
|
case "method":
|
2021-02-03 07:38:24 +03:00
|
|
|
if ver == "1" {
|
|
|
|
delete(pkgi.methodSince[row.recv], row.name)
|
|
|
|
break
|
|
|
|
}
|
2017-12-23 04:30:39 +03:00
|
|
|
if _, ok := pkgi.methodSince[row.recv]; !ok {
|
|
|
|
pkgi.methodSince[row.recv] = make(map[string]string)
|
|
|
|
}
|
|
|
|
pkgi.methodSince[row.recv][row.name] = ver
|
2018-07-18 00:23:18 +03:00
|
|
|
case "field":
|
2021-02-03 07:38:24 +03:00
|
|
|
if ver == "1" {
|
|
|
|
delete(pkgi.fieldSince[row.structName], row.name)
|
|
|
|
break
|
|
|
|
}
|
2018-07-18 00:23:18 +03:00
|
|
|
if _, ok := pkgi.fieldSince[row.structName]; !ok {
|
|
|
|
pkgi.fieldSince[row.structName] = make(map[string]string)
|
|
|
|
}
|
|
|
|
pkgi.fieldSince[row.structName][row.name] = ver
|
2017-12-23 04:30:39 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return sc.Err()
|
|
|
|
}
|
|
|
|
|
|
|
|
func parseRow(s string) (vr versionedRow, ok bool) {
|
|
|
|
if !strings.HasPrefix(s, "pkg ") {
|
|
|
|
// Skip comments, blank lines, etc.
|
|
|
|
return
|
|
|
|
}
|
|
|
|
rest := s[len("pkg "):]
|
2018-07-22 14:07:42 +03:00
|
|
|
endPkg := strings.IndexFunc(rest, func(r rune) bool { return !(unicode.IsLetter(r) || r == '/' || unicode.IsDigit(r)) })
|
2017-12-23 04:30:39 +03:00
|
|
|
if endPkg == -1 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
vr.pkg, rest = rest[:endPkg], rest[endPkg:]
|
|
|
|
if !strings.HasPrefix(rest, ", ") {
|
|
|
|
// If the part after the pkg name isn't ", ", then it's a OS/ARCH-dependent line of the form:
|
|
|
|
// pkg syscall (darwin-amd64), const ImplementsGetwd = false
|
|
|
|
// We skip those for now.
|
|
|
|
return
|
|
|
|
}
|
|
|
|
rest = rest[len(", "):]
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case strings.HasPrefix(rest, "type "):
|
|
|
|
rest = rest[len("type "):]
|
|
|
|
sp := strings.IndexByte(rest, ' ')
|
|
|
|
if sp == -1 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
vr.name, rest = rest[:sp], rest[sp+1:]
|
2018-07-18 00:23:18 +03:00
|
|
|
if !strings.HasPrefix(rest, "struct, ") {
|
|
|
|
vr.kind = "type"
|
|
|
|
return vr, true
|
|
|
|
}
|
|
|
|
rest = rest[len("struct, "):]
|
|
|
|
if i := strings.IndexByte(rest, ' '); i != -1 {
|
|
|
|
vr.kind = "field"
|
|
|
|
vr.structName = vr.name
|
|
|
|
vr.name = rest[:i]
|
|
|
|
return vr, true
|
2017-12-23 04:30:39 +03:00
|
|
|
}
|
|
|
|
case strings.HasPrefix(rest, "func "):
|
|
|
|
vr.kind = "func"
|
|
|
|
rest = rest[len("func "):]
|
|
|
|
if i := strings.IndexByte(rest, '('); i != -1 {
|
|
|
|
vr.name = rest[:i]
|
|
|
|
return vr, true
|
|
|
|
}
|
|
|
|
case strings.HasPrefix(rest, "method "): // "method (*File) SetModTime(time.Time)"
|
|
|
|
vr.kind = "method"
|
|
|
|
rest = rest[len("method "):] // "(*File) SetModTime(time.Time)"
|
|
|
|
sp := strings.IndexByte(rest, ' ')
|
|
|
|
if sp == -1 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
vr.recv = strings.Trim(rest[:sp], "()") // "*File"
|
|
|
|
rest = rest[sp+1:] // SetMode(os.FileMode)
|
|
|
|
paren := strings.IndexByte(rest, '(')
|
|
|
|
if paren == -1 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
vr.name = rest[:paren]
|
|
|
|
return vr, true
|
|
|
|
}
|
|
|
|
return // TODO: handle more cases
|
|
|
|
}
|
|
|
|
|
|
|
|
// InitVersionInfo parses the $GOROOT/api/go*.txt API definition files to discover
|
|
|
|
// which API features were added in which Go releases.
|
|
|
|
func (c *Corpus) InitVersionInfo() {
|
|
|
|
var err error
|
|
|
|
c.pkgAPIInfo, err = parsePackageAPIInfo()
|
|
|
|
if err != nil {
|
|
|
|
// TODO: consider making this fatal, after the Go 1.11 cycle.
|
|
|
|
log.Printf("godoc: error parsing API version files: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func parsePackageAPIInfo() (apiVersions, error) {
|
|
|
|
var apiGlob string
|
|
|
|
if os.Getenv("GOROOT") == "" {
|
|
|
|
apiGlob = filepath.Join(build.Default.GOROOT, "api", "go*.txt")
|
|
|
|
} else {
|
|
|
|
apiGlob = filepath.Join(os.Getenv("GOROOT"), "api", "go*.txt")
|
|
|
|
}
|
|
|
|
|
|
|
|
files, err := filepath.Glob(apiGlob)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2021-02-03 07:38:24 +03:00
|
|
|
// Process files in go1.n, go1.n-1, ..., go1.2, go1.1, go1 order.
|
|
|
|
//
|
|
|
|
// It's rare, but the signature of an identifier may change
|
|
|
|
// (for example, a function that accepts a type replaced with
|
|
|
|
// an alias), and so an existing symbol may show up again in
|
|
|
|
// a later api/go1.N.txt file. Parsing in reverse version
|
|
|
|
// order means we end up with the earliest version of Go
|
|
|
|
// when the symbol was added. See golang.org/issue/44081.
|
|
|
|
//
|
|
|
|
ver := func(name string) int {
|
|
|
|
base := filepath.Base(name)
|
|
|
|
ver := strings.TrimPrefix(strings.TrimSuffix(base, ".txt"), "go1.")
|
|
|
|
if ver == "go1" {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
v, _ := strconv.Atoi(ver)
|
|
|
|
return v
|
|
|
|
}
|
|
|
|
sort.Slice(files, func(i, j int) bool { return ver(files[i]) > ver(files[j]) })
|
2017-12-23 04:30:39 +03:00
|
|
|
vp := new(versionParser)
|
|
|
|
for _, f := range files {
|
|
|
|
if err := vp.parseFile(f); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return vp.res, nil
|
|
|
|
}
|