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