mirror of
https://github.com/golang/go
synced 2024-11-18 20:44:45 -07:00
138c814f66
Now we offer an error-check-and-return completion candidate when appropriate: func myFunc() (int, error) { f, err := os.Open("foo") <> } offers the candidate: if err != nil { return 0, <err> } where <> denotes a placeholder so you can easily alter "err". The completion will only be offered when: 1. The position is in a function that returns an error as final result value, and 2. The statement preceding position is an assignment whose final LHS value is an error. The completion will contain zero values for the non-error return values as necessary. Using the above example, the completion will be offered after the user has typed: i if if err Basically the candidate will be offered after every keystroke as the user types "if err". I call this new type of completion a statement completion - perfect for when you want to make a statement! Change-Id: I0a330e1c1fa81a2757d3afc84c24e853f46f26b0 Reviewed-on: https://go-review.googlesource.com/c/tools/+/221613 Run-TryBot: Muir Manders <muir@mnd.rs> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
164 lines
3.9 KiB
Go
164 lines
3.9 KiB
Go
// Copyright 2020 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 (
|
|
"fmt"
|
|
"go/ast"
|
|
"go/token"
|
|
"go/types"
|
|
|
|
"golang.org/x/tools/internal/lsp/protocol"
|
|
"golang.org/x/tools/internal/lsp/snippet"
|
|
)
|
|
|
|
// addStatementCandidates adds full statement completion candidates
|
|
// appropriate for the current context.
|
|
func (c *completer) addStatementCandidates() {
|
|
c.addErrCheckAndReturn()
|
|
}
|
|
|
|
// addErrCheckAndReturn offers a completion candidate of the form:
|
|
//
|
|
// if err != nil {
|
|
// return nil, err
|
|
// }
|
|
//
|
|
// The position must be in a function that returns an error, and the
|
|
// statement preceding the position must be an assignment where the
|
|
// final LHS object is an error. addErrCheckAndReturn will synthesize
|
|
// zero values as necessary to make the return statement valid.
|
|
func (c *completer) addErrCheckAndReturn() {
|
|
if len(c.path) < 2 || c.enclosingFunc == nil || !c.opts.placeholders {
|
|
return
|
|
}
|
|
|
|
var (
|
|
errorType = types.Universe.Lookup("error").Type()
|
|
result = c.enclosingFunc.sig.Results()
|
|
)
|
|
// Make sure our enclosing function returns an error.
|
|
if result.Len() == 0 || !types.Identical(result.At(result.Len()-1).Type(), errorType) {
|
|
return
|
|
}
|
|
|
|
prevLine := prevStmt(c.pos, c.path)
|
|
if prevLine == nil {
|
|
return
|
|
}
|
|
|
|
// Make sure our preceding statement was as assignment.
|
|
assign, _ := prevLine.(*ast.AssignStmt)
|
|
if assign == nil || len(assign.Lhs) == 0 {
|
|
return
|
|
}
|
|
|
|
lastAssignee := assign.Lhs[len(assign.Lhs)-1]
|
|
|
|
// Make sure the final assignee is an error.
|
|
if !types.Identical(c.pkg.GetTypesInfo().TypeOf(lastAssignee), errorType) {
|
|
return
|
|
}
|
|
|
|
var (
|
|
// errText is e.g. "err" in "foo, err := bar()".
|
|
errText = formatNode(c.snapshot.View().Session().Cache().FileSet(), lastAssignee)
|
|
|
|
// Whether we need to include the "if" keyword in our candidate.
|
|
needsIf = true
|
|
)
|
|
|
|
// "_" isn't a real object.
|
|
if errText == "_" {
|
|
return
|
|
}
|
|
|
|
// Below we try to detect if the user has already started typing "if
|
|
// err" so we can replace what they've typed with our complete
|
|
// statement.
|
|
switch n := c.path[0].(type) {
|
|
case *ast.Ident:
|
|
switch c.path[1].(type) {
|
|
case *ast.ExprStmt:
|
|
// This handles:
|
|
//
|
|
// f, err := os.Open("foo")
|
|
// i<>
|
|
|
|
// Make sure they are typing "if".
|
|
if c.matcher.Score("if") <= 0 {
|
|
return
|
|
}
|
|
case *ast.IfStmt:
|
|
// This handles:
|
|
//
|
|
// f, err := os.Open("foo")
|
|
// if er<>
|
|
|
|
// Make sure they are typing the error's name.
|
|
if c.matcher.Score(errText) <= 0 {
|
|
return
|
|
}
|
|
|
|
needsIf = false
|
|
default:
|
|
return
|
|
}
|
|
case *ast.IfStmt:
|
|
// This handles:
|
|
//
|
|
// f, err := os.Open("foo")
|
|
// if <>
|
|
|
|
// Avoid false positives by ensuring the if's cond is a bad
|
|
// expression. For example, don't offer the completion in cases
|
|
// like "if <> somethingElse".
|
|
if _, bad := n.Cond.(*ast.BadExpr); !bad {
|
|
return
|
|
}
|
|
|
|
// If "if" is our direct prefix, we need to include it in our
|
|
// candidate since the existing "if" will be overwritten.
|
|
needsIf = c.pos == n.Pos()+token.Pos(len("if"))
|
|
}
|
|
|
|
// Build up a snippet that looks like:
|
|
//
|
|
// if err != nil {
|
|
// return <zero value>, ..., ${1:err}
|
|
// }
|
|
//
|
|
// We make the error a placeholder so it is easy to alter the error.
|
|
var snip snippet.Builder
|
|
if needsIf {
|
|
snip.WriteText("if ")
|
|
}
|
|
snip.WriteText(fmt.Sprintf("%s != nil {\n\treturn ", errText))
|
|
|
|
for i := 0; i < result.Len()-1; i++ {
|
|
snip.WriteText(formatZeroValue(result.At(i).Type(), c.qf))
|
|
snip.WriteText(", ")
|
|
}
|
|
|
|
snip.WritePlaceholder(func(b *snippet.Builder) {
|
|
b.WriteText(errText)
|
|
})
|
|
|
|
snip.WriteText("\n}")
|
|
|
|
label := fmt.Sprintf("%[1]s != nil { return %[1]s }", errText)
|
|
if needsIf {
|
|
label = "if " + label
|
|
}
|
|
|
|
c.items = append(c.items, CompletionItem{
|
|
Label: label,
|
|
// There doesn't seem to be a more appropriate kind.
|
|
Kind: protocol.KeywordCompletion,
|
|
Score: highScore,
|
|
snippet: &snip,
|
|
})
|
|
}
|