+++ /dev/null
-// 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)
-}