aboutsummaryrefslogtreecommitdiff
path: root/internal/lsp/analysis/stubmethods/stubmethods.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/lsp/analysis/stubmethods/stubmethods.go')
-rw-r--r--internal/lsp/analysis/stubmethods/stubmethods.go351
1 files changed, 0 insertions, 351 deletions
diff --git a/internal/lsp/analysis/stubmethods/stubmethods.go b/internal/lsp/analysis/stubmethods/stubmethods.go
deleted file mode 100644
index c2a4138fa..000000000
--- a/internal/lsp/analysis/stubmethods/stubmethods.go
+++ /dev/null
@@ -1,351 +0,0 @@
-// Copyright 2022 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 stubmethods
-
-import (
- "bytes"
- "fmt"
- "go/ast"
- "go/format"
- "go/token"
- "go/types"
- "strconv"
- "strings"
-
- "golang.org/x/tools/go/analysis"
- "golang.org/x/tools/go/analysis/passes/inspect"
- "golang.org/x/tools/go/ast/astutil"
- "golang.org/x/tools/internal/analysisinternal"
- "golang.org/x/tools/internal/typesinternal"
-)
-
-const Doc = `stub methods analyzer
-
-This analyzer generates method stubs for concrete types
-in order to implement a target interface`
-
-var Analyzer = &analysis.Analyzer{
- Name: "stubmethods",
- Doc: Doc,
- Requires: []*analysis.Analyzer{inspect.Analyzer},
- Run: run,
- RunDespiteErrors: true,
-}
-
-func run(pass *analysis.Pass) (interface{}, error) {
- for _, err := range analysisinternal.GetTypeErrors(pass) {
- ifaceErr := strings.Contains(err.Msg, "missing method") || strings.HasPrefix(err.Msg, "cannot convert")
- if !ifaceErr {
- continue
- }
- var file *ast.File
- for _, f := range pass.Files {
- if f.Pos() <= err.Pos && err.Pos < f.End() {
- file = f
- break
- }
- }
- if file == nil {
- continue
- }
- // Get the end position of the error.
- _, _, endPos, ok := typesinternal.ReadGo116ErrorData(err)
- if !ok {
- var buf bytes.Buffer
- if err := format.Node(&buf, pass.Fset, file); err != nil {
- continue
- }
- endPos = analysisinternal.TypeErrorEndPos(pass.Fset, buf.Bytes(), err.Pos)
- }
- path, _ := astutil.PathEnclosingInterval(file, err.Pos, endPos)
- si := GetStubInfo(pass.TypesInfo, path, err.Pos)
- if si == nil {
- continue
- }
- qf := RelativeToFiles(si.Concrete.Obj().Pkg(), file, nil, nil)
- pass.Report(analysis.Diagnostic{
- Pos: err.Pos,
- End: endPos,
- Message: fmt.Sprintf("Implement %s", types.TypeString(si.Interface.Type(), qf)),
- })
- }
- return nil, nil
-}
-
-// StubInfo represents a concrete type
-// that wants to stub out an interface type
-type StubInfo struct {
- // Interface is the interface that the client wants to implement.
- // When the interface is defined, the underlying object will be a TypeName.
- // Note that we keep track of types.Object instead of types.Type in order
- // to keep a reference to the declaring object's package and the ast file
- // in the case where the concrete type file requires a new import that happens to be renamed
- // in the interface file.
- // TODO(marwan-at-work): implement interface literals.
- Interface types.Object
- Concrete *types.Named
- Pointer bool
-}
-
-// GetStubInfo determines whether the "missing method error"
-// can be used to deduced what the concrete and interface types are.
-func GetStubInfo(ti *types.Info, path []ast.Node, pos token.Pos) *StubInfo {
- for _, n := range path {
- switch n := n.(type) {
- case *ast.ValueSpec:
- return fromValueSpec(ti, n, pos)
- case *ast.ReturnStmt:
- // An error here may not indicate a real error the user should know about, but it may.
- // Therefore, it would be best to log it out for debugging/reporting purposes instead of ignoring
- // it. However, event.Log takes a context which is not passed via the analysis package.
- // TODO(marwan-at-work): properly log this error.
- si, _ := fromReturnStmt(ti, pos, path, n)
- return si
- case *ast.AssignStmt:
- return fromAssignStmt(ti, n, pos)
- }
- }
- return nil
-}
-
-// fromReturnStmt analyzes a "return" statement to extract
-// a concrete type that is trying to be returned as an interface type.
-//
-// For example, func() io.Writer { return myType{} }
-// would return StubInfo with the interface being io.Writer and the concrete type being myType{}.
-func fromReturnStmt(ti *types.Info, pos token.Pos, path []ast.Node, rs *ast.ReturnStmt) (*StubInfo, error) {
- returnIdx := -1
- for i, r := range rs.Results {
- if pos >= r.Pos() && pos <= r.End() {
- returnIdx = i
- }
- }
- if returnIdx == -1 {
- return nil, fmt.Errorf("pos %d not within return statement bounds: [%d-%d]", pos, rs.Pos(), rs.End())
- }
- concObj, pointer := concreteType(rs.Results[returnIdx], ti)
- if concObj == nil || concObj.Obj().Pkg() == nil {
- return nil, nil
- }
- ef := enclosingFunction(path, ti)
- if ef == nil {
- return nil, fmt.Errorf("could not find the enclosing function of the return statement")
- }
- iface := ifaceType(ef.Results.List[returnIdx].Type, ti)
- if iface == nil {
- return nil, nil
- }
- return &StubInfo{
- Concrete: concObj,
- Pointer: pointer,
- Interface: iface,
- }, nil
-}
-
-// fromValueSpec returns *StubInfo from a variable declaration such as
-// var x io.Writer = &T{}
-func fromValueSpec(ti *types.Info, vs *ast.ValueSpec, pos token.Pos) *StubInfo {
- var idx int
- for i, vs := range vs.Values {
- if pos >= vs.Pos() && pos <= vs.End() {
- idx = i
- break
- }
- }
-
- valueNode := vs.Values[idx]
- ifaceNode := vs.Type
- callExp, ok := valueNode.(*ast.CallExpr)
- // if the ValueSpec is `var _ = myInterface(...)`
- // as opposed to `var _ myInterface = ...`
- if ifaceNode == nil && ok && len(callExp.Args) == 1 {
- ifaceNode = callExp.Fun
- valueNode = callExp.Args[0]
- }
- concObj, pointer := concreteType(valueNode, ti)
- if concObj == nil || concObj.Obj().Pkg() == nil {
- return nil
- }
- ifaceObj := ifaceType(ifaceNode, ti)
- if ifaceObj == nil {
- return nil
- }
- return &StubInfo{
- Concrete: concObj,
- Interface: ifaceObj,
- Pointer: pointer,
- }
-}
-
-// fromAssignStmt returns *StubInfo from a variable re-assignment such as
-// var x io.Writer
-// x = &T{}
-func fromAssignStmt(ti *types.Info, as *ast.AssignStmt, pos token.Pos) *StubInfo {
- idx := -1
- var lhs, rhs ast.Expr
- // Given a re-assignment interface conversion error,
- // the compiler error shows up on the right hand side of the expression.
- // For example, x = &T{} where x is io.Writer highlights the error
- // under "&T{}" and not "x".
- for i, hs := range as.Rhs {
- if pos >= hs.Pos() && pos <= hs.End() {
- idx = i
- break
- }
- }
- if idx == -1 {
- return nil
- }
- // Technically, this should never happen as
- // we would get a "cannot assign N values to M variables"
- // before we get an interface conversion error. Nonetheless,
- // guard against out of range index errors.
- if idx >= len(as.Lhs) {
- return nil
- }
- lhs, rhs = as.Lhs[idx], as.Rhs[idx]
- ifaceObj := ifaceType(lhs, ti)
- if ifaceObj == nil {
- return nil
- }
- concType, pointer := concreteType(rhs, ti)
- if concType == nil || concType.Obj().Pkg() == nil {
- return nil
- }
- return &StubInfo{
- Concrete: concType,
- Interface: ifaceObj,
- Pointer: pointer,
- }
-}
-
-// RelativeToFiles returns a types.Qualifier that formats package names
-// according to the files where the concrete and interface types are defined.
-//
-// This is similar to types.RelativeTo except if a file imports the package with a different name,
-// then it will use it. And if the file does import the package but it is ignored,
-// then it will return the original name. It also prefers package names in ifaceFile in case
-// an import is missing from concFile but is present in ifaceFile.
-//
-// Additionally, if missingImport is not nil, the function will be called whenever the concFile
-// is presented with a package that is not imported. This is useful so that as types.TypeString is
-// formatting a function signature, it is identifying packages that will need to be imported when
-// stubbing an interface.
-func RelativeToFiles(concPkg *types.Package, concFile, ifaceFile *ast.File, missingImport func(name, path string)) types.Qualifier {
- return func(other *types.Package) string {
- if other == concPkg {
- return ""
- }
-
- // Check if the concrete file already has the given import,
- // if so return the default package name or the renamed import statement.
- for _, imp := range concFile.Imports {
- impPath, _ := strconv.Unquote(imp.Path.Value)
- isIgnored := imp.Name != nil && (imp.Name.Name == "." || imp.Name.Name == "_")
- if impPath == other.Path() && !isIgnored {
- importName := other.Name()
- if imp.Name != nil {
- importName = imp.Name.Name
- }
- return importName
- }
- }
-
- // If the concrete file does not have the import, check if the package
- // is renamed in the interface file and prefer that.
- var importName string
- if ifaceFile != nil {
- for _, imp := range ifaceFile.Imports {
- impPath, _ := strconv.Unquote(imp.Path.Value)
- isIgnored := imp.Name != nil && (imp.Name.Name == "." || imp.Name.Name == "_")
- if impPath == other.Path() && !isIgnored {
- if imp.Name != nil && imp.Name.Name != concPkg.Name() {
- importName = imp.Name.Name
- }
- break
- }
- }
- }
-
- if missingImport != nil {
- missingImport(importName, other.Path())
- }
-
- // Up until this point, importName must stay empty when calling missingImport,
- // otherwise we'd end up with `import time "time"` which doesn't look idiomatic.
- if importName == "" {
- importName = other.Name()
- }
- return importName
- }
-}
-
-// ifaceType will try to extract the types.Object that defines
-// the interface given the ast.Expr where the "missing method"
-// or "conversion" errors happen.
-func ifaceType(n ast.Expr, ti *types.Info) types.Object {
- tv, ok := ti.Types[n]
- if !ok {
- return nil
- }
- typ := tv.Type
- named, ok := typ.(*types.Named)
- if !ok {
- return nil
- }
- _, ok = named.Underlying().(*types.Interface)
- if !ok {
- return nil
- }
- // Interfaces defined in the "builtin" package return nil a Pkg().
- // But they are still real interfaces that we need to make a special case for.
- // Therefore, protect gopls from panicking if a new interface type was added in the future.
- if named.Obj().Pkg() == nil && named.Obj().Name() != "error" {
- return nil
- }
- return named.Obj()
-}
-
-// concreteType tries to extract the *types.Named that defines
-// the concrete type given the ast.Expr where the "missing method"
-// or "conversion" errors happened. If the concrete type is something
-// that cannot have methods defined on it (such as basic types), this
-// method will return a nil *types.Named. The second return parameter
-// is a boolean that indicates whether the concreteType was defined as a
-// pointer or value.
-func concreteType(n ast.Expr, ti *types.Info) (*types.Named, bool) {
- tv, ok := ti.Types[n]
- if !ok {
- return nil, false
- }
- typ := tv.Type
- ptr, isPtr := typ.(*types.Pointer)
- if isPtr {
- typ = ptr.Elem()
- }
- named, ok := typ.(*types.Named)
- if !ok {
- return nil, false
- }
- return named, isPtr
-}
-
-// enclosingFunction returns the signature and type of the function
-// enclosing the given position.
-func enclosingFunction(path []ast.Node, info *types.Info) *ast.FuncType {
- for _, node := range path {
- switch t := node.(type) {
- case *ast.FuncDecl:
- if _, ok := info.Defs[t.Name]; ok {
- return t.Type
- }
- case *ast.FuncLit:
- if _, ok := info.Types[t]; ok {
- return t.Type
- }
- }
- }
- return nil
-}