1
0
mirror of https://github.com/golang/go synced 2024-09-30 22:48:32 -06:00
go/internal/lsp/analysis/undeclaredname/undeclared.go
Rebecca Stambler 9267083701 internal/lsp: support refactor.extract through commands
The logic for extracting a function is quite signficant, and the code
is expensive enough that we should only call it when requested by the
user. This means that we should support extracting through a command
rather than text edits in the code action.

To that end, we create a new struct for commands. Features like extract
variable and extract function can supply functions to determine if they
are relevant to the given range, and if so, to generate their text
edits. source.Analyzers now point to Commands, rather than
SuggestedFixFuncs. The "canExtractVariable" and "canExtractFunction"
functions still need improvements, but I think that can be done in a
follow-up.

Change-Id: I9ec894c5abdbb28505a0f84ad7c76aa50977827a
Reviewed-on: https://go-review.googlesource.com/c/tools/+/244598
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
2020-07-27 19:25:51 +00:00

129 lines
3.5 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 undeclaredname defines an Analyzer that applies suggested fixes
// to errors of the type "undeclared name: %s".
package undeclaredname
import (
"bytes"
"fmt"
"go/ast"
"go/token"
"go/types"
"strings"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/span"
)
const Doc = `suggested fixes for "undeclared name: <>"
This checker provides suggested fixes for type errors of the
type "undeclared name: <>". It will insert a new statement:
"<> := ".`
var Analyzer = &analysis.Analyzer{
Name: string(analysisinternal.UndeclaredName),
Doc: Doc,
Requires: []*analysis.Analyzer{},
Run: run,
RunDespiteErrors: true,
}
const undeclaredNamePrefix = "undeclared name: "
func run(pass *analysis.Pass) (interface{}, error) {
for _, err := range analysisinternal.GetTypeErrors(pass) {
if !FixesError(err.Msg) {
continue
}
name := strings.TrimPrefix(err.Msg, undeclaredNamePrefix)
var file *ast.File
for _, f := range pass.Files {
if f.Pos() <= err.Pos && err.Pos < f.End() {
file = f
break
}
}
if file == nil {
continue
}
// Get the path for the relevant range.
path, _ := astutil.PathEnclosingInterval(file, err.Pos, err.Pos)
if len(path) < 2 {
continue
}
ident, ok := path[0].(*ast.Ident)
if !ok || ident.Name != name {
continue
}
// Skip selector expressions because it might be too complex
// to try and provide a suggested fix for fields and methods.
if _, ok := path[1].(*ast.SelectorExpr); ok {
continue
}
// TODO(golang.org/issue/34644): Handle call expressions with suggested
// fixes to create a function.
if _, ok := path[1].(*ast.CallExpr); ok {
continue
}
tok := pass.Fset.File(file.Pos())
if tok == nil {
continue
}
offset := pass.Fset.Position(err.Pos).Offset
end := tok.Pos(offset + len(name))
pass.Report(analysis.Diagnostic{
Pos: err.Pos,
End: end,
Message: err.Msg,
})
}
return nil, nil
}
func SuggestedFix(fset *token.FileSet, rng span.Range, content []byte, file *ast.File, _ *types.Package, _ *types.Info) (*analysis.SuggestedFix, error) {
pos := rng.Start // don't use the end
path, _ := astutil.PathEnclosingInterval(file, pos, pos)
if len(path) < 2 {
return nil, fmt.Errorf("")
}
ident, ok := path[0].(*ast.Ident)
if !ok {
return nil, fmt.Errorf("")
}
// Get the place to insert the new statement.
insertBeforeStmt := analysisinternal.StmtToInsertVarBefore(path)
if insertBeforeStmt == nil {
return nil, fmt.Errorf("")
}
insertBefore := fset.Position(insertBeforeStmt.Pos()).Offset
// Get the indent to add on the line after the new statement.
// Since this will have a parse error, we can not use format.Source().
contentBeforeStmt, indent := content[:insertBefore], "\n"
if nl := bytes.LastIndex(contentBeforeStmt, []byte("\n")); nl != -1 {
indent = string(contentBeforeStmt[nl:])
}
// Create the new local variable statement.
newStmt := fmt.Sprintf("%s := %s", ident.Name, indent)
return &analysis.SuggestedFix{
Message: fmt.Sprintf("Create variable \"%s\"", ident.Name),
TextEdits: []analysis.TextEdit{{
Pos: insertBeforeStmt.Pos(),
End: insertBeforeStmt.Pos(),
NewText: []byte(newStmt),
}},
}, nil
}
func FixesError(msg string) bool {
return strings.HasPrefix(msg, undeclaredNamePrefix)
}