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