1
0
mirror of https://github.com/golang/go synced 2024-10-01 06:18:31 -06:00
go/internal/lsp/source/completion_labels.go
Muir Manders 5bac78f585 internal/lsp: add label completion candidates
Now we offer completion candidates for labels when completing "break",
"continue", and "goto" statements. We are reasonably smart about
filtering unusable labels, except we don't filter "goto" candidates
that jump across variable definitions.

Fixes golang/go#33987.

Change-Id: If296a7579845aba5d86c7050ab195c35d4b147ed
Reviewed-on: https://go-review.googlesource.com/c/tools/+/197417
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2019-10-16 23:06:01 +00:00

112 lines
2.6 KiB
Go

// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package source
import (
"go/ast"
"go/token"
)
type labelType int
const (
labelNone labelType = iota
labelBreak
labelContinue
labelGoto
)
// wantLabelCompletion returns true if we want (only) label
// completions at the position.
func (c *completer) wantLabelCompletion() labelType {
if _, ok := c.path[0].(*ast.Ident); ok && len(c.path) > 1 {
// We want a label if we are an *ast.Ident child of a statement
// that accepts a label, e.g. "break Lo<>".
return takesLabel(c.path[1])
}
return labelNone
}
// takesLabel returns the corresponding labelType if n is a statement
// that accepts a label, otherwise labelNone.
func takesLabel(n ast.Node) labelType {
if bs, ok := n.(*ast.BranchStmt); ok {
switch bs.Tok {
case token.BREAK:
return labelBreak
case token.CONTINUE:
return labelContinue
case token.GOTO:
return labelGoto
}
}
return labelNone
}
// labels adds completion items for labels defined in the enclosing
// function.
func (c *completer) labels(lt labelType) {
if c.enclosingFunc == nil {
return
}
addLabel := func(l *ast.LabeledStmt) {
labelObj := c.pkg.GetTypesInfo().ObjectOf(l.Label)
if labelObj != nil {
c.found(labelObj, highScore, nil)
}
}
switch lt {
case labelBreak, labelContinue:
// "break" and "continue" only accept labels from enclosing statements.
for _, p := range c.path {
switch p := p.(type) {
case *ast.FuncLit:
// Labels are function scoped, so don't continue out of functions.
return
case *ast.LabeledStmt:
switch p.Stmt.(type) {
case *ast.ForStmt:
// Loop labels can be used for "break" or "continue".
addLabel(p)
case *ast.SwitchStmt, *ast.SelectStmt:
// Switch and select labels can be used only for "break".
if lt == labelBreak {
addLabel(p)
}
}
}
}
case labelGoto:
// Goto accepts any label in the same function not in a nested
// block. It also doesn't take labels that would jump across
// variable definitions, but ignore that case for now.
ast.Inspect(c.enclosingFunc.body, func(n ast.Node) bool {
if n == nil {
return false
}
switch n := n.(type) {
// Only search into block-like nodes enclosing our "goto".
// This prevents us from finding labels in nested blocks.
case *ast.BlockStmt, *ast.CommClause, *ast.CaseClause:
for _, p := range c.path {
if n == p {
return true
}
}
return false
case *ast.LabeledStmt:
addLabel(n)
}
return true
})
}
}