github.com/jd-ly/tools@v0.5.7/internal/lsp/source/completion/literal.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  	"context"
     9  	"fmt"
    10  	"go/ast"
    11  	"go/token"
    12  	"go/types"
    13  	"strings"
    14  	"unicode"
    15  
    16  	"github.com/jd-ly/tools/internal/event"
    17  	"github.com/jd-ly/tools/internal/lsp/diff"
    18  	"github.com/jd-ly/tools/internal/lsp/protocol"
    19  	"github.com/jd-ly/tools/internal/lsp/snippet"
    20  	"github.com/jd-ly/tools/internal/lsp/source"
    21  )
    22  
    23  // literal generates composite literal, function literal, and make()
    24  // completion items.
    25  func (c *completer) literal(ctx context.Context, literalType types.Type, imp *importInfo) {
    26  	if !c.opts.literal {
    27  		return
    28  	}
    29  
    30  	expType := c.inference.objType
    31  
    32  	if c.inference.matchesVariadic(literalType) {
    33  		// Don't offer literal slice candidates for variadic arguments.
    34  		// For example, don't offer "[]interface{}{}" in "fmt.Print(<>)".
    35  		return
    36  	}
    37  
    38  	// Avoid literal candidates if the expected type is an empty
    39  	// interface. It isn't very useful to suggest a literal candidate of
    40  	// every possible type.
    41  	if expType != nil && isEmptyInterface(expType) {
    42  		return
    43  	}
    44  
    45  	// We handle unnamed literal completions explicitly before searching
    46  	// for candidates. Avoid named-type literal completions for
    47  	// unnamed-type expected type since that results in duplicate
    48  	// candidates. For example, in
    49  	//
    50  	// type mySlice []int
    51  	// var []int = <>
    52  	//
    53  	// don't offer "mySlice{}" since we have already added a candidate
    54  	// of "[]int{}".
    55  	if _, named := literalType.(*types.Named); named && expType != nil {
    56  		if _, named := source.Deref(expType).(*types.Named); !named {
    57  			return
    58  		}
    59  	}
    60  
    61  	// Check if an object of type literalType would match our expected type.
    62  	cand := candidate{
    63  		obj: c.fakeObj(literalType),
    64  	}
    65  
    66  	switch literalType.Underlying().(type) {
    67  	// These literal types are addressable (e.g. "&[]int{}"), others are
    68  	// not (e.g. can't do "&(func(){})").
    69  	case *types.Struct, *types.Array, *types.Slice, *types.Map:
    70  		cand.addressable = true
    71  	}
    72  
    73  	if !c.matchingCandidate(&cand) {
    74  		return
    75  	}
    76  
    77  	var (
    78  		qf  = c.qf
    79  		sel = enclosingSelector(c.path, c.pos)
    80  	)
    81  
    82  	// Don't qualify the type name if we are in a selector expression
    83  	// since the package name is already present.
    84  	if sel != nil {
    85  		qf = func(_ *types.Package) string { return "" }
    86  	}
    87  
    88  	typeName := types.TypeString(literalType, qf)
    89  
    90  	// A type name of "[]int" doesn't work very will with the matcher
    91  	// since "[" isn't a valid identifier prefix. Here we strip off the
    92  	// slice (and array) prefix yielding just "int".
    93  	matchName := typeName
    94  	switch t := literalType.(type) {
    95  	case *types.Slice:
    96  		matchName = types.TypeString(t.Elem(), qf)
    97  	case *types.Array:
    98  		matchName = types.TypeString(t.Elem(), qf)
    99  	}
   100  
   101  	addlEdits, err := c.importEdits(imp)
   102  	if err != nil {
   103  		event.Error(ctx, "error adding import for literal candidate", err)
   104  		return
   105  	}
   106  
   107  	// If prefix matches the type name, client may want a composite literal.
   108  	if score := c.matcher.Score(matchName); score > 0 {
   109  		if cand.takeAddress {
   110  			if sel != nil {
   111  				// If we are in a selector we must place the "&" before the selector.
   112  				// For example, "foo.B<>" must complete to "&foo.Bar{}", not
   113  				// "foo.&Bar{}".
   114  				edits, err := prependEdit(c.snapshot.FileSet(), c.mapper, sel, "&")
   115  				if err != nil {
   116  					event.Error(ctx, "error making edit for literal pointer completion", err)
   117  					return
   118  				}
   119  				addlEdits = append(addlEdits, edits...)
   120  			} else {
   121  				// Otherwise we can stick the "&" directly before the type name.
   122  				typeName = "&" + typeName
   123  			}
   124  		}
   125  
   126  		switch t := literalType.Underlying().(type) {
   127  		case *types.Struct, *types.Array, *types.Slice, *types.Map:
   128  			c.compositeLiteral(t, typeName, float64(score), addlEdits)
   129  		case *types.Signature:
   130  			// Add a literal completion for a signature type that implements
   131  			// an interface. For example, offer "http.HandlerFunc()" when
   132  			// expected type is "http.Handler".
   133  			if source.IsInterface(expType) {
   134  				c.basicLiteral(t, typeName, float64(score), addlEdits)
   135  			}
   136  		case *types.Basic:
   137  			// Add a literal completion for basic types that implement our
   138  			// expected interface (e.g. named string type http.Dir
   139  			// implements http.FileSystem), or are identical to our expected
   140  			// type (i.e. yielding a type conversion such as "float64()").
   141  			if source.IsInterface(expType) || types.Identical(expType, literalType) {
   142  				c.basicLiteral(t, typeName, float64(score), addlEdits)
   143  			}
   144  		}
   145  	}
   146  
   147  	// If prefix matches "make", client may want a "make()"
   148  	// invocation. We also include the type name to allow for more
   149  	// flexible fuzzy matching.
   150  	if score := c.matcher.Score("make." + matchName); !cand.takeAddress && score > 0 {
   151  		switch literalType.Underlying().(type) {
   152  		case *types.Slice:
   153  			// The second argument to "make()" for slices is required, so default to "0".
   154  			c.makeCall(typeName, "0", float64(score), addlEdits)
   155  		case *types.Map, *types.Chan:
   156  			// Maps and channels don't require the second argument, so omit
   157  			// to keep things simple for now.
   158  			c.makeCall(typeName, "", float64(score), addlEdits)
   159  		}
   160  	}
   161  
   162  	// If prefix matches "func", client may want a function literal.
   163  	if score := c.matcher.Score("func"); !cand.takeAddress && score > 0 && !source.IsInterface(expType) {
   164  		switch t := literalType.Underlying().(type) {
   165  		case *types.Signature:
   166  			c.functionLiteral(ctx, t, float64(score))
   167  		}
   168  	}
   169  }
   170  
   171  // prependEdit produces text edits that preprend the specified prefix
   172  // to the specified node.
   173  func prependEdit(fset *token.FileSet, m *protocol.ColumnMapper, node ast.Node, prefix string) ([]protocol.TextEdit, error) {
   174  	rng := source.NewMappedRange(fset, m, node.Pos(), node.Pos())
   175  	spn, err := rng.Span()
   176  	if err != nil {
   177  		return nil, err
   178  	}
   179  	return source.ToProtocolEdits(m, []diff.TextEdit{{
   180  		Span:    spn,
   181  		NewText: prefix,
   182  	}})
   183  }
   184  
   185  // literalCandidateScore is the base score for literal candidates.
   186  // Literal candidates match the expected type so they should be high
   187  // scoring, but we want them ranked below lexical objects of the
   188  // correct type, so scale down highScore.
   189  const literalCandidateScore = highScore / 2
   190  
   191  // functionLiteral adds a function literal completion item for the
   192  // given signature.
   193  func (c *completer) functionLiteral(ctx context.Context, sig *types.Signature, matchScore float64) {
   194  	snip := &snippet.Builder{}
   195  	snip.WriteText("func(")
   196  
   197  	// First we generate names for each param and keep a seen count so
   198  	// we know if we need to uniquify param names. For example,
   199  	// "func(int)" will become "func(i int)", but "func(int, int64)"
   200  	// will become "func(i1 int, i2 int64)".
   201  	var (
   202  		paramNames     = make([]string, sig.Params().Len())
   203  		paramNameCount = make(map[string]int)
   204  	)
   205  	for i := 0; i < sig.Params().Len(); i++ {
   206  		var (
   207  			p    = sig.Params().At(i)
   208  			name = p.Name()
   209  		)
   210  		if name == "" {
   211  			// If the param has no name in the signature, guess a name based
   212  			// on the type. Use an empty qualifier to ignore the package.
   213  			// For example, we want to name "http.Request" "r", not "hr".
   214  			name = source.FormatVarType(ctx, c.snapshot, c.pkg, p, func(p *types.Package) string {
   215  				return ""
   216  			})
   217  			name = abbreviateTypeName(name)
   218  		}
   219  		paramNames[i] = name
   220  		if name != "_" {
   221  			paramNameCount[name]++
   222  		}
   223  	}
   224  
   225  	for n, c := range paramNameCount {
   226  		// Any names we saw more than once will need a unique suffix added
   227  		// on. Reset the count to 1 to act as the suffix for the first
   228  		// name.
   229  		if c >= 2 {
   230  			paramNameCount[n] = 1
   231  		} else {
   232  			delete(paramNameCount, n)
   233  		}
   234  	}
   235  
   236  	for i := 0; i < sig.Params().Len(); i++ {
   237  		if i > 0 {
   238  			snip.WriteText(", ")
   239  		}
   240  
   241  		var (
   242  			p    = sig.Params().At(i)
   243  			name = paramNames[i]
   244  		)
   245  
   246  		// Uniquify names by adding on an incrementing numeric suffix.
   247  		if idx, found := paramNameCount[name]; found {
   248  			paramNameCount[name]++
   249  			name = fmt.Sprintf("%s%d", name, idx)
   250  		}
   251  
   252  		if name != p.Name() && c.opts.placeholders {
   253  			// If we didn't use the signature's param name verbatim then we
   254  			// may have chosen a poor name. Give the user a placeholder so
   255  			// they can easily fix the name.
   256  			snip.WritePlaceholder(func(b *snippet.Builder) {
   257  				b.WriteText(name)
   258  			})
   259  		} else {
   260  			snip.WriteText(name)
   261  		}
   262  
   263  		// If the following param's type is identical to this one, omit
   264  		// this param's type string. For example, emit "i, j int" instead
   265  		// of "i int, j int".
   266  		if i == sig.Params().Len()-1 || !types.Identical(p.Type(), sig.Params().At(i+1).Type()) {
   267  			snip.WriteText(" ")
   268  			typeStr := source.FormatVarType(ctx, c.snapshot, c.pkg, p, c.qf)
   269  			if sig.Variadic() && i == sig.Params().Len()-1 {
   270  				typeStr = strings.Replace(typeStr, "[]", "...", 1)
   271  			}
   272  			snip.WriteText(typeStr)
   273  		}
   274  	}
   275  	snip.WriteText(")")
   276  
   277  	results := sig.Results()
   278  	if results.Len() > 0 {
   279  		snip.WriteText(" ")
   280  	}
   281  
   282  	resultsNeedParens := results.Len() > 1 ||
   283  		results.Len() == 1 && results.At(0).Name() != ""
   284  
   285  	if resultsNeedParens {
   286  		snip.WriteText("(")
   287  	}
   288  	for i := 0; i < results.Len(); i++ {
   289  		if i > 0 {
   290  			snip.WriteText(", ")
   291  		}
   292  		r := results.At(i)
   293  		if name := r.Name(); name != "" {
   294  			snip.WriteText(name + " ")
   295  		}
   296  		snip.WriteText(source.FormatVarType(ctx, c.snapshot, c.pkg, r, c.qf))
   297  	}
   298  	if resultsNeedParens {
   299  		snip.WriteText(")")
   300  	}
   301  
   302  	snip.WriteText(" {")
   303  	snip.WriteFinalTabstop()
   304  	snip.WriteText("}")
   305  
   306  	c.items = append(c.items, CompletionItem{
   307  		Label:   "func(...) {}",
   308  		Score:   matchScore * literalCandidateScore,
   309  		Kind:    protocol.VariableCompletion,
   310  		snippet: snip,
   311  	})
   312  }
   313  
   314  // abbreviateTypeName abbreviates type names into acronyms. For
   315  // example, "fooBar" is abbreviated "fb". Care is taken to ignore
   316  // non-identifier runes. For example, "[]int" becomes "i", and
   317  // "struct { i int }" becomes "s".
   318  func abbreviateTypeName(s string) string {
   319  	var (
   320  		b            strings.Builder
   321  		useNextUpper bool
   322  	)
   323  
   324  	// Trim off leading non-letters. We trim everything between "[" and
   325  	// "]" to handle array types like "[someConst]int".
   326  	var inBracket bool
   327  	s = strings.TrimFunc(s, func(r rune) bool {
   328  		if inBracket {
   329  			inBracket = r != ']'
   330  			return true
   331  		}
   332  
   333  		if r == '[' {
   334  			inBracket = true
   335  		}
   336  
   337  		return !unicode.IsLetter(r)
   338  	})
   339  
   340  	for i, r := range s {
   341  		// Stop if we encounter a non-identifier rune.
   342  		if !unicode.IsLetter(r) && !unicode.IsNumber(r) {
   343  			break
   344  		}
   345  
   346  		if i == 0 {
   347  			b.WriteRune(unicode.ToLower(r))
   348  		}
   349  
   350  		if unicode.IsUpper(r) {
   351  			if useNextUpper {
   352  				b.WriteRune(unicode.ToLower(r))
   353  				useNextUpper = false
   354  			}
   355  		} else {
   356  			useNextUpper = true
   357  		}
   358  	}
   359  
   360  	return b.String()
   361  }
   362  
   363  // compositeLiteral adds a composite literal completion item for the given typeName.
   364  func (c *completer) compositeLiteral(T types.Type, typeName string, matchScore float64, edits []protocol.TextEdit) {
   365  	snip := &snippet.Builder{}
   366  	snip.WriteText(typeName + "{")
   367  	// Don't put the tab stop inside the composite literal curlies "{}"
   368  	// for structs that have no accessible fields.
   369  	if strct, ok := T.(*types.Struct); !ok || fieldsAccessible(strct, c.pkg.GetTypes()) {
   370  		snip.WriteFinalTabstop()
   371  	}
   372  	snip.WriteText("}")
   373  
   374  	nonSnippet := typeName + "{}"
   375  
   376  	c.items = append(c.items, CompletionItem{
   377  		Label:               nonSnippet,
   378  		InsertText:          nonSnippet,
   379  		Score:               matchScore * literalCandidateScore,
   380  		Kind:                protocol.VariableCompletion,
   381  		AdditionalTextEdits: edits,
   382  		snippet:             snip,
   383  	})
   384  }
   385  
   386  // basicLiteral adds a literal completion item for the given basic
   387  // type name typeName.
   388  func (c *completer) basicLiteral(T types.Type, typeName string, matchScore float64, edits []protocol.TextEdit) {
   389  	snip := &snippet.Builder{}
   390  	snip.WriteText(typeName + "(")
   391  	snip.WriteFinalTabstop()
   392  	snip.WriteText(")")
   393  
   394  	nonSnippet := typeName + "()"
   395  
   396  	c.items = append(c.items, CompletionItem{
   397  		Label:               nonSnippet,
   398  		InsertText:          nonSnippet,
   399  		Detail:              T.String(),
   400  		Score:               matchScore * literalCandidateScore,
   401  		Kind:                protocol.VariableCompletion,
   402  		AdditionalTextEdits: edits,
   403  		snippet:             snip,
   404  	})
   405  }
   406  
   407  // makeCall adds a completion item for a "make()" call given a specific type.
   408  func (c *completer) makeCall(typeName string, secondArg string, matchScore float64, edits []protocol.TextEdit) {
   409  	// Keep it simple and don't add any placeholders for optional "make()" arguments.
   410  
   411  	snip := &snippet.Builder{}
   412  	snip.WriteText("make(" + typeName)
   413  	if secondArg != "" {
   414  		snip.WriteText(", ")
   415  		snip.WritePlaceholder(func(b *snippet.Builder) {
   416  			if c.opts.placeholders {
   417  				b.WriteText(secondArg)
   418  			}
   419  		})
   420  	}
   421  	snip.WriteText(")")
   422  
   423  	var nonSnippet strings.Builder
   424  	nonSnippet.WriteString("make(" + typeName)
   425  	if secondArg != "" {
   426  		nonSnippet.WriteString(", ")
   427  		nonSnippet.WriteString(secondArg)
   428  	}
   429  	nonSnippet.WriteByte(')')
   430  
   431  	c.items = append(c.items, CompletionItem{
   432  		Label:               nonSnippet.String(),
   433  		InsertText:          nonSnippet.String(),
   434  		Score:               matchScore * literalCandidateScore,
   435  		Kind:                protocol.FunctionCompletion,
   436  		AdditionalTextEdits: edits,
   437  		snippet:             snip,
   438  	})
   439  }