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