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-04-24 17:26:34 -06:00
|
|
|
"fmt"
|
|
|
|
"go/ast"
|
|
|
|
"go/token"
|
|
|
|
"go/types"
|
2019-06-27 12:59:09 -06:00
|
|
|
"path/filepath"
|
2019-08-02 17:45:56 -06:00
|
|
|
"regexp"
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
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-08-26 22:26:45 -06:00
|
|
|
func fileToMapper(ctx context.Context, view View, uri span.URI) (*ast.File, Package, *protocol.ColumnMapper, error) {
|
|
|
|
f, err := view.GetFile(ctx, uri)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, nil, err
|
|
|
|
}
|
|
|
|
gof, ok := f.(GoFile)
|
|
|
|
if !ok {
|
|
|
|
return nil, nil, nil, errors.Errorf("%s is not a Go file", f.URI())
|
|
|
|
}
|
|
|
|
pkg, err := gof.GetPackage(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, nil, err
|
|
|
|
}
|
|
|
|
file, m, err := pkgToMapper(ctx, view, pkg, uri)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, nil, err
|
|
|
|
}
|
|
|
|
return file, pkg, m, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func cachedFileToMapper(ctx context.Context, view View, uri span.URI) (*ast.File, *protocol.ColumnMapper, error) {
|
|
|
|
f, err := view.GetFile(ctx, uri)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
gof, ok := f.(GoFile)
|
|
|
|
if !ok {
|
|
|
|
return nil, nil, errors.Errorf("%s is not a Go file", f.URI())
|
|
|
|
}
|
|
|
|
if file, ok := gof.Builtin(); ok {
|
|
|
|
return builtinFileToMapper(ctx, view, gof, file)
|
|
|
|
}
|
|
|
|
pkg, err := gof.GetCachedPackage(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
file, m, err := pkgToMapper(ctx, view, pkg, uri)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
return file, m, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func pkgToMapper(ctx context.Context, view View, pkg Package, uri span.URI) (*ast.File, *protocol.ColumnMapper, error) {
|
|
|
|
var ph ParseGoHandle
|
|
|
|
for _, h := range pkg.GetHandles() {
|
|
|
|
if h.File().Identity().URI == uri {
|
|
|
|
ph = h
|
|
|
|
}
|
|
|
|
}
|
|
|
|
file, err := ph.Cached(ctx)
|
|
|
|
if file == nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
data, _, err := ph.File().Read(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
fset := view.Session().Cache().FileSet()
|
|
|
|
tok := fset.File(file.Pos())
|
|
|
|
if tok == nil {
|
|
|
|
return nil, nil, errors.Errorf("no token.File for %s", uri)
|
|
|
|
}
|
|
|
|
return file, protocol.NewColumnMapper(uri, uri.Filename(), fset, tok, data), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func builtinFileToMapper(ctx context.Context, view View, f GoFile, file *ast.File) (*ast.File, *protocol.ColumnMapper, error) {
|
|
|
|
fh := f.Handle(ctx)
|
|
|
|
data, _, err := fh.Read(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
fset := view.Session().Cache().FileSet()
|
|
|
|
tok := fset.File(file.Pos())
|
|
|
|
if tok == nil {
|
|
|
|
return nil, nil, errors.Errorf("no token.File for %s", f.URI())
|
|
|
|
}
|
|
|
|
return nil, protocol.NewColumnMapper(f.URI(), f.URI().Filename(), fset, tok, data), nil
|
|
|
|
}
|
|
|
|
|
2019-08-02 17:45:56 -06:00
|
|
|
func IsGenerated(ctx context.Context, view View, uri span.URI) bool {
|
|
|
|
f, err := view.GetFile(ctx, uri)
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
ph := view.Session().Cache().ParseGoHandle(f.Handle(ctx), ParseHeader)
|
|
|
|
parsed, err := ph.Parse(ctx)
|
|
|
|
if parsed == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
tok := view.Session().Cache().FileSet().File(parsed.Pos())
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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-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
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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.
|
|
|
|
func resolveInvalid(obj types.Object, node ast.Node, info *types.Info) types.Object {
|
|
|
|
// Construct a fake type for the object and return a fake object with this type.
|
|
|
|
formatResult := func(expr ast.Expr) types.Object {
|
|
|
|
var typename string
|
|
|
|
switch t := expr.(type) {
|
|
|
|
case *ast.SelectorExpr:
|
|
|
|
typename = fmt.Sprintf("%s.%s", t.X, t.Sel)
|
|
|
|
case *ast.Ident:
|
|
|
|
typename = t.String()
|
|
|
|
default:
|
|
|
|
return nil
|
|
|
|
}
|
2019-06-17 12:11:13 -06:00
|
|
|
typ := types.NewNamed(types.NewTypeName(token.NoPos, obj.Pkg(), typename, nil), types.Typ[types.Invalid], nil)
|
2019-04-24 17:26:34 -06:00
|
|
|
return types.NewVar(obj.Pos(), obj.Pkg(), obj.Name(), typ)
|
|
|
|
}
|
|
|
|
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
|
|
|
|
// TODO(rstambler): Handle range statements.
|
|
|
|
default:
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
})
|
|
|
|
return formatResult(resultExpr)
|
|
|
|
}
|
|
|
|
|
2019-05-15 15:58:16 -06:00
|
|
|
func lookupBuiltinDecl(v View, name string) interface{} {
|
|
|
|
builtinPkg := v.BuiltinPackage()
|
|
|
|
if builtinPkg == nil || builtinPkg.Scope == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
obj := builtinPkg.Scope.Lookup(name)
|
|
|
|
if obj == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return obj.Decl
|
|
|
|
}
|
|
|
|
|
2019-04-24 17:26:34 -06:00
|
|
|
func isPointer(T types.Type) bool {
|
|
|
|
_, ok := T.(*types.Pointer)
|
|
|
|
return ok
|
|
|
|
}
|
|
|
|
|
|
|
|
// deref returns a pointer's element type; otherwise it returns typ.
|
|
|
|
func deref(typ types.Type) types.Type {
|
|
|
|
if p, ok := typ.Underlying().(*types.Pointer); ok {
|
|
|
|
return p.Elem()
|
|
|
|
}
|
|
|
|
return typ
|
|
|
|
}
|
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
|
|
|
|
}
|
|
|
|
|
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-04-29 19:08:16 -06:00
|
|
|
func formatParams(tup *types.Tuple, variadic bool, qf types.Qualifier) []string {
|
|
|
|
params := make([]string, 0, tup.Len())
|
|
|
|
for i := 0; i < tup.Len(); i++ {
|
|
|
|
el := tup.At(i)
|
|
|
|
typ := types.TypeString(el.Type(), qf)
|
|
|
|
|
|
|
|
// Handle a variadic parameter (can only be the final parameter).
|
|
|
|
if variadic && i == tup.Len()-1 {
|
|
|
|
typ = strings.Replace(typ, "[]", "...", 1)
|
|
|
|
}
|
|
|
|
|
|
|
|
if el.Name() == "" {
|
|
|
|
params = append(params, typ)
|
|
|
|
} else {
|
|
|
|
params = append(params, el.Name()+" "+typ)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return params
|
|
|
|
}
|
|
|
|
|
|
|
|
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.
|
|
|
|
func formatType(typ types.Type, qf types.Qualifier) (detail string, kind CompletionItemKind) {
|
|
|
|
if types.IsInterface(typ) {
|
|
|
|
detail = "interface{...}"
|
|
|
|
kind = InterfaceCompletionItem
|
|
|
|
} else if _, ok := typ.(*types.Struct); ok {
|
|
|
|
detail = "struct{...}"
|
|
|
|
kind = StructCompletionItem
|
|
|
|
} else if typ != typ.Underlying() {
|
|
|
|
detail, kind = formatType(typ.Underlying(), qf)
|
|
|
|
} else {
|
|
|
|
detail = types.TypeString(typ, qf)
|
|
|
|
kind = TypeCompletionItem
|
|
|
|
}
|
|
|
|
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
|
|
|
}
|