github.com/v2fly/tools@v0.100.0/internal/lsp/analysis/undeclaredname/undeclared.go (about)

     1  // Copyright 2020 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package undeclaredname defines an Analyzer that applies suggested fixes
     6  // to errors of the type "undeclared name: %s".
     7  package undeclaredname
     8  
     9  import (
    10  	"bytes"
    11  	"fmt"
    12  	"go/ast"
    13  	"go/token"
    14  	"go/types"
    15  	"strings"
    16  
    17  	"github.com/v2fly/tools/go/analysis"
    18  	"github.com/v2fly/tools/go/ast/astutil"
    19  	"github.com/v2fly/tools/internal/analysisinternal"
    20  	"github.com/v2fly/tools/internal/span"
    21  )
    22  
    23  const Doc = `suggested fixes for "undeclared name: <>"
    24  
    25  This checker provides suggested fixes for type errors of the
    26  type "undeclared name: <>". It will insert a new statement:
    27  "<> := ".`
    28  
    29  var Analyzer = &analysis.Analyzer{
    30  	Name:             string(analysisinternal.UndeclaredName),
    31  	Doc:              Doc,
    32  	Requires:         []*analysis.Analyzer{},
    33  	Run:              run,
    34  	RunDespiteErrors: true,
    35  }
    36  
    37  const undeclaredNamePrefix = "undeclared name: "
    38  
    39  func run(pass *analysis.Pass) (interface{}, error) {
    40  	for _, err := range analysisinternal.GetTypeErrors(pass) {
    41  		if !FixesError(err.Msg) {
    42  			continue
    43  		}
    44  		name := strings.TrimPrefix(err.Msg, undeclaredNamePrefix)
    45  		var file *ast.File
    46  		for _, f := range pass.Files {
    47  			if f.Pos() <= err.Pos && err.Pos < f.End() {
    48  				file = f
    49  				break
    50  			}
    51  		}
    52  		if file == nil {
    53  			continue
    54  		}
    55  
    56  		// Get the path for the relevant range.
    57  		path, _ := astutil.PathEnclosingInterval(file, err.Pos, err.Pos)
    58  		if len(path) < 2 {
    59  			continue
    60  		}
    61  		ident, ok := path[0].(*ast.Ident)
    62  		if !ok || ident.Name != name {
    63  			continue
    64  		}
    65  		// Skip selector expressions because it might be too complex
    66  		// to try and provide a suggested fix for fields and methods.
    67  		if _, ok := path[1].(*ast.SelectorExpr); ok {
    68  			continue
    69  		}
    70  		// TODO(golang.org/issue/34644): Handle call expressions with suggested
    71  		// fixes to create a function.
    72  		if _, ok := path[1].(*ast.CallExpr); ok {
    73  			continue
    74  		}
    75  		tok := pass.Fset.File(file.Pos())
    76  		if tok == nil {
    77  			continue
    78  		}
    79  		offset := pass.Fset.Position(err.Pos).Offset
    80  		end := tok.Pos(offset + len(name))
    81  		pass.Report(analysis.Diagnostic{
    82  			Pos:     err.Pos,
    83  			End:     end,
    84  			Message: err.Msg,
    85  		})
    86  	}
    87  	return nil, nil
    88  }
    89  
    90  func SuggestedFix(fset *token.FileSet, rng span.Range, content []byte, file *ast.File, _ *types.Package, _ *types.Info) (*analysis.SuggestedFix, error) {
    91  	pos := rng.Start // don't use the end
    92  	path, _ := astutil.PathEnclosingInterval(file, pos, pos)
    93  	if len(path) < 2 {
    94  		return nil, fmt.Errorf("")
    95  	}
    96  	ident, ok := path[0].(*ast.Ident)
    97  	if !ok {
    98  		return nil, fmt.Errorf("")
    99  	}
   100  	// Get the place to insert the new statement.
   101  	insertBeforeStmt := analysisinternal.StmtToInsertVarBefore(path)
   102  	if insertBeforeStmt == nil {
   103  		return nil, fmt.Errorf("")
   104  	}
   105  
   106  	insertBefore := fset.Position(insertBeforeStmt.Pos()).Offset
   107  
   108  	// Get the indent to add on the line after the new statement.
   109  	// Since this will have a parse error, we can not use format.Source().
   110  	contentBeforeStmt, indent := content[:insertBefore], "\n"
   111  	if nl := bytes.LastIndex(contentBeforeStmt, []byte("\n")); nl != -1 {
   112  		indent = string(contentBeforeStmt[nl:])
   113  	}
   114  	// Create the new local variable statement.
   115  	newStmt := fmt.Sprintf("%s := %s", ident.Name, indent)
   116  	return &analysis.SuggestedFix{
   117  		Message: fmt.Sprintf("Create variable \"%s\"", ident.Name),
   118  		TextEdits: []analysis.TextEdit{{
   119  			Pos:     insertBeforeStmt.Pos(),
   120  			End:     insertBeforeStmt.Pos(),
   121  			NewText: []byte(newStmt),
   122  		}},
   123  	}, nil
   124  }
   125  
   126  func FixesError(msg string) bool {
   127  	return strings.HasPrefix(msg, undeclaredNamePrefix)
   128  }