2020-06-09 15:09:06 -06:00
|
|
|
// Copyright 2020 The Go Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
// Package fillstruct defines an Analyzer that automatically
|
|
|
|
// fills in a struct declaration with zero value elements for each field.
|
|
|
|
package fillstruct
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2020-06-12 15:10:06 -06:00
|
|
|
"fmt"
|
2020-06-09 15:09:06 -06:00
|
|
|
"go/ast"
|
|
|
|
"go/format"
|
2020-06-15 13:20:17 -06:00
|
|
|
"go/token"
|
2020-06-09 15:09:06 -06:00
|
|
|
"go/types"
|
|
|
|
|
|
|
|
"golang.org/x/tools/go/analysis"
|
|
|
|
"golang.org/x/tools/go/analysis/passes/inspect"
|
|
|
|
"golang.org/x/tools/go/ast/inspector"
|
|
|
|
"golang.org/x/tools/internal/analysisinternal"
|
|
|
|
)
|
|
|
|
|
|
|
|
const Doc = `suggested input for incomplete struct initializations
|
|
|
|
|
|
|
|
This analyzer provides the appropriate zero values for all
|
2020-06-15 13:20:17 -06:00
|
|
|
uninitialized fields of an empty struct. For example, given the following struct:
|
2020-06-09 15:09:06 -06:00
|
|
|
type Foo struct {
|
|
|
|
ID int64
|
|
|
|
Name string
|
|
|
|
}
|
|
|
|
the initialization
|
|
|
|
var _ = Foo{}
|
|
|
|
will turn into
|
|
|
|
var _ = Foo{
|
|
|
|
ID: 0,
|
|
|
|
Name: "",
|
|
|
|
}
|
|
|
|
`
|
|
|
|
|
|
|
|
var Analyzer = &analysis.Analyzer{
|
|
|
|
Name: "fillstruct",
|
|
|
|
Doc: Doc,
|
|
|
|
Requires: []*analysis.Analyzer{inspect.Analyzer},
|
|
|
|
Run: run,
|
|
|
|
RunDespiteErrors: true,
|
|
|
|
}
|
|
|
|
|
|
|
|
func run(pass *analysis.Pass) (interface{}, error) {
|
|
|
|
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
|
|
|
|
nodeFilter := []ast.Node{(*ast.CompositeLit)(nil)}
|
|
|
|
inspect.Preorder(nodeFilter, func(n ast.Node) {
|
|
|
|
info := pass.TypesInfo
|
|
|
|
if info == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
expr := n.(*ast.CompositeLit)
|
2020-06-15 13:20:17 -06:00
|
|
|
|
2020-06-09 15:09:06 -06:00
|
|
|
// TODO: Handle partially-filled structs as well.
|
|
|
|
if len(expr.Elts) != 0 {
|
|
|
|
return
|
|
|
|
}
|
2020-06-15 13:20:17 -06:00
|
|
|
|
2020-06-09 15:09:06 -06:00
|
|
|
var file *ast.File
|
|
|
|
for _, f := range pass.Files {
|
|
|
|
if f.Pos() <= expr.Pos() && expr.Pos() <= f.End() {
|
|
|
|
file = f
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if file == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
typ := info.TypeOf(expr)
|
|
|
|
if typ == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find reference to the type declaration of the struct being initialized.
|
|
|
|
for {
|
|
|
|
p, ok := typ.Underlying().(*types.Pointer)
|
|
|
|
if !ok {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
typ = p.Elem()
|
|
|
|
}
|
|
|
|
typ = typ.Underlying()
|
|
|
|
|
2020-06-12 15:10:06 -06:00
|
|
|
obj, ok := typ.(*types.Struct)
|
|
|
|
if !ok {
|
2020-06-09 15:09:06 -06:00
|
|
|
return
|
|
|
|
}
|
2020-06-12 15:10:06 -06:00
|
|
|
fieldCount := obj.NumFields()
|
2020-06-15 13:20:17 -06:00
|
|
|
// Skip any struct that is already populated or that has no fields.
|
|
|
|
if fieldCount == 0 || fieldCount == len(expr.Elts) {
|
2020-06-12 15:10:06 -06:00
|
|
|
return
|
|
|
|
}
|
2020-06-15 13:20:17 -06:00
|
|
|
|
|
|
|
// Don't mutate the existing token.File. Instead, create a copy that we can use to modify
|
|
|
|
// position information.
|
|
|
|
original := pass.Fset.File(expr.Lbrace)
|
|
|
|
fset := token.NewFileSet()
|
|
|
|
tok := fset.AddFile(original.Name(), -1, original.Size())
|
|
|
|
|
|
|
|
pos := token.Pos(1)
|
|
|
|
var elts []ast.Expr
|
2020-06-12 15:10:06 -06:00
|
|
|
for i := 0; i < fieldCount; i++ {
|
|
|
|
field := obj.Field(i)
|
|
|
|
// Ignore fields that are not accessible in the current package.
|
|
|
|
if field.Pkg() != nil && field.Pkg() != pass.Pkg && !field.Exported() {
|
|
|
|
continue
|
2020-06-09 15:09:06 -06:00
|
|
|
}
|
|
|
|
|
2020-06-12 15:10:06 -06:00
|
|
|
value := analysisinternal.ZeroValue(pass.Fset, file, pass.Pkg, field.Type())
|
|
|
|
if value == nil {
|
|
|
|
continue
|
2020-06-09 15:09:06 -06:00
|
|
|
}
|
2020-06-15 13:20:17 -06:00
|
|
|
pos = nextLinePos(tok, pos)
|
|
|
|
kv := &ast.KeyValueExpr{
|
|
|
|
Key: &ast.Ident{
|
|
|
|
NamePos: pos,
|
|
|
|
Name: field.Name(),
|
|
|
|
},
|
|
|
|
Colon: pos,
|
|
|
|
Value: value, // 'value' has no position. fomat.Node corrects for AST nodes with no position.
|
2020-06-09 15:09:06 -06:00
|
|
|
}
|
2020-06-15 13:20:17 -06:00
|
|
|
elts = append(elts, kv)
|
2020-06-12 15:10:06 -06:00
|
|
|
}
|
|
|
|
|
2020-06-15 13:20:17 -06:00
|
|
|
// If all of the struct's fields are unexported, we have nothing to do.
|
|
|
|
if len(elts) == 0 {
|
2020-06-12 15:10:06 -06:00
|
|
|
return
|
|
|
|
}
|
2020-06-09 15:09:06 -06:00
|
|
|
|
2020-06-15 13:20:17 -06:00
|
|
|
cl := ast.CompositeLit{
|
|
|
|
Type: expr.Type, // Don't adjust the expr.Type's position.
|
|
|
|
Lbrace: token.Pos(1),
|
|
|
|
Elts: elts,
|
|
|
|
Rbrace: nextLinePos(tok, elts[len(elts)-1].Pos()),
|
|
|
|
}
|
2020-06-09 15:09:06 -06:00
|
|
|
|
2020-06-15 13:20:17 -06:00
|
|
|
var buf bytes.Buffer
|
|
|
|
if err := format.Node(&buf, fset, &cl); err != nil {
|
|
|
|
return
|
|
|
|
}
|
2020-06-12 15:10:06 -06:00
|
|
|
|
|
|
|
msg := "Fill struct with default values"
|
|
|
|
if name, ok := expr.Type.(*ast.Ident); ok {
|
|
|
|
msg = fmt.Sprintf("Fill %s with default values", name)
|
2020-06-09 15:09:06 -06:00
|
|
|
}
|
2020-06-15 13:20:17 -06:00
|
|
|
|
2020-06-12 15:10:06 -06:00
|
|
|
pass.Report(analysis.Diagnostic{
|
|
|
|
Pos: expr.Lbrace,
|
|
|
|
End: expr.Rbrace,
|
|
|
|
SuggestedFixes: []analysis.SuggestedFix{{
|
|
|
|
Message: msg,
|
|
|
|
TextEdits: []analysis.TextEdit{{
|
2020-06-15 13:20:17 -06:00
|
|
|
Pos: expr.Pos(),
|
|
|
|
End: expr.End(),
|
|
|
|
NewText: buf.Bytes(),
|
2020-06-12 15:10:06 -06:00
|
|
|
}},
|
|
|
|
}},
|
|
|
|
})
|
2020-06-09 15:09:06 -06:00
|
|
|
})
|
|
|
|
return nil, nil
|
|
|
|
}
|
2020-06-15 13:20:17 -06:00
|
|
|
|
|
|
|
func nextLinePos(tok *token.File, pos token.Pos) token.Pos {
|
|
|
|
line := tok.Line(pos)
|
|
|
|
if line+1 > tok.LineCount() {
|
|
|
|
tok.AddLine(tok.Offset(pos) + 1)
|
|
|
|
}
|
|
|
|
return tok.LineStart(line + 1)
|
|
|
|
}
|