github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/internal/lsp/analysis/fillstruct/fillstruct.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 fillstruct defines an Analyzer that automatically 6 // fills in a struct declaration with zero value elements for each field. 7 package fillstruct 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/analysis/passes/inspect" 21 "github.com/powerman/golang-tools/go/ast/astutil" 22 "github.com/powerman/golang-tools/go/ast/inspector" 23 "github.com/powerman/golang-tools/internal/analysisinternal" 24 "github.com/powerman/golang-tools/internal/span" 25 "github.com/powerman/golang-tools/internal/typeparams" 26 ) 27 28 const Doc = `note incomplete struct initializations 29 30 This analyzer provides diagnostics for any struct literals that do not have 31 any fields initialized. Because the suggested fix for this analysis is 32 expensive to compute, callers should compute it separately, using the 33 SuggestedFix function below. 34 ` 35 36 var Analyzer = &analysis.Analyzer{ 37 Name: "fillstruct", 38 Doc: Doc, 39 Requires: []*analysis.Analyzer{inspect.Analyzer}, 40 Run: run, 41 RunDespiteErrors: true, 42 } 43 44 func run(pass *analysis.Pass) (interface{}, error) { 45 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) 46 nodeFilter := []ast.Node{(*ast.CompositeLit)(nil)} 47 inspect.Preorder(nodeFilter, func(n ast.Node) { 48 info := pass.TypesInfo 49 if info == nil { 50 return 51 } 52 expr := n.(*ast.CompositeLit) 53 54 var file *ast.File 55 for _, f := range pass.Files { 56 if f.Pos() <= expr.Pos() && expr.Pos() <= f.End() { 57 file = f 58 break 59 } 60 } 61 if file == nil { 62 return 63 } 64 65 typ := info.TypeOf(expr) 66 if typ == nil { 67 return 68 } 69 70 // Ignore types that have type parameters for now. 71 // TODO: support type params. 72 if typ, ok := typ.(*types.Named); ok { 73 if tparams := typeparams.ForNamed(typ); tparams != nil && tparams.Len() > 0 { 74 return 75 } 76 } 77 78 // Find reference to the type declaration of the struct being initialized. 79 for { 80 p, ok := typ.Underlying().(*types.Pointer) 81 if !ok { 82 break 83 } 84 typ = p.Elem() 85 } 86 typ = typ.Underlying() 87 88 obj, ok := typ.(*types.Struct) 89 if !ok { 90 return 91 } 92 fieldCount := obj.NumFields() 93 94 // Skip any struct that is already populated or that has no fields. 95 if fieldCount == 0 || fieldCount == len(expr.Elts) { 96 return 97 } 98 99 var fillable bool 100 var fillableFields []string 101 for i := 0; i < fieldCount; i++ { 102 field := obj.Field(i) 103 // Ignore fields that are not accessible in the current package. 104 if field.Pkg() != nil && field.Pkg() != pass.Pkg && !field.Exported() { 105 continue 106 } 107 // Ignore structs containing fields that have type parameters for now. 108 // TODO: support type params. 109 if typ, ok := field.Type().(*types.Named); ok { 110 if tparams := typeparams.ForNamed(typ); tparams != nil && tparams.Len() > 0 { 111 return 112 } 113 } 114 if _, ok := field.Type().(*typeparams.TypeParam); ok { 115 return 116 } 117 fillable = true 118 fillableFields = append(fillableFields, fmt.Sprintf("%s: %s", field.Name(), field.Type().String())) 119 } 120 if !fillable { 121 return 122 } 123 var name string 124 switch typ := expr.Type.(type) { 125 case *ast.Ident: 126 name = typ.Name 127 case *ast.SelectorExpr: 128 name = fmt.Sprintf("%s.%s", typ.X, typ.Sel.Name) 129 default: 130 totalFields := len(fillableFields) 131 maxLen := 20 132 // Find the index to cut off printing of fields. 133 var i, fieldLen int 134 for i = range fillableFields { 135 if fieldLen > maxLen { 136 break 137 } 138 fieldLen += len(fillableFields[i]) 139 } 140 fillableFields = fillableFields[:i] 141 if i < totalFields { 142 fillableFields = append(fillableFields, "...") 143 } 144 name = fmt.Sprintf("anonymous struct { %s }", strings.Join(fillableFields, ", ")) 145 } 146 pass.Report(analysis.Diagnostic{ 147 Message: fmt.Sprintf("Fill %s", name), 148 Pos: expr.Pos(), 149 End: expr.End(), 150 }) 151 }) 152 return nil, nil 153 } 154 155 func SuggestedFix(fset *token.FileSet, rng span.Range, content []byte, file *ast.File, pkg *types.Package, info *types.Info) (*analysis.SuggestedFix, error) { 156 pos := rng.Start // don't use the end 157 158 // TODO(rstambler): Using ast.Inspect would probably be more efficient than 159 // calling PathEnclosingInterval. Switch this approach. 160 path, _ := astutil.PathEnclosingInterval(file, pos, pos) 161 if len(path) == 0 { 162 return nil, fmt.Errorf("no enclosing ast.Node") 163 } 164 var expr *ast.CompositeLit 165 for _, n := range path { 166 if node, ok := n.(*ast.CompositeLit); ok { 167 expr = node 168 break 169 } 170 } 171 172 if info == nil { 173 return nil, fmt.Errorf("nil types.Info") 174 } 175 typ := info.TypeOf(expr) 176 if typ == nil { 177 return nil, fmt.Errorf("no composite literal") 178 } 179 180 // Find reference to the type declaration of the struct being initialized. 181 for { 182 p, ok := typ.Underlying().(*types.Pointer) 183 if !ok { 184 break 185 } 186 typ = p.Elem() 187 } 188 typ = typ.Underlying() 189 190 obj, ok := typ.(*types.Struct) 191 if !ok { 192 return nil, fmt.Errorf("unexpected type %v (%T), expected *types.Struct", typ, typ) 193 } 194 fieldCount := obj.NumFields() 195 196 // Check which types have already been filled in. (we only want to fill in 197 // the unfilled types, or else we'll blat user-supplied details) 198 prefilledTypes := map[string]ast.Expr{} 199 for _, e := range expr.Elts { 200 if kv, ok := e.(*ast.KeyValueExpr); ok { 201 if key, ok := kv.Key.(*ast.Ident); ok { 202 prefilledTypes[key.Name] = kv.Value 203 } 204 } 205 } 206 207 // Use a new fileset to build up a token.File for the new composite 208 // literal. We need one line for foo{, one line for }, and one line for 209 // each field we're going to set. format.Node only cares about line 210 // numbers, so we don't need to set columns, and each line can be 211 // 1 byte long. 212 fakeFset := token.NewFileSet() 213 tok := fakeFset.AddFile("", -1, fieldCount+2) 214 215 line := 2 // account for 1-based lines and the left brace 216 var elts []ast.Expr 217 var fieldTyps []types.Type 218 for i := 0; i < fieldCount; i++ { 219 field := obj.Field(i) 220 // Ignore fields that are not accessible in the current package. 221 if field.Pkg() != nil && field.Pkg() != pkg && !field.Exported() { 222 fieldTyps = append(fieldTyps, nil) 223 continue 224 } 225 fieldTyps = append(fieldTyps, field.Type()) 226 } 227 matches := analysisinternal.FindMatchingIdents(fieldTyps, file, rng.Start, info, pkg) 228 for i, fieldTyp := range fieldTyps { 229 if fieldTyp == nil { 230 continue 231 } 232 233 tok.AddLine(line - 1) // add 1 byte per line 234 if line > tok.LineCount() { 235 panic(fmt.Sprintf("invalid line number %v (of %v) for fillstruct", line, tok.LineCount())) 236 } 237 pos := tok.LineStart(line) 238 239 kv := &ast.KeyValueExpr{ 240 Key: &ast.Ident{ 241 NamePos: pos, 242 Name: obj.Field(i).Name(), 243 }, 244 Colon: pos, 245 } 246 if expr, ok := prefilledTypes[obj.Field(i).Name()]; ok { 247 kv.Value = expr 248 } else { 249 idents, ok := matches[fieldTyp] 250 if !ok { 251 return nil, fmt.Errorf("invalid struct field type: %v", fieldTyp) 252 } 253 254 // Find the identifier whose name is most similar to the name of the field's key. 255 // If we do not find any identifier that matches the pattern, generate a new value. 256 // NOTE: We currently match on the name of the field key rather than the field type. 257 value := analysisinternal.FindBestMatch(obj.Field(i).Name(), idents) 258 if value == nil { 259 value = populateValue(fset, file, pkg, fieldTyp) 260 } 261 if value == nil { 262 return nil, nil 263 } 264 265 kv.Value = value 266 } 267 elts = append(elts, kv) 268 line++ 269 } 270 271 // If all of the struct's fields are unexported, we have nothing to do. 272 if len(elts) == 0 { 273 return nil, fmt.Errorf("no elements to fill") 274 } 275 276 // Add the final line for the right brace. Offset is the number of 277 // bytes already added plus 1. 278 tok.AddLine(len(elts) + 1) 279 line = len(elts) + 2 280 if line > tok.LineCount() { 281 panic(fmt.Sprintf("invalid line number %v (of %v) for fillstruct", line, tok.LineCount())) 282 } 283 284 cl := &ast.CompositeLit{ 285 Type: expr.Type, 286 Lbrace: tok.LineStart(1), 287 Elts: elts, 288 Rbrace: tok.LineStart(line), 289 } 290 291 // Find the line on which the composite literal is declared. 292 split := bytes.Split(content, []byte("\n")) 293 lineNumber := fset.Position(expr.Lbrace).Line 294 firstLine := split[lineNumber-1] // lines are 1-indexed 295 296 // Trim the whitespace from the left of the line, and use the index 297 // to get the amount of whitespace on the left. 298 trimmed := bytes.TrimLeftFunc(firstLine, unicode.IsSpace) 299 index := bytes.Index(firstLine, trimmed) 300 whitespace := firstLine[:index] 301 302 // First pass through the formatter: turn the expr into a string. 303 var formatBuf bytes.Buffer 304 if err := format.Node(&formatBuf, fakeFset, cl); err != nil { 305 return nil, fmt.Errorf("failed to run first format on:\n%s\ngot err: %v", cl.Type, err) 306 } 307 sug := indent(formatBuf.Bytes(), whitespace) 308 309 if len(prefilledTypes) > 0 { 310 // Attempt a second pass through the formatter to line up columns. 311 sourced, err := format.Source(sug) 312 if err == nil { 313 sug = indent(sourced, whitespace) 314 } 315 } 316 317 return &analysis.SuggestedFix{ 318 TextEdits: []analysis.TextEdit{ 319 { 320 Pos: expr.Pos(), 321 End: expr.End(), 322 NewText: sug, 323 }, 324 }, 325 }, nil 326 } 327 328 // indent works line by line through str, indenting (prefixing) each line with 329 // ind. 330 func indent(str, ind []byte) []byte { 331 split := bytes.Split(str, []byte("\n")) 332 newText := bytes.NewBuffer(nil) 333 for i, s := range split { 334 if len(s) == 0 { 335 continue 336 } 337 // Don't add the extra indentation to the first line. 338 if i != 0 { 339 newText.Write(ind) 340 } 341 newText.Write(s) 342 if i < len(split)-1 { 343 newText.WriteByte('\n') 344 } 345 } 346 return newText.Bytes() 347 } 348 349 // populateValue constructs an expression to fill the value of a struct field. 350 // 351 // When the type of a struct field is a basic literal or interface, we return 352 // default values. For other types, such as maps, slices, and channels, we create 353 // expressions rather than using default values. 354 // 355 // The reasoning here is that users will call fillstruct with the intention of 356 // initializing the struct, in which case setting these fields to nil has no effect. 357 func populateValue(fset *token.FileSet, f *ast.File, pkg *types.Package, typ types.Type) ast.Expr { 358 under := typ 359 if n, ok := typ.(*types.Named); ok { 360 under = n.Underlying() 361 } 362 switch u := under.(type) { 363 case *types.Basic: 364 switch { 365 case u.Info()&types.IsNumeric != 0: 366 return &ast.BasicLit{Kind: token.INT, Value: "0"} 367 case u.Info()&types.IsBoolean != 0: 368 return &ast.Ident{Name: "false"} 369 case u.Info()&types.IsString != 0: 370 return &ast.BasicLit{Kind: token.STRING, Value: `""`} 371 default: 372 panic("unknown basic type") 373 } 374 case *types.Map: 375 k := analysisinternal.TypeExpr(fset, f, pkg, u.Key()) 376 v := analysisinternal.TypeExpr(fset, f, pkg, u.Elem()) 377 if k == nil || v == nil { 378 return nil 379 } 380 return &ast.CompositeLit{ 381 Type: &ast.MapType{ 382 Key: k, 383 Value: v, 384 }, 385 } 386 case *types.Slice: 387 s := analysisinternal.TypeExpr(fset, f, pkg, u.Elem()) 388 if s == nil { 389 return nil 390 } 391 return &ast.CompositeLit{ 392 Type: &ast.ArrayType{ 393 Elt: s, 394 }, 395 } 396 case *types.Array: 397 a := analysisinternal.TypeExpr(fset, f, pkg, u.Elem()) 398 if a == nil { 399 return nil 400 } 401 return &ast.CompositeLit{ 402 Type: &ast.ArrayType{ 403 Elt: a, 404 Len: &ast.BasicLit{ 405 Kind: token.INT, Value: fmt.Sprintf("%v", u.Len()), 406 }, 407 }, 408 } 409 case *types.Chan: 410 v := analysisinternal.TypeExpr(fset, f, pkg, u.Elem()) 411 if v == nil { 412 return nil 413 } 414 dir := ast.ChanDir(u.Dir()) 415 if u.Dir() == types.SendRecv { 416 dir = ast.SEND | ast.RECV 417 } 418 return &ast.CallExpr{ 419 Fun: ast.NewIdent("make"), 420 Args: []ast.Expr{ 421 &ast.ChanType{ 422 Dir: dir, 423 Value: v, 424 }, 425 }, 426 } 427 case *types.Struct: 428 s := analysisinternal.TypeExpr(fset, f, pkg, typ) 429 if s == nil { 430 return nil 431 } 432 return &ast.CompositeLit{ 433 Type: s, 434 } 435 case *types.Signature: 436 var params []*ast.Field 437 for i := 0; i < u.Params().Len(); i++ { 438 p := analysisinternal.TypeExpr(fset, f, pkg, u.Params().At(i).Type()) 439 if p == nil { 440 return nil 441 } 442 params = append(params, &ast.Field{ 443 Type: p, 444 Names: []*ast.Ident{ 445 { 446 Name: u.Params().At(i).Name(), 447 }, 448 }, 449 }) 450 } 451 var returns []*ast.Field 452 for i := 0; i < u.Results().Len(); i++ { 453 r := analysisinternal.TypeExpr(fset, f, pkg, u.Results().At(i).Type()) 454 if r == nil { 455 return nil 456 } 457 returns = append(returns, &ast.Field{ 458 Type: r, 459 }) 460 } 461 return &ast.FuncLit{ 462 Type: &ast.FuncType{ 463 Params: &ast.FieldList{ 464 List: params, 465 }, 466 Results: &ast.FieldList{ 467 List: returns, 468 }, 469 }, 470 Body: &ast.BlockStmt{}, 471 } 472 case *types.Pointer: 473 switch u.Elem().(type) { 474 case *types.Basic: 475 return &ast.CallExpr{ 476 Fun: &ast.Ident{ 477 Name: "new", 478 }, 479 Args: []ast.Expr{ 480 &ast.Ident{ 481 Name: u.Elem().String(), 482 }, 483 }, 484 } 485 default: 486 return &ast.UnaryExpr{ 487 Op: token.AND, 488 X: populateValue(fset, f, pkg, u.Elem()), 489 } 490 } 491 case *types.Interface: 492 return ast.NewIdent("nil") 493 } 494 return nil 495 }