github.com/jhump/golang-x-tools@v0.0.0-20220218190644-4958d6d39439/internal/lsp/source/completion/format.go (about)

     1  // Copyright 2019 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package completion
     6  
     7  import (
     8  	"bytes"
     9  	"context"
    10  	"fmt"
    11  	"go/ast"
    12  	"go/types"
    13  	"strings"
    14  
    15  	"github.com/jhump/golang-x-tools/internal/event"
    16  	"github.com/jhump/golang-x-tools/internal/imports"
    17  	"github.com/jhump/golang-x-tools/internal/lsp/debug/tag"
    18  	"github.com/jhump/golang-x-tools/internal/lsp/protocol"
    19  	"github.com/jhump/golang-x-tools/internal/lsp/snippet"
    20  	"github.com/jhump/golang-x-tools/internal/lsp/source"
    21  	"github.com/jhump/golang-x-tools/internal/span"
    22  	"github.com/jhump/golang-x-tools/internal/typeparams"
    23  	errors "golang.org/x/xerrors"
    24  )
    25  
    26  var (
    27  	errNoMatch  = errors.New("not a surrounding match")
    28  	errLowScore = errors.New("not a high scoring candidate")
    29  )
    30  
    31  // item formats a candidate to a CompletionItem.
    32  func (c *completer) item(ctx context.Context, cand candidate) (CompletionItem, error) {
    33  	obj := cand.obj
    34  
    35  	// if the object isn't a valid match against the surrounding, return early.
    36  	matchScore := c.matcher.Score(cand.name)
    37  	if matchScore <= 0 {
    38  		return CompletionItem{}, errNoMatch
    39  	}
    40  	cand.score *= float64(matchScore)
    41  
    42  	// Ignore deep candidates that wont be in the MaxDeepCompletions anyway.
    43  	if len(cand.path) != 0 && !c.deepState.isHighScore(cand.score) {
    44  		return CompletionItem{}, errLowScore
    45  	}
    46  
    47  	// Handle builtin types separately.
    48  	if obj.Parent() == types.Universe {
    49  		return c.formatBuiltin(ctx, cand)
    50  	}
    51  
    52  	var (
    53  		label         = cand.name
    54  		detail        = types.TypeString(obj.Type(), c.qf)
    55  		insert        = label
    56  		kind          = protocol.TextCompletion
    57  		snip          snippet.Builder
    58  		protocolEdits []protocol.TextEdit
    59  	)
    60  	if obj.Type() == nil {
    61  		detail = ""
    62  	}
    63  	if isTypeName(obj) && c.wantTypeParams() {
    64  		x := cand.obj.(*types.TypeName)
    65  		if named, ok := x.Type().(*types.Named); ok {
    66  			tp := typeparams.ForNamed(named)
    67  			label += string(formatTypeParams(tp))
    68  			insert = label // maintain invariant above (label == insert)
    69  		}
    70  	}
    71  
    72  	snip.WriteText(insert)
    73  
    74  	switch obj := obj.(type) {
    75  	case *types.TypeName:
    76  		detail, kind = source.FormatType(obj.Type(), c.qf)
    77  	case *types.Const:
    78  		kind = protocol.ConstantCompletion
    79  	case *types.Var:
    80  		if _, ok := obj.Type().(*types.Struct); ok {
    81  			detail = "struct{...}" // for anonymous structs
    82  		} else if obj.IsField() {
    83  			detail = source.FormatVarType(ctx, c.snapshot, c.pkg, obj, c.qf)
    84  		}
    85  		if obj.IsField() {
    86  			kind = protocol.FieldCompletion
    87  			c.structFieldSnippet(cand, detail, &snip)
    88  		} else {
    89  			kind = protocol.VariableCompletion
    90  		}
    91  		if obj.Type() == nil {
    92  			break
    93  		}
    94  	case *types.Func:
    95  		sig, ok := obj.Type().Underlying().(*types.Signature)
    96  		if !ok {
    97  			break
    98  		}
    99  		kind = protocol.FunctionCompletion
   100  		if sig != nil && sig.Recv() != nil {
   101  			kind = protocol.MethodCompletion
   102  		}
   103  	case *types.PkgName:
   104  		kind = protocol.ModuleCompletion
   105  		detail = fmt.Sprintf("%q", obj.Imported().Path())
   106  	case *types.Label:
   107  		kind = protocol.ConstantCompletion
   108  		detail = "label"
   109  	}
   110  
   111  	var prefix string
   112  	for _, mod := range cand.mods {
   113  		switch mod {
   114  		case reference:
   115  			prefix = "&" + prefix
   116  		case dereference:
   117  			prefix = "*" + prefix
   118  		case chanRead:
   119  			prefix = "<-" + prefix
   120  		}
   121  	}
   122  
   123  	var (
   124  		suffix   string
   125  		funcType = obj.Type()
   126  	)
   127  Suffixes:
   128  	for _, mod := range cand.mods {
   129  		switch mod {
   130  		case invoke:
   131  			if sig, ok := funcType.Underlying().(*types.Signature); ok {
   132  				s := source.NewSignature(ctx, c.snapshot, c.pkg, sig, nil, c.qf)
   133  				c.functionCallSnippet("", s.Params(), &snip)
   134  				if sig.Results().Len() == 1 {
   135  					funcType = sig.Results().At(0).Type()
   136  				}
   137  				detail = "func" + s.Format()
   138  			}
   139  
   140  			if !c.opts.snippets {
   141  				// Without snippets the candidate will not include "()". Don't
   142  				// add further suffixes since they will be invalid. For
   143  				// example, with snippets "foo()..." would become "foo..."
   144  				// without snippets if we added the dotDotDot.
   145  				break Suffixes
   146  			}
   147  		case takeSlice:
   148  			suffix += "[:]"
   149  		case takeDotDotDot:
   150  			suffix += "..."
   151  		case index:
   152  			snip.WriteText("[")
   153  			snip.WritePlaceholder(nil)
   154  			snip.WriteText("]")
   155  		}
   156  	}
   157  
   158  	// If this candidate needs an additional import statement,
   159  	// add the additional text edits needed.
   160  	if cand.imp != nil {
   161  		addlEdits, err := c.importEdits(cand.imp)
   162  
   163  		if err != nil {
   164  			return CompletionItem{}, err
   165  		}
   166  
   167  		protocolEdits = append(protocolEdits, addlEdits...)
   168  		if kind != protocol.ModuleCompletion {
   169  			if detail != "" {
   170  				detail += " "
   171  			}
   172  			detail += fmt.Sprintf("(from %q)", cand.imp.importPath)
   173  		}
   174  	}
   175  
   176  	if cand.convertTo != nil {
   177  		typeName := types.TypeString(cand.convertTo, c.qf)
   178  
   179  		switch cand.convertTo.(type) {
   180  		// We need extra parens when casting to these types. For example,
   181  		// we need "(*int)(foo)", not "*int(foo)".
   182  		case *types.Pointer, *types.Signature:
   183  			typeName = "(" + typeName + ")"
   184  		}
   185  
   186  		prefix = typeName + "(" + prefix
   187  		suffix = ")"
   188  	}
   189  
   190  	if prefix != "" {
   191  		// If we are in a selector, add an edit to place prefix before selector.
   192  		if sel := enclosingSelector(c.path, c.pos); sel != nil {
   193  			edits, err := c.editText(sel.Pos(), sel.Pos(), prefix)
   194  			if err != nil {
   195  				return CompletionItem{}, err
   196  			}
   197  			protocolEdits = append(protocolEdits, edits...)
   198  		} else {
   199  			// If there is no selector, just stick the prefix at the start.
   200  			insert = prefix + insert
   201  			snip.PrependText(prefix)
   202  		}
   203  	}
   204  
   205  	if suffix != "" {
   206  		insert += suffix
   207  		snip.WriteText(suffix)
   208  	}
   209  
   210  	detail = strings.TrimPrefix(detail, "untyped ")
   211  	// override computed detail with provided detail, if something is provided.
   212  	if cand.detail != "" {
   213  		detail = cand.detail
   214  	}
   215  	item := CompletionItem{
   216  		Label:               label,
   217  		InsertText:          insert,
   218  		AdditionalTextEdits: protocolEdits,
   219  		Detail:              detail,
   220  		Kind:                kind,
   221  		Score:               cand.score,
   222  		Depth:               len(cand.path),
   223  		snippet:             &snip,
   224  		obj:                 obj,
   225  	}
   226  	// If the user doesn't want documentation for completion items.
   227  	if !c.opts.documentation {
   228  		return item, nil
   229  	}
   230  	pos := c.snapshot.FileSet().Position(obj.Pos())
   231  
   232  	// We ignore errors here, because some types, like "unsafe" or "error",
   233  	// may not have valid positions that we can use to get documentation.
   234  	if !pos.IsValid() {
   235  		return item, nil
   236  	}
   237  	uri := span.URIFromPath(pos.Filename)
   238  
   239  	// Find the source file of the candidate.
   240  	pkg, err := source.FindPackageFromPos(ctx, c.snapshot, obj.Pos())
   241  	if err != nil {
   242  		return item, nil
   243  	}
   244  
   245  	decl, err := c.snapshot.PosToDecl(ctx, pkg, obj.Pos())
   246  	if err != nil {
   247  		return CompletionItem{}, err
   248  	}
   249  	hover, err := source.HoverInfo(ctx, c.snapshot, pkg, obj, decl, nil)
   250  	if err != nil {
   251  		event.Error(ctx, "failed to find Hover", err, tag.URI.Of(uri))
   252  		return item, nil
   253  	}
   254  	item.Documentation = hover.Synopsis
   255  	if c.opts.fullDocumentation {
   256  		item.Documentation = hover.FullDocumentation
   257  	}
   258  	// The desired pattern is `^// Deprecated`, but the prefix has been removed
   259  	if strings.HasPrefix(hover.FullDocumentation, "Deprecated") {
   260  		if c.snapshot.View().Options().CompletionTags {
   261  			item.Tags = []protocol.CompletionItemTag{protocol.ComplDeprecated}
   262  		} else if c.snapshot.View().Options().CompletionDeprecated {
   263  			item.Deprecated = true
   264  		}
   265  	}
   266  
   267  	return item, nil
   268  }
   269  
   270  // importEdits produces the text edits necessary to add the given import to the current file.
   271  func (c *completer) importEdits(imp *importInfo) ([]protocol.TextEdit, error) {
   272  	if imp == nil {
   273  		return nil, nil
   274  	}
   275  
   276  	pgf, err := c.pkg.File(span.URIFromPath(c.filename))
   277  	if err != nil {
   278  		return nil, err
   279  	}
   280  
   281  	return source.ComputeOneImportFixEdits(c.snapshot, pgf, &imports.ImportFix{
   282  		StmtInfo: imports.ImportInfo{
   283  			ImportPath: imp.importPath,
   284  			Name:       imp.name,
   285  		},
   286  		// IdentName is unused on this path and is difficult to get.
   287  		FixType: imports.AddImport,
   288  	})
   289  }
   290  
   291  func (c *completer) formatBuiltin(ctx context.Context, cand candidate) (CompletionItem, error) {
   292  	obj := cand.obj
   293  	item := CompletionItem{
   294  		Label:      obj.Name(),
   295  		InsertText: obj.Name(),
   296  		Score:      cand.score,
   297  	}
   298  	switch obj.(type) {
   299  	case *types.Const:
   300  		item.Kind = protocol.ConstantCompletion
   301  	case *types.Builtin:
   302  		item.Kind = protocol.FunctionCompletion
   303  		sig, err := source.NewBuiltinSignature(ctx, c.snapshot, obj.Name())
   304  		if err != nil {
   305  			return CompletionItem{}, err
   306  		}
   307  		item.Detail = "func" + sig.Format()
   308  		item.snippet = &snippet.Builder{}
   309  		c.functionCallSnippet(obj.Name(), sig.Params(), item.snippet)
   310  	case *types.TypeName:
   311  		if types.IsInterface(obj.Type()) {
   312  			item.Kind = protocol.InterfaceCompletion
   313  		} else {
   314  			item.Kind = protocol.ClassCompletion
   315  		}
   316  	case *types.Nil:
   317  		item.Kind = protocol.VariableCompletion
   318  	}
   319  	return item, nil
   320  }
   321  
   322  // decide if the type params (if any) should be part of the completion
   323  // which only possible for types.Named and types.Signature
   324  // (so far, only in receivers, e.g.; func (s *GENERIC[K, V])..., which is a types.Named)
   325  func (c *completer) wantTypeParams() bool {
   326  	// Need to be lexically in a receiver, and a child of an IndexListExpr
   327  	// (but IndexListExpr only exists with go1.18)
   328  	start := c.path[0].Pos()
   329  	for i, nd := range c.path {
   330  		if fd, ok := nd.(*ast.FuncDecl); ok {
   331  			if i > 0 && fd.Recv != nil && start < fd.Recv.End() {
   332  				return true
   333  			} else {
   334  				return false
   335  			}
   336  		}
   337  	}
   338  	return false
   339  }
   340  
   341  func formatTypeParams(tp *typeparams.TypeParamList) []byte {
   342  	var buf bytes.Buffer
   343  	if tp == nil || tp.Len() == 0 {
   344  		return nil
   345  	}
   346  	buf.WriteByte('[')
   347  	for i := 0; i < tp.Len(); i++ {
   348  		if i > 0 {
   349  			buf.WriteString(", ")
   350  		}
   351  		buf.WriteString(tp.At(i).Obj().Name())
   352  	}
   353  	buf.WriteByte(']')
   354  	return buf.Bytes()
   355  }