Add staticcheck tool

This commit is contained in:
dwrz
2026-06-06 01:17:20 +00:00
parent 6fb63c8c90
commit ad58cd78ff
315 changed files with 56337 additions and 4 deletions

View File

@@ -0,0 +1,33 @@
// Copyright 2025 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 typeindex defines an analyzer that provides a
// [golang.org/x/tools/internal/typesinternal/typeindex.Index].
//
// Like [golang.org/x/tools/go/analysis/passes/inspect], it is
// intended to be used as a helper by other analyzers; it reports no
// diagnostics of its own.
package typeindex
import (
"reflect"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"honnef.co/go/tools/internal/typesinternal/typeindex"
)
var Analyzer = &analysis.Analyzer{
Name: "typeindex",
Doc: "indexes of type information for later passes",
URL: "https://pkg.go.dev/golang.org/x/tools/internal/analysisinternal/typeindex",
Run: func(pass *analysis.Pass) (any, error) {
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
return typeindex.New(inspect, pass.Pkg, pass.TypesInfo), nil
},
RunDespiteErrors: true,
Requires: []*analysis.Analyzer{inspect.Analyzer},
ResultType: reflect.TypeFor[*typeindex.Index](),
}

View File

@@ -0,0 +1,101 @@
// Copyright 2018 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 buildir defines an Analyzer that constructs the IR
// of an error-free package and returns the set of all
// functions within it. It does not report any diagnostics itself but
// may be used as an input to other analyzers.
//
// THIS INTERFACE IS EXPERIMENTAL AND MAY BE SUBJECT TO INCOMPATIBLE CHANGE.
package buildir
import (
"go/types"
"reflect"
"honnef.co/go/tools/go/ir"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/ctrlflow"
)
var Debug = struct {
Mode ir.BuilderMode
}{}
var Analyzer = &analysis.Analyzer{
Name: "buildir",
Doc: "build IR for later passes",
Run: run,
ResultType: reflect.TypeFor[*IR](),
Requires: []*analysis.Analyzer{ctrlflow.Analyzer},
}
// IR provides intermediate representation for all the
// source functions in the current package.
type IR struct {
Pkg *ir.Package
SrcFuncs []*ir.Function
}
func run(pass *analysis.Pass) (any, error) {
cfgs := pass.ResultOf[ctrlflow.Analyzer].(*ctrlflow.CFGs)
// Plundered from ssautil.BuildPackage.
// We must create a new Program for each Package because the
// analysis API provides no place to hang a Program shared by
// all Packages. Consequently, IR Packages and Functions do not
// have a canonical representation across an analysis session of
// multiple packages. This is unlikely to be a problem in
// practice because the analysis API essentially forces all
// packages to be analysed independently, so any given call to
// Analysis.Run on a package will see only IR objects belonging
// to a single Program.
mode := ir.GlobalDebug
if Debug.Mode != 0 {
mode = Debug.Mode
}
prog := ir.NewProgram(pass.Fset, mode)
prog.SetNoReturn(cfgs.NoReturn)
// Create IR packages for all imports.
// Order is not significant.
created := make(map[*types.Package]bool)
var createAll func(pkgs []*types.Package)
createAll = func(pkgs []*types.Package) {
for _, p := range pkgs {
if !created[p] {
created[p] = true
prog.CreatePackage(p, nil, nil, true)
createAll(p.Imports())
}
}
}
createAll(pass.Pkg.Imports())
// Create and build the primary package.
irpkg := prog.CreatePackage(pass.Pkg, pass.Files, pass.TypesInfo, false)
irpkg.Build()
// Compute list of source functions, including literals,
// in source order.
var addAnons func(f *ir.Function)
funcs := make([]*ir.Function, len(irpkg.Functions))
copy(funcs, irpkg.Functions)
addAnons = func(f *ir.Function) {
for _, anon := range f.AnonFuncs {
funcs = append(funcs, anon)
addAnons(anon)
}
}
for _, fn := range irpkg.Functions {
addAnons(fn)
}
return &IR{Pkg: irpkg, SrcFuncs: funcs}, nil
}

View File

@@ -0,0 +1,2 @@
This package is a copy of cmd/go/internal/renameio.
The upstream package no longer exists, as the Go project replaced all of its uses with the lockedfile package.

View File

@@ -0,0 +1,93 @@
// Copyright 2018 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 renameio writes files atomically by renaming temporary files.
package renameio
import (
"bytes"
"io"
"math/rand"
"os"
"path/filepath"
"strconv"
"honnef.co/go/tools/internal/robustio"
)
const patternSuffix = ".tmp"
// Pattern returns a glob pattern that matches the unrenamed temporary files
// created when writing to filename.
func Pattern(filename string) string {
return filepath.Join(filepath.Dir(filename), filepath.Base(filename)+patternSuffix)
}
// WriteFile is like ioutil.WriteFile, but first writes data to an arbitrary
// file in the same directory as filename, then renames it atomically to the
// final name.
//
// That ensures that the final location, if it exists, is always a complete file.
func WriteFile(filename string, data []byte, perm os.FileMode) (err error) {
return WriteToFile(filename, bytes.NewReader(data), perm)
}
// WriteToFile is a variant of WriteFile that accepts the data as an io.Reader
// instead of a slice.
func WriteToFile(filename string, data io.Reader, perm os.FileMode) (err error) {
f, err := tempFile(filepath.Dir(filename), filepath.Base(filename), perm)
if err != nil {
return err
}
defer func() {
// Only call os.Remove on f.Name() if we failed to rename it: otherwise,
// some other process may have created a new file with the same name after
// that.
if err != nil {
f.Close()
os.Remove(f.Name())
}
}()
if _, err := io.Copy(f, data); err != nil {
return err
}
// Sync the file before renaming it: otherwise, after a crash the reader may
// observe a 0-length file instead of the actual contents.
// See https://golang.org/issue/22397#issuecomment-380831736.
if err := f.Sync(); err != nil {
return err
}
if err := f.Close(); err != nil {
return err
}
return robustio.Rename(f.Name(), filename)
}
// ReadFile is like ioutil.ReadFile, but on Windows retries spurious errors that
// may occur if the file is concurrently replaced.
//
// Errors are classified heuristically and retries are bounded, so even this
// function may occasionally return a spurious error on Windows.
// If so, the error will likely wrap one of:
// - syscall.ERROR_ACCESS_DENIED
// - syscall.ERROR_FILE_NOT_FOUND
// - internal/syscall/windows.ERROR_SHARING_VIOLATION
func ReadFile(filename string) ([]byte, error) {
return robustio.ReadFile(filename)
}
// tempFile creates a new temporary file with given permission bits.
func tempFile(dir, prefix string, perm os.FileMode) (f *os.File, err error) {
for range 10000 {
name := filepath.Join(dir, prefix+strconv.Itoa(rand.Intn(1000000000))+patternSuffix)
f, err = os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, perm)
if os.IsExist(err) {
continue
}
break
}
return
}

View File

@@ -0,0 +1,6 @@
This package is a copy of cmd/go/internal/robustio.
It is mostly in sync with upstream according to the last commit we've looked at,
with the exception of still using I/O functions that work with older Go versions.
The last upstream commit we've looked at was:
dc04f3ba1f25313bc9c97e728620206c235db9ee

View File

@@ -0,0 +1,53 @@
// Copyright 2019 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 robustio wraps I/O functions that are prone to failure on Windows,
// transparently retrying errors up to an arbitrary timeout.
//
// Errors are classified heuristically and retries are bounded, so the functions
// in this package do not completely eliminate spurious errors. However, they do
// significantly reduce the rate of failure in practice.
//
// If so, the error will likely wrap one of:
// The functions in this package do not completely eliminate spurious errors,
// but substantially reduce their rate of occurrence in practice.
package robustio
// Rename is like os.Rename, but on Windows retries errors that may occur if the
// file is concurrently read or overwritten.
//
// (See golang.org/issue/31247 and golang.org/issue/32188.)
func Rename(oldpath, newpath string) error {
return rename(oldpath, newpath)
}
// ReadFile is like os.ReadFile, but on Windows retries errors that may
// occur if the file is concurrently replaced.
//
// (See golang.org/issue/31247 and golang.org/issue/32188.)
func ReadFile(filename string) ([]byte, error) {
return readFile(filename)
}
// RemoveAll is like os.RemoveAll, but on Windows retries errors that may occur
// if an executable file in the directory has recently been executed.
//
// (See golang.org/issue/19491.)
func RemoveAll(path string) error {
return removeAll(path)
}
// IsEphemeralError reports whether err is one of the errors that the functions
// in this package attempt to mitigate.
//
// Errors considered ephemeral include:
// - syscall.ERROR_ACCESS_DENIED
// - syscall.ERROR_FILE_NOT_FOUND
// - internal/syscall/windows.ERROR_SHARING_VIOLATION
//
// This set may be expanded in the future; programs must not rely on the
// non-ephemerality of any given error.
func IsEphemeralError(err error) bool {
return isEphemeralError(err)
}

View File

@@ -0,0 +1,21 @@
// Copyright 2019 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 robustio
import (
"errors"
"syscall"
)
const errFileNotFound = syscall.ENOENT
// isEphemeralError returns true if err may be resolved by waiting.
func isEphemeralError(err error) bool {
var errno syscall.Errno
if errors.As(err, &errno) {
return errno == errFileNotFound
}
return false
}

View File

@@ -0,0 +1,91 @@
// Copyright 2019 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.
//go:build windows || darwin
package robustio
import (
"errors"
"math/rand"
"os"
"syscall"
"time"
)
const arbitraryTimeout = 2000 * time.Millisecond
// retry retries ephemeral errors from f up to an arbitrary timeout
// to work around filesystem flakiness on Windows and Darwin.
func retry(f func() (err error, mayRetry bool)) error {
var (
bestErr error
lowestErrno syscall.Errno
start time.Time
nextSleep time.Duration = 1 * time.Millisecond
)
for {
err, mayRetry := f()
if err == nil || !mayRetry {
return err
}
var errno syscall.Errno
if errors.As(err, &errno) && (lowestErrno == 0 || errno < lowestErrno) {
bestErr = err
lowestErrno = errno
} else if bestErr == nil {
bestErr = err
}
if start.IsZero() {
start = time.Now()
} else if d := time.Since(start) + nextSleep; d >= arbitraryTimeout {
break
}
time.Sleep(nextSleep)
nextSleep += time.Duration(rand.Int63n(int64(nextSleep)))
}
return bestErr
}
// rename is like os.Rename, but retries ephemeral errors.
//
// On Windows it wraps os.Rename, which (as of 2019-06-04) uses MoveFileEx with
// MOVEFILE_REPLACE_EXISTING.
//
// Windows also provides a different system call, ReplaceFile,
// that provides similar semantics, but perhaps preserves more metadata. (The
// documentation on the differences between the two is very sparse.)
//
// Empirical error rates with MoveFileEx are lower under modest concurrency, so
// for now we're sticking with what the os package already provides.
func rename(oldpath, newpath string) (err error) {
return retry(func() (err error, mayRetry bool) {
err = os.Rename(oldpath, newpath)
return err, isEphemeralError(err)
})
}
// readFile is like os.ReadFile, but retries ephemeral errors.
func readFile(filename string) ([]byte, error) {
var b []byte
err := retry(func() (err error, mayRetry bool) {
b, err = os.ReadFile(filename)
// Unlike in rename, we do not retry errFileNotFound here: it can occur
// as a spurious error, but the file may also genuinely not exist, so the
// increase in robustness is probably not worth the extra latency.
return err, isEphemeralError(err) && !errors.Is(err, errFileNotFound)
})
return b, err
}
func removeAll(path string) error {
return retry(func() (err error, mayRetry bool) {
err = os.RemoveAll(path)
return err, isEphemeralError(err)
})
}

View File

@@ -0,0 +1,27 @@
// Copyright 2019 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.
//go:build !windows && !darwin
package robustio
import (
"os"
)
func rename(oldpath, newpath string) error {
return os.Rename(oldpath, newpath)
}
func readFile(filename string) ([]byte, error) {
return os.ReadFile(filename)
}
func removeAll(path string) error {
return os.RemoveAll(path)
}
func isEphemeralError(err error) bool {
return false
}

View File

@@ -0,0 +1,27 @@
// Copyright 2019 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 robustio
import (
"errors"
"syscall"
)
const ERROR_SHARING_VIOLATION = 32
const errFileNotFound = syscall.ERROR_FILE_NOT_FOUND
// isEphemeralError returns true if err may be resolved by waiting.
func isEphemeralError(err error) bool {
var errno syscall.Errno
if errors.As(err, &errno) {
switch errno {
case syscall.ERROR_ACCESS_DENIED,
syscall.ERROR_FILE_NOT_FOUND,
ERROR_SHARING_VIOLATION:
return true
}
}
return false
}

View File

@@ -0,0 +1,209 @@
package sharedcheck
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/facts/tokenfile"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/ast/astutil"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/ir/irutil"
"honnef.co/go/tools/go/types/typeutil"
"honnef.co/go/tools/internal/passes/buildir"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
)
func CheckRangeStringRunes(pass *analysis.Pass) (any, error) {
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
cb := func(node ast.Node) bool {
rng, ok := node.(*ast.RangeStmt)
if !ok || !astutil.IsBlank(rng.Key) {
return true
}
v, _ := fn.ValueForExpr(rng.X)
// Check that we're converting from string to []rune
val, _ := v.(*ir.Convert)
if val == nil {
return true
}
Tsrc, ok := typeutil.CoreType(val.X.Type()).(*types.Basic)
if !ok || Tsrc.Kind() != types.String {
return true
}
Tdst, ok := typeutil.CoreType(val.Type()).(*types.Slice)
if !ok {
return true
}
TdstElem, ok := types.Unalias(Tdst.Elem()).(*types.Basic)
if !ok || TdstElem.Kind() != types.Int32 {
return true
}
// Check that the result of the conversion is only used to
// range over
refs := val.Referrers()
if refs == nil {
return true
}
// Expect two refs: one for obtaining the length of the slice,
// one for accessing the elements
if len(irutil.FilterDebug(*refs)) != 2 {
// TODO(dh): right now, we check that only one place
// refers to our slice. This will miss cases such as
// ranging over the slice twice. Ideally, we'd ensure that
// the slice is only used for ranging over (without
// accessing the key), but that is harder to do because in
// IR form, ranging over a slice looks like an ordinary
// loop with index increments and slice accesses. We'd
// have to look at the associated AST node to check that
// it's a range statement.
return true
}
pass.Reportf(rng.Pos(), "should range over string, not []rune(string)")
return true
}
if source := fn.Source(); source != nil {
ast.Inspect(source, cb)
}
}
return nil, nil
}
// RedundantTypeInDeclarationChecker returns a checker that flags variable declarations with redundantly specified types.
// That is, it flags 'var v T = e' where e's type is identical to T and 'var v = e' (or 'v := e') would have the same effect.
//
// It does not flag variables under the following conditions, to reduce the number of false positives:
// - global variables these often specify types to aid godoc
// - files that use cgo cgo code generation and pointer checking emits redundant types
//
// It does not flag variables under the following conditions, unless flagHelpfulTypes is true, to reduce the number of noisy positives:
// - packages that import syscall or unsafe these sometimes use this form of assignment to make sure types are as expected
// - variables named the blank identifier a pattern used to confirm the types of variables
// - untyped expressions on the rhs the explicitness might aid readability
func RedundantTypeInDeclarationChecker(verb string, flagHelpfulTypes bool) *analysis.Analyzer {
fn := func(pass *analysis.Pass) (any, error) {
eval := func(expr ast.Expr) (types.TypeAndValue, error) {
info := &types.Info{
Types: map[ast.Expr]types.TypeAndValue{},
}
err := types.CheckExpr(pass.Fset, pass.Pkg, expr.Pos(), expr, info)
return info.Types[expr], err
}
if !flagHelpfulTypes {
// Don't look at code in low-level packages
for _, imp := range pass.Pkg.Imports() {
if imp.Path() == "syscall" || imp.Path() == "unsafe" {
return nil, nil
}
}
}
fn := func(node ast.Node) {
decl := node.(*ast.GenDecl)
if decl.Tok != token.VAR {
return
}
gen, _ := code.Generator(pass, decl.Pos())
if gen == generated.Cgo {
// TODO(dh): remove this exception once we can use UsesCgo
return
}
// Delay looking up parent AST nodes until we have to
checkedDecl := false
specLoop:
for _, spec := range decl.Specs {
spec := spec.(*ast.ValueSpec)
if spec.Type == nil {
continue
}
if len(spec.Names) != len(spec.Values) {
continue
}
Tlhs := pass.TypesInfo.TypeOf(spec.Type)
for i, v := range spec.Values {
if !flagHelpfulTypes && spec.Names[i].Name == "_" {
continue specLoop
}
Trhs := pass.TypesInfo.TypeOf(v)
if !types.Identical(Tlhs, Trhs) {
continue specLoop
}
// Some expressions are untyped and get converted to the lhs type implicitly.
// This applies to untyped constants, shift operations with an untyped lhs, and possibly others.
//
// Check if the type is truly redundant, i.e. if the type on the lhs doesn't match the default type of the untyped constant.
tv, err := eval(v)
if err != nil {
panic(err)
}
if b, ok := types.Unalias(tv.Type).(*types.Basic); ok && (b.Info()&types.IsUntyped) != 0 {
if Tlhs != types.Default(b) {
// The rhs is untyped and its default type differs from the explicit type on the lhs
continue specLoop
}
switch v := v.(type) {
case *ast.Ident:
// Only flag named constant rhs if it's a predeclared identifier.
// Don't flag other named constants, as the explicit type may aid readability.
if pass.TypesInfo.ObjectOf(v).Pkg() != nil && !flagHelpfulTypes {
continue specLoop
}
case *ast.BasicLit:
// Do flag basic literals
default:
// Don't flag untyped rhs expressions unless flagHelpfulTypes is set
if !flagHelpfulTypes {
continue specLoop
}
}
}
}
if !checkedDecl {
// Don't flag global variables. These often have explicit types for godoc's sake.
path, _ := astutil.PathEnclosingInterval(code.File(pass, decl), decl.Pos(), decl.Pos())
pathLoop:
for _, el := range path {
switch el.(type) {
case *ast.FuncDecl, *ast.FuncLit:
checkedDecl = true
break pathLoop
}
}
if !checkedDecl {
// decl is not inside a function
break specLoop
}
}
report.Report(pass, spec.Type, fmt.Sprintf("%s omit type %s from declaration; it will be inferred from the right-hand side", verb, report.Render(pass, spec.Type)), report.FilterGenerated(),
report.Fixes(edit.Fix("Remove redundant type", edit.Delete(spec.Type))))
}
}
code.Preorder(pass, fn, (*ast.GenDecl)(nil))
return nil, nil
}
return &analysis.Analyzer{
Run: fn,
Requires: []*analysis.Analyzer{generated.Analyzer, inspect.Analyzer, tokenfile.Analyzer},
}
}

View File

@@ -0,0 +1,36 @@
package sync
type Semaphore struct {
ch chan struct{}
}
func NewSemaphore(size int) Semaphore {
return Semaphore{
ch: make(chan struct{}, size),
}
}
func (sem Semaphore) Acquire() {
sem.ch <- struct{}{}
}
func (sem Semaphore) AcquireMaybe() bool {
select {
case sem.ch <- struct{}{}:
return true
default:
return false
}
}
func (sem Semaphore) Release() {
<-sem.ch
}
func (sem Semaphore) Len() int {
return len(sem.ch)
}
func (sem Semaphore) Cap() int {
return cap(sem.ch)
}

View File

@@ -0,0 +1,226 @@
// Copyright 2025 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 typeindex provides an [Index] of type information for a
// package, allowing efficient lookup of, say, whether a given symbol
// is referenced and, if so, where from; or of the [inspector.Cursor] for
// the declaration of a particular [types.Object] symbol.
package typeindex
import (
"encoding/binary"
"go/ast"
"go/types"
"iter"
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
)
// IsPackageLevel reports whether obj is a package-level symbol.
func IsPackageLevel(obj types.Object) bool {
return obj.Pkg() != nil && obj.Parent() == obj.Pkg().Scope()
}
// New constructs an Index for the package of type-annotated syntax
//
// TODO(adonovan): accept a FileSet too?
// We regret not requiring one in inspector.New.
func New(inspect *inspector.Inspector, pkg *types.Package, info *types.Info) *Index {
ix := &Index{
inspect: inspect,
info: info,
packages: make(map[string]*types.Package),
def: make(map[types.Object]inspector.Cursor),
uses: make(map[types.Object]*uses),
}
addPackage := func(pkg2 *types.Package) {
if pkg2 != nil && pkg2 != pkg {
ix.packages[pkg2.Path()] = pkg2
}
}
for cur := range inspect.Root().Preorder((*ast.ImportSpec)(nil), (*ast.Ident)(nil)) {
switch n := cur.Node().(type) {
case *ast.ImportSpec:
// Index direct imports, including blank ones.
if pkgname := info.PkgNameOf(n); pkgname != nil {
addPackage(pkgname.Imported())
}
case *ast.Ident:
// Index all defining and using identifiers.
if obj := info.Defs[n]; obj != nil {
ix.def[obj] = cur
}
if obj := info.Uses[n]; obj != nil {
// Index indirect dependencies (via fields and methods).
if !IsPackageLevel(obj) {
addPackage(obj.Pkg())
}
us, ok := ix.uses[obj]
if !ok {
us = &uses{}
us.code = us.initial[:0]
ix.uses[obj] = us
}
delta := cur.Index() - us.last
if delta < 0 {
panic("non-monotonic")
}
us.code = binary.AppendUvarint(us.code, uint64(delta))
us.last = cur.Index()
}
}
}
return ix
}
// An Index holds an index mapping [types.Object] symbols to their syntax.
// In effect, it is the inverse of [types.Info].
type Index struct {
inspect *inspector.Inspector
info *types.Info
packages map[string]*types.Package // packages of all symbols referenced from this package
def map[types.Object]inspector.Cursor // Cursor of *ast.Ident that defines the Object
uses map[types.Object]*uses // Cursors of *ast.Idents that use the Object
}
// A uses holds the list of Cursors of Idents that use a given symbol.
//
// The Uses map of [types.Info] is substantial, so it pays to compress
// its inverse mapping here, both in space and in CPU due to reduced
// allocation. A Cursor is 2 words; a Cursor.Index is 4 bytes; but
// since Cursors are naturally delivered in ascending order, we can
// use varint-encoded deltas at a cost of only ~1.7-2.2 bytes per use.
//
// Many variables have only one or two uses, so their encoded uses may
// fit in the 4 bytes of initial, saving further CPU and space
// essentially for free since the struct's size class is 4 words.
type uses struct {
code []byte // varint-encoded deltas of successive Cursor.Index values
last int32 // most recent Cursor.Index value; used during encoding
initial [4]byte // use slack in size class as initial space for code
}
// Uses returns the sequence of Cursors of [*ast.Ident]s in this package
// that refer to obj. If obj is nil, the sequence is empty.
func (ix *Index) Uses(obj types.Object) iter.Seq[inspector.Cursor] {
return func(yield func(inspector.Cursor) bool) {
if uses := ix.uses[obj]; uses != nil {
var last int32
for code := uses.code; len(code) > 0; {
delta, n := binary.Uvarint(code)
last += int32(delta)
if !yield(ix.inspect.At(last)) {
return
}
code = code[n:]
}
}
}
}
// Used reports whether any of the specified objects are used, in
// other words, obj != nil && Uses(obj) is non-empty for some obj in objs.
//
// (This treatment of nil allows Used to be called directly on the
// result of [Index.Object] so that analyzers can conveniently skip
// packages that don't use a symbol of interest.)
func (ix *Index) Used(objs ...types.Object) bool {
for _, obj := range objs {
if obj != nil && ix.uses[obj] != nil {
return true
}
}
return false
}
// Def returns the Cursor of the [*ast.Ident] in this package
// that declares the specified object, if any.
func (ix *Index) Def(obj types.Object) (inspector.Cursor, bool) {
cur, ok := ix.def[obj]
return cur, ok
}
// Package returns the package of the specified path,
// or nil if it is not referenced from this package.
func (ix *Index) Package(path string) *types.Package {
return ix.packages[path]
}
// Object returns the package-level symbol name within the package of
// the specified path, or nil if the package or symbol does not exist
// or is not visible from this package.
func (ix *Index) Object(path, name string) types.Object {
if pkg := ix.Package(path); pkg != nil {
return pkg.Scope().Lookup(name)
}
return nil
}
// Selection returns the named method or field belonging to the
// package-level type returned by Object(path, typename).
func (ix *Index) Selection(path, typename, name string) types.Object {
if obj := ix.Object(path, typename); obj != nil {
if tname, ok := obj.(*types.TypeName); ok {
obj, _, _ := types.LookupFieldOrMethod(tname.Type(), true, obj.Pkg(), name)
return obj
}
}
return nil
}
// Calls returns the sequence of cursors for *ast.CallExpr nodes that
// call the specified callee, as defined by [typeutil.Callee].
// If callee is nil, the sequence is empty.
func (ix *Index) Calls(callee types.Object) iter.Seq[inspector.Cursor] {
return func(yield func(inspector.Cursor) bool) {
for cur := range ix.Uses(callee) {
ek, _ := cur.ParentEdge()
// The call may be of the form f() or x.f(),
// optionally with parens; ascend from f to call.
//
// It is tempting but wrong to use the first
// CallExpr ancestor: we have to make sure the
// ident is in the CallExpr.Fun position, otherwise
// f(f, f) would have two spurious matches.
// Avoiding Enclosing is also significantly faster.
// inverse unparen: f -> (f)
for ek == edge.ParenExpr_X {
cur = cur.Parent()
ek, _ = cur.ParentEdge()
}
// ascend selector: f -> x.f
if ek == edge.SelectorExpr_Sel {
cur = cur.Parent()
ek, _ = cur.ParentEdge()
}
// inverse unparen again
for ek == edge.ParenExpr_X {
cur = cur.Parent()
ek, _ = cur.ParentEdge()
}
// ascend from f or x.f to call
if ek == edge.CallExpr_Fun {
curCall := cur.Parent()
call := curCall.Node().(*ast.CallExpr)
if typeutil.Callee(ix.info, call) == callee {
if !yield(curCall) {
return
}
}
}
}
}
}