// 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 completion import ( "context" "errors" "fmt" "go/ast" "go/doc" "go/types" "strings" "golang.org/x/tools/internal/event" "golang.org/x/tools/internal/imports" "golang.org/x/tools/internal/lsp/debug/tag" "golang.org/x/tools/internal/lsp/protocol" "golang.org/x/tools/internal/lsp/snippet" "golang.org/x/tools/internal/lsp/source" "golang.org/x/tools/internal/span" "golang.org/x/tools/internal/typeparams" ) var ( errNoMatch = errors.New("not a surrounding match") errLowScore = errors.New("not a high scoring candidate") ) // item formats a candidate to a CompletionItem. func (c *completer) item(ctx context.Context, cand candidate) (CompletionItem, error) { obj := cand.obj // if the object isn't a valid match against the surrounding, return early. matchScore := c.matcher.Score(cand.name) if matchScore <= 0 { return CompletionItem{}, errNoMatch } cand.score *= float64(matchScore) // Ignore deep candidates that wont be in the MaxDeepCompletions anyway. if len(cand.path) != 0 && !c.deepState.isHighScore(cand.score) { return CompletionItem{}, errLowScore } // Handle builtin types separately. if obj.Parent() == types.Universe { return c.formatBuiltin(ctx, cand) } var ( label = cand.name detail = types.TypeString(obj.Type(), c.qf) insert = label kind = protocol.TextCompletion snip snippet.Builder protocolEdits []protocol.TextEdit ) if obj.Type() == nil { detail = "" } if isTypeName(obj) && c.wantTypeParams() { x := cand.obj.(*types.TypeName) if named, ok := x.Type().(*types.Named); ok { tp := typeparams.ForNamed(named) label += source.FormatTypeParams(tp) insert = label // maintain invariant above (label == insert) } } snip.WriteText(insert) switch obj := obj.(type) { case *types.TypeName: detail, kind = source.FormatType(obj.Type(), c.qf) case *types.Const: kind = protocol.ConstantCompletion case *types.Var: if _, ok := obj.Type().(*types.Struct); ok { detail = "struct{...}" // for anonymous structs } else if obj.IsField() { detail = source.FormatVarType(ctx, c.snapshot, c.pkg, obj, c.qf) } if obj.IsField() { kind = protocol.FieldCompletion c.structFieldSnippet(cand, detail, &snip) } else { kind = protocol.VariableCompletion } if obj.Type() == nil { break } case *types.Func: sig, ok := obj.Type().Underlying().(*types.Signature) if !ok { break } kind = protocol.FunctionCompletion if sig != nil && sig.Recv() != nil { kind = protocol.MethodCompletion } case *types.PkgName: kind = protocol.ModuleCompletion detail = fmt.Sprintf("%q", obj.Imported().Path()) case *types.Label: kind = protocol.ConstantCompletion detail = "label" } var prefix string for _, mod := range cand.mods { switch mod { case reference: prefix = "&" + prefix case dereference: prefix = "*" + prefix case chanRead: prefix = "<-" + prefix } } var ( suffix string funcType = obj.Type() ) Suffixes: for _, mod := range cand.mods { switch mod { case invoke: if sig, ok := funcType.Underlying().(*types.Signature); ok { s := source.NewSignature(ctx, c.snapshot, c.pkg, sig, nil, c.qf) c.functionCallSnippet("", s.TypeParams(), s.Params(), &snip) if sig.Results().Len() == 1 { funcType = sig.Results().At(0).Type() } detail = "func" + s.Format() } if !c.opts.snippets { // Without snippets the candidate will not include "()". Don't // add further suffixes since they will be invalid. For // example, with snippets "foo()..." would become "foo..." // without snippets if we added the dotDotDot. break Suffixes } case takeSlice: suffix += "[:]" case takeDotDotDot: suffix += "..." case index: snip.WriteText("[") snip.WritePlaceholder(nil) snip.WriteText("]") } } // If this candidate needs an additional import statement, // add the additional text edits needed. if cand.imp != nil { addlEdits, err := c.importEdits(cand.imp) if err != nil { return CompletionItem{}, err } protocolEdits = append(protocolEdits, addlEdits...) if kind != protocol.ModuleCompletion { if detail != "" { detail += " " } detail += fmt.Sprintf("(from %q)", cand.imp.importPath) } } if cand.convertTo != nil { typeName := types.TypeString(cand.convertTo, c.qf) switch cand.convertTo.(type) { // We need extra parens when casting to these types. For example, // we need "(*int)(foo)", not "*int(foo)". case *types.Pointer, *types.Signature: typeName = "(" + typeName + ")" } prefix = typeName + "(" + prefix suffix = ")" } if prefix != "" { // If we are in a selector, add an edit to place prefix before selector. if sel := enclosingSelector(c.path, c.pos); sel != nil { edits, err := c.editText(sel.Pos(), sel.Pos(), prefix) if err != nil { return CompletionItem{}, err } protocolEdits = append(protocolEdits, edits...) } else { // If there is no selector, just stick the prefix at the start. insert = prefix + insert snip.PrependText(prefix) } } if suffix != "" { insert += suffix snip.WriteText(suffix) } detail = strings.TrimPrefix(detail, "untyped ") // override computed detail with provided detail, if something is provided. if cand.detail != "" { detail = cand.detail } item := CompletionItem{ Label: label, InsertText: insert, AdditionalTextEdits: protocolEdits, Detail: detail, Kind: kind, Score: cand.score, Depth: len(cand.path), snippet: &snip, obj: obj, } // If the user doesn't want documentation for completion items. if !c.opts.documentation { return item, nil } pos := c.snapshot.FileSet().Position(obj.Pos()) // We ignore errors here, because some types, like "unsafe" or "error", // may not have valid positions that we can use to get documentation. if !pos.IsValid() { return item, nil } uri := span.URIFromPath(pos.Filename) // Find the source file of the candidate. pkg, err := source.FindPackageFromPos(ctx, c.snapshot, obj.Pos()) if err != nil { return item, nil } decl, err := c.snapshot.PosToDecl(ctx, pkg, obj.Pos()) if err != nil { return CompletionItem{}, err } hover, err := source.FindHoverContext(ctx, c.snapshot, pkg, obj, decl, nil) if err != nil { event.Error(ctx, "failed to find Hover", err, tag.URI.Of(uri)) return item, nil } if c.opts.fullDocumentation { item.Documentation = hover.Comment.Text() } else { item.Documentation = doc.Synopsis(hover.Comment.Text()) } // The desired pattern is `^// Deprecated`, but the prefix has been removed if strings.HasPrefix(hover.Comment.Text(), "Deprecated") { if c.snapshot.View().Options().CompletionTags { item.Tags = []protocol.CompletionItemTag{protocol.ComplDeprecated} } else if c.snapshot.View().Options().CompletionDeprecated { item.Deprecated = true } } return item, nil } // importEdits produces the text edits necessary to add the given import to the current file. func (c *completer) importEdits(imp *importInfo) ([]protocol.TextEdit, error) { if imp == nil { return nil, nil } pgf, err := c.pkg.File(span.URIFromPath(c.filename)) if err != nil { return nil, err } return source.ComputeOneImportFixEdits(c.snapshot, pgf, &imports.ImportFix{ StmtInfo: imports.ImportInfo{ ImportPath: imp.importPath, Name: imp.name, }, // IdentName is unused on this path and is difficult to get. FixType: imports.AddImport, }) } func (c *completer) formatBuiltin(ctx context.Context, 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 = protocol.ConstantCompletion case *types.Builtin: item.Kind = protocol.FunctionCompletion sig, err := source.NewBuiltinSignature(ctx, c.snapshot, obj.Name()) if err != nil { return CompletionItem{}, err } item.Detail = "func" + sig.Format() item.snippet = &snippet.Builder{} c.functionCallSnippet(obj.Name(), sig.TypeParams(), sig.Params(), item.snippet) case *types.TypeName: if types.IsInterface(obj.Type()) { item.Kind = protocol.InterfaceCompletion } else { item.Kind = protocol.ClassCompletion } case *types.Nil: item.Kind = protocol.VariableCompletion } return item, nil } // decide if the type params (if any) should be part of the completion // which only possible for types.Named and types.Signature // (so far, only in receivers, e.g.; func (s *GENERIC[K, V])..., which is a types.Named) func (c *completer) wantTypeParams() bool { // Need to be lexically in a receiver, and a child of an IndexListExpr // (but IndexListExpr only exists with go1.18) start := c.path[0].Pos() for i, nd := range c.path { if fd, ok := nd.(*ast.FuncDecl); ok { if i > 0 && fd.Recv != nil && start < fd.Recv.End() { return true } else { return false } } } return false }