github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/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/format" 14 "go/token" 15 "go/types" 16 "strings" 17 "unicode" 18 19 "github.com/powerman/golang-tools/go/analysis" 20 "github.com/powerman/golang-tools/go/ast/astutil" 21 "github.com/powerman/golang-tools/internal/analysisinternal" 22 "github.com/powerman/golang-tools/internal/span" 23 ) 24 25 const Doc = `suggested fixes for "undeclared name: <>" 26 27 This checker provides suggested fixes for type errors of the 28 type "undeclared name: <>". It will either insert a new statement, 29 such as: 30 31 "<> := " 32 33 or a new function declaration, such as: 34 35 func <>(inferred parameters) { 36 panic("implement me!") 37 } 38 ` 39 40 var Analyzer = &analysis.Analyzer{ 41 Name: string(analysisinternal.UndeclaredName), 42 Doc: Doc, 43 Requires: []*analysis.Analyzer{}, 44 Run: run, 45 RunDespiteErrors: true, 46 } 47 48 const undeclaredNamePrefix = "undeclared name: " 49 50 func run(pass *analysis.Pass) (interface{}, error) { 51 for _, err := range analysisinternal.GetTypeErrors(pass) { 52 runForError(pass, err) 53 } 54 return nil, nil 55 } 56 57 func runForError(pass *analysis.Pass, err types.Error) { 58 if !strings.HasPrefix(err.Msg, undeclaredNamePrefix) { 59 return 60 } 61 name := strings.TrimPrefix(err.Msg, undeclaredNamePrefix) 62 var file *ast.File 63 for _, f := range pass.Files { 64 if f.Pos() <= err.Pos && err.Pos < f.End() { 65 file = f 66 break 67 } 68 } 69 if file == nil { 70 return 71 } 72 73 // Get the path for the relevant range. 74 path, _ := astutil.PathEnclosingInterval(file, err.Pos, err.Pos) 75 if len(path) < 2 { 76 return 77 } 78 ident, ok := path[0].(*ast.Ident) 79 if !ok || ident.Name != name { 80 return 81 } 82 83 // Undeclared quick fixes only work in function bodies. 84 inFunc := false 85 for i := range path { 86 if _, inFunc = path[i].(*ast.FuncDecl); inFunc { 87 if i == 0 { 88 return 89 } 90 if _, isBody := path[i-1].(*ast.BlockStmt); !isBody { 91 return 92 } 93 break 94 } 95 } 96 if !inFunc { 97 return 98 } 99 // Skip selector expressions because it might be too complex 100 // to try and provide a suggested fix for fields and methods. 101 if _, ok := path[1].(*ast.SelectorExpr); ok { 102 return 103 } 104 tok := pass.Fset.File(file.Pos()) 105 if tok == nil { 106 return 107 } 108 offset := pass.Fset.Position(err.Pos).Offset 109 end := tok.Pos(offset + len(name)) 110 pass.Report(analysis.Diagnostic{ 111 Pos: err.Pos, 112 End: end, 113 Message: err.Msg, 114 }) 115 } 116 117 func SuggestedFix(fset *token.FileSet, rng span.Range, content []byte, file *ast.File, pkg *types.Package, info *types.Info) (*analysis.SuggestedFix, error) { 118 pos := rng.Start // don't use the end 119 path, _ := astutil.PathEnclosingInterval(file, pos, pos) 120 if len(path) < 2 { 121 return nil, fmt.Errorf("no expression found") 122 } 123 ident, ok := path[0].(*ast.Ident) 124 if !ok { 125 return nil, fmt.Errorf("no identifier found") 126 } 127 128 // Check for a possible call expression, in which case we should add a 129 // new function declaration. 130 if len(path) > 1 { 131 if _, ok := path[1].(*ast.CallExpr); ok { 132 return newFunctionDeclaration(path, file, pkg, info, fset) 133 } 134 } 135 136 // Get the place to insert the new statement. 137 insertBeforeStmt := analysisinternal.StmtToInsertVarBefore(path) 138 if insertBeforeStmt == nil { 139 return nil, fmt.Errorf("could not locate insertion point") 140 } 141 142 insertBefore := fset.Position(insertBeforeStmt.Pos()).Offset 143 144 // Get the indent to add on the line after the new statement. 145 // Since this will have a parse error, we can not use format.Source(). 146 contentBeforeStmt, indent := content[:insertBefore], "\n" 147 if nl := bytes.LastIndex(contentBeforeStmt, []byte("\n")); nl != -1 { 148 indent = string(contentBeforeStmt[nl:]) 149 } 150 151 // Create the new local variable statement. 152 newStmt := fmt.Sprintf("%s := %s", ident.Name, indent) 153 return &analysis.SuggestedFix{ 154 Message: fmt.Sprintf("Create variable \"%s\"", ident.Name), 155 TextEdits: []analysis.TextEdit{{ 156 Pos: insertBeforeStmt.Pos(), 157 End: insertBeforeStmt.Pos(), 158 NewText: []byte(newStmt), 159 }}, 160 }, nil 161 } 162 163 func newFunctionDeclaration(path []ast.Node, file *ast.File, pkg *types.Package, info *types.Info, fset *token.FileSet) (*analysis.SuggestedFix, error) { 164 if len(path) < 3 { 165 return nil, fmt.Errorf("unexpected set of enclosing nodes: %v", path) 166 } 167 ident, ok := path[0].(*ast.Ident) 168 if !ok { 169 return nil, fmt.Errorf("no name for function declaration %v (%T)", path[0], path[0]) 170 } 171 call, ok := path[1].(*ast.CallExpr) 172 if !ok { 173 return nil, fmt.Errorf("no call expression found %v (%T)", path[1], path[1]) 174 } 175 176 // Find the enclosing function, so that we can add the new declaration 177 // below. 178 var enclosing *ast.FuncDecl 179 for _, n := range path { 180 if n, ok := n.(*ast.FuncDecl); ok { 181 enclosing = n 182 break 183 } 184 } 185 // TODO(rstambler): Support the situation when there is no enclosing 186 // function. 187 if enclosing == nil { 188 return nil, fmt.Errorf("no enclosing function found: %v", path) 189 } 190 191 pos := enclosing.End() 192 193 var paramNames []string 194 var paramTypes []types.Type 195 // keep track of all param names to later ensure uniqueness 196 nameCounts := map[string]int{} 197 for _, arg := range call.Args { 198 typ := info.TypeOf(arg) 199 if typ == nil { 200 return nil, fmt.Errorf("unable to determine type for %s", arg) 201 } 202 203 switch t := typ.(type) { 204 // this is the case where another function call returning multiple 205 // results is used as an argument 206 case *types.Tuple: 207 n := t.Len() 208 for i := 0; i < n; i++ { 209 name := typeToArgName(t.At(i).Type()) 210 nameCounts[name]++ 211 212 paramNames = append(paramNames, name) 213 paramTypes = append(paramTypes, types.Default(t.At(i).Type())) 214 } 215 216 default: 217 // does the argument have a name we can reuse? 218 // only happens in case of a *ast.Ident 219 var name string 220 if ident, ok := arg.(*ast.Ident); ok { 221 name = ident.Name 222 } 223 224 if name == "" { 225 name = typeToArgName(typ) 226 } 227 228 nameCounts[name]++ 229 230 paramNames = append(paramNames, name) 231 paramTypes = append(paramTypes, types.Default(typ)) 232 } 233 } 234 235 for n, c := range nameCounts { 236 // Any names we saw more than once will need a unique suffix added 237 // on. Reset the count to 1 to act as the suffix for the first 238 // occurrence of that name. 239 if c >= 2 { 240 nameCounts[n] = 1 241 } else { 242 delete(nameCounts, n) 243 } 244 } 245 246 params := &ast.FieldList{} 247 248 for i, name := range paramNames { 249 if suffix, repeats := nameCounts[name]; repeats { 250 nameCounts[name]++ 251 name = fmt.Sprintf("%s%d", name, suffix) 252 } 253 254 // only worth checking after previous param in the list 255 if i > 0 { 256 // if type of parameter at hand is the same as the previous one, 257 // add it to the previous param list of identifiers so to have: 258 // (s1, s2 string) 259 // and not 260 // (s1 string, s2 string) 261 if paramTypes[i] == paramTypes[i-1] { 262 params.List[len(params.List)-1].Names = append(params.List[len(params.List)-1].Names, ast.NewIdent(name)) 263 continue 264 } 265 } 266 267 params.List = append(params.List, &ast.Field{ 268 Names: []*ast.Ident{ 269 ast.NewIdent(name), 270 }, 271 Type: analysisinternal.TypeExpr(fset, file, pkg, paramTypes[i]), 272 }) 273 } 274 275 decl := &ast.FuncDecl{ 276 Name: ast.NewIdent(ident.Name), 277 Type: &ast.FuncType{ 278 Params: params, 279 // TODO(rstambler): Also handle result parameters here. 280 }, 281 Body: &ast.BlockStmt{ 282 List: []ast.Stmt{ 283 &ast.ExprStmt{ 284 X: &ast.CallExpr{ 285 Fun: ast.NewIdent("panic"), 286 Args: []ast.Expr{ 287 &ast.BasicLit{ 288 Value: `"unimplemented"`, 289 }, 290 }, 291 }, 292 }, 293 }, 294 }, 295 } 296 297 b := bytes.NewBufferString("\n\n") 298 if err := format.Node(b, fset, decl); err != nil { 299 return nil, err 300 } 301 return &analysis.SuggestedFix{ 302 Message: fmt.Sprintf("Create function \"%s\"", ident.Name), 303 TextEdits: []analysis.TextEdit{{ 304 Pos: pos, 305 End: pos, 306 NewText: b.Bytes(), 307 }}, 308 }, nil 309 } 310 func typeToArgName(ty types.Type) string { 311 s := types.Default(ty).String() 312 313 switch t := ty.(type) { 314 case *types.Basic: 315 // use first letter in type name for basic types 316 return s[0:1] 317 case *types.Slice: 318 // use element type to decide var name for slices 319 return typeToArgName(t.Elem()) 320 case *types.Array: 321 // use element type to decide var name for arrays 322 return typeToArgName(t.Elem()) 323 case *types.Chan: 324 return "ch" 325 } 326 327 s = strings.TrimFunc(s, func(r rune) bool { 328 return !unicode.IsLetter(r) 329 }) 330 331 if s == "error" { 332 return "err" 333 } 334 335 // remove package (if present) 336 // and make first letter lowercase 337 a := []rune(s[strings.LastIndexByte(s, '.')+1:]) 338 a[0] = unicode.ToLower(a[0]) 339 return string(a) 340 }