GoPLS Viewer

Home|gopls/godoc/versions.go
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    "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.
27type 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.
34type 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").
52func (v apiVersionssinceVersionFunc(kindreceivernamepkg stringstring {
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.
67type 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 in its rows field.
76type 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.
86func (vp *versionParserparseFile(name stringerror {
87    ferr := 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        rowok := parseRow(sc.Text())
99        if !ok {
100            continue
101        }
102        if vp.res == nil {
103            vp.res = make(apiVersions)
104        }
105        pkgiok := vp.res[row.pkg]
106        if !ok {
107            pkgi = pkgAPIVersions{
108                typeSince:   make(map[string]string),
109                methodSincemake(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.funcSincerow.name)
119                break
120            }
121            pkgi.funcSince[row.name] = ver
122        case "type":
123            if ver == "1" {
124                delete(pkgi.typeSincerow.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
151func parseRow(s string) (vr versionedRowok 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 runebool { return !(unicode.IsLetter(r) || r == '/' || unicode.IsDigit(r)) })
158    if endPkg == -1 {
159        return
160    }
161    vr.pkgrest = 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.namerest = rest[:sp], rest[sp+1:]
178        if !strings.HasPrefix(rest"struct, ") {
179            vr.kind = "type"
180            return vrtrue
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 vrtrue
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 vrtrue
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 vrtrue
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.
217func (c *CorpusInitVersionInfo() {
218    var err error
219    c.pkgAPIInfoerr = 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
226func parsePackageAPIInfo() (apiVersionserror) {
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    fileserr := filepath.Glob(apiGlob)
235    if err != nil {
236        return nilerr
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 stringint {
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(ij intbool { 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 nilerr
262        }
263    }
264    return vp.resnil
265}
266
MembersX
parseRow.vr
parseRow.BlockStmt.i
parsePackageAPIInfo.vp
apiVersions
pkgAPIVersions.methodSince
apiVersions.sinceVersionFunc
versionedRow
versionedRow.structName
parseRow
versionParser.res
versionParser.parseFile.f
Corpus.InitVersionInfo.c
parsePackageAPIInfo.RangeStmt_7503.BlockStmt.err
parseRow.endPkg
Corpus.InitVersionInfo.err
pkgAPIVersions.fieldSince
apiVersions.sinceVersionFunc.pkg
versionedRow.kind
versionedRow.recv
versionParser.parseFile.base
parseRow.ok
parsePackageAPIInfo
parsePackageAPIInfo.files
parsePackageAPIInfo.err
parsePackageAPIInfo.BlockStmt._
apiVersions.sinceVersionFunc.kind
apiVersions.sinceVersionFunc.receiver
apiVersions.sinceVersionFunc.name
parsePackageAPIInfo.BlockStmt.base
versionParser.parseFile.BlockStmt.ok
parseRow.s
apiVersions.sinceVersionFunc.v
versionedRow.name
versionParser.parseFile.vp
versionParser.parseFile
versionParser.parseFile.name
versionParser.parseFile.ver
parseRow.BlockStmt.sp
parseRow.BlockStmt.paren
parsePackageAPIInfo.apiGlob
parsePackageAPIInfo.BlockStmt.v
pkgAPIVersions.funcSince
versionParser.parseFile.err
Corpus.InitVersionInfo
parsePackageAPIInfo.BlockStmt.ver
parsePackageAPIInfo.RangeStmt_7503.f
pkgAPIVersions
pkgAPIVersions.typeSince
versionedRow.pkg
versionParser
versionParser.parseFile.sc
versionParser.parseFile.BlockStmt.row
Members
X