mirror of
https://github.com/golang/go
synced 2024-11-18 20:04:52 -07:00
9267083701
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>
129 lines
3.5 KiB
Go
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)
|
|
}
|