github.com/v2fly/tools@v0.100.0/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/v2fly/tools/internal/event"
    20  	"github.com/v2fly/tools/internal/imports"
    21  	"github.com/v2fly/tools/internal/lsp/protocol"
    22  	"github.com/v2fly/tools/internal/lsp/snippet"
    23  	"github.com/v2fly/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  
   179  // Cursor indicates where the client's cursor should end up after the
   180  // snippet is done.
   181  func (a *postfixTmplArgs) Cursor() string {
   182  	a.snip.WriteFinalTabstop()
   183  	return ""
   184  }
   185  
   186  // Import makes sure the package corresponding to path is imported,
   187  // returning the identifier to use to refer to the package.
   188  func (a *postfixTmplArgs) Import(path string) (string, error) {
   189  	name, edits, err := a.importIfNeeded(path, a.scope)
   190  	if err != nil {
   191  		return "", errors.Errorf("couldn't import %q: %w", path, err)
   192  	}
   193  	a.edits = append(a.edits, edits...)
   194  	return name, nil
   195  }
   196  
   197  func (a *postfixTmplArgs) EscapeQuotes(v string) string {
   198  	return strings.ReplaceAll(v, `"`, `\\"`)
   199  }
   200  
   201  // ElemType returns the Elem() type of xType, if applicable.
   202  func (a *postfixTmplArgs) ElemType() types.Type {
   203  	if e, _ := a.Type.(interface{ Elem() types.Type }); e != nil {
   204  		return e.Elem()
   205  	}
   206  	return nil
   207  }
   208  
   209  // Kind returns the underlying kind of type, e.g. "slice", "struct",
   210  // etc.
   211  func (a *postfixTmplArgs) Kind() string {
   212  	t := reflect.TypeOf(a.Type.Underlying())
   213  	return strings.ToLower(strings.TrimPrefix(t.String(), "*types."))
   214  }
   215  
   216  // KeyType returns the type of X's key. KeyType panics if X is not a
   217  // map.
   218  func (a *postfixTmplArgs) KeyType() types.Type {
   219  	return a.Type.Underlying().(*types.Map).Key()
   220  }
   221  
   222  // Tuple returns the tuple result vars if X is a call expression.
   223  func (a *postfixTmplArgs) Tuple() []*types.Var {
   224  	tuple, _ := a.Type.(*types.Tuple)
   225  	if tuple == nil {
   226  		return nil
   227  	}
   228  
   229  	typs := make([]*types.Var, 0, tuple.Len())
   230  	for i := 0; i < tuple.Len(); i++ {
   231  		typs = append(typs, tuple.At(i))
   232  	}
   233  	return typs
   234  }
   235  
   236  // TypeName returns the textual representation of type t.
   237  func (a *postfixTmplArgs) TypeName(t types.Type) (string, error) {
   238  	if t == nil || t == types.Typ[types.Invalid] {
   239  		return "", fmt.Errorf("invalid type: %v", t)
   240  	}
   241  	return types.TypeString(t, a.qf), nil
   242  }
   243  
   244  // VarName returns a suitable variable name for the type t. If t
   245  // implements the error interface, "err" is used. If t is not a named
   246  // type then nonNamedDefault is used. Otherwise a name is made by
   247  // abbreviating the type name. If the resultant name is already in
   248  // scope, an integer is appended to make a unique name.
   249  func (a *postfixTmplArgs) VarName(t types.Type, nonNamedDefault string) string {
   250  	if t == nil {
   251  		t = types.Typ[types.Invalid]
   252  	}
   253  
   254  	var name string
   255  	if types.Implements(t, errorIntf) {
   256  		name = "err"
   257  	} else if _, isNamed := source.Deref(t).(*types.Named); !isNamed {
   258  		name = nonNamedDefault
   259  	}
   260  
   261  	if name == "" {
   262  		name = types.TypeString(t, func(p *types.Package) string {
   263  			return ""
   264  		})
   265  		name = abbreviateTypeName(name)
   266  	}
   267  
   268  	if dot := strings.LastIndex(name, "."); dot > -1 {
   269  		name = name[dot+1:]
   270  	}
   271  
   272  	uniqueName := name
   273  	for i := 2; ; i++ {
   274  		if s, _ := a.scope.LookupParent(uniqueName, token.NoPos); s == nil && !a.varNames[uniqueName] {
   275  			break
   276  		}
   277  		uniqueName = fmt.Sprintf("%s%d", name, i)
   278  	}
   279  
   280  	a.varNames[uniqueName] = true
   281  
   282  	return uniqueName
   283  }
   284  
   285  func (c *completer) addPostfixSnippetCandidates(ctx context.Context, sel *ast.SelectorExpr) {
   286  	if !c.opts.postfix {
   287  		return
   288  	}
   289  
   290  	initPostfixRules()
   291  
   292  	if sel == nil || sel.Sel == nil {
   293  		return
   294  	}
   295  
   296  	selType := c.pkg.GetTypesInfo().TypeOf(sel.X)
   297  	if selType == nil {
   298  		return
   299  	}
   300  
   301  	// Skip empty tuples since there is no value to operate on.
   302  	if tuple, ok := selType.Underlying().(*types.Tuple); ok && tuple == nil {
   303  		return
   304  	}
   305  
   306  	tokFile := c.snapshot.FileSet().File(c.pos)
   307  
   308  	// Only replace sel with a statement if sel is already a statement.
   309  	var stmtOK bool
   310  	for i, n := range c.path {
   311  		if n == sel && i < len(c.path)-1 {
   312  			switch p := c.path[i+1].(type) {
   313  			case *ast.ExprStmt:
   314  				stmtOK = true
   315  			case *ast.AssignStmt:
   316  				// In cases like:
   317  				//
   318  				//   foo.<>
   319  				//   bar = 123
   320  				//
   321  				// detect that "foo." makes up the entire statement since the
   322  				// apparent selector spans lines.
   323  				stmtOK = tokFile.Line(c.pos) < tokFile.Line(p.TokPos)
   324  			}
   325  			break
   326  		}
   327  	}
   328  
   329  	scope := c.pkg.GetTypes().Scope().Innermost(c.pos)
   330  	if scope == nil {
   331  		return
   332  	}
   333  
   334  	// afterDot is the position after selector dot, e.g. "|" in
   335  	// "foo.|print".
   336  	afterDot := sel.Sel.Pos()
   337  
   338  	// We must detect dangling selectors such as:
   339  	//
   340  	//    foo.<>
   341  	//    bar
   342  	//
   343  	// and adjust afterDot so that we don't mistakenly delete the
   344  	// newline thinking "bar" is part of our selector.
   345  	if startLine := tokFile.Line(sel.Pos()); startLine != tokFile.Line(afterDot) {
   346  		if tokFile.Line(c.pos) != startLine {
   347  			return
   348  		}
   349  		afterDot = c.pos
   350  	}
   351  
   352  	for _, rule := range postfixTmpls {
   353  		// When completing foo.print<>, "print" is naturally overwritten,
   354  		// but we need to also remove "foo." so the snippet has a clean
   355  		// slate.
   356  		edits, err := c.editText(sel.Pos(), afterDot, "")
   357  		if err != nil {
   358  			event.Error(ctx, "error calculating postfix edits", err)
   359  			return
   360  		}
   361  
   362  		tmplArgs := postfixTmplArgs{
   363  			X:              source.FormatNode(c.snapshot.FileSet(), sel.X),
   364  			StmtOK:         stmtOK,
   365  			Obj:            exprObj(c.pkg.GetTypesInfo(), sel.X),
   366  			Type:           selType,
   367  			qf:             c.qf,
   368  			importIfNeeded: c.importIfNeeded,
   369  			scope:          scope,
   370  			varNames:       make(map[string]bool),
   371  		}
   372  
   373  		// Feed the template straight into the snippet builder. This
   374  		// allows templates to build snippets as they are executed.
   375  		err = rule.tmpl.Execute(&tmplArgs.snip, &tmplArgs)
   376  		if err != nil {
   377  			event.Error(ctx, "error executing postfix template", err)
   378  			continue
   379  		}
   380  
   381  		if strings.TrimSpace(tmplArgs.snip.String()) == "" {
   382  			continue
   383  		}
   384  
   385  		score := c.matcher.Score(rule.label)
   386  		if score <= 0 {
   387  			continue
   388  		}
   389  
   390  		c.items = append(c.items, CompletionItem{
   391  			Label:               rule.label + "!",
   392  			Detail:              rule.details,
   393  			Score:               float64(score) * 0.01,
   394  			Kind:                protocol.SnippetCompletion,
   395  			snippet:             &tmplArgs.snip,
   396  			AdditionalTextEdits: append(edits, tmplArgs.edits...),
   397  		})
   398  	}
   399  }
   400  
   401  var postfixRulesOnce sync.Once
   402  
   403  func initPostfixRules() {
   404  	postfixRulesOnce.Do(func() {
   405  		var idx int
   406  		for _, rule := range postfixTmpls {
   407  			var err error
   408  			rule.tmpl, err = template.New("postfix_snippet").Parse(rule.body)
   409  			if err != nil {
   410  				log.Panicf("error parsing postfix snippet template: %v", err)
   411  			}
   412  			postfixTmpls[idx] = rule
   413  			idx++
   414  		}
   415  		postfixTmpls = postfixTmpls[:idx]
   416  	})
   417  }
   418  
   419  // importIfNeeded returns the package identifier and any necessary
   420  // edits to import package pkgPath.
   421  func (c *completer) importIfNeeded(pkgPath string, scope *types.Scope) (string, []protocol.TextEdit, error) {
   422  	defaultName := imports.ImportPathToAssumedName(pkgPath)
   423  
   424  	// Check if file already imports pkgPath.
   425  	for _, s := range c.file.Imports {
   426  		if source.ImportPath(s) == pkgPath {
   427  			if s.Name == nil {
   428  				return defaultName, nil, nil
   429  			}
   430  			if s.Name.Name != "_" {
   431  				return s.Name.Name, nil, nil
   432  			}
   433  		}
   434  	}
   435  
   436  	// Give up if the package's name is already in use by another object.
   437  	if _, obj := scope.LookupParent(defaultName, token.NoPos); obj != nil {
   438  		return "", nil, fmt.Errorf("import name %q of %q already in use", defaultName, pkgPath)
   439  	}
   440  
   441  	edits, err := c.importEdits(&importInfo{
   442  		importPath: pkgPath,
   443  	})
   444  	if err != nil {
   445  		return "", nil, err
   446  	}
   447  
   448  	return defaultName, edits, nil
   449  }