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 }