1 // Copyright 2018 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
5 // This file caches information about which standard library types, methods,
6 // and functions appeared in what version of Go
20 // apiVersions is a map of packages to information about those packages'
21 // symbols and when they were added to Go.
23 // Only things added after Go1 are tracked. Version strings are of the
24 // form "1.1", "1.2", etc.
25 type apiVersions map[string]pkgAPIVersions // keyed by Go package ("net/http")
27 // pkgAPIVersions contains information about which version of Go added
28 // certain package symbols.
30 // Only things added after Go1 are tracked. Version strings are of the
31 // form "1.1", "1.2", etc.
32 type pkgAPIVersions struct {
33 typeSince map[string]string // "Server" -> "1.7"
34 methodSince map[string]map[string]string // "*Server" ->"Shutdown"->1.8
35 funcSince map[string]string // "NewServer" -> "1.7"
36 fieldSince map[string]map[string]string // "ClientTrace" -> "Got1xxResponse" -> "1.11"
39 // sinceVersionFunc returns a string (such as "1.7") specifying which Go
40 // version introduced a symbol, unless it was introduced in Go1, in
41 // which case it returns the empty string.
43 // The kind is one of "type", "method", or "func".
45 // The receiver is only used for "methods" and specifies the receiver type,
48 // The name is the symbol name ("Server") and the pkg is the package
50 func (v apiVersions) sinceVersionFunc(kind, receiver, name, pkg string) string {
54 return pv.funcSince[name]
56 return pv.typeSince[name]
58 return pv.methodSince[receiver][name]
63 // versionedRow represents an API feature, a parsed line of a
64 // $GOROOT/api/go.*txt file.
65 type versionedRow struct {
66 pkg string // "net/http"
67 kind string // "type", "func", "method", "field" TODO: "const", "var"
68 recv string // for methods, the receiver type ("Server", "*Server")
69 name string // name of type, (struct) field, func, method
70 structName string // for struct fields, the outer struct name
73 // versionParser parses $GOROOT/api/go*.txt files and stores them in in its rows field.
74 type versionParser struct {
75 res apiVersions // initialized lazily
78 func (vp *versionParser) parseFile(name string) error {
79 base := filepath.Base(name)
80 ver := strings.TrimPrefix(strings.TrimSuffix(base, ".txt"), "go")
84 f, err := os.Open(name)
90 sc := bufio.NewScanner(f)
92 row, ok := parseRow(sc.Text())
97 vp.res = make(apiVersions)
99 pkgi, ok := vp.res[row.pkg]
101 pkgi = pkgAPIVersions{
102 typeSince: make(map[string]string),
103 methodSince: make(map[string]map[string]string),
104 funcSince: make(map[string]string),
105 fieldSince: make(map[string]map[string]string),
107 vp.res[row.pkg] = pkgi
111 pkgi.funcSince[row.name] = ver
113 pkgi.typeSince[row.name] = ver
115 if _, ok := pkgi.methodSince[row.recv]; !ok {
116 pkgi.methodSince[row.recv] = make(map[string]string)
118 pkgi.methodSince[row.recv][row.name] = ver
120 if _, ok := pkgi.fieldSince[row.structName]; !ok {
121 pkgi.fieldSince[row.structName] = make(map[string]string)
123 pkgi.fieldSince[row.structName][row.name] = ver
129 func parseRow(s string) (vr versionedRow, ok bool) {
130 if !strings.HasPrefix(s, "pkg ") {
131 // Skip comments, blank lines, etc.
134 rest := s[len("pkg "):]
135 endPkg := strings.IndexFunc(rest, func(r rune) bool { return !(unicode.IsLetter(r) || r == '/' || unicode.IsDigit(r)) })
139 vr.pkg, rest = rest[:endPkg], rest[endPkg:]
140 if !strings.HasPrefix(rest, ", ") {
141 // If the part after the pkg name isn't ", ", then it's a OS/ARCH-dependent line of the form:
142 // pkg syscall (darwin-amd64), const ImplementsGetwd = false
143 // We skip those for now.
146 rest = rest[len(", "):]
149 case strings.HasPrefix(rest, "type "):
150 rest = rest[len("type "):]
151 sp := strings.IndexByte(rest, ' ')
155 vr.name, rest = rest[:sp], rest[sp+1:]
156 if !strings.HasPrefix(rest, "struct, ") {
160 rest = rest[len("struct, "):]
161 if i := strings.IndexByte(rest, ' '); i != -1 {
163 vr.structName = vr.name
167 case strings.HasPrefix(rest, "func "):
169 rest = rest[len("func "):]
170 if i := strings.IndexByte(rest, '('); i != -1 {
174 case strings.HasPrefix(rest, "method "): // "method (*File) SetModTime(time.Time)"
176 rest = rest[len("method "):] // "(*File) SetModTime(time.Time)"
177 sp := strings.IndexByte(rest, ' ')
181 vr.recv = strings.Trim(rest[:sp], "()") // "*File"
182 rest = rest[sp+1:] // SetMode(os.FileMode)
183 paren := strings.IndexByte(rest, '(')
187 vr.name = rest[:paren]
190 return // TODO: handle more cases
193 // InitVersionInfo parses the $GOROOT/api/go*.txt API definition files to discover
194 // which API features were added in which Go releases.
195 func (c *Corpus) InitVersionInfo() {
197 c.pkgAPIInfo, err = parsePackageAPIInfo()
199 // TODO: consider making this fatal, after the Go 1.11 cycle.
200 log.Printf("godoc: error parsing API version files: %v", err)
204 func parsePackageAPIInfo() (apiVersions, error) {
206 if os.Getenv("GOROOT") == "" {
207 apiGlob = filepath.Join(build.Default.GOROOT, "api", "go*.txt")
209 apiGlob = filepath.Join(os.Getenv("GOROOT"), "api", "go*.txt")
212 files, err := filepath.Glob(apiGlob)
217 vp := new(versionParser)
218 for _, f := range files {
219 if err := vp.parseFile(f); err != nil {