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	"go/ast"
9
10	"golang.org/x/tools/internal/lsp/protocol"
11	"golang.org/x/tools/internal/lsp/source"
12)
13
14const (
15	BREAK       = "break"
16	CASE        = "case"
17	CHAN        = "chan"
18	CONST       = "const"
19	CONTINUE    = "continue"
20	DEFAULT     = "default"
21	DEFER       = "defer"
22	ELSE        = "else"
23	FALLTHROUGH = "fallthrough"
24	FOR         = "for"
25	FUNC        = "func"
26	GO          = "go"
27	GOTO        = "goto"
28	IF          = "if"
29	IMPORT      = "import"
30	INTERFACE   = "interface"
31	MAP         = "map"
32	PACKAGE     = "package"
33	RANGE       = "range"
34	RETURN      = "return"
35	SELECT      = "select"
36	STRUCT      = "struct"
37	SWITCH      = "switch"
38	TYPE        = "type"
39	VAR         = "var"
40)
41
42// addKeywordCompletions offers keyword candidates appropriate at the position.
43func (c *completer) addKeywordCompletions() {
44	seen := make(map[string]bool)
45
46	if c.wantTypeName() && c.inference.objType == nil {
47		// If we want a type name but don't have an expected obj type,
48		// include "interface", "struct", "func", "chan", and "map".
49
50		// "interface" and "struct" are more common declaring named types.
51		// Give them a higher score if we are in a type declaration.
52		structIntf, funcChanMap := stdScore, highScore
53		if len(c.path) > 1 {
54			if _, namedDecl := c.path[1].(*ast.TypeSpec); namedDecl {
55				structIntf, funcChanMap = highScore, stdScore
56			}
57		}
58
59		c.addKeywordItems(seen, structIntf, STRUCT, INTERFACE)
60		c.addKeywordItems(seen, funcChanMap, FUNC, CHAN, MAP)
61	}
62
63	// If we are at the file scope, only offer decl keywords. We don't
64	// get *ast.Idents at the file scope because non-keyword identifiers
65	// turn into *ast.BadDecl, not *ast.Ident.
66	if len(c.path) == 1 || isASTFile(c.path[1]) {
67		c.addKeywordItems(seen, stdScore, TYPE, CONST, VAR, FUNC, IMPORT)
68		return
69	} else if _, ok := c.path[0].(*ast.Ident); !ok {
70		// Otherwise only offer keywords if the client is completing an identifier.
71		return
72	}
73
74	if len(c.path) > 2 {
75		// Offer "range" if we are in ast.ForStmt.Init. This is what the
76		// AST looks like before "range" is typed, e.g. "for i := r<>".
77		if loop, ok := c.path[2].(*ast.ForStmt); ok && source.NodeContains(loop.Init, c.pos) {
78			c.addKeywordItems(seen, stdScore, RANGE)
79		}
80	}
81
82	// Only suggest keywords if we are beginning a statement.
83	switch n := c.path[1].(type) {
84	case *ast.BlockStmt, *ast.ExprStmt:
85		// OK - our ident must be at beginning of statement.
86	case *ast.CommClause:
87		// Make sure we aren't in the Comm statement.
88		if !n.Colon.IsValid() || c.pos <= n.Colon {
89			return
90		}
91	case *ast.CaseClause:
92		// Make sure we aren't in the case List.
93		if !n.Colon.IsValid() || c.pos <= n.Colon {
94			return
95		}
96	default:
97		return
98	}
99
100	// Filter out keywords depending on scope
101	// Skip the first one because we want to look at the enclosing scopes
102	path := c.path[1:]
103	for i, n := range path {
104		switch node := n.(type) {
105		case *ast.CaseClause:
106			// only recommend "fallthrough" and "break" within the bodies of a case clause
107			if c.pos > node.Colon {
108				c.addKeywordItems(seen, stdScore, BREAK)
109				// "fallthrough" is only valid in switch statements.
110				// A case clause is always nested within a block statement in a switch statement,
111				// that block statement is nested within either a TypeSwitchStmt or a SwitchStmt.
112				if i+2 >= len(path) {
113					continue
114				}
115				if _, ok := path[i+2].(*ast.SwitchStmt); ok {
116					c.addKeywordItems(seen, stdScore, FALLTHROUGH)
117				}
118			}
119		case *ast.CommClause:
120			if c.pos > node.Colon {
121				c.addKeywordItems(seen, stdScore, BREAK)
122			}
123		case *ast.TypeSwitchStmt, *ast.SelectStmt, *ast.SwitchStmt:
124			c.addKeywordItems(seen, stdScore, CASE, DEFAULT)
125		case *ast.ForStmt, *ast.RangeStmt:
126			c.addKeywordItems(seen, stdScore, BREAK, CONTINUE)
127		// This is a bit weak, functions allow for many keywords
128		case *ast.FuncDecl:
129			if node.Body != nil && c.pos > node.Body.Lbrace {
130				c.addKeywordItems(seen, stdScore, DEFER, RETURN, FOR, GO, SWITCH, SELECT, IF, ELSE, VAR, CONST, GOTO, TYPE)
131			}
132		}
133	}
134}
135
136// addKeywordItems dedupes and adds completion items for the specified
137// keywords with the specified score.
138func (c *completer) addKeywordItems(seen map[string]bool, score float64, kws ...string) {
139	for _, kw := range kws {
140		if seen[kw] {
141			continue
142		}
143		seen[kw] = true
144
145		if matchScore := c.matcher.Score(kw); matchScore > 0 {
146			c.items = append(c.items, CompletionItem{
147				Label:      kw,
148				Kind:       protocol.KeywordCompletion,
149				InsertText: kw,
150				Score:      score * float64(matchScore),
151			})
152		}
153	}
154}
155