mirror of
https://github.com/golang/go
synced 2024-11-18 22:04:43 -07:00
5bac78f585
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>
112 lines
2.6 KiB
Go
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
|
|
})
|
|
}
|
|
}
|