summaryrefslogtreecommitdiff
path: root/vendor/golang.org/x/tools/internal/modindex
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/golang.org/x/tools/internal/modindex')
-rw-r--r--vendor/golang.org/x/tools/internal/modindex/directories.go135
-rw-r--r--vendor/golang.org/x/tools/internal/modindex/index.go262
-rw-r--r--vendor/golang.org/x/tools/internal/modindex/lookup.go145
-rw-r--r--vendor/golang.org/x/tools/internal/modindex/modindex.go164
-rw-r--r--vendor/golang.org/x/tools/internal/modindex/symbols.go189
-rw-r--r--vendor/golang.org/x/tools/internal/modindex/types.go25
6 files changed, 920 insertions, 0 deletions
diff --git a/vendor/golang.org/x/tools/internal/modindex/directories.go b/vendor/golang.org/x/tools/internal/modindex/directories.go
new file mode 100644
index 000000000..1e1a02f23
--- /dev/null
+++ b/vendor/golang.org/x/tools/internal/modindex/directories.go
@@ -0,0 +1,135 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package modindex
+
+import (
+ "fmt"
+ "log"
+ "os"
+ "path/filepath"
+ "regexp"
+ "slices"
+ "strings"
+ "sync"
+ "time"
+
+ "golang.org/x/mod/semver"
+ "golang.org/x/tools/internal/gopathwalk"
+)
+
+type directory struct {
+ path Relpath
+ importPath string
+ version string // semantic version
+ syms []symbol
+}
+
+// filterDirs groups the directories by import path,
+// sorting the ones with the same import path by semantic version,
+// most recent first.
+func byImportPath(dirs []Relpath) (map[string][]*directory, error) {
+ ans := make(map[string][]*directory) // key is import path
+ for _, d := range dirs {
+ ip, sv, err := DirToImportPathVersion(d)
+ if err != nil {
+ return nil, err
+ }
+ ans[ip] = append(ans[ip], &directory{
+ path: d,
+ importPath: ip,
+ version: sv,
+ })
+ }
+ for k, v := range ans {
+ semanticSort(v)
+ ans[k] = v
+ }
+ return ans, nil
+}
+
+// sort the directories by semantic version, latest first
+func semanticSort(v []*directory) {
+ slices.SortFunc(v, func(l, r *directory) int {
+ if n := semver.Compare(l.version, r.version); n != 0 {
+ return -n // latest first
+ }
+ return strings.Compare(string(l.path), string(r.path))
+ })
+}
+
+// modCacheRegexp splits a relpathpath into module, module version, and package.
+var modCacheRegexp = regexp.MustCompile(`(.*)@([^/\\]*)(.*)`)
+
+// DirToImportPathVersion computes import path and semantic version
+func DirToImportPathVersion(dir Relpath) (string, string, error) {
+ m := modCacheRegexp.FindStringSubmatch(string(dir))
+ // m[1] is the module path
+ // m[2] is the version major.minor.patch(-<pre release identifier)
+ // m[3] is the rest of the package path
+ if len(m) != 4 {
+ return "", "", fmt.Errorf("bad dir %s", dir)
+ }
+ if !semver.IsValid(m[2]) {
+ return "", "", fmt.Errorf("bad semantic version %s", m[2])
+ }
+ // ToSlash is required for Windows.
+ return filepath.ToSlash(m[1] + m[3]), m[2], nil
+}
+
+// a region controls what directories to look at, for
+// updating the index incrementally, and for testing that.
+// (for testing one builds an index as of A, incrementally
+// updates it to B, and compares the result to an index build
+// as of B.)
+type region struct {
+ onlyAfter, onlyBefore time.Time
+ sync.Mutex
+ ans []Relpath
+}
+
+func findDirs(root string, onlyAfter, onlyBefore time.Time) []Relpath {
+ roots := []gopathwalk.Root{{Path: root, Type: gopathwalk.RootModuleCache}}
+ // TODO(PJW): adjust concurrency
+ opts := gopathwalk.Options{ModulesEnabled: true, Concurrency: 1 /* ,Logf: log.Printf*/}
+ betw := &region{
+ onlyAfter: onlyAfter,
+ onlyBefore: onlyBefore,
+ }
+ gopathwalk.WalkSkip(roots, betw.addDir, betw.skipDir, opts)
+ return betw.ans
+}
+
+func (r *region) addDir(rt gopathwalk.Root, dir string) {
+ // do we need to check times?
+ r.Lock()
+ defer r.Unlock()
+ x := filepath.ToSlash(string(toRelpath(Abspath(rt.Path), dir)))
+ r.ans = append(r.ans, toRelpath(Abspath(rt.Path), x))
+}
+
+func (r *region) skipDir(_ gopathwalk.Root, dir string) bool {
+ // The cache directory is already ignored in gopathwalk\
+ if filepath.Base(dir) == "internal" {
+ return true
+ }
+ if strings.Contains(dir, "toolchain@") {
+ return true
+ }
+ // don't look inside @ directories that are too old
+ if strings.Contains(filepath.Base(dir), "@") {
+ st, err := os.Stat(dir)
+ if err != nil {
+ log.Printf("can't stat dir %s %v", dir, err)
+ return true
+ }
+ if st.ModTime().Before(r.onlyAfter) {
+ return true
+ }
+ if st.ModTime().After(r.onlyBefore) {
+ return true
+ }
+ }
+ return false
+}
diff --git a/vendor/golang.org/x/tools/internal/modindex/index.go b/vendor/golang.org/x/tools/internal/modindex/index.go
new file mode 100644
index 000000000..27b6dd832
--- /dev/null
+++ b/vendor/golang.org/x/tools/internal/modindex/index.go
@@ -0,0 +1,262 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package modindex
+
+import (
+ "bufio"
+ "encoding/csv"
+ "errors"
+ "fmt"
+ "hash/crc64"
+ "io"
+ "io/fs"
+ "log"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+ "time"
+)
+
+/*
+The on-disk index is a text file.
+The first 3 lines are header information containing CurrentVersion,
+the value of GOMODCACHE, and the validity date of the index.
+(This is when the code started building the index.)
+Following the header are sections of lines, one section for each
+import path. These sections are sorted by package name.
+The first line of each section, marked by a leading :, contains
+the package name, the import path, the name of the directory relative
+to GOMODCACHE, and its semantic version.
+The rest of each section consists of one line per exported symbol.
+The lines are sorted by the symbol's name and contain the name,
+an indication of its lexical type (C, T, V, F), and if it is the
+name of a function, information about the signature.
+
+The fields in the section header lines are separated by commas, and
+in the unlikely event this would be confusing, the csv package is used
+to write (and read) them.
+
+In the lines containing exported names, C=const, V=var, T=type, F=func.
+If it is a func, the next field is the number of returned values,
+followed by pairs consisting of formal parameter names and types.
+All these fields are separated by spaces. Any spaces in a type
+(e.g., chan struct{}) are replaced by $s on the disk. The $s are
+turned back into spaces when read.
+
+Here is an index header (the comments are not part of the index):
+0 // version (of the index format)
+/usr/local/google/home/pjw/go/pkg/mod // GOMODCACHE
+2024-09-11 18:55:09 // validity date of the index
+
+Here is an index section:
+:yaml,gopkg.in/yaml.v1,gopkg.in/yaml.v1@v1.0.0-20140924161607-9f9df34309c0,v1.0.0-20140924161607-9f9df34309c0
+Getter T
+Marshal F 2 in interface{}
+Setter T
+Unmarshal F 1 in []byte out interface{}
+
+The package name is yaml, the import path is gopkg.in/yaml.v1.
+Getter and Setter are types, and Marshal and Unmarshal are functions.
+The latter returns one value and has two arguments, 'in' and 'out'
+whose types are []byte and interface{}.
+*/
+
+// CurrentVersion tells readers about the format of the index.
+const CurrentVersion int = 0
+
+// Index is returned by ReadIndex().
+type Index struct {
+ Version int
+ Cachedir Abspath // The directory containing the module cache
+ Changed time.Time // The index is up to date as of Changed
+ Entries []Entry
+}
+
+// An Entry contains information for an import path.
+type Entry struct {
+ Dir Relpath // directory in modcache
+ ImportPath string
+ PkgName string
+ Version string
+ //ModTime STime // is this useful?
+ Names []string // exported names and information
+}
+
+// ReadIndex reads the latest version of the on-disk index
+// for the cache directory cd.
+// It returns (nil, nil) if there is no index, but returns
+// a non-nil error if the index exists but could not be read.
+func ReadIndex(cachedir string) (*Index, error) {
+ cachedir, err := filepath.Abs(cachedir)
+ if err != nil {
+ return nil, err
+ }
+ cd := Abspath(cachedir)
+ dir, err := IndexDir()
+ if err != nil {
+ return nil, err
+ }
+ base := indexNameBase(cd)
+ iname := filepath.Join(dir, base)
+ buf, err := os.ReadFile(iname)
+ if err != nil {
+ if errors.Is(err, fs.ErrNotExist) {
+ return nil, nil
+ }
+ return nil, fmt.Errorf("cannot read %s: %w", iname, err)
+ }
+ fname := filepath.Join(dir, string(buf))
+ fd, err := os.Open(fname)
+ if err != nil {
+ return nil, err
+ }
+ defer fd.Close()
+ r := bufio.NewReader(fd)
+ ix, err := readIndexFrom(cd, r)
+ if err != nil {
+ return nil, err
+ }
+ return ix, nil
+}
+
+func readIndexFrom(cd Abspath, bx io.Reader) (*Index, error) {
+ b := bufio.NewScanner(bx)
+ var ans Index
+ // header
+ ok := b.Scan()
+ if !ok {
+ return nil, fmt.Errorf("unexpected scan error")
+ }
+ l := b.Text()
+ var err error
+ ans.Version, err = strconv.Atoi(l)
+ if err != nil {
+ return nil, err
+ }
+ if ans.Version != CurrentVersion {
+ return nil, fmt.Errorf("got version %d, expected %d", ans.Version, CurrentVersion)
+ }
+ if ok := b.Scan(); !ok {
+ return nil, fmt.Errorf("scanner error reading cachedir")
+ }
+ ans.Cachedir = Abspath(b.Text())
+ if ok := b.Scan(); !ok {
+ return nil, fmt.Errorf("scanner error reading index creation time")
+ }
+ // TODO(pjw): need to check that this is the expected cachedir
+ // so the tag should be passed in to this function
+ ans.Changed, err = time.ParseInLocation(time.DateTime, b.Text(), time.Local)
+ if err != nil {
+ return nil, err
+ }
+ var curEntry *Entry
+ for b.Scan() {
+ v := b.Text()
+ if v[0] == ':' {
+ if curEntry != nil {
+ ans.Entries = append(ans.Entries, *curEntry)
+ }
+ // as directories may contain commas and quotes, they need to be read as csv.
+ rdr := strings.NewReader(v[1:])
+ cs := csv.NewReader(rdr)
+ flds, err := cs.Read()
+ if err != nil {
+ return nil, err
+ }
+ if len(flds) != 4 {
+ return nil, fmt.Errorf("header contains %d fields, not 4: %q", len(v), v)
+ }
+ curEntry = &Entry{PkgName: flds[0], ImportPath: flds[1], Dir: toRelpath(cd, flds[2]), Version: flds[3]}
+ continue
+ }
+ curEntry.Names = append(curEntry.Names, v)
+ }
+ if curEntry != nil {
+ ans.Entries = append(ans.Entries, *curEntry)
+ }
+ if err := b.Err(); err != nil {
+ return nil, fmt.Errorf("scanner failed %v", err)
+ }
+ return &ans, nil
+}
+
+// write the index as a text file
+func writeIndex(cachedir Abspath, ix *Index) error {
+ dir, err := IndexDir()
+ if err != nil {
+ return err
+ }
+ ipat := fmt.Sprintf("index-%d-*", CurrentVersion)
+ fd, err := os.CreateTemp(dir, ipat)
+ if err != nil {
+ return err // can this happen?
+ }
+ defer fd.Close()
+ if err := writeIndexToFile(ix, fd); err != nil {
+ return err
+ }
+ content := fd.Name()
+ content = filepath.Base(content)
+ base := indexNameBase(cachedir)
+ nm := filepath.Join(dir, base)
+ err = os.WriteFile(nm, []byte(content), 0666)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func writeIndexToFile(x *Index, fd *os.File) error {
+ cnt := 0
+ w := bufio.NewWriter(fd)
+ fmt.Fprintf(w, "%d\n", x.Version)
+ fmt.Fprintf(w, "%s\n", x.Cachedir)
+ // round the time down
+ tm := x.Changed.Add(-time.Second / 2)
+ fmt.Fprintf(w, "%s\n", tm.Format(time.DateTime))
+ for _, e := range x.Entries {
+ if e.ImportPath == "" {
+ continue // shouldn't happen
+ }
+ // PJW: maybe always write these headers as csv?
+ if strings.ContainsAny(string(e.Dir), ",\"") {
+ log.Printf("DIR: %s", e.Dir)
+ cw := csv.NewWriter(w)
+ cw.Write([]string{":" + e.PkgName, e.ImportPath, string(e.Dir), e.Version})
+ cw.Flush()
+ } else {
+ fmt.Fprintf(w, ":%s,%s,%s,%s\n", e.PkgName, e.ImportPath, e.Dir, e.Version)
+ }
+ for _, x := range e.Names {
+ fmt.Fprintf(w, "%s\n", x)
+ cnt++
+ }
+ }
+ if err := w.Flush(); err != nil {
+ return err
+ }
+ return nil
+}
+
+// tests can override this
+var IndexDir = indexDir
+
+// IndexDir computes the directory containing the index
+func indexDir() (string, error) {
+ dir, err := os.UserCacheDir()
+ if err != nil {
+ return "", fmt.Errorf("cannot open UserCacheDir, %w", err)
+ }
+ return filepath.Join(dir, "go", "imports"), nil
+}
+
+// return the base name of the file containing the name of the current index
+func indexNameBase(cachedir Abspath) string {
+ // crc64 is a way to convert path names into 16 hex digits.
+ h := crc64.Checksum([]byte(cachedir), crc64.MakeTable(crc64.ECMA))
+ fname := fmt.Sprintf("index-name-%d-%016x", CurrentVersion, h)
+ return fname
+}
diff --git a/vendor/golang.org/x/tools/internal/modindex/lookup.go b/vendor/golang.org/x/tools/internal/modindex/lookup.go
new file mode 100644
index 000000000..29d4e3d7a
--- /dev/null
+++ b/vendor/golang.org/x/tools/internal/modindex/lookup.go
@@ -0,0 +1,145 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package modindex
+
+import (
+ "slices"
+ "strconv"
+ "strings"
+)
+
+type Candidate struct {
+ PkgName string
+ Name string
+ Dir string
+ ImportPath string
+ Type LexType
+ // information for Funcs
+ Results int16 // how many results
+ Sig []Field // arg names and types
+}
+
+type Field struct {
+ Arg, Type string
+}
+
+type LexType int8
+
+const (
+ Const LexType = iota
+ Var
+ Type
+ Func
+)
+
+// Lookup finds all the symbols in the index with the given PkgName and name.
+// If prefix is true, it finds all of these with name as a prefix.
+func (ix *Index) Lookup(pkg, name string, prefix bool) []Candidate {
+ loc, ok := slices.BinarySearchFunc(ix.Entries, pkg, func(e Entry, pkg string) int {
+ return strings.Compare(e.PkgName, pkg)
+ })
+ if !ok {
+ return nil // didn't find the package
+ }
+ var ans []Candidate
+ // loc is the first entry for this package name, but there may be severeal
+ for i := loc; i < len(ix.Entries); i++ {
+ e := ix.Entries[i]
+ if e.PkgName != pkg {
+ break // end of sorted package names
+ }
+ nloc, ok := slices.BinarySearchFunc(e.Names, name, func(s string, name string) int {
+ if strings.HasPrefix(s, name) {
+ return 0
+ }
+ if s < name {
+ return -1
+ }
+ return 1
+ })
+ if !ok {
+ continue // didn't find the name, nor any symbols with name as a prefix
+ }
+ for j := nloc; j < len(e.Names); j++ {
+ nstr := e.Names[j]
+ // benchmarks show this makes a difference when there are a lot of Possibilities
+ flds := fastSplit(nstr)
+ if !(flds[0] == name || prefix && strings.HasPrefix(flds[0], name)) {
+ // past range of matching Names
+ break
+ }
+ if len(flds) < 2 {
+ continue // should never happen
+ }
+ px := Candidate{
+ PkgName: pkg,
+ Name: flds[0],
+ Dir: string(e.Dir),
+ ImportPath: e.ImportPath,
+ Type: asLexType(flds[1][0]),
+ }
+ if flds[1] == "F" {
+ n, err := strconv.Atoi(flds[2])
+ if err != nil {
+ continue // should never happen
+ }
+ px.Results = int16(n)
+ if len(flds) >= 4 {
+ sig := strings.Split(flds[3], " ")
+ for i := 0; i < len(sig); i++ {
+ // $ cannot otherwise occur. removing the spaces
+ // almost works, but for chan struct{}, e.g.
+ sig[i] = strings.Replace(sig[i], "$", " ", -1)
+ }
+ px.Sig = toFields(sig)
+ }
+ }
+ ans = append(ans, px)
+ }
+ }
+ return ans
+}
+
+func toFields(sig []string) []Field {
+ ans := make([]Field, len(sig)/2)
+ for i := 0; i < len(ans); i++ {
+ ans[i] = Field{Arg: sig[2*i], Type: sig[2*i+1]}
+ }
+ return ans
+}
+
+// benchmarks show this is measurably better than strings.Split
+func fastSplit(x string) []string {
+ ans := make([]string, 0, 4)
+ nxt := 0
+ start := 0
+ for i := 0; i < len(x); i++ {
+ if x[i] != ' ' {
+ continue
+ }
+ ans = append(ans, x[start:i])
+ nxt++
+ start = i + 1
+ if nxt >= 3 {
+ break
+ }
+ }
+ ans = append(ans, x[start:])
+ return ans
+}
+
+func asLexType(c byte) LexType {
+ switch c {
+ case 'C':
+ return Const
+ case 'V':
+ return Var
+ case 'T':
+ return Type
+ case 'F':
+ return Func
+ }
+ return -1
+}
diff --git a/vendor/golang.org/x/tools/internal/modindex/modindex.go b/vendor/golang.org/x/tools/internal/modindex/modindex.go
new file mode 100644
index 000000000..355a53e71
--- /dev/null
+++ b/vendor/golang.org/x/tools/internal/modindex/modindex.go
@@ -0,0 +1,164 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package modindex contains code for building and searching an index to
+// the Go module cache. The directory containing the index, returned by
+// IndexDir(), contains a file index-name-<ver> that contains the name
+// of the current index. We believe writing that short file is atomic.
+// ReadIndex reads that file to get the file name of the index.
+// WriteIndex writes an index with a unique name and then
+// writes that name into a new version of index-name-<ver>.
+// (<ver> stands for the CurrentVersion of the index format.)
+package modindex
+
+import (
+ "path/filepath"
+ "slices"
+ "strings"
+ "time"
+
+ "golang.org/x/mod/semver"
+)
+
+// Create always creates a new index for the go module cache that is in cachedir.
+func Create(cachedir string) error {
+ _, err := indexModCache(cachedir, true)
+ return err
+}
+
+// Update the index for the go module cache that is in cachedir,
+// If there is no existing index it will build one.
+// If there are changed directories since the last index, it will
+// write a new one and return true. Otherwise it returns false.
+func Update(cachedir string) (bool, error) {
+ return indexModCache(cachedir, false)
+}
+
+// indexModCache writes an index current as of when it is called.
+// If clear is true the index is constructed from all of GOMODCACHE
+// otherwise the index is constructed from the last previous index
+// and the updates to the cache. It returns true if it wrote an index,
+// false otherwise.
+func indexModCache(cachedir string, clear bool) (bool, error) {
+ cachedir, err := filepath.Abs(cachedir)
+ if err != nil {
+ return false, err
+ }
+ cd := Abspath(cachedir)
+ future := time.Now().Add(24 * time.Hour) // safely in the future
+ ok, err := modindexTimed(future, cd, clear)
+ if err != nil {
+ return false, err
+ }
+ return ok, nil
+}
+
+// modindexTimed writes an index current as of onlyBefore.
+// If clear is true the index is constructed from all of GOMODCACHE
+// otherwise the index is constructed from the last previous index
+// and all the updates to the cache before onlyBefore.
+// It returns true if it wrote a new index, false if it wrote nothing.
+func modindexTimed(onlyBefore time.Time, cachedir Abspath, clear bool) (bool, error) {
+ var curIndex *Index
+ if !clear {
+ var err error
+ curIndex, err = ReadIndex(string(cachedir))
+ if clear && err != nil {
+ return false, err
+ }
+ // TODO(pjw): check that most of those directories still exist
+ }
+ cfg := &work{
+ onlyBefore: onlyBefore,
+ oldIndex: curIndex,
+ cacheDir: cachedir,
+ }
+ if curIndex != nil {
+ cfg.onlyAfter = curIndex.Changed
+ }
+ if err := cfg.buildIndex(); err != nil {
+ return false, err
+ }
+ if len(cfg.newIndex.Entries) == 0 && curIndex != nil {
+ // no changes from existing curIndex, don't write a new index
+ return false, nil
+ }
+ if err := cfg.writeIndex(); err != nil {
+ return false, err
+ }
+ return true, nil
+}
+
+type work struct {
+ onlyBefore time.Time // do not use directories later than this
+ onlyAfter time.Time // only interested in directories after this
+ // directories from before onlyAfter come from oldIndex
+ oldIndex *Index
+ newIndex *Index
+ cacheDir Abspath
+}
+
+func (w *work) buildIndex() error {
+ // The effective date of the new index should be at least
+ // slightly earlier than when the directories are scanned
+ // so set it now.
+ w.newIndex = &Index{Changed: time.Now(), Cachedir: w.cacheDir}
+ dirs := findDirs(string(w.cacheDir), w.onlyAfter, w.onlyBefore)
+ if len(dirs) == 0 {
+ return nil
+ }
+ newdirs, err := byImportPath(dirs)
+ if err != nil {
+ return err
+ }
+ // for each import path it might occur only in newdirs,
+ // only in w.oldIndex, or in both.
+ // If it occurs in both, use the semantically later one
+ if w.oldIndex != nil {
+ for _, e := range w.oldIndex.Entries {
+ found, ok := newdirs[e.ImportPath]
+ if !ok {
+ w.newIndex.Entries = append(w.newIndex.Entries, e)
+ continue // use this one, there is no new one
+ }
+ if semver.Compare(found[0].version, e.Version) > 0 {
+ // use the new one
+ } else {
+ // use the old one, forget the new one
+ w.newIndex.Entries = append(w.newIndex.Entries, e)
+ delete(newdirs, e.ImportPath)
+ }
+ }
+ }
+ // get symbol information for all the new diredtories
+ getSymbols(w.cacheDir, newdirs)
+ // assemble the new index entries
+ for k, v := range newdirs {
+ d := v[0]
+ pkg, names := processSyms(d.syms)
+ if pkg == "" {
+ continue // PJW: does this ever happen?
+ }
+ entry := Entry{
+ PkgName: pkg,
+ Dir: d.path,
+ ImportPath: k,
+ Version: d.version,
+ Names: names,
+ }
+ w.newIndex.Entries = append(w.newIndex.Entries, entry)
+ }
+ // sort the entries in the new index
+ slices.SortFunc(w.newIndex.Entries, func(l, r Entry) int {
+ if n := strings.Compare(l.PkgName, r.PkgName); n != 0 {
+ return n
+ }
+ return strings.Compare(l.ImportPath, r.ImportPath)
+ })
+ return nil
+}
+
+func (w *work) writeIndex() error {
+ return writeIndex(w.cacheDir, w.newIndex)
+}
diff --git a/vendor/golang.org/x/tools/internal/modindex/symbols.go b/vendor/golang.org/x/tools/internal/modindex/symbols.go
new file mode 100644
index 000000000..2e285ed99
--- /dev/null
+++ b/vendor/golang.org/x/tools/internal/modindex/symbols.go
@@ -0,0 +1,189 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package modindex
+
+import (
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "go/types"
+ "os"
+ "path/filepath"
+ "slices"
+ "strings"
+
+ "golang.org/x/sync/errgroup"
+)
+
+// The name of a symbol contains information about the symbol:
+// <name> T for types
+// <name> C for consts
+// <name> V for vars
+// and for funcs: <name> F <num of return values> (<arg-name> <arg-type>)*
+// any spaces in <arg-type> are replaced by $s so that the fields
+// of the name are space separated
+type symbol struct {
+ pkg string // name of the symbols's package
+ name string // declared name
+ kind string // T, C, V, or F
+ sig string // signature information, for F
+}
+
+// find the symbols for the best directories
+func getSymbols(cd Abspath, dirs map[string][]*directory) {
+ var g errgroup.Group
+ g.SetLimit(-1) // maybe throttle this some day
+ for _, vv := range dirs {
+ // throttling some day?
+ d := vv[0]
+ g.Go(func() error {
+ thedir := filepath.Join(string(cd), string(d.path))
+ mode := parser.SkipObjectResolution
+
+ fi, err := os.ReadDir(thedir)
+ if err != nil {
+ return nil // log this someday?
+ }
+ for _, fx := range fi {
+ if !strings.HasSuffix(fx.Name(), ".go") || strings.HasSuffix(fx.Name(), "_test.go") {
+ continue
+ }
+ fname := filepath.Join(thedir, fx.Name())
+ tr, err := parser.ParseFile(token.NewFileSet(), fname, nil, mode)
+ if err != nil {
+ continue // ignore errors, someday log them?
+ }
+ d.syms = append(d.syms, getFileExports(tr)...)
+ }
+ return nil
+ })
+ }
+ g.Wait()
+}
+
+func getFileExports(f *ast.File) []symbol {
+ pkg := f.Name.Name
+ if pkg == "main" {
+ return nil
+ }
+ var ans []symbol
+ // should we look for //go:build ignore?
+ for _, decl := range f.Decls {
+ switch decl := decl.(type) {
+ case *ast.FuncDecl:
+ if decl.Recv != nil {
+ // ignore methods, as we are completing package selections
+ continue
+ }
+ name := decl.Name.Name
+ dtype := decl.Type
+ // not looking at dtype.TypeParams. That is, treating
+ // generic functions just like non-generic ones.
+ sig := dtype.Params
+ kind := "F"
+ result := []string{fmt.Sprintf("%d", dtype.Results.NumFields())}
+ for _, x := range sig.List {
+ // This code creates a string representing the type.
+ // TODO(pjw): it may be fragile:
+ // 1. x.Type could be nil, perhaps in ill-formed code
+ // 2. ExprString might someday change incompatibly to
+ // include struct tags, which can be arbitrary strings
+ if x.Type == nil {
+ // Can this happen without a parse error? (Files with parse
+ // errors are ignored in getSymbols)
+ continue // maybe report this someday
+ }
+ tp := types.ExprString(x.Type)
+ if len(tp) == 0 {
+ // Can this happen?
+ continue // maybe report this someday
+ }
+ // This is only safe if ExprString never returns anything with a $
+ // The only place a $ can occur seems to be in a struct tag, which
+ // can be an arbitrary string literal, and ExprString does not presently
+ // print struct tags. So for this to happen the type of a formal parameter
+ // has to be a explict struct, e.g. foo(x struct{a int "$"}) and ExprString
+ // would have to show the struct tag. Even testing for this case seems
+ // a waste of effort, but let's not ignore such pathologies
+ if strings.Contains(tp, "$") {
+ continue
+ }
+ tp = strings.Replace(tp, " ", "$", -1)
+ if len(x.Names) == 0 {
+ result = append(result, "_")
+ result = append(result, tp)
+ } else {
+ for _, y := range x.Names {
+ result = append(result, y.Name)
+ result = append(result, tp)
+ }
+ }
+ }
+ sigs := strings.Join(result, " ")
+ if s := newsym(pkg, name, kind, sigs); s != nil {
+ ans = append(ans, *s)
+ }
+ case *ast.GenDecl:
+ switch decl.Tok {
+ case token.CONST, token.VAR:
+ tp := "V"
+ if decl.Tok == token.CONST {
+ tp = "C"
+ }
+ for _, sp := range decl.Specs {
+ for _, x := range sp.(*ast.ValueSpec).Names {
+ if s := newsym(pkg, x.Name, tp, ""); s != nil {
+ ans = append(ans, *s)
+ }
+ }
+ }
+ case token.TYPE:
+ for _, sp := range decl.Specs {
+ if s := newsym(pkg, sp.(*ast.TypeSpec).Name.Name, "T", ""); s != nil {
+ ans = append(ans, *s)
+ }
+ }
+ }
+ }
+ }
+ return ans
+}
+
+func newsym(pkg, name, kind, sig string) *symbol {
+ if len(name) == 0 || !ast.IsExported(name) {
+ return nil
+ }
+ sym := symbol{pkg: pkg, name: name, kind: kind, sig: sig}
+ return &sym
+}
+
+// return the package name and the value for the symbols.
+// if there are multiple packages, choose one arbitrarily
+// the returned slice is sorted lexicographically
+func processSyms(syms []symbol) (string, []string) {
+ if len(syms) == 0 {
+ return "", nil
+ }
+ slices.SortFunc(syms, func(l, r symbol) int {
+ return strings.Compare(l.name, r.name)
+ })
+ pkg := syms[0].pkg
+ var names []string
+ for _, s := range syms {
+ var nx string
+ if s.pkg == pkg {
+ if s.sig != "" {
+ nx = fmt.Sprintf("%s %s %s", s.name, s.kind, s.sig)
+ } else {
+ nx = fmt.Sprintf("%s %s", s.name, s.kind)
+ }
+ names = append(names, nx)
+ } else {
+ continue // PJW: do we want to keep track of these?
+ }
+ }
+ return pkg, names
+}
diff --git a/vendor/golang.org/x/tools/internal/modindex/types.go b/vendor/golang.org/x/tools/internal/modindex/types.go
new file mode 100644
index 000000000..ece448863
--- /dev/null
+++ b/vendor/golang.org/x/tools/internal/modindex/types.go
@@ -0,0 +1,25 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package modindex
+
+import (
+ "strings"
+)
+
+// some special types to avoid confusions
+
+// distinguish various types of directory names. It's easy to get confused.
+type Abspath string // absolute paths
+type Relpath string // paths with GOMODCACHE prefix removed
+
+func toRelpath(cachedir Abspath, s string) Relpath {
+ if strings.HasPrefix(s, string(cachedir)) {
+ if s == string(cachedir) {
+ return Relpath("")
+ }
+ return Relpath(s[len(cachedir)+1:])
+ }
+ return Relpath(s)
+}