|  | // 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 | 
|  | } |