+++ /dev/null
-// 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"
- "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"
- methodSince map[string]map[string]string // "*Server" ->"Shutdown"->1.8
- funcSince map[string]string // "NewServer" -> "1.7"
- fieldSince map[string]map[string]string // "ClientTrace" -> "Got1xxResponse" -> "1.11"
-}
-
-// 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 {
- 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
-}
-
-// versionParser parses $GOROOT/api/go*.txt files and stores them in in its rows field.
-type versionParser struct {
- res apiVersions // initialized lazily
-}
-
-func (vp *versionParser) parseFile(name string) error {
- base := filepath.Base(name)
- ver := strings.TrimPrefix(strings.TrimSuffix(base, ".txt"), "go")
- if ver == "1" {
- return nil
- }
- f, err := os.Open(name)
- if err != nil {
- return err
- }
- defer f.Close()
-
- 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),
- fieldSince: make(map[string]map[string]string),
- }
- vp.res[row.pkg] = pkgi
- }
- switch row.kind {
- case "func":
- pkgi.funcSince[row.name] = ver
- case "type":
- pkgi.typeSince[row.name] = ver
- case "method":
- if _, ok := pkgi.methodSince[row.recv]; !ok {
- pkgi.methodSince[row.recv] = make(map[string]string)
- }
- pkgi.methodSince[row.recv][row.name] = ver
- case "field":
- if _, ok := pkgi.fieldSince[row.structName]; !ok {
- pkgi.fieldSince[row.structName] = make(map[string]string)
- }
- pkgi.fieldSince[row.structName][row.name] = ver
- }
- }
- 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 "):]
- endPkg := strings.IndexFunc(rest, func(r rune) bool { return !(unicode.IsLetter(r) || r == '/' || unicode.IsDigit(r)) })
- 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:]
- 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
- }
- 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
- }
-
- vp := new(versionParser)
- for _, f := range files {
- if err := vp.parseFile(f); err != nil {
- return nil, err
- }
- }
- return vp.res, nil
-}