github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/internal/lsp/source/completion/postfix_snippets.go (about)

     1  // Copyright 2020 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  	"log"
    14  	"reflect"
    15  	"strings"
    16  	"sync"
    17  	"text/template"
    18  
    19  	"github.com/powerman/golang-tools/internal/event"
    20  	"github.com/powerman/golang-tools/internal/imports"
    21  	"github.com/powerman/golang-tools/internal/lsp/protocol"
    22  	"github.com/powerman/golang-tools/internal/lsp/snippet"
    23  	"github.com/powerman/golang-tools/internal/lsp/source"
    24  	errors "golang.org/x/xerrors"
    25  )
    26  
    27  // Postfix snippets are artificial methods that allow the user to
    28  // compose common operations in an "argument oriented" fashion. For
    29  // example, instead of "sort.Slice(someSlice, ...)" a user can expand
    30  // "someSlice.sort!".
    31  
    32  // postfixTmpl represents a postfix snippet completion candidate.
    33  type postfixTmpl struct {
    34  	// label is the completion candidate's label presented to the user.
    35  	label string
    36  
    37  	// details is passed along to the client as the candidate's details.
    38  	details string
    39  
    40  	// body is the template text. See postfixTmplArgs for details on the
    41  	// facilities available to the template.
    42  	body string
    43  
    44  	tmpl *template.Template
    45  }
    46  
    47  // postfixTmplArgs are the template execution arguments available to
    48  // the postfix snippet templates.
    49  type postfixTmplArgs struct {
    50  	// StmtOK is true if it is valid to replace the selector with a
    51  	// statement. For example:
    52  	//
    53  	//    func foo() {
    54  	//      bar.sort! // statement okay
    55  	//
    56  	//      someMethod(bar.sort!) // statement not okay
    57  	//    }
    58  	StmtOK bool
    59  
    60  	// X is the textual SelectorExpr.X. For example, when completing
    61  	// "foo.bar.print!", "X" is "foo.bar".
    62  	X string
    63  
    64  	// Obj is the types.Object of SelectorExpr.X, if any.
    65  	Obj types.Object
    66  
    67  	// Type is the type of "foo.bar" in "foo.bar.print!".
    68  	Type types.Type
    69  
    70  	scope          *types.Scope
    71  	snip           snippet.Builder
    72  	importIfNeeded func(pkgPath string, scope *types.Scope) (name string, edits []protocol.TextEdit, err error)
    73  	edits          []protocol.TextEdit
    74  	qf             types.Qualifier
    75  	varNames       map[string]bool
    76  }
    77  
    78  var postfixTmpls = []postfixTmpl{{
    79  	label:   "sort",
    80  	details: "sort.Slice()",
    81  	body: `{{if and (eq .Kind "slice") .StmtOK -}}
    82  {{.Import "sort"}}.Slice({{.X}}, func({{.VarName nil "i"}}, {{.VarName nil "j"}} int) bool {
    83  	{{.Cursor}}
    84  })
    85  {{- end}}`,
    86  }, {
    87  	label:   "last",
    88  	details: "s[len(s)-1]",
    89  	body: `{{if and (eq .Kind "slice") .Obj -}}
    90  {{.X}}[len({{.X}})-1]
    91  {{- end}}`,
    92  }, {
    93  	label:   "reverse",
    94  	details: "reverse slice",
    95  	body: `{{if and (eq .Kind "slice") .StmtOK -}}
    96  {{$i := .VarName nil "i"}}{{$j := .VarName nil "j" -}}
    97  for {{$i}}, {{$j}} := 0, len({{.X}})-1; {{$i}} < {{$j}}; {{$i}}, {{$j}} = {{$i}}+1, {{$j}}-1 {
    98  	{{.X}}[{{$i}}], {{.X}}[{{$j}}] = {{.X}}[{{$j}}], {{.X}}[{{$i}}]
    99  }
   100  {{end}}`,
   101  }, {
   102  	label:   "range",
   103  	details: "range over slice",
   104  	body: `{{if and (eq .Kind "slice") .StmtOK -}}
   105  for {{.VarName nil "i"}}, {{.VarName .ElemType "v"}} := range {{.X}} {
   106  	{{.Cursor}}
   107  }
   108  {{- end}}`,
   109  }, {
   110  	label:   "append",
   111  	details: "append and re-assign slice",
   112  	body: `{{if and (eq .Kind "slice") .StmtOK .Obj -}}
   113  {{.X}} = append({{.X}}, {{.Cursor}})
   114  {{- end}}`,
   115  }, {
   116  	label:   "append",
   117  	details: "append to slice",
   118  	body: `{{if and (eq .Kind "slice") (not .StmtOK) -}}
   119  append({{.X}}, {{.Cursor}})
   120  {{- end}}`,
   121  }, {
   122  	label:   "copy",
   123  	details: "duplicate slice",
   124  	body: `{{if and (eq .Kind "slice") .StmtOK .Obj -}}
   125  {{$v := (.VarName nil (printf "%sCopy" .X))}}{{$v}} := make([]{{.TypeName .ElemType}}, len({{.X}}))
   126  copy({{$v}}, {{.X}})
   127  {{end}}`,
   128  }, {
   129  	label:   "range",
   130  	details: "range over map",
   131  	body: `{{if and (eq .Kind "map") .StmtOK -}}
   132  for {{.VarName .KeyType "k"}}, {{.VarName .ElemType "v"}} := range {{.X}} {
   133  	{{.Cursor}}
   134  }
   135  {{- end}}`,
   136  }, {
   137  	label:   "clear",
   138  	details: "clear map contents",
   139  	body: `{{if and (eq .Kind "map") .StmtOK -}}
   140  {{$k := (.VarName .KeyType "k")}}for {{$k}} := range {{.X}} {
   141  	delete({{.X}}, {{$k}})
   142  }
   143  {{end}}`,
   144  }, {
   145  	label:   "keys",
   146  	details: "create slice of keys",
   147  	body: `{{if and (eq .Kind "map") .StmtOK -}}
   148  {{$keysVar := (.VarName nil "keys")}}{{$keysVar}} := make([]{{.TypeName .KeyType}}, 0, len({{.X}}))
   149  {{$k := (.VarName .KeyType "k")}}for {{$k}} := range {{.X}} {
   150  	{{$keysVar}} = append({{$keysVar}}, {{$k}})
   151  }
   152  {{end}}`,
   153  }, {
   154  	label:   "var",
   155  	details: "assign to variables",
   156  	body: `{{if and (eq .Kind "tuple") .StmtOK -}}
   157  {{$a := .}}{{range $i, $v := .Tuple}}{{if $i}}, {{end}}{{$a.VarName $v.Type $v.Name}}{{end}} := {{.X}}
   158  {{- end}}`,
   159  }, {
   160  	label:   "var",
   161  	details: "assign to variable",
   162  	body: `{{if and (ne .Kind "tuple") .StmtOK -}}
   163  {{.VarName .Type ""}} := {{.X}}
   164  {{- end}}`,
   165  }, {
   166  	label:   "print",
   167  	details: "print to stdout",
   168  	body: `{{if and (ne .Kind "tuple") .StmtOK -}}
   169  {{.Import "fmt"}}.Printf("{{.EscapeQuotes .X}}: %v\n", {{.X}})
   170  {{- end}}`,
   171  }, {
   172  	label:   "print",
   173  	details: "print to stdout",
   174  	body: `{{if and (eq .Kind "tuple") .StmtOK -}}
   175  {{.Import "fmt"}}.Println({{.X}})
   176  {{- end}}`,
   177  }, {
   178  	label:   "split",
   179  	details: "split string",
   180  	body: `{{if (eq (.TypeName .Type) "string") -}}
   181  {{.Import "strings"}}.Split({{.X}}, "{{.Cursor}}")
   182  {{- end}}`,
   183  }, {
   184  	label:   "join",
   185  	details: "join string slice",
   186  	body: `{{if and (eq .Kind "slice") (eq (.TypeName .ElemType) "string") -}}
   187  {{.Import "strings"}}.Join({{.X}}, "{{.Cursor}}")
   188  {{- end}}`,
   189  }}
   190  
   191  // Cursor indicates where the client's cursor should end up after the
   192  // snippet is done.
   193  func (a *postfixTmplArgs) Cursor() string {
   194  	a.snip.WriteFinalTabstop()
   195  	return ""
   196  }
   197  
   198  // Import makes sure the package corresponding to path is imported,
   199  // returning the identifier to use to refer to the package.
   200  func (a *postfixTmplArgs) Import(path string) (string, error) {
   201  	name, edits, err := a.importIfNeeded(path, a.scope)
   202  	if err != nil {
   203  		return "", errors.Errorf("couldn't import %q: %w", path, err)
   204  	}
   205  	a.edits = append(a.edits, edits...)
   206  	return name, nil
   207  }
   208  
   209  func (a *postfixTmplArgs) EscapeQuotes(v string) string {
   210  	return strings.ReplaceAll(v, `"`, `\\"`)
   211  }
   212  
   213  // ElemType returns the Elem() type of xType, if applicable.
   214  func (a *postfixTmplArgs) ElemType() types.Type {
   215  	if e, _ := a.Type.(interface{ Elem() types.Type }); e != nil {
   216  		return e.Elem()
   217  	}
   218  	return nil
   219  }
   220  
   221  // Kind returns the underlying kind of type, e.g. "slice", "struct",
   222  // etc.
   223  func (a *postfixTmplArgs) Kind() string {
   224  	t := reflect.TypeOf(a.Type.Underlying())
   225  	return strings.ToLower(strings.TrimPrefix(t.String(), "*types."))
   226  }
   227  
   228  // KeyType returns the type of X's key. KeyType panics if X is not a
   229  // map.
   230  func (a *postfixTmplArgs) KeyType() types.Type {
   231  	return a.Type.Underlying().(*types.Map).Key()
   232  }
   233  
   234  // Tuple returns the tuple result vars if X is a call expression.
   235  func (a *postfixTmplArgs) Tuple() []*types.Var {
   236  	tuple, _ := a.Type.(*types.Tuple)
   237  	if tuple == nil {
   238  		return nil
   239  	}
   240  
   241  	typs := make([]*types.Var, 0, tuple.Len())
   242  	for i := 0; i < tuple.Len(); i++ {
   243  		typs = append(typs, tuple.At(i))
   244  	}
   245  	return typs
   246  }
   247  
   248  // TypeName returns the textual representation of type t.
   249  func (a *postfixTmplArgs) TypeName(t types.Type) (string, error) {
   250  	if t == nil || t == types.Typ[types.Invalid] {
   251  		return "", fmt.Errorf("invalid type: %v", t)
   252  	}
   253  	return types.TypeString(t, a.qf), nil
   254  }
   255  
   256  // VarName returns a suitable variable name for the type t. If t
   257  // implements the error interface, "err" is used. If t is not a named
   258  // type then nonNamedDefault is used. Otherwise a name is made by
   259  // abbreviating the type name. If the resultant name is already in
   260  // scope, an integer is appended to make a unique name.
   261  func (a *postfixTmplArgs) VarName(t types.Type, nonNamedDefault string) string {
   262  	if t == nil {
   263  		t = types.Typ[types.Invalid]
   264  	}
   265  
   266  	var name string
   267  	if types.Implements(t, errorIntf) {
   268  		name = "err"
   269  	} else if _, isNamed := source.Deref(t).(*types.Named); !isNamed {
   270  		name = nonNamedDefault
   271  	}
   272  
   273  	if name == "" {
   274  		name = types.TypeString(t, func(p *types.Package) string {
   275  			return ""
   276  		})
   277  		name = abbreviateTypeName(name)
   278  	}
   279  
   280  	if dot := strings.LastIndex(name, "."); dot > -1 {
   281  		name = name[dot+1:]
   282  	}
   283  
   284  	uniqueName := name
   285  	for i := 2; ; i++ {
   286  		if s, _ := a.scope.LookupParent(uniqueName, token.NoPos); s == nil && !a.varNames[uniqueName] {
   287  			break
   288  		}
   289  		uniqueName = fmt.Sprintf("%s%d", name, i)
   290  	}
   291  
   292  	a.varNames[uniqueName] = true
   293  
   294  	return uniqueName
   295  }
   296  
   297  func (c *completer) addPostfixSnippetCandidates(ctx context.Context, sel *ast.SelectorExpr) {
   298  	if !c.opts.postfix {
   299  		return
   300  	}
   301  
   302  	initPostfixRules()
   303  
   304  	if sel == nil || sel.Sel == nil {
   305  		return
   306  	}
   307  
   308  	selType := c.pkg.GetTypesInfo().TypeOf(sel.X)
   309  	if selType == nil {
   310  		return
   311  	}
   312  
   313  	// Skip empty tuples since there is no value to operate on.
   314  	if tuple, ok := selType.Underlying().(*types.Tuple); ok && tuple == nil {
   315  		return
   316  	}
   317  
   318  	tokFile := c.snapshot.FileSet().File(c.pos)
   319  
   320  	// Only replace sel with a statement if sel is already a statement.
   321  	var stmtOK bool
   322  	for i, n := range c.path {
   323  		if n == sel && i < len(c.path)-1 {
   324  			switch p := c.path[i+1].(type) {
   325  			case *ast.ExprStmt:
   326  				stmtOK = true
   327  			case *ast.AssignStmt:
   328  				// In cases like:
   329  				//
   330  				//   foo.<>
   331  				//   bar = 123
   332  				//
   333  				// detect that "foo." makes up the entire statement since the
   334  				// apparent selector spans lines.
   335  				stmtOK = tokFile.Line(c.pos) < tokFile.Line(p.TokPos)
   336  			}
   337  			break
   338  		}
   339  	}
   340  
   341  	scope := c.pkg.GetTypes().Scope().Innermost(c.pos)
   342  	if scope == nil {
   343  		return
   344  	}
   345  
   346  	// afterDot is the position after selector dot, e.g. "|" in
   347  	// "foo.|print".
   348  	afterDot := sel.Sel.Pos()
   349  
   350  	// We must detect dangling selectors such as:
   351  	//
   352  	//    foo.<>
   353  	//    bar
   354  	//
   355  	// and adjust afterDot so that we don't mistakenly delete the
   356  	// newline thinking "bar" is part of our selector.
   357  	if startLine := tokFile.Line(sel.Pos()); startLine != tokFile.Line(afterDot) {
   358  		if tokFile.Line(c.pos) != startLine {
   359  			return
   360  		}
   361  		afterDot = c.pos
   362  	}
   363  
   364  	for _, rule := range postfixTmpls {
   365  		// When completing foo.print<>, "print" is naturally overwritten,
   366  		// but we need to also remove "foo." so the snippet has a clean
   367  		// slate.
   368  		edits, err := c.editText(sel.Pos(), afterDot, "")
   369  		if err != nil {
   370  			event.Error(ctx, "error calculating postfix edits", err)
   371  			return
   372  		}
   373  
   374  		tmplArgs := postfixTmplArgs{
   375  			X:              source.FormatNode(c.snapshot.FileSet(), sel.X),
   376  			StmtOK:         stmtOK,
   377  			Obj:            exprObj(c.pkg.GetTypesInfo(), sel.X),
   378  			Type:           selType,
   379  			qf:             c.qf,
   380  			importIfNeeded: c.importIfNeeded,
   381  			scope:          scope,
   382  			varNames:       make(map[string]bool),
   383  		}
   384  
   385  		// Feed the template straight into the snippet builder. This
   386  		// allows templates to build snippets as they are executed.
   387  		err = rule.tmpl.Execute(&tmplArgs.snip, &tmplArgs)
   388  		if err != nil {
   389  			event.Error(ctx, "error executing postfix template", err)
   390  			continue
   391  		}
   392  
   393  		if strings.TrimSpace(tmplArgs.snip.String()) == "" {
   394  			continue
   395  		}
   396  
   397  		score := c.matcher.Score(rule.label)
   398  		if score <= 0 {
   399  			continue
   400  		}
   401  
   402  		c.items = append(c.items, CompletionItem{
   403  			Label:               rule.label + "!",
   404  			Detail:              rule.details,
   405  			Score:               float64(score) * 0.01,
   406  			Kind:                protocol.SnippetCompletion,
   407  			snippet:             &tmplArgs.snip,
   408  			AdditionalTextEdits: append(edits, tmplArgs.edits...),
   409  		})
   410  	}
   411  }
   412  
   413  var postfixRulesOnce sync.Once
   414  
   415  func initPostfixRules() {
   416  	postfixRulesOnce.Do(func() {
   417  		var idx int
   418  		for _, rule := range postfixTmpls {
   419  			var err error
   420  			rule.tmpl, err = template.New("postfix_snippet").Parse(rule.body)
   421  			if err != nil {
   422  				log.Panicf("error parsing postfix snippet template: %v", err)
   423  			}
   424  			postfixTmpls[idx] = rule
   425  			idx++
   426  		}
   427  		postfixTmpls = postfixTmpls[:idx]
   428  	})
   429  }
   430  
   431  // importIfNeeded returns the package identifier and any necessary
   432  // edits to import package pkgPath.
   433  func (c *completer) importIfNeeded(pkgPath string, scope *types.Scope) (string, []protocol.TextEdit, error) {
   434  	defaultName := imports.ImportPathToAssumedName(pkgPath)
   435  
   436  	// Check if file already imports pkgPath.
   437  	for _, s := range c.file.Imports {
   438  		if source.ImportPath(s) == pkgPath {
   439  			if s.Name == nil {
   440  				return defaultName, nil, nil
   441  			}
   442  			if s.Name.Name != "_" {
   443  				return s.Name.Name, nil, nil
   444  			}
   445  		}
   446  	}
   447  
   448  	// Give up if the package's name is already in use by another object.
   449  	if _, obj := scope.LookupParent(defaultName, token.NoPos); obj != nil {
   450  		return "", nil, fmt.Errorf("import name %q of %q already in use", defaultName, pkgPath)
   451  	}
   452  
   453  	edits, err := c.importEdits(&importInfo{
   454  		importPath: pkgPath,
   455  	})
   456  	if err != nil {
   457  		return "", nil, err
   458  	}
   459  
   460  	return defaultName, edits, nil
   461  }