aboutsummaryrefslogtreecommitdiff
path: root/internal/lsp/source/stub.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/lsp/source/stub.go')
-rw-r--r--internal/lsp/source/stub.go330
1 files changed, 0 insertions, 330 deletions
diff --git a/internal/lsp/source/stub.go b/internal/lsp/source/stub.go
deleted file mode 100644
index 6810f1d20..000000000
--- a/internal/lsp/source/stub.go
+++ /dev/null
@@ -1,330 +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 source
-
-import (
- "bytes"
- "context"
- "fmt"
- "go/ast"
- "go/format"
- "go/parser"
- "go/token"
- "go/types"
- "strings"
-
- "golang.org/x/tools/go/analysis"
- "golang.org/x/tools/go/ast/astutil"
- "golang.org/x/tools/internal/lsp/analysis/stubmethods"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/span"
- "golang.org/x/tools/internal/typeparams"
-)
-
-func stubSuggestedFixFunc(ctx context.Context, snapshot Snapshot, fh VersionedFileHandle, rng protocol.Range) (*analysis.SuggestedFix, error) {
- pkg, pgf, err := GetParsedFile(ctx, snapshot, fh, NarrowestPackage)
- if err != nil {
- return nil, fmt.Errorf("GetParsedFile: %w", err)
- }
- nodes, pos, err := getStubNodes(pgf, rng)
- if err != nil {
- return nil, fmt.Errorf("getNodes: %w", err)
- }
- si := stubmethods.GetStubInfo(pkg.GetTypesInfo(), nodes, pos)
- if si == nil {
- return nil, fmt.Errorf("nil interface request")
- }
- parsedConcreteFile, concreteFH, err := getStubFile(ctx, si.Concrete.Obj(), snapshot)
- if err != nil {
- return nil, fmt.Errorf("getFile(concrete): %w", err)
- }
- var (
- methodsSrc []byte
- stubImports []*stubImport // additional imports needed for method stubs
- )
- if si.Interface.Pkg() == nil && si.Interface.Name() == "error" && si.Interface.Parent() == types.Universe {
- methodsSrc = stubErr(ctx, parsedConcreteFile.File, si, snapshot)
- } else {
- methodsSrc, stubImports, err = stubMethods(ctx, parsedConcreteFile.File, si, snapshot)
- }
- if err != nil {
- return nil, fmt.Errorf("stubMethods: %w", err)
- }
- nodes, _ = astutil.PathEnclosingInterval(parsedConcreteFile.File, si.Concrete.Obj().Pos(), si.Concrete.Obj().Pos())
- concreteSrc, err := concreteFH.Read()
- if err != nil {
- return nil, fmt.Errorf("error reading concrete file source: %w", err)
- }
- insertPos := snapshot.FileSet().Position(nodes[1].End()).Offset
- if insertPos >= len(concreteSrc) {
- return nil, fmt.Errorf("insertion position is past the end of the file")
- }
- var buf bytes.Buffer
- buf.Write(concreteSrc[:insertPos])
- buf.WriteByte('\n')
- buf.Write(methodsSrc)
- buf.Write(concreteSrc[insertPos:])
- fset := token.NewFileSet()
- newF, err := parser.ParseFile(fset, parsedConcreteFile.File.Name.Name, buf.Bytes(), parser.ParseComments)
- if err != nil {
- return nil, fmt.Errorf("could not reparse file: %w", err)
- }
- for _, imp := range stubImports {
- astutil.AddNamedImport(fset, newF, imp.Name, imp.Path)
- }
- var source bytes.Buffer
- err = format.Node(&source, fset, newF)
- if err != nil {
- return nil, fmt.Errorf("format.Node: %w", err)
- }
- diffEdits, err := snapshot.View().Options().ComputeEdits(parsedConcreteFile.URI, string(parsedConcreteFile.Src), source.String())
- if err != nil {
- return nil, err
- }
- var edits []analysis.TextEdit
- for _, edit := range diffEdits {
- rng, err := edit.Span.Range(parsedConcreteFile.Mapper.Converter)
- if err != nil {
- return nil, err
- }
- edits = append(edits, analysis.TextEdit{
- Pos: rng.Start,
- End: rng.End,
- NewText: []byte(edit.NewText),
- })
- }
- return &analysis.SuggestedFix{
- TextEdits: edits,
- }, nil
-}
-
-// stubMethods returns the Go code of all methods
-// that implement the given interface
-func stubMethods(ctx context.Context, concreteFile *ast.File, si *stubmethods.StubInfo, snapshot Snapshot) ([]byte, []*stubImport, error) {
- ifacePkg, err := deducePkgFromTypes(ctx, snapshot, si.Interface)
- if err != nil {
- return nil, nil, err
- }
- si.Concrete.Obj().Type()
- concMS := types.NewMethodSet(types.NewPointer(si.Concrete.Obj().Type()))
- missing, err := missingMethods(ctx, snapshot, concMS, si.Concrete.Obj().Pkg(), si.Interface, ifacePkg, map[string]struct{}{})
- if err != nil {
- return nil, nil, fmt.Errorf("missingMethods: %w", err)
- }
- if len(missing) == 0 {
- return nil, nil, fmt.Errorf("no missing methods found")
- }
- var (
- stubImports []*stubImport
- methodsBuffer bytes.Buffer
- )
- for _, mi := range missing {
- for _, m := range mi.missing {
- // TODO(marwan-at-work): this should share the same logic with source.FormatVarType
- // as it also accounts for type aliases.
- sig := types.TypeString(m.Type(), stubmethods.RelativeToFiles(si.Concrete.Obj().Pkg(), concreteFile, mi.file, func(name, path string) {
- for _, imp := range stubImports {
- if imp.Name == name && imp.Path == path {
- return
- }
- }
- stubImports = append(stubImports, &stubImport{name, path})
- }))
- _, err = methodsBuffer.Write(printStubMethod(methodData{
- Method: m.Name(),
- Concrete: getStubReceiver(si),
- Interface: deduceIfaceName(si.Concrete.Obj().Pkg(), si.Interface.Pkg(), si.Interface),
- Signature: strings.TrimPrefix(sig, "func"),
- }))
- if err != nil {
- return nil, nil, fmt.Errorf("error printing method: %w", err)
- }
- methodsBuffer.WriteRune('\n')
- }
- }
- return methodsBuffer.Bytes(), stubImports, nil
-}
-
-// stubErr reurns the Go code implementation
-// of an error interface relevant to the
-// concrete type
-func stubErr(ctx context.Context, concreteFile *ast.File, si *stubmethods.StubInfo, snapshot Snapshot) []byte {
- return printStubMethod(methodData{
- Method: "Error",
- Interface: "error",
- Concrete: getStubReceiver(si),
- Signature: "() string",
- })
-}
-
-// getStubReceiver returns the concrete type's name as a method receiver.
-// It accounts for type parameters if they exist.
-func getStubReceiver(si *stubmethods.StubInfo) string {
- var concrete string
- if si.Pointer {
- concrete += "*"
- }
- concrete += si.Concrete.Obj().Name()
- concrete += FormatTypeParams(typeparams.ForNamed(si.Concrete))
- return concrete
-}
-
-type methodData struct {
- Method string
- Interface string
- Concrete string
- Signature string
-}
-
-// printStubMethod takes methodData and returns Go code that represents the given method such as:
-// // {{ .Method }} implements {{ .Interface }}
-// func ({{ .Concrete }}) {{ .Method }}{{ .Signature }} {
-// panic("unimplemented")
-// }
-func printStubMethod(md methodData) []byte {
- var b bytes.Buffer
- fmt.Fprintf(&b, "// %s implements %s\n", md.Method, md.Interface)
- fmt.Fprintf(&b, "func (%s) %s%s {\n\t", md.Concrete, md.Method, md.Signature)
- fmt.Fprintln(&b, `panic("unimplemented")`)
- fmt.Fprintln(&b, "}")
- return b.Bytes()
-}
-
-func deducePkgFromTypes(ctx context.Context, snapshot Snapshot, ifaceObj types.Object) (Package, error) {
- pkgs, err := snapshot.KnownPackages(ctx)
- if err != nil {
- return nil, err
- }
- for _, p := range pkgs {
- if p.PkgPath() == ifaceObj.Pkg().Path() {
- return p, nil
- }
- }
- return nil, fmt.Errorf("pkg %q not found", ifaceObj.Pkg().Path())
-}
-
-func deduceIfaceName(concretePkg, ifacePkg *types.Package, ifaceObj types.Object) string {
- if concretePkg.Path() == ifacePkg.Path() {
- return ifaceObj.Name()
- }
- return fmt.Sprintf("%s.%s", ifacePkg.Name(), ifaceObj.Name())
-}
-
-func getStubNodes(pgf *ParsedGoFile, pRng protocol.Range) ([]ast.Node, token.Pos, error) {
- spn, err := pgf.Mapper.RangeSpan(pRng)
- if err != nil {
- return nil, 0, err
- }
- rng, err := spn.Range(pgf.Mapper.Converter)
- if err != nil {
- return nil, 0, err
- }
- nodes, _ := astutil.PathEnclosingInterval(pgf.File, rng.Start, rng.End)
- return nodes, rng.Start, nil
-}
-
-/*
-missingMethods takes a concrete type and returns any missing methods for the given interface as well as
-any missing interface that might have been embedded to its parent. For example:
-
-type I interface {
- io.Writer
- Hello()
-}
-returns []*missingInterface{
- {
- iface: *types.Interface (io.Writer),
- file: *ast.File: io.go,
- missing []*types.Func{Write},
- },
- {
- iface: *types.Interface (I),
- file: *ast.File: myfile.go,
- missing: []*types.Func{Hello}
- },
-}
-*/
-func missingMethods(ctx context.Context, snapshot Snapshot, concMS *types.MethodSet, concPkg *types.Package, ifaceObj types.Object, ifacePkg Package, visited map[string]struct{}) ([]*missingInterface, error) {
- iface, ok := ifaceObj.Type().Underlying().(*types.Interface)
- if !ok {
- return nil, fmt.Errorf("expected %v to be an interface but got %T", iface, ifaceObj.Type().Underlying())
- }
- missing := []*missingInterface{}
- for i := 0; i < iface.NumEmbeddeds(); i++ {
- eiface := iface.Embedded(i).Obj()
- depPkg := ifacePkg
- if eiface.Pkg().Path() != ifacePkg.PkgPath() {
- var err error
- depPkg, err = ifacePkg.GetImport(eiface.Pkg().Path())
- if err != nil {
- return nil, err
- }
- }
- em, err := missingMethods(ctx, snapshot, concMS, concPkg, eiface, depPkg, visited)
- if err != nil {
- return nil, err
- }
- missing = append(missing, em...)
- }
- parsedFile, _, err := getStubFile(ctx, ifaceObj, snapshot)
- if err != nil {
- return nil, fmt.Errorf("error getting iface file: %w", err)
- }
- mi := &missingInterface{
- pkg: ifacePkg,
- iface: iface,
- file: parsedFile.File,
- }
- if mi.file == nil {
- return nil, fmt.Errorf("could not find ast.File for %v", ifaceObj.Name())
- }
- for i := 0; i < iface.NumExplicitMethods(); i++ {
- method := iface.ExplicitMethod(i)
- // if the concrete type does not have the interface method
- if concMS.Lookup(concPkg, method.Name()) == nil {
- if _, ok := visited[method.Name()]; !ok {
- mi.missing = append(mi.missing, method)
- visited[method.Name()] = struct{}{}
- }
- }
- if sel := concMS.Lookup(concPkg, method.Name()); sel != nil {
- implSig := sel.Type().(*types.Signature)
- ifaceSig := method.Type().(*types.Signature)
- if !types.Identical(ifaceSig, implSig) {
- return nil, fmt.Errorf("mimsatched %q function signatures:\nhave: %s\nwant: %s", method.Name(), implSig, ifaceSig)
- }
- }
- }
- if len(mi.missing) > 0 {
- missing = append(missing, mi)
- }
- return missing, nil
-}
-
-func getStubFile(ctx context.Context, obj types.Object, snapshot Snapshot) (*ParsedGoFile, VersionedFileHandle, error) {
- objPos := snapshot.FileSet().Position(obj.Pos())
- objFile := span.URIFromPath(objPos.Filename)
- objectFH := snapshot.FindFile(objFile)
- _, goFile, err := GetParsedFile(ctx, snapshot, objectFH, WidestPackage)
- if err != nil {
- return nil, nil, fmt.Errorf("GetParsedFile: %w", err)
- }
- return goFile, objectFH, nil
-}
-
-// missingInterface represents an interface
-// that has all or some of its methods missing
-// from the destination concrete type
-type missingInterface struct {
- iface *types.Interface
- file *ast.File
- pkg Package
- missing []*types.Func
-}
-
-// stubImport represents a newly added import
-// statement to the concrete type. If name is not
-// empty, then that import is required to have that name.
-type stubImport struct{ Name, Path string }