2019-05-10 14:35:43 -06:00
|
|
|
// 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.
|
|
|
|
|
2019-04-24 17:26:34 -06:00
|
|
|
package source
|
|
|
|
|
|
|
|
import (
|
2019-08-02 17:45:56 -06:00
|
|
|
"context"
|
2019-12-09 09:36:55 -07:00
|
|
|
"fmt"
|
2019-04-24 17:26:34 -06:00
|
|
|
"go/ast"
|
2019-11-22 12:49:12 -07:00
|
|
|
"go/printer"
|
2019-04-24 17:26:34 -06:00
|
|
|
"go/token"
|
|
|
|
"go/types"
|
2019-06-27 12:59:09 -06:00
|
|
|
"path/filepath"
|
2019-08-02 17:45:56 -06:00
|
|
|
"regexp"
|
2019-11-20 23:24:43 -07:00
|
|
|
"sort"
|
2019-04-29 19:08:16 -06:00
|
|
|
"strings"
|
2019-08-02 17:45:56 -06:00
|
|
|
|
2019-08-16 15:05:40 -06:00
|
|
|
"golang.org/x/tools/internal/lsp/protocol"
|
2019-08-02 17:45:56 -06:00
|
|
|
"golang.org/x/tools/internal/span"
|
2019-08-26 22:26:45 -06:00
|
|
|
errors "golang.org/x/xerrors"
|
2019-04-24 17:26:34 -06:00
|
|
|
)
|
|
|
|
|
2019-08-16 15:05:40 -06:00
|
|
|
type mappedRange struct {
|
|
|
|
spanRange span.Range
|
|
|
|
m *protocol.ColumnMapper
|
|
|
|
|
|
|
|
// protocolRange is the result of converting the spanRange using the mapper.
|
|
|
|
// It is computed on-demand.
|
|
|
|
protocolRange *protocol.Range
|
|
|
|
}
|
|
|
|
|
2019-11-22 13:20:08 -07:00
|
|
|
func newMappedRange(fset *token.FileSet, m *protocol.ColumnMapper, start, end token.Pos) mappedRange {
|
|
|
|
return mappedRange{
|
|
|
|
spanRange: span.Range{
|
|
|
|
FileSet: fset,
|
|
|
|
Start: start,
|
|
|
|
End: end,
|
|
|
|
Converter: m.Converter,
|
|
|
|
},
|
|
|
|
m: m,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-16 15:05:40 -06:00
|
|
|
func (s mappedRange) Range() (protocol.Range, error) {
|
|
|
|
if s.protocolRange == nil {
|
|
|
|
spn, err := s.spanRange.Span()
|
|
|
|
if err != nil {
|
|
|
|
return protocol.Range{}, err
|
|
|
|
}
|
|
|
|
prng, err := s.m.Range(spn)
|
|
|
|
if err != nil {
|
|
|
|
return protocol.Range{}, err
|
|
|
|
}
|
|
|
|
s.protocolRange = &prng
|
|
|
|
}
|
|
|
|
return *s.protocolRange, nil
|
|
|
|
}
|
|
|
|
|
2019-08-26 22:26:45 -06:00
|
|
|
func (s mappedRange) Span() (span.Span, error) {
|
|
|
|
return s.spanRange.Span()
|
|
|
|
}
|
|
|
|
|
2019-08-16 15:05:40 -06:00
|
|
|
func (s mappedRange) URI() span.URI {
|
|
|
|
return s.m.URI
|
|
|
|
}
|
|
|
|
|
2019-12-04 11:45:53 -07:00
|
|
|
// getParsedFile is a convenience function that extracts the Package and ParseGoHandle for a File in a Snapshot.
|
2020-01-14 16:29:21 -07:00
|
|
|
// selectPackage is typically Narrowest/WidestPackageHandle below.
|
2019-12-17 16:57:54 -07:00
|
|
|
func getParsedFile(ctx context.Context, snapshot Snapshot, fh FileHandle, selectPackage PackagePolicy) (Package, ParseGoHandle, error) {
|
2019-12-04 11:45:53 -07:00
|
|
|
phs, err := snapshot.PackageHandles(ctx, fh)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
ph, err := selectPackage(phs)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
pkg, err := ph.Check(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
2019-12-17 16:57:54 -07:00
|
|
|
pgh, err := pkg.File(fh.Identity().URI)
|
2019-12-04 11:45:53 -07:00
|
|
|
return pkg, pgh, err
|
|
|
|
}
|
|
|
|
|
2019-12-07 23:07:30 -07:00
|
|
|
type PackagePolicy func([]PackageHandle) (PackageHandle, error)
|
|
|
|
|
2020-01-14 16:29:21 -07:00
|
|
|
// NarrowestPackageHandle picks the "narrowest" package for a given file.
|
2019-09-09 18:22:42 -06:00
|
|
|
//
|
|
|
|
// By "narrowest" package, we mean the package with the fewest number of files
|
|
|
|
// that includes the given file. This solves the problem of test variants,
|
|
|
|
// as the test will have more files than the non-test package.
|
2020-01-14 16:29:21 -07:00
|
|
|
func NarrowestPackageHandle(handles []PackageHandle) (PackageHandle, error) {
|
2019-09-09 17:26:26 -06:00
|
|
|
if len(handles) < 1 {
|
2020-01-14 16:29:21 -07:00
|
|
|
return nil, errors.Errorf("no PackageHandles")
|
2019-09-09 17:26:26 -06:00
|
|
|
}
|
|
|
|
result := handles[0]
|
|
|
|
for _, handle := range handles[1:] {
|
2019-11-20 14:15:00 -07:00
|
|
|
if result == nil || len(handle.CompiledGoFiles()) < len(result.CompiledGoFiles()) {
|
2019-09-09 17:26:26 -06:00
|
|
|
result = handle
|
2019-09-09 18:22:42 -06:00
|
|
|
}
|
|
|
|
}
|
2019-10-10 13:22:30 -06:00
|
|
|
if result == nil {
|
2020-01-14 16:29:21 -07:00
|
|
|
return nil, errors.Errorf("nil PackageHandles have been returned")
|
2019-10-10 13:22:30 -06:00
|
|
|
}
|
|
|
|
return result, nil
|
2019-09-09 17:26:26 -06:00
|
|
|
}
|
|
|
|
|
2020-01-14 16:29:21 -07:00
|
|
|
// WidestPackageHandle returns the PackageHandle containing the most files.
|
2019-09-09 17:26:26 -06:00
|
|
|
//
|
|
|
|
// This is useful for something like diagnostics, where we'd prefer to offer diagnostics
|
|
|
|
// for as many files as possible.
|
2020-01-14 16:29:21 -07:00
|
|
|
func WidestPackageHandle(handles []PackageHandle) (PackageHandle, error) {
|
2019-09-09 17:26:26 -06:00
|
|
|
if len(handles) < 1 {
|
2020-01-14 16:29:21 -07:00
|
|
|
return nil, errors.Errorf("no PackageHandles")
|
2019-09-09 17:26:26 -06:00
|
|
|
}
|
|
|
|
result := handles[0]
|
|
|
|
for _, handle := range handles[1:] {
|
2019-11-20 14:15:00 -07:00
|
|
|
if result == nil || len(handle.CompiledGoFiles()) > len(result.CompiledGoFiles()) {
|
2019-09-09 17:26:26 -06:00
|
|
|
result = handle
|
|
|
|
}
|
2019-09-09 18:22:42 -06:00
|
|
|
}
|
2019-10-10 13:22:30 -06:00
|
|
|
if result == nil {
|
2020-01-14 16:29:21 -07:00
|
|
|
return nil, errors.Errorf("nil PackageHandles have been returned")
|
2019-10-10 13:22:30 -06:00
|
|
|
}
|
|
|
|
return result, nil
|
2019-09-09 18:22:42 -06:00
|
|
|
}
|
|
|
|
|
2019-12-07 23:07:30 -07:00
|
|
|
// SpecificPackageHandle creates a PackagePolicy to select a
|
|
|
|
// particular PackageHandle when you alread know the one you want.
|
|
|
|
func SpecificPackageHandle(desiredID string) PackagePolicy {
|
|
|
|
return func(handles []PackageHandle) (PackageHandle, error) {
|
|
|
|
for _, h := range handles {
|
|
|
|
if h.ID() == desiredID {
|
|
|
|
return h, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, fmt.Errorf("no package handle with expected id %q", desiredID)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-11 21:59:57 -07:00
|
|
|
func IsGenerated(ctx context.Context, snapshot Snapshot, uri span.URI) bool {
|
|
|
|
fh, err := snapshot.GetFile(uri)
|
2019-08-02 17:45:56 -06:00
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
2020-01-11 21:59:57 -07:00
|
|
|
ph := snapshot.View().Session().Cache().ParseGoHandle(fh, ParseHeader)
|
2019-09-17 09:19:11 -06:00
|
|
|
parsed, _, _, err := ph.Parse(ctx)
|
|
|
|
if err != nil {
|
2019-08-02 17:45:56 -06:00
|
|
|
return false
|
|
|
|
}
|
2020-01-11 21:59:57 -07:00
|
|
|
tok := snapshot.View().Session().Cache().FileSet().File(parsed.Pos())
|
2019-08-02 17:45:56 -06:00
|
|
|
if tok == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
for _, commentGroup := range parsed.Comments {
|
|
|
|
for _, comment := range commentGroup.List {
|
|
|
|
if matched := generatedRx.MatchString(comment.Text); matched {
|
|
|
|
// Check if comment is at the beginning of the line in source.
|
|
|
|
if pos := tok.Position(comment.Slash); pos.Column == 1 {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2020-01-15 17:55:03 -07:00
|
|
|
func nodeToProtocolRange(view View, pkg Package, n ast.Node) (protocol.Range, error) {
|
|
|
|
mrng, err := posToMappedRange(view, pkg, n.Pos(), n.End())
|
2019-09-05 16:54:05 -06:00
|
|
|
if err != nil {
|
|
|
|
return protocol.Range{}, err
|
|
|
|
}
|
|
|
|
return mrng.Range()
|
|
|
|
}
|
|
|
|
|
2019-11-22 12:49:12 -07:00
|
|
|
func objToMappedRange(v View, pkg Package, obj types.Object) (mappedRange, error) {
|
2019-09-05 16:54:05 -06:00
|
|
|
if pkgName, ok := obj.(*types.PkgName); ok {
|
|
|
|
// An imported Go package has a package-local, unqualified name.
|
|
|
|
// When the name matches the imported package name, there is no
|
|
|
|
// identifier in the import spec with the local package name.
|
|
|
|
//
|
|
|
|
// For example:
|
|
|
|
// import "go/ast" // name "ast" matches package name
|
|
|
|
// import a "go/ast" // name "a" does not match package name
|
|
|
|
//
|
|
|
|
// When the identifier does not appear in the source, have the range
|
2019-11-19 12:51:46 -07:00
|
|
|
// of the object be the import path, including quotes.
|
2019-09-05 16:54:05 -06:00
|
|
|
if pkgName.Imported().Name() == pkgName.Name() {
|
2019-11-22 12:49:12 -07:00
|
|
|
return posToMappedRange(v, pkg, obj.Pos(), obj.Pos()+token.Pos(len(pkgName.Imported().Path())+2))
|
2019-09-05 16:54:05 -06:00
|
|
|
}
|
|
|
|
}
|
2019-11-22 12:49:12 -07:00
|
|
|
return nameToMappedRange(v, pkg, obj.Pos(), obj.Name())
|
2019-09-05 16:54:05 -06:00
|
|
|
}
|
|
|
|
|
2019-11-22 12:49:12 -07:00
|
|
|
func nameToMappedRange(v View, pkg Package, pos token.Pos, name string) (mappedRange, error) {
|
|
|
|
return posToMappedRange(v, pkg, pos, pos+token.Pos(len(name)))
|
2019-09-05 16:54:05 -06:00
|
|
|
}
|
|
|
|
|
2019-11-22 12:49:12 -07:00
|
|
|
func posToMappedRange(v View, pkg Package, pos, end token.Pos) (mappedRange, error) {
|
2019-11-21 12:54:31 -07:00
|
|
|
logicalFilename := v.Session().Cache().FileSet().File(pos).Position(pos).Filename
|
2020-01-10 15:37:29 -07:00
|
|
|
m, err := findMapperInPackage(v, pkg, span.FileURI(logicalFilename))
|
2019-09-16 16:17:51 -06:00
|
|
|
if err != nil {
|
|
|
|
return mappedRange{}, err
|
|
|
|
}
|
2019-09-05 16:54:05 -06:00
|
|
|
if !pos.IsValid() {
|
|
|
|
return mappedRange{}, errors.Errorf("invalid position for %v", pos)
|
|
|
|
}
|
|
|
|
if !end.IsValid() {
|
|
|
|
return mappedRange{}, errors.Errorf("invalid position for %v", end)
|
|
|
|
}
|
2020-01-15 17:55:03 -07:00
|
|
|
return newMappedRange(v.Session().Cache().FileSet(), m, pos, end), nil
|
2019-09-05 16:54:05 -06:00
|
|
|
}
|
|
|
|
|
2019-08-02 17:45:56 -06:00
|
|
|
// Matches cgo generated comment as well as the proposed standard:
|
|
|
|
// https://golang.org/s/generatedcode
|
|
|
|
var generatedRx = regexp.MustCompile(`// .*DO NOT EDIT\.?`)
|
|
|
|
|
2019-06-27 12:59:09 -06:00
|
|
|
func DetectLanguage(langID, filename string) FileKind {
|
|
|
|
switch langID {
|
|
|
|
case "go":
|
|
|
|
return Go
|
|
|
|
case "go.mod":
|
|
|
|
return Mod
|
|
|
|
case "go.sum":
|
|
|
|
return Sum
|
|
|
|
}
|
|
|
|
// Fallback to detecting the language based on the file extension.
|
|
|
|
switch filepath.Ext(filename) {
|
|
|
|
case ".mod":
|
|
|
|
return Mod
|
|
|
|
case ".sum":
|
|
|
|
return Sum
|
|
|
|
default: // fallback to Go
|
|
|
|
return Go
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (k FileKind) String() string {
|
|
|
|
switch k {
|
|
|
|
case Mod:
|
|
|
|
return "go.mod"
|
|
|
|
case Sum:
|
|
|
|
return "go.sum"
|
|
|
|
default:
|
|
|
|
return "go"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-21 17:26:14 -07:00
|
|
|
// Returns the index and the node whose position is contained inside the node list.
|
|
|
|
func nodeAtPos(nodes []ast.Node, pos token.Pos) (ast.Node, int) {
|
|
|
|
if nodes == nil {
|
|
|
|
return nil, -1
|
|
|
|
}
|
|
|
|
for i, node := range nodes {
|
|
|
|
if node.Pos() <= pos && pos <= node.End() {
|
|
|
|
return node, i
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil, -1
|
|
|
|
}
|
|
|
|
|
2019-04-24 17:26:34 -06:00
|
|
|
// indexExprAtPos returns the index of the expression containing pos.
|
|
|
|
func indexExprAtPos(pos token.Pos, args []ast.Expr) int {
|
|
|
|
for i, expr := range args {
|
|
|
|
if expr.Pos() <= pos && pos <= expr.End() {
|
|
|
|
return i
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return len(args)
|
|
|
|
}
|
|
|
|
|
|
|
|
func exprAtPos(pos token.Pos, args []ast.Expr) ast.Expr {
|
|
|
|
for _, expr := range args {
|
|
|
|
if expr.Pos() <= pos && pos <= expr.End() {
|
|
|
|
return expr
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// fieldSelections returns the set of fields that can
|
|
|
|
// be selected from a value of type T.
|
|
|
|
func fieldSelections(T types.Type) (fields []*types.Var) {
|
|
|
|
// TODO(adonovan): this algorithm doesn't exclude ambiguous
|
|
|
|
// selections that match more than one field/method.
|
|
|
|
// types.NewSelectionSet should do that for us.
|
|
|
|
|
2019-06-27 11:50:01 -06:00
|
|
|
seen := make(map[*types.Var]bool) // for termination on recursive types
|
|
|
|
|
2019-04-24 17:26:34 -06:00
|
|
|
var visit func(T types.Type)
|
|
|
|
visit = func(T types.Type) {
|
2019-06-27 11:50:01 -06:00
|
|
|
if T, ok := deref(T).Underlying().(*types.Struct); ok {
|
|
|
|
for i := 0; i < T.NumFields(); i++ {
|
|
|
|
f := T.Field(i)
|
|
|
|
if seen[f] {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
seen[f] = true
|
|
|
|
fields = append(fields, f)
|
|
|
|
if f.Anonymous() {
|
|
|
|
visit(f.Type())
|
2019-04-24 17:26:34 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
visit(T)
|
|
|
|
|
|
|
|
return fields
|
|
|
|
}
|
|
|
|
|
2019-12-06 10:59:56 -07:00
|
|
|
// typeIsValid reports whether typ doesn't contain any Invalid types.
|
|
|
|
func typeIsValid(typ types.Type) bool {
|
|
|
|
switch typ := typ.Underlying().(type) {
|
|
|
|
case *types.Basic:
|
|
|
|
return typ.Kind() != types.Invalid
|
|
|
|
case *types.Array:
|
|
|
|
return typeIsValid(typ.Elem())
|
|
|
|
case *types.Slice:
|
|
|
|
return typeIsValid(typ.Elem())
|
|
|
|
case *types.Pointer:
|
|
|
|
return typeIsValid(typ.Elem())
|
|
|
|
case *types.Map:
|
|
|
|
return typeIsValid(typ.Key()) && typeIsValid(typ.Elem())
|
|
|
|
case *types.Chan:
|
|
|
|
return typeIsValid(typ.Elem())
|
|
|
|
case *types.Signature:
|
|
|
|
return typeIsValid(typ.Params()) && typeIsValid(typ.Results())
|
|
|
|
case *types.Tuple:
|
|
|
|
for i := 0; i < typ.Len(); i++ {
|
|
|
|
if !typeIsValid(typ.At(i).Type()) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
case *types.Struct, *types.Interface, *types.Named:
|
|
|
|
// Don't bother checking structs, interfaces, or named types for validity.
|
|
|
|
return true
|
|
|
|
default:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-24 17:26:34 -06:00
|
|
|
// resolveInvalid traverses the node of the AST that defines the scope
|
|
|
|
// containing the declaration of obj, and attempts to find a user-friendly
|
|
|
|
// name for its invalid type. The resulting Object and its Type are fake.
|
2019-12-06 10:59:56 -07:00
|
|
|
func resolveInvalid(fset *token.FileSet, obj types.Object, node ast.Node, info *types.Info) types.Object {
|
2019-04-24 17:26:34 -06:00
|
|
|
var resultExpr ast.Expr
|
|
|
|
ast.Inspect(node, func(node ast.Node) bool {
|
|
|
|
switch n := node.(type) {
|
|
|
|
case *ast.ValueSpec:
|
|
|
|
for _, name := range n.Names {
|
|
|
|
if info.Defs[name] == obj {
|
|
|
|
resultExpr = n.Type
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
case *ast.Field: // This case handles parameters and results of a FuncDecl or FuncLit.
|
|
|
|
for _, name := range n.Names {
|
|
|
|
if info.Defs[name] == obj {
|
|
|
|
resultExpr = n.Type
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
default:
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
})
|
2019-12-06 10:59:56 -07:00
|
|
|
// Construct a fake type for the object and return a fake object with this type.
|
|
|
|
typename := formatNode(fset, resultExpr)
|
|
|
|
typ := types.NewNamed(types.NewTypeName(token.NoPos, obj.Pkg(), typename, nil), types.Typ[types.Invalid], nil)
|
|
|
|
return types.NewVar(obj.Pos(), obj.Pkg(), obj.Name(), typ)
|
2019-04-24 17:26:34 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
func isPointer(T types.Type) bool {
|
|
|
|
_, ok := T.(*types.Pointer)
|
|
|
|
return ok
|
|
|
|
}
|
|
|
|
|
2019-12-22 10:58:14 -07:00
|
|
|
func isVar(obj types.Object) bool {
|
|
|
|
_, ok := obj.(*types.Var)
|
|
|
|
return ok
|
|
|
|
}
|
|
|
|
|
2019-12-06 00:44:16 -07:00
|
|
|
// deref returns a pointer's element type, traversing as many levels as needed.
|
|
|
|
// Otherwise it returns typ.
|
2019-04-24 17:26:34 -06:00
|
|
|
func deref(typ types.Type) types.Type {
|
2019-12-06 00:44:16 -07:00
|
|
|
for {
|
|
|
|
p, ok := typ.Underlying().(*types.Pointer)
|
|
|
|
if !ok {
|
|
|
|
return typ
|
|
|
|
}
|
|
|
|
typ = p.Elem()
|
2019-04-24 17:26:34 -06:00
|
|
|
}
|
|
|
|
}
|
2019-04-29 19:08:16 -06:00
|
|
|
|
2019-06-17 12:11:13 -06:00
|
|
|
func isTypeName(obj types.Object) bool {
|
|
|
|
_, ok := obj.(*types.TypeName)
|
|
|
|
return ok
|
|
|
|
}
|
|
|
|
|
2019-06-19 16:24:05 -06:00
|
|
|
func isFunc(obj types.Object) bool {
|
|
|
|
_, ok := obj.(*types.Func)
|
|
|
|
return ok
|
|
|
|
}
|
|
|
|
|
internal/lsp: add literal completion candidates
Add support for literal completion candidates such as "[]int{}" or
"make([]int, 0)". We support both named and unnamed types. I used the
existing type matching logic, so, for example, if the expected type is
an interface, we will suggest literal candidates that implement the
interface.
The literal candidates have a lower score than normal matching
candidates, so they shouldn't be disruptive in cases where you don't
want a literal candidate.
This commit adds support for slice, array, struct, map, and channel
literal candidates since they are pretty similar. Functions will be
supported in a subsequent commit.
I also added support for setting a snippet's final tab stop. This is
useful if you want the cursor to end up somewhere other than the
character after the snippet.
Change-Id: Id3b74260fff4d61703989b422267021b00cec005
Reviewed-on: https://go-review.googlesource.com/c/tools/+/193698
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2019-09-04 17:12:37 -06:00
|
|
|
func isEmptyInterface(T types.Type) bool {
|
|
|
|
intf, _ := T.(*types.Interface)
|
|
|
|
return intf != nil && intf.NumMethods() == 0
|
|
|
|
}
|
|
|
|
|
2019-11-15 17:14:15 -07:00
|
|
|
func isUntyped(T types.Type) bool {
|
|
|
|
if basic, ok := T.(*types.Basic); ok {
|
|
|
|
return basic.Info()&types.IsUntyped > 0
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2019-10-15 10:26:11 -06:00
|
|
|
// isSelector returns the enclosing *ast.SelectorExpr when pos is in the
|
|
|
|
// selector.
|
|
|
|
func enclosingSelector(path []ast.Node, pos token.Pos) *ast.SelectorExpr {
|
|
|
|
if len(path) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if sel, ok := path[0].(*ast.SelectorExpr); ok {
|
|
|
|
return sel
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, ok := path[0].(*ast.Ident); ok && len(path) > 1 {
|
|
|
|
if sel, ok := path[1].(*ast.SelectorExpr); ok && pos >= sel.Sel.Pos() {
|
|
|
|
return sel
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-12-22 16:04:15 -07:00
|
|
|
func enclosingValueSpec(path []ast.Node, pos token.Pos) *ast.ValueSpec {
|
|
|
|
for _, n := range path {
|
|
|
|
if vs, ok := n.(*ast.ValueSpec); ok {
|
|
|
|
return vs
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-06-27 16:37:50 -06:00
|
|
|
// typeConversion returns the type being converted to if call is a type
|
|
|
|
// conversion expression.
|
|
|
|
func typeConversion(call *ast.CallExpr, info *types.Info) types.Type {
|
|
|
|
var ident *ast.Ident
|
|
|
|
switch expr := call.Fun.(type) {
|
|
|
|
case *ast.Ident:
|
|
|
|
ident = expr
|
|
|
|
case *ast.SelectorExpr:
|
|
|
|
ident = expr.Sel
|
|
|
|
default:
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Type conversion (e.g. "float64(foo)").
|
|
|
|
if fun, _ := info.ObjectOf(ident).(*types.TypeName); fun != nil {
|
|
|
|
return fun.Type()
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-11-15 23:28:29 -07:00
|
|
|
// fieldsAccessible returns whether s has at least one field accessible by p.
|
|
|
|
func fieldsAccessible(s *types.Struct, p *types.Package) bool {
|
|
|
|
for i := 0; i < s.NumFields(); i++ {
|
|
|
|
f := s.Field(i)
|
|
|
|
if f.Exported() || f.Pkg() == p {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2020-01-15 17:11:40 -07:00
|
|
|
func formatParams(ctx context.Context, s Snapshot, pkg Package, sig *types.Signature, qf types.Qualifier) []string {
|
2019-11-22 12:49:12 -07:00
|
|
|
params := make([]string, 0, sig.Params().Len())
|
|
|
|
for i := 0; i < sig.Params().Len(); i++ {
|
|
|
|
el := sig.Params().At(i)
|
2020-01-15 17:11:40 -07:00
|
|
|
typ, err := formatFieldType(ctx, s, pkg, el, qf)
|
2019-11-22 12:49:12 -07:00
|
|
|
if err != nil {
|
|
|
|
typ = types.TypeString(el.Type(), qf)
|
|
|
|
}
|
2019-04-29 19:08:16 -06:00
|
|
|
|
|
|
|
// Handle a variadic parameter (can only be the final parameter).
|
2019-11-22 12:49:12 -07:00
|
|
|
if sig.Variadic() && i == sig.Params().Len()-1 {
|
2019-04-29 19:08:16 -06:00
|
|
|
typ = strings.Replace(typ, "[]", "...", 1)
|
|
|
|
}
|
|
|
|
|
|
|
|
if el.Name() == "" {
|
|
|
|
params = append(params, typ)
|
|
|
|
} else {
|
|
|
|
params = append(params, el.Name()+" "+typ)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return params
|
|
|
|
}
|
|
|
|
|
2020-01-15 17:11:40 -07:00
|
|
|
func formatFieldType(ctx context.Context, s Snapshot, srcpkg Package, obj types.Object, qf types.Qualifier) (string, error) {
|
2020-01-10 15:37:29 -07:00
|
|
|
file, pkg, err := findPosInPackage(s.View(), srcpkg, obj.Pos())
|
2019-11-22 12:49:12 -07:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2020-01-15 17:11:40 -07:00
|
|
|
ident, err := findIdentifier(ctx, s, pkg, file, obj.Pos())
|
2019-11-22 12:49:12 -07:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
if i := ident.ident; i == nil || i.Obj == nil || i.Obj.Decl == nil {
|
|
|
|
return "", errors.Errorf("no object for ident %v", i.Name)
|
|
|
|
}
|
|
|
|
f, ok := ident.ident.Obj.Decl.(*ast.Field)
|
|
|
|
if !ok {
|
|
|
|
return "", errors.Errorf("ident %s is not a field type", ident.Name)
|
|
|
|
}
|
2019-12-06 10:59:56 -07:00
|
|
|
return formatNode(s.View().Session().Cache().FileSet(), f.Type), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func formatNode(fset *token.FileSet, n ast.Node) string {
|
|
|
|
var buf strings.Builder
|
|
|
|
if err := printer.Fprint(&buf, fset, n); err != nil {
|
|
|
|
return ""
|
2019-11-22 12:49:12 -07:00
|
|
|
}
|
2019-12-06 10:59:56 -07:00
|
|
|
return buf.String()
|
2019-11-22 12:49:12 -07:00
|
|
|
}
|
|
|
|
|
2019-04-29 19:08:16 -06:00
|
|
|
func formatResults(tup *types.Tuple, qf types.Qualifier) ([]string, bool) {
|
|
|
|
var writeResultParens bool
|
|
|
|
results := make([]string, 0, tup.Len())
|
|
|
|
for i := 0; i < tup.Len(); i++ {
|
|
|
|
if i >= 1 {
|
|
|
|
writeResultParens = true
|
|
|
|
}
|
|
|
|
el := tup.At(i)
|
|
|
|
typ := types.TypeString(el.Type(), qf)
|
|
|
|
|
|
|
|
if el.Name() == "" {
|
|
|
|
results = append(results, typ)
|
|
|
|
} else {
|
|
|
|
if i == 0 {
|
|
|
|
writeResultParens = true
|
|
|
|
}
|
|
|
|
results = append(results, el.Name()+" "+typ)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return results, writeResultParens
|
|
|
|
}
|
|
|
|
|
|
|
|
// formatType returns the detail and kind for an object of type *types.TypeName.
|
2019-09-24 22:46:57 -06:00
|
|
|
func formatType(typ types.Type, qf types.Qualifier) (detail string, kind protocol.CompletionItemKind) {
|
2019-04-29 19:08:16 -06:00
|
|
|
if types.IsInterface(typ) {
|
|
|
|
detail = "interface{...}"
|
2019-09-24 22:46:57 -06:00
|
|
|
kind = protocol.InterfaceCompletion
|
2019-04-29 19:08:16 -06:00
|
|
|
} else if _, ok := typ.(*types.Struct); ok {
|
|
|
|
detail = "struct{...}"
|
2019-09-24 22:46:57 -06:00
|
|
|
kind = protocol.StructCompletion
|
2019-04-29 19:08:16 -06:00
|
|
|
} else if typ != typ.Underlying() {
|
|
|
|
detail, kind = formatType(typ.Underlying(), qf)
|
|
|
|
} else {
|
|
|
|
detail = types.TypeString(typ, qf)
|
2019-09-24 22:46:57 -06:00
|
|
|
kind = protocol.ClassCompletion
|
2019-04-29 19:08:16 -06:00
|
|
|
}
|
|
|
|
return detail, kind
|
|
|
|
}
|
|
|
|
|
2019-07-02 16:10:05 -06:00
|
|
|
func formatFunction(params []string, results []string, writeResultParens bool) string {
|
|
|
|
var detail strings.Builder
|
|
|
|
|
|
|
|
detail.WriteByte('(')
|
2019-04-29 19:08:16 -06:00
|
|
|
for i, p := range params {
|
|
|
|
if i > 0 {
|
2019-07-02 16:10:05 -06:00
|
|
|
detail.WriteString(", ")
|
2019-04-29 19:08:16 -06:00
|
|
|
}
|
2019-07-02 16:10:05 -06:00
|
|
|
detail.WriteString(p)
|
|
|
|
}
|
|
|
|
detail.WriteByte(')')
|
|
|
|
|
|
|
|
// Add space between parameters and results.
|
|
|
|
if len(results) > 0 {
|
|
|
|
detail.WriteByte(' ')
|
2019-04-29 19:08:16 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
if writeResultParens {
|
|
|
|
detail.WriteByte('(')
|
|
|
|
}
|
|
|
|
for i, p := range results {
|
|
|
|
if i > 0 {
|
|
|
|
detail.WriteString(", ")
|
|
|
|
}
|
|
|
|
detail.WriteString(p)
|
|
|
|
}
|
|
|
|
if writeResultParens {
|
|
|
|
detail.WriteByte(')')
|
|
|
|
}
|
|
|
|
|
2019-07-02 16:10:05 -06:00
|
|
|
return detail.String()
|
2019-04-29 19:08:16 -06:00
|
|
|
}
|
2019-11-20 23:24:43 -07:00
|
|
|
|
|
|
|
func SortDiagnostics(d []Diagnostic) {
|
|
|
|
sort.Slice(d, func(i int, j int) bool {
|
|
|
|
return CompareDiagnostic(d[i], d[j]) < 0
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func CompareDiagnostic(a, b Diagnostic) int {
|
|
|
|
if r := protocol.CompareRange(a.Range, b.Range); r != 0 {
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
if a.Message < b.Message {
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
if a.Message == b.Message {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
return 1
|
|
|
|
}
|
2020-01-10 15:37:29 -07:00
|
|
|
|
|
|
|
func findPosInPackage(v View, searchpkg Package, pos token.Pos) (*ast.File, Package, error) {
|
|
|
|
tok := v.Session().Cache().FileSet().File(pos)
|
|
|
|
if tok == nil {
|
|
|
|
return nil, nil, errors.Errorf("no file for pos in package %s", searchpkg.ID())
|
|
|
|
}
|
|
|
|
uri := span.FileURI(tok.Name())
|
|
|
|
|
|
|
|
var (
|
|
|
|
ph ParseGoHandle
|
|
|
|
pkg Package
|
|
|
|
err error
|
|
|
|
)
|
|
|
|
// Special case for ignored files.
|
|
|
|
if v.Ignore(uri) {
|
|
|
|
ph, err = findIgnoredFile(v, uri)
|
|
|
|
} else {
|
|
|
|
ph, pkg, err = findFileInPackage(searchpkg, uri)
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
file, _, _, err := ph.Cached()
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
if !(file.Pos() <= pos && pos <= file.End()) {
|
|
|
|
return nil, nil, fmt.Errorf("pos %v, apparently in file %q, is not between %v and %v", pos, ph.File().Identity().URI, file.Pos(), file.End())
|
|
|
|
}
|
|
|
|
return file, pkg, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func findMapperInPackage(v View, searchpkg Package, uri span.URI) (*protocol.ColumnMapper, error) {
|
|
|
|
var (
|
|
|
|
ph ParseGoHandle
|
|
|
|
err error
|
|
|
|
)
|
|
|
|
// Special case for ignored files.
|
|
|
|
if v.Ignore(uri) {
|
|
|
|
ph, err = findIgnoredFile(v, uri)
|
|
|
|
} else {
|
|
|
|
ph, _, err = findFileInPackage(searchpkg, uri)
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
_, m, _, err := ph.Cached()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return m, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func findIgnoredFile(v View, uri span.URI) (ParseGoHandle, error) {
|
|
|
|
fh, err := v.Snapshot().GetFile(uri)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return v.Session().Cache().ParseGoHandle(fh, ParseFull), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func findFileInPackage(pkg Package, uri span.URI) (ParseGoHandle, Package, error) {
|
|
|
|
queue := []Package{pkg}
|
|
|
|
seen := make(map[string]bool)
|
|
|
|
|
|
|
|
for len(queue) > 0 {
|
|
|
|
pkg := queue[0]
|
|
|
|
queue = queue[1:]
|
|
|
|
seen[pkg.ID()] = true
|
|
|
|
|
|
|
|
if f, err := pkg.File(uri); err == nil {
|
|
|
|
return f, pkg, nil
|
|
|
|
}
|
|
|
|
for _, dep := range pkg.Imports() {
|
|
|
|
if !seen[dep.ID()] {
|
|
|
|
queue = append(queue, dep)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil, nil, errors.Errorf("no file for %s in package %s", uri, pkg.ID())
|
|
|
|
}
|