blob: e6a3194318fa24acf6c4df85ea8f41bcb940fce9 [file] [log] [blame]
// 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 source
import (
"bytes"
"context"
"fmt"
"go/ast"
"go/printer"
"go/types"
"strings"
"golang.org/x/tools/internal/lsp/snippet"
"golang.org/x/tools/internal/lsp/telemetry/log"
"golang.org/x/tools/internal/lsp/telemetry/tag"
"golang.org/x/tools/internal/span"
)
// formatCompletion creates a completion item for a given candidate.
func (c *completer) item(cand candidate) (CompletionItem, error) {
obj := cand.obj
// Handle builtin types separately.
if obj.Parent() == types.Universe {
return c.formatBuiltin(cand)
}
var (
label = c.deepState.chainString(obj.Name())
detail = types.TypeString(obj.Type(), c.qf)
insert = label
kind CompletionItemKind
plainSnippet *snippet.Builder
placeholderSnippet *snippet.Builder
)
// expandFuncCall mutates the completion label, detail, and snippets
// to that of an invocation of sig.
expandFuncCall := func(sig *types.Signature) {
params := formatParams(sig.Params(), sig.Variadic(), c.qf)
plainSnippet, placeholderSnippet = c.functionCallSnippets(label, params)
results, writeParens := formatResults(sig.Results(), c.qf)
detail = "func" + formatFunction(params, results, writeParens)
}
switch obj := obj.(type) {
case *types.TypeName:
detail, kind = formatType(obj.Type(), c.qf)
case *types.Const:
kind = ConstantCompletionItem
case *types.Var:
if _, ok := obj.Type().(*types.Struct); ok {
detail = "struct{...}" // for anonymous structs
}
if obj.IsField() {
kind = FieldCompletionItem
plainSnippet, placeholderSnippet = c.structFieldSnippets(label, detail)
} else if c.isParameter(obj) {
kind = ParameterCompletionItem
} else {
kind = VariableCompletionItem
}
if sig, ok := obj.Type().Underlying().(*types.Signature); ok && cand.expandFuncCall {
expandFuncCall(sig)
}
case *types.Func:
sig, ok := obj.Type().Underlying().(*types.Signature)
if !ok {
break
}
kind = FunctionCompletionItem
if sig != nil && sig.Recv() != nil {
kind = MethodCompletionItem
}
if cand.expandFuncCall {
expandFuncCall(sig)
}
case *types.PkgName:
kind = PackageCompletionItem
detail = fmt.Sprintf("%q", obj.Imported().Path())
}
detail = strings.TrimPrefix(detail, "untyped ")
item := CompletionItem{
Label: label,
InsertText: insert,
Detail: detail,
Kind: kind,
Score: cand.score,
Depth: len(c.deepState.chain),
plainSnippet: plainSnippet,
placeholderSnippet: placeholderSnippet,
}
// TODO(rstambler): Log errors when this feature is enabled.
if c.opts.WantDocumentaton {
declRange, err := objToRange(c.ctx, c.view.Session().Cache().FileSet(), obj)
if err != nil {
goto Return
}
pos := declRange.FileSet.Position(declRange.Start)
if !pos.IsValid() {
goto Return
}
uri := span.FileURI(pos.Filename)
f, err := c.view.GetFile(c.ctx, uri)
if err != nil {
goto Return
}
gof, ok := f.(GoFile)
if !ok {
goto Return
}
pkg, err := gof.GetCachedPackage(c.ctx)
if err != nil {
goto Return
}
var file *ast.File
for _, ph := range pkg.GetHandles() {
if ph.File().Identity().URI == gof.URI() {
file, _ = ph.Cached(c.ctx)
}
}
if file == nil {
goto Return
}
ident, err := findIdentifier(c.ctx, gof, pkg, file, declRange.Start)
if err != nil {
goto Return
}
hover, err := ident.Hover(c.ctx)
if err != nil {
goto Return
}
item.Documentation = hover.Synopsis
}
Return:
return item, nil
}
// isParameter returns true if the given *types.Var is a parameter
// of the enclosingFunction.
func (c *completer) isParameter(v *types.Var) bool {
if c.enclosingFunction == nil {
return false
}
for i := 0; i < c.enclosingFunction.Params().Len(); i++ {
if c.enclosingFunction.Params().At(i) == v {
return true
}
}
return false
}
func (c *completer) formatBuiltin(cand candidate) (CompletionItem, error) {
obj := cand.obj
item := CompletionItem{
Label: obj.Name(),
InsertText: obj.Name(),
Score: cand.score,
}
switch obj.(type) {
case *types.Const:
item.Kind = ConstantCompletionItem
case *types.Builtin:
item.Kind = FunctionCompletionItem
decl, ok := lookupBuiltinDecl(c.view, obj.Name()).(*ast.FuncDecl)
if !ok {
break
}
params, _ := formatFieldList(c.ctx, c.view, decl.Type.Params)
results, writeResultParens := formatFieldList(c.ctx, c.view, decl.Type.Results)
item.Label = obj.Name()
item.Detail = "func" + formatFunction(params, results, writeResultParens)
item.plainSnippet, item.placeholderSnippet = c.functionCallSnippets(obj.Name(), params)
case *types.TypeName:
if types.IsInterface(obj.Type()) {
item.Kind = InterfaceCompletionItem
} else {
item.Kind = TypeCompletionItem
}
case *types.Nil:
item.Kind = VariableCompletionItem
}
return item, nil
}
var replacer = strings.NewReplacer(
`ComplexType`, `complex128`,
`FloatType`, `float64`,
`IntegerType`, `int`,
)
func formatFieldList(ctx context.Context, v View, list *ast.FieldList) ([]string, bool) {
if list == nil {
return nil, false
}
var writeResultParens bool
var result []string
for i := 0; i < len(list.List); i++ {
if i >= 1 {
writeResultParens = true
}
p := list.List[i]
cfg := printer.Config{Mode: printer.UseSpaces | printer.TabIndent, Tabwidth: 4}
b := &bytes.Buffer{}
if err := cfg.Fprint(b, v.Session().Cache().FileSet(), p.Type); err != nil {
log.Error(ctx, "unable to print type", nil, tag.Of("Type", p.Type))
continue
}
typ := replacer.Replace(b.String())
if len(p.Names) == 0 {
result = append(result, fmt.Sprintf("%s", typ))
}
for _, name := range p.Names {
if name.Name != "" {
if i == 0 {
writeResultParens = true
}
result = append(result, fmt.Sprintf("%s %s", name.Name, typ))
} else {
result = append(result, fmt.Sprintf("%s", typ))
}
}
}
return result, writeResultParens
}
// qualifier returns a function that appropriately formats a types.PkgName
// appearing in a *ast.File.
func qualifier(f *ast.File, pkg *types.Package, info *types.Info) types.Qualifier {
// Construct mapping of import paths to their defined or implicit names.
imports := make(map[*types.Package]string)
for _, imp := range f.Imports {
var obj types.Object
if imp.Name != nil {
obj = info.Defs[imp.Name]
} else {
obj = info.Implicits[imp]
}
if pkgname, ok := obj.(*types.PkgName); ok {
imports[pkgname.Imported()] = pkgname.Name()
}
}
// Define qualifier to replace full package paths with names of the imports.
return func(p *types.Package) string {
if p == pkg {
return ""
}
if name, ok := imports[p]; ok {
return name
}
return p.Name()
}
}