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.
5 // Package fillstruct defines an Analyzer that automatically
6 // fills in a struct declaration with zero value elements for each field.
18 "golang.org/x/tools/go/analysis"
19 "golang.org/x/tools/go/analysis/passes/inspect"
20 "golang.org/x/tools/go/ast/astutil"
21 "golang.org/x/tools/go/ast/inspector"
22 "golang.org/x/tools/internal/analysisinternal"
23 "golang.org/x/tools/internal/span"
26 const Doc = `note incomplete struct initializations
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.
34 var Analyzer = &analysis.Analyzer{
37 Requires: []*analysis.Analyzer{inspect.Analyzer},
39 RunDespiteErrors: true,
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
50 expr := n.(*ast.CompositeLit)
53 for _, f := range pass.Files {
54 if f.Pos() <= expr.Pos() && expr.Pos() <= f.End() {
63 typ := info.TypeOf(expr)
68 // Find reference to the type declaration of the struct being initialized.
70 p, ok := typ.Underlying().(*types.Pointer)
76 typ = typ.Underlying()
78 obj, ok := typ.(*types.Struct)
82 fieldCount := obj.NumFields()
84 // Skip any struct that is already populated or that has no fields.
85 if fieldCount == 0 || fieldCount == len(expr.Elts) {
90 for i := 0; i < fieldCount; i++ {
92 // Ignore fields that are not accessible in the current package.
93 if field.Pkg() != nil && field.Pkg() != pass.Pkg && !field.Exported() {
102 switch typ := expr.Type.(type) {
105 case *ast.SelectorExpr:
106 name = fmt.Sprintf("%s.%s", typ.X, typ.Sel.Name)
108 name = "anonymous struct"
110 pass.Report(analysis.Diagnostic{
111 Message: fmt.Sprintf("Fill %s", name),
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
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)
126 return nil, fmt.Errorf("no enclosing ast.Node")
128 var expr *ast.CompositeLit
129 for _, n := range path {
130 if node, ok := n.(*ast.CompositeLit); ok {
137 return nil, fmt.Errorf("nil types.Info")
139 typ := info.TypeOf(expr)
141 return nil, fmt.Errorf("no composite literal")
144 // Find reference to the type declaration of the struct being initialized.
146 p, ok := typ.Underlying().(*types.Pointer)
152 typ = typ.Underlying()
154 obj, ok := typ.(*types.Struct)
156 return nil, fmt.Errorf("unexpected type %v (%T), expected *types.Struct", typ, typ)
158 fieldCount := obj.NumFields()
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
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
176 fakeFset := token.NewFileSet()
177 tok := fakeFset.AddFile("", -1, fieldCount+2)
179 line := 2 // account for 1-based lines and the left brace
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)
189 fieldTyps = append(fieldTyps, field.Type())
191 matches := analysisinternal.FindMatchingIdents(fieldTyps, file, rng.Start, info, pkg)
192 for i, fieldTyp := range fieldTyps {
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()))
201 pos := tok.LineStart(line)
203 kv := &ast.KeyValueExpr{
206 Name: obj.Field(i).Name(),
210 if expr, ok := prefilledTypes[obj.Field(i).Name()]; ok {
213 idents, ok := matches[fieldTyp]
215 return nil, fmt.Errorf("invalid struct field type: %v", fieldTyp)
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)
223 value = populateValue(fset, file, pkg, fieldTyp)
231 elts = append(elts, kv)
235 // If all of the struct's fields are unexported, we have nothing to do.
237 return nil, fmt.Errorf("no elements to fill")
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)
244 if line > tok.LineCount() {
245 panic(fmt.Sprintf("invalid line number %v (of %v) for fillstruct", line, tok.LineCount()))
248 cl := &ast.CompositeLit{
250 Lbrace: tok.LineStart(1),
252 Rbrace: tok.LineStart(line),
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
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]
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)
271 sug := indent(formatBuf.Bytes(), whitespace)
273 if len(prefilledTypes) > 0 {
274 // Attempt a second pass through the formatter to line up columns.
275 sourced, err := format.Source(sug)
277 sug = indent(sourced, whitespace)
281 return &analysis.SuggestedFix{
282 TextEdits: []analysis.TextEdit{
292 // indent works line by line through str, indenting (prefixing) each line with
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 {
301 // Don't add the extra indentation to the first line.
306 if i < len(split)-1 {
307 newText.WriteByte('\n')
310 return newText.Bytes()
313 // populateValue constructs an expression to fill the value of a struct field.
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.
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 {
323 if n, ok := typ.(*types.Named); ok {
324 under = n.Underlying()
326 switch u := under.(type) {
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: `""`}
336 panic("unknown basic type")
339 k := analysisinternal.TypeExpr(fset, f, pkg, u.Key())
340 v := analysisinternal.TypeExpr(fset, f, pkg, u.Elem())
341 if k == nil || v == nil {
344 return &ast.CompositeLit{
351 s := analysisinternal.TypeExpr(fset, f, pkg, u.Elem())
355 return &ast.CompositeLit{
356 Type: &ast.ArrayType{
361 a := analysisinternal.TypeExpr(fset, f, pkg, u.Elem())
365 return &ast.CompositeLit{
366 Type: &ast.ArrayType{
369 Kind: token.INT, Value: fmt.Sprintf("%v", u.Len()),
374 v := analysisinternal.TypeExpr(fset, f, pkg, u.Elem())
378 dir := ast.ChanDir(u.Dir())
379 if u.Dir() == types.SendRecv {
380 dir = ast.SEND | ast.RECV
382 return &ast.CallExpr{
383 Fun: ast.NewIdent("make"),
392 s := analysisinternal.TypeExpr(fset, f, pkg, typ)
396 return &ast.CompositeLit{
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())
406 params = append(params, &ast.Field{
410 Name: u.Params().At(i).Name(),
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())
421 returns = append(returns, &ast.Field{
427 Params: &ast.FieldList{
430 Results: &ast.FieldList{
434 Body: &ast.BlockStmt{},
437 switch u.Elem().(type) {
439 return &ast.CallExpr{
445 Name: u.Elem().String(),
450 return &ast.UnaryExpr{
452 X: populateValue(fset, f, pkg, u.Elem()),
455 case *types.Interface:
456 return ast.NewIdent("nil")