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
5package completion
6
7import (
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	"golang.org/x/tools/internal/event"
20	"golang.org/x/tools/internal/imports"
21	"golang.org/x/tools/internal/lsp/protocol"
22	"golang.org/x/tools/internal/lsp/snippet"
23	"golang.org/x/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.
33type 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.
49type 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
78var 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" -}}
97for {{$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 -}}
105for {{.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) -}}
119append({{.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}}))
126copy({{$v}}, {{.X}})
127{{end}}`,
128}, {
129	label:   "range",
130	details: "range over map",
131	body: `{{if and (eq .Kind "map") .StmtOK -}}
132for {{.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.
181func (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.
188func (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
197func (a *postfixTmplArgs) EscapeQuotes(v string) string {
198	return strings.ReplaceAll(v, `"`, `\\"`)
199}
200
201// ElemType returns the Elem() type of xType, if applicable.
202func (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.
211func (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.
218func (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.
223func (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.
237func (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.
249func (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
285func (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
401var postfixRulesOnce sync.Once
402
403func 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.
421func (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}
450