...

Source file src/golang.org/x/tools/godoc/versions.go

Documentation: golang.org/x/tools/godoc

     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 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  }
   266  

View as plain text