Initialize module and dependencies

This commit is contained in:
dwrz
2026-02-13 14:59:42 +00:00
commit 0740968bca
390 changed files with 131652 additions and 0 deletions

View File

@@ -0,0 +1,131 @@
// 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"
"strings"
"sync"
"time"
"golang.org/x/mod/semver"
"golang.org/x/tools/internal/gopathwalk"
)
type directory struct {
path string // relative to GOMODCACHE
importPath string
version string // semantic version
}
// bestDirByImportPath returns the best directory for each import
// path, where "best" means most recent semantic version. These import
// paths are inferred from the GOMODCACHE-relative dir names in dirs.
func bestDirByImportPath(dirs []string) (map[string]directory, error) {
dirsByPath := make(map[string]directory)
for _, dir := range dirs {
importPath, version, err := dirToImportPathVersion(dir)
if err != nil {
return nil, err
}
new := directory{
path: dir,
importPath: importPath,
version: version,
}
if old, ok := dirsByPath[importPath]; !ok || compareDirectory(new, old) < 0 {
dirsByPath[importPath] = new
}
}
return dirsByPath, nil
}
// compareDirectory defines an ordering of path@version directories,
// by descending version, then by ascending path.
func compareDirectory(x, y directory) int {
if sign := -semver.Compare(x.version, y.version); sign != 0 {
return sign // latest first
}
return strings.Compare(string(x.path), string(y.path))
}
// modCacheRegexp splits a relpathpath into module, module version, and package.
var modCacheRegexp = regexp.MustCompile(`(.*)@([^/\\]*)(.*)`)
// dirToImportPathVersion computes import path and semantic version
// from a GOMODCACHE-relative directory name.
func dirToImportPathVersion(dir string) (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 to convert Windows file paths
// into Go package import paths.
return filepath.ToSlash(m[1] + m[3]), m[2], nil
}
// findDirs returns an unordered list of relevant package directories,
// relative to the specified module cache root. The result includes only
// module dirs whose mtime is within (start, end).
func findDirs(root string, start, end time.Time) []string {
var (
resMu sync.Mutex
res []string
)
addDir := func(root gopathwalk.Root, dir string) {
// TODO(pjw): do we need to check times?
resMu.Lock()
defer resMu.Unlock()
res = append(res, relative(root.Path, dir))
}
skipDir := func(_ gopathwalk.Root, dir string) bool {
// The cache directory is already ignored in gopathwalk.
if filepath.Base(dir) == "internal" {
return true
}
// Skip toolchains.
if strings.Contains(dir, "toolchain@") {
return true
}
// Don't look inside @ directories that are too old/new.
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
}
mtime := st.ModTime()
return mtime.Before(start) || mtime.After(end)
}
return false
}
// TODO(adonovan): parallelize this. Even with a hot buffer cache,
// find $(go env GOMODCACHE) -type d
// can easily take up a minute.
roots := []gopathwalk.Root{{Path: root, Type: gopathwalk.RootModuleCache}}
gopathwalk.WalkSkip(roots, addDir, skipDir, gopathwalk.Options{
ModulesEnabled: true,
Concurrency: 1, // TODO(pjw): adjust concurrency
// Logf: log.Printf,
})
return res
}

292
vendor/golang.org/x/tools/internal/modindex/index.go generated vendored Normal file
View File

@@ -0,0 +1,292 @@
// 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"
"crypto/sha256"
"encoding/csv"
"fmt"
"io"
"os"
"path/filepath"
"strconv"
"strings"
"testing"
"time"
)
/*
The on-disk index ("payload") 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 [Read].
type Index struct {
Version int
GOMODCACHE string // absolute path of Go module cache dir
ValidAt time.Time // moment at which the index was up to date
Entries []Entry
}
func (ix *Index) String() string {
return fmt.Sprintf("Index(%s v%d has %d entries at %v)",
ix.GOMODCACHE, ix.Version, len(ix.Entries), ix.ValidAt)
}
// An Entry contains information for an import path.
type Entry struct {
Dir string // package directory relative to GOMODCACHE; uses OS path separator
ImportPath string
PkgName string
Version string
Names []string // exported names and information
}
// IndexDir is where the module index is stored.
// Each logical index entry consists of a pair of files:
//
// - the "payload" (index-VERSION-XXX), whose name is
// randomized, holds the actual index; and
// - the "link" (index-name-VERSION-HASH),
// whose name is predictable, contains the
// name of the payload file.
//
// Since the link file is small (<512B),
// reads and writes to it may be assumed atomic.
var IndexDir string = func() string {
var dir string
if testing.Testing() {
dir = os.TempDir()
} else {
var err error
dir, err = os.UserCacheDir()
// shouldn't happen, but TempDir is better than
// creating ./goimports
if err != nil {
dir = os.TempDir()
}
}
dir = filepath.Join(dir, "goimports")
if err := os.MkdirAll(dir, 0777); err != nil {
dir = "" // #75505, people complain about the error message
}
return dir
}()
// Read reads the latest version of the on-disk index
// for the specified Go module cache directory.
// If there is no index, it returns a nil Index and an fs.ErrNotExist error.
func Read(gomodcache string) (*Index, error) {
gomodcache, err := filepath.Abs(gomodcache)
if err != nil {
return nil, err
}
if IndexDir == "" {
return nil, os.ErrNotExist
}
// Read the "link" file for the specified gomodcache directory.
// It names the payload file.
content, err := os.ReadFile(filepath.Join(IndexDir, linkFileBasename(gomodcache)))
if err != nil {
return nil, err
}
payloadFile := filepath.Join(IndexDir, string(content))
// Read the index out of the payload file.
f, err := os.Open(payloadFile)
if err != nil {
return nil, err
}
defer f.Close()
return readIndexFrom(gomodcache, bufio.NewReader(f))
}
func readIndexFrom(gomodcache string, r io.Reader) (*Index, error) {
scan := bufio.NewScanner(r)
// version
if !scan.Scan() {
return nil, fmt.Errorf("unexpected scan error: %v", scan.Err())
}
version, err := strconv.Atoi(scan.Text())
if err != nil {
return nil, err
}
if version != CurrentVersion {
return nil, fmt.Errorf("got version %d, expected %d", version, CurrentVersion)
}
// gomodcache
if !scan.Scan() {
return nil, fmt.Errorf("scanner error reading module cache dir: %v", scan.Err())
}
// TODO(pjw): need to check that this is the expected cache dir
// so the tag should be passed in to this function
if dir := string(scan.Text()); dir != gomodcache {
return nil, fmt.Errorf("index file GOMODCACHE mismatch: got %q, want %q", dir, gomodcache)
}
// changed
if !scan.Scan() {
return nil, fmt.Errorf("scanner error reading index creation time: %v", scan.Err())
}
changed, err := time.ParseInLocation(time.DateTime, scan.Text(), time.Local)
if err != nil {
return nil, err
}
// entries
var (
curEntry *Entry
entries []Entry
)
for scan.Scan() {
v := scan.Text()
if v[0] == ':' {
if curEntry != nil {
entries = append(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: relative(gomodcache, flds[2]),
Version: flds[3],
}
continue
}
curEntry.Names = append(curEntry.Names, v)
}
if err := scan.Err(); err != nil {
return nil, fmt.Errorf("scanner failed while reading modindex entry: %v", err)
}
if curEntry != nil {
entries = append(entries, *curEntry)
}
return &Index{
Version: version,
GOMODCACHE: gomodcache,
ValidAt: changed,
Entries: entries,
}, nil
}
// write writes the index file and updates the index directory to refer to it.
func write(gomodcache string, ix *Index) error {
if IndexDir == "" {
return os.ErrNotExist
}
// Write the index into a payload file with a fresh name.
f, err := os.CreateTemp(IndexDir, fmt.Sprintf("index-%d-*", CurrentVersion))
if err != nil {
return err // e.g. disk full, or index dir deleted
}
if err := writeIndexToFile(ix, bufio.NewWriter(f)); err != nil {
_ = f.Close() // ignore error
return err
}
if err := f.Close(); err != nil {
return err
}
// Write the name of the payload file into a link file.
indexDirFile := filepath.Join(IndexDir, linkFileBasename(gomodcache))
content := []byte(filepath.Base(f.Name()))
return os.WriteFile(indexDirFile, content, 0666)
}
func writeIndexToFile(x *Index, w *bufio.Writer) error {
fmt.Fprintf(w, "%d\n", x.Version)
fmt.Fprintf(w, "%s\n", x.GOMODCACHE)
tm := x.ValidAt.Truncate(time.Second) // round the time down
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), ",\"") {
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)
}
}
return w.Flush()
}
// linkFileBasename returns the base name of the link file in the
// index directory that holds the name of the payload file for the
// specified (absolute) Go module cache dir.
func linkFileBasename(gomodcache string) string {
// Note: coupled to logic in ./gomodindex/cmd.go. TODO: factor.
h := sha256.Sum256([]byte(gomodcache)) // collision-resistant hash
return fmt.Sprintf("index-name-%d-%032x", CurrentVersion, h)
}
func relative(base, file string) string {
if rel, err := filepath.Rel(base, file); err == nil {
return rel
}
return file
}

184
vendor/golang.org/x/tools/internal/modindex/lookup.go generated vendored Normal file
View File

@@ -0,0 +1,184 @@
// 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"
"golang.org/x/mod/module"
)
type Candidate struct {
PkgName string
Name string
Dir string
ImportPath string
Type LexType
Deprecated bool
// 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
)
// LookupAll only returns those Candidates whose import path
// finds all the names.
func (ix *Index) LookupAll(pkgName string, names ...string) map[string][]Candidate {
// this can be made faster when benchmarks show that it needs to be
names = uniquify(names)
byImpPath := make(map[string][]Candidate)
for _, nm := range names {
cands := ix.Lookup(pkgName, nm, false)
for _, c := range cands {
byImpPath[c.ImportPath] = append(byImpPath[c.ImportPath], c)
}
}
for k, v := range byImpPath {
if len(v) != len(names) {
delete(byImpPath, k)
}
}
return byImpPath
}
// remove duplicates
func uniquify(in []string) []string {
if len(in) == 0 {
return in
}
in = slices.Clone(in)
slices.Sort(in)
return slices.Compact(in)
}
// 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(pkgName, name string, prefix bool) []Candidate {
loc, ok := slices.BinarySearchFunc(ix.Entries, pkgName, func(e Entry, pkg string) int {
return strings.Compare(e.PkgName, pkgName)
})
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 several
for i := loc; i < len(ix.Entries); i++ {
e := ix.Entries[i]
if e.PkgName != pkgName {
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
}
impPath, err := module.UnescapePath(e.ImportPath)
if err != nil {
continue
}
px := Candidate{
PkgName: pkgName,
Name: flds[0],
Dir: string(e.Dir),
ImportPath: impPath,
Type: asLexType(flds[1][0]),
Deprecated: len(flds[1]) > 1 && flds[1][1] == 'D',
}
if px.Type == Func {
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 := range sig {
// $ 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 := range ans {
ans[i] = Field{Arg: sig[2*i], Type: sig[2*i+1]}
}
return ans
}
// benchmarks show this is measurably better than strings.Split
// split into first 4 fields separated by single space
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
}

119
vendor/golang.org/x/tools/internal/modindex/modindex.go generated vendored Normal file
View File

@@ -0,0 +1,119 @@
// 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] of the Go module cache.
package modindex
// 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.
// [Read] 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.)
import (
"maps"
"os"
"path/filepath"
"slices"
"strings"
"time"
"golang.org/x/mod/semver"
)
// Update updates the index for the specified Go
// module cache directory, creating it as needed.
// On success it returns the current index.
func Update(gomodcache string) (*Index, error) {
prev, err := Read(gomodcache)
if err != nil {
if !os.IsNotExist(err) {
return nil, err
}
prev = nil
}
return update(gomodcache, prev)
}
// update builds, writes, and returns the current index.
//
// If old is nil, the new index is built from all of GOMODCACHE;
// otherwise it is built from the old index plus cache updates
// since the previous index's time.
func update(gomodcache string, old *Index) (*Index, error) {
gomodcache, err := filepath.Abs(gomodcache)
if err != nil {
return nil, err
}
new, changed, err := build(gomodcache, old)
if err != nil {
return nil, err
}
if old == nil || changed {
if err := write(gomodcache, new); err != nil {
return nil, err
}
}
return new, nil
}
// build returns a new index for the specified Go module cache (an
// absolute path).
//
// If an old index is provided, only directories more recent than it
// that it are scanned; older directories are provided by the old
// Index.
//
// The boolean result indicates whether new entries were found.
func build(gomodcache string, old *Index) (*Index, bool, error) {
// Set the time window.
var start time.Time // = dawn of time
if old != nil {
start = old.ValidAt
}
now := time.Now()
end := now.Add(24 * time.Hour) // safely in the future
// Enumerate GOMODCACHE package directories.
// Choose the best (latest) package for each import path.
pkgDirs := findDirs(gomodcache, start, end)
dirByPath, err := bestDirByImportPath(pkgDirs)
if err != nil {
return nil, false, err
}
// For each import path it might occur only in
// dirByPath, only in old, or in both.
// If both, use the semantically later one.
var entries []Entry
if old != nil {
for _, entry := range old.Entries {
dir, ok := dirByPath[entry.ImportPath]
if !ok || semver.Compare(dir.version, entry.Version) <= 0 {
// New dir is missing or not more recent; use old entry.
entries = append(entries, entry)
delete(dirByPath, entry.ImportPath)
}
}
}
// Extract symbol information for all the new directories.
newEntries := extractSymbols(gomodcache, maps.Values(dirByPath))
entries = append(entries, newEntries...)
slices.SortFunc(entries, func(x, y Entry) int {
if n := strings.Compare(x.PkgName, y.PkgName); n != 0 {
return n
}
return strings.Compare(x.ImportPath, y.ImportPath)
})
return &Index{
GOMODCACHE: gomodcache,
ValidAt: now, // time before the directories were scanned
Entries: entries,
}, len(newEntries) > 0, nil
}

244
vendor/golang.org/x/tools/internal/modindex/symbols.go generated vendored Normal file
View File

@@ -0,0 +1,244 @@
// 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"
"iter"
"os"
"path/filepath"
"runtime"
"slices"
"strings"
"sync"
"golang.org/x/sync/errgroup"
)
// The name of a symbol contains information about the symbol:
// <name> T for types, TD if the type is deprecated
// <name> C for consts, CD if the const is deprecated
// <name> V for vars, VD if the var is deprecated
// 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. F is replaced by FD if the func
// is deprecated.
type symbol struct {
pkg string // name of the symbols's package
name string // declared name
kind string // T, C, V, or F, followed by D if deprecated
sig string // signature information, for F
}
// extractSymbols returns a (new, unordered) array of Entries, one for
// each provided package directory, describing its exported symbols.
func extractSymbols(cwd string, dirs iter.Seq[directory]) []Entry {
var (
mu sync.Mutex
entries []Entry
)
var g errgroup.Group
g.SetLimit(max(2, runtime.GOMAXPROCS(0)/2))
for dir := range dirs {
g.Go(func() error {
thedir := filepath.Join(cwd, string(dir.path))
mode := parser.SkipObjectResolution | parser.ParseComments
// Parse all Go files in dir and extract symbols.
dirents, err := os.ReadDir(thedir)
if err != nil {
return nil // log this someday?
}
var syms []symbol
for _, dirent := range dirents {
if !strings.HasSuffix(dirent.Name(), ".go") ||
strings.HasSuffix(dirent.Name(), "_test.go") {
continue
}
fname := filepath.Join(thedir, dirent.Name())
tr, err := parser.ParseFile(token.NewFileSet(), fname, nil, mode)
if err != nil {
continue // ignore errors, someday log them?
}
syms = append(syms, getFileExports(tr)...)
}
// Create an entry for the package.
pkg, names := processSyms(syms)
if pkg != "" {
mu.Lock()
defer mu.Unlock()
entries = append(entries, Entry{
PkgName: pkg,
Dir: dir.path,
ImportPath: dir.importPath,
Version: dir.version,
Names: names,
})
}
return nil
})
}
g.Wait() // ignore error
return entries
}
func getFileExports(f *ast.File) []symbol {
pkg := f.Name.Name
if pkg == "main" || pkg == "" {
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"
if isDeprecated(decl.Doc) {
kind += "D"
}
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 explicit 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 remember the possibility
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:
depr := isDeprecated(decl.Doc)
switch decl.Tok {
case token.CONST, token.VAR:
tp := "V"
if decl.Tok == token.CONST {
tp = "C"
}
if depr {
tp += "D"
}
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:
tp := "T"
if depr {
tp += "D"
}
for _, sp := range decl.Specs {
if s := newsym(pkg, sp.(*ast.TypeSpec).Name.Name, tp, ""); 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
}
func isDeprecated(doc *ast.CommentGroup) bool {
if doc == nil {
return false
}
// go.dev/wiki/Deprecated Paragraph starting 'Deprecated:'
// This code fails for /* Deprecated: */, but it's the code from
// gopls/internal/analysis/deprecated
for line := range strings.SplitSeq(doc.Text(), "\n\n") {
if strings.HasPrefix(line, "Deprecated:") {
return true
}
}
return false
}
// 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 {
if s.pkg != pkg {
// Symbols came from two files in same dir
// with different package declarations.
continue
}
var nx string
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)
}
return pkg, names
}