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.
4
5// This file caches information about which standard library types, methods,
6// and functions appeared in what version of Go
7
8package godoc
9
10import (
11	"bufio"
12	"go/build"
13	"log"
14	"os"
15	"path/filepath"
16	"strings"
17	"unicode"
18)
19
20// apiVersions is a map of packages to information about those packages'
21// symbols and when they were added to Go.
22//
23// Only things added after Go1 are tracked. Version strings are of the
24// form "1.1", "1.2", etc.
25type apiVersions map[string]pkgAPIVersions // keyed by Go package ("net/http")
26
27// pkgAPIVersions contains information about which version of Go added
28// certain package symbols.
29//
30// Only things added after Go1 are tracked. Version strings are of the
31// form "1.1", "1.2", etc.
32type 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"
37}
38
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.
42//
43// The kind is one of "type", "method", or "func".
44//
45// The receiver is only used for "methods" and specifies the receiver type,
46// such as "*Server".
47//
48// The name is the symbol name ("Server") and the pkg is the package
49// ("net/http").
50func (v apiVersions) sinceVersionFunc(kind, receiver, name, pkg string) string {
51	pv := v[pkg]
52	switch kind {
53	case "func":
54		return pv.funcSince[name]
55	case "type":
56		return pv.typeSince[name]
57	case "method":
58		return pv.methodSince[receiver][name]
59	}
60	return ""
61}
62
63// versionedRow represents an API feature, a parsed line of a
64// $GOROOT/api/go.*txt file.
65type 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
71}
72
73// versionParser parses $GOROOT/api/go*.txt files and stores them in in its rows field.
74type versionParser struct {
75	res apiVersions // initialized lazily
76}
77
78func (vp *versionParser) parseFile(name string) error {
79	base := filepath.Base(name)
80	ver := strings.TrimPrefix(strings.TrimSuffix(base, ".txt"), "go")
81	if ver == "1" {
82		return nil
83	}
84	f, err := os.Open(name)
85	if err != nil {
86		return err
87	}
88	defer f.Close()
89
90	sc := bufio.NewScanner(f)
91	for sc.Scan() {
92		row, ok := parseRow(sc.Text())
93		if !ok {
94			continue
95		}
96		if vp.res == nil {
97			vp.res = make(apiVersions)
98		}
99		pkgi, ok := vp.res[row.pkg]
100		if !ok {
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),
106			}
107			vp.res[row.pkg] = pkgi
108		}
109		switch row.kind {
110		case "func":
111			pkgi.funcSince[row.name] = ver
112		case "type":
113			pkgi.typeSince[row.name] = ver
114		case "method":
115			if _, ok := pkgi.methodSince[row.recv]; !ok {
116				pkgi.methodSince[row.recv] = make(map[string]string)
117			}
118			pkgi.methodSince[row.recv][row.name] = ver
119		case "field":
120			if _, ok := pkgi.fieldSince[row.structName]; !ok {
121				pkgi.fieldSince[row.structName] = make(map[string]string)
122			}
123			pkgi.fieldSince[row.structName][row.name] = ver
124		}
125	}
126	return sc.Err()
127}
128
129func parseRow(s string) (vr versionedRow, ok bool) {
130	if !strings.HasPrefix(s, "pkg ") {
131		// Skip comments, blank lines, etc.
132		return
133	}
134	rest := s[len("pkg "):]
135	endPkg := strings.IndexFunc(rest, func(r rune) bool { return !(unicode.IsLetter(r) || r == '/' || unicode.IsDigit(r)) })
136	if endPkg == -1 {
137		return
138	}
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.
144		return
145	}
146	rest = rest[len(", "):]
147
148	switch {
149	case strings.HasPrefix(rest, "type "):
150		rest = rest[len("type "):]
151		sp := strings.IndexByte(rest, ' ')
152		if sp == -1 {
153			return
154		}
155		vr.name, rest = rest[:sp], rest[sp+1:]
156		if !strings.HasPrefix(rest, "struct, ") {
157			vr.kind = "type"
158			return vr, true
159		}
160		rest = rest[len("struct, "):]
161		if i := strings.IndexByte(rest, ' '); i != -1 {
162			vr.kind = "field"
163			vr.structName = vr.name
164			vr.name = rest[:i]
165			return vr, true
166		}
167	case strings.HasPrefix(rest, "func "):
168		vr.kind = "func"
169		rest = rest[len("func "):]
170		if i := strings.IndexByte(rest, '('); i != -1 {
171			vr.name = rest[:i]
172			return vr, true
173		}
174	case strings.HasPrefix(rest, "method "): // "method (*File) SetModTime(time.Time)"
175		vr.kind = "method"
176		rest = rest[len("method "):] // "(*File) SetModTime(time.Time)"
177		sp := strings.IndexByte(rest, ' ')
178		if sp == -1 {
179			return
180		}
181		vr.recv = strings.Trim(rest[:sp], "()") // "*File"
182		rest = rest[sp+1:]                      // SetMode(os.FileMode)
183		paren := strings.IndexByte(rest, '(')
184		if paren == -1 {
185			return
186		}
187		vr.name = rest[:paren]
188		return vr, true
189	}
190	return // TODO: handle more cases
191}
192
193// InitVersionInfo parses the $GOROOT/api/go*.txt API definition files to discover
194// which API features were added in which Go releases.
195func (c *Corpus) InitVersionInfo() {
196	var err error
197	c.pkgAPIInfo, err = parsePackageAPIInfo()
198	if err != nil {
199		// TODO: consider making this fatal, after the Go 1.11 cycle.
200		log.Printf("godoc: error parsing API version files: %v", err)
201	}
202}
203
204func parsePackageAPIInfo() (apiVersions, error) {
205	var apiGlob string
206	if os.Getenv("GOROOT") == "" {
207		apiGlob = filepath.Join(build.Default.GOROOT, "api", "go*.txt")
208	} else {
209		apiGlob = filepath.Join(os.Getenv("GOROOT"), "api", "go*.txt")
210	}
211
212	files, err := filepath.Glob(apiGlob)
213	if err != nil {
214		return nil, err
215	}
216
217	vp := new(versionParser)
218	for _, f := range files {
219		if err := vp.parseFile(f); err != nil {
220			return nil, err
221		}
222	}
223	return vp.res, nil
224}
225