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