golang.org/x/tools@v0.21.0/godoc/versions.go (about) 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 8 package godoc 9 10 import ( 11 "bufio" 12 "go/build" 13 "log" 14 "os" 15 "path/filepath" 16 "sort" 17 "strconv" 18 "strings" 19 "unicode" 20 ) 21 22 // apiVersions is a map of packages to information about those packages' 23 // symbols and when they were added to Go. 24 // 25 // Only things added after Go1 are tracked. Version strings are of the 26 // form "1.1", "1.2", etc. 27 type apiVersions map[string]pkgAPIVersions // keyed by Go package ("net/http") 28 29 // pkgAPIVersions contains information about which version of Go added 30 // certain package symbols. 31 // 32 // Only things added after Go1 are tracked. Version strings are of the 33 // form "1.1", "1.2", etc. 34 type pkgAPIVersions struct { 35 typeSince map[string]string // "Server" -> "1.7" 36 methodSince map[string]map[string]string // "*Server" ->"Shutdown"->1.8 37 funcSince map[string]string // "NewServer" -> "1.7" 38 fieldSince map[string]map[string]string // "ClientTrace" -> "Got1xxResponse" -> "1.11" 39 } 40 41 // sinceVersionFunc returns a string (such as "1.7") specifying which Go 42 // version introduced a symbol, unless it was introduced in Go1, in 43 // which case it returns the empty string. 44 // 45 // The kind is one of "type", "method", or "func". 46 // 47 // The receiver is only used for "methods" and specifies the receiver type, 48 // such as "*Server". 49 // 50 // The name is the symbol name ("Server") and the pkg is the package 51 // ("net/http"). 52 func (v apiVersions) sinceVersionFunc(kind, receiver, name, pkg string) string { 53 pv := v[pkg] 54 switch kind { 55 case "func": 56 return pv.funcSince[name] 57 case "type": 58 return pv.typeSince[name] 59 case "method": 60 return pv.methodSince[receiver][name] 61 } 62 return "" 63 } 64 65 // versionedRow represents an API feature, a parsed line of a 66 // $GOROOT/api/go.*txt file. 67 type versionedRow struct { 68 pkg string // "net/http" 69 kind string // "type", "func", "method", "field" TODO: "const", "var" 70 recv string // for methods, the receiver type ("Server", "*Server") 71 name string // name of type, (struct) field, func, method 72 structName string // for struct fields, the outer struct name 73 } 74 75 // versionParser parses $GOROOT/api/go*.txt files and stores them in its rows field. 76 type versionParser struct { 77 res apiVersions // initialized lazily 78 } 79 80 // parseFile parses the named $GOROOT/api/goVERSION.txt file. 81 // 82 // For each row, it updates the corresponding entry in 83 // vp.res to VERSION, overwriting any previous value. 84 // As a special case, if goVERSION is "go1", it deletes 85 // from the map instead. 86 func (vp *versionParser) parseFile(name string) error { 87 f, err := os.Open(name) 88 if err != nil { 89 return err 90 } 91 defer f.Close() 92 93 base := filepath.Base(name) 94 ver := strings.TrimPrefix(strings.TrimSuffix(base, ".txt"), "go") 95 96 sc := bufio.NewScanner(f) 97 for sc.Scan() { 98 row, ok := parseRow(sc.Text()) 99 if !ok { 100 continue 101 } 102 if vp.res == nil { 103 vp.res = make(apiVersions) 104 } 105 pkgi, ok := vp.res[row.pkg] 106 if !ok { 107 pkgi = pkgAPIVersions{ 108 typeSince: make(map[string]string), 109 methodSince: make(map[string]map[string]string), 110 funcSince: make(map[string]string), 111 fieldSince: make(map[string]map[string]string), 112 } 113 vp.res[row.pkg] = pkgi 114 } 115 switch row.kind { 116 case "func": 117 if ver == "1" { 118 delete(pkgi.funcSince, row.name) 119 break 120 } 121 pkgi.funcSince[row.name] = ver 122 case "type": 123 if ver == "1" { 124 delete(pkgi.typeSince, row.name) 125 break 126 } 127 pkgi.typeSince[row.name] = ver 128 case "method": 129 if ver == "1" { 130 delete(pkgi.methodSince[row.recv], row.name) 131 break 132 } 133 if _, ok := pkgi.methodSince[row.recv]; !ok { 134 pkgi.methodSince[row.recv] = make(map[string]string) 135 } 136 pkgi.methodSince[row.recv][row.name] = ver 137 case "field": 138 if ver == "1" { 139 delete(pkgi.fieldSince[row.structName], row.name) 140 break 141 } 142 if _, ok := pkgi.fieldSince[row.structName]; !ok { 143 pkgi.fieldSince[row.structName] = make(map[string]string) 144 } 145 pkgi.fieldSince[row.structName][row.name] = ver 146 } 147 } 148 return sc.Err() 149 } 150 151 func parseRow(s string) (vr versionedRow, ok bool) { 152 if !strings.HasPrefix(s, "pkg ") { 153 // Skip comments, blank lines, etc. 154 return 155 } 156 rest := s[len("pkg "):] 157 endPkg := strings.IndexFunc(rest, func(r rune) bool { return !(unicode.IsLetter(r) || r == '/' || unicode.IsDigit(r)) }) 158 if endPkg == -1 { 159 return 160 } 161 vr.pkg, rest = rest[:endPkg], rest[endPkg:] 162 if !strings.HasPrefix(rest, ", ") { 163 // If the part after the pkg name isn't ", ", then it's a OS/ARCH-dependent line of the form: 164 // pkg syscall (darwin-amd64), const ImplementsGetwd = false 165 // We skip those for now. 166 return 167 } 168 rest = rest[len(", "):] 169 170 switch { 171 case strings.HasPrefix(rest, "type "): 172 rest = rest[len("type "):] 173 sp := strings.IndexByte(rest, ' ') 174 if sp == -1 { 175 return 176 } 177 vr.name, rest = rest[:sp], rest[sp+1:] 178 if !strings.HasPrefix(rest, "struct, ") { 179 vr.kind = "type" 180 return vr, true 181 } 182 rest = rest[len("struct, "):] 183 if i := strings.IndexByte(rest, ' '); i != -1 { 184 vr.kind = "field" 185 vr.structName = vr.name 186 vr.name = rest[:i] 187 return vr, true 188 } 189 case strings.HasPrefix(rest, "func "): 190 vr.kind = "func" 191 rest = rest[len("func "):] 192 if i := strings.IndexByte(rest, '('); i != -1 { 193 vr.name = rest[:i] 194 return vr, true 195 } 196 case strings.HasPrefix(rest, "method "): // "method (*File) SetModTime(time.Time)" 197 vr.kind = "method" 198 rest = rest[len("method "):] // "(*File) SetModTime(time.Time)" 199 sp := strings.IndexByte(rest, ' ') 200 if sp == -1 { 201 return 202 } 203 vr.recv = strings.Trim(rest[:sp], "()") // "*File" 204 rest = rest[sp+1:] // SetMode(os.FileMode) 205 paren := strings.IndexByte(rest, '(') 206 if paren == -1 { 207 return 208 } 209 vr.name = rest[:paren] 210 return vr, true 211 } 212 return // TODO: handle more cases 213 } 214 215 // InitVersionInfo parses the $GOROOT/api/go*.txt API definition files to discover 216 // which API features were added in which Go releases. 217 func (c *Corpus) InitVersionInfo() { 218 var err error 219 c.pkgAPIInfo, err = parsePackageAPIInfo() 220 if err != nil { 221 // TODO: consider making this fatal, after the Go 1.11 cycle. 222 log.Printf("godoc: error parsing API version files: %v", err) 223 } 224 } 225 226 func parsePackageAPIInfo() (apiVersions, error) { 227 var apiGlob string 228 if os.Getenv("GOROOT") == "" { 229 apiGlob = filepath.Join(build.Default.GOROOT, "api", "go*.txt") 230 } else { 231 apiGlob = filepath.Join(os.Getenv("GOROOT"), "api", "go*.txt") 232 } 233 234 files, err := filepath.Glob(apiGlob) 235 if err != nil { 236 return nil, err 237 } 238 239 // Process files in go1.n, go1.n-1, ..., go1.2, go1.1, go1 order. 240 // 241 // It's rare, but the signature of an identifier may change 242 // (for example, a function that accepts a type replaced with 243 // an alias), and so an existing symbol may show up again in 244 // a later api/go1.N.txt file. Parsing in reverse version 245 // order means we end up with the earliest version of Go 246 // when the symbol was added. See golang.org/issue/44081. 247 // 248 ver := func(name string) int { 249 base := filepath.Base(name) 250 ver := strings.TrimPrefix(strings.TrimSuffix(base, ".txt"), "go1.") 251 if ver == "go1" { 252 return 0 253 } 254 v, _ := strconv.Atoi(ver) 255 return v 256 } 257 sort.Slice(files, func(i, j int) bool { return ver(files[i]) > ver(files[j]) }) 258 vp := new(versionParser) 259 for _, f := range files { 260 if err := vp.parseFile(f); err != nil { 261 return nil, err 262 } 263 } 264 return vp.res, nil 265 }