mirror of
https://github.com/golang/go
synced 2024-11-06 02:26:17 -07:00
146 lines
3.2 KiB
Go
146 lines
3.2 KiB
Go
|
// 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"
|
||
|
"go/ast"
|
||
|
"go/format"
|
||
|
"go/types"
|
||
|
"strings"
|
||
|
|
||
|
"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
|
||
|
uninitialized fields of a struct. For example, given the following struct:
|
||
|
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)
|
||
|
|
||
|
// TODO: Handle partially-filled structs as well.
|
||
|
if len(expr.Elts) != 0 {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
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()
|
||
|
|
||
|
if typ == nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
switch obj := typ.(type) {
|
||
|
case *types.Struct:
|
||
|
fieldCount := obj.NumFields()
|
||
|
if fieldCount == 0 {
|
||
|
return
|
||
|
}
|
||
|
var fieldSourceCode strings.Builder
|
||
|
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
|
||
|
}
|
||
|
|
||
|
label := field.Name()
|
||
|
value := analysisinternal.ZeroValue(pass.Fset, file, pass.Pkg, field.Type())
|
||
|
if value == nil {
|
||
|
continue
|
||
|
}
|
||
|
var valBuf bytes.Buffer
|
||
|
if err := format.Node(&valBuf, pass.Fset, value); err != nil {
|
||
|
return
|
||
|
}
|
||
|
fieldSourceCode.WriteString("\n")
|
||
|
fieldSourceCode.WriteString(label)
|
||
|
fieldSourceCode.WriteString(" : ")
|
||
|
fieldSourceCode.WriteString(valBuf.String())
|
||
|
fieldSourceCode.WriteString(",")
|
||
|
}
|
||
|
|
||
|
if fieldSourceCode.Len() == 0 {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
fieldSourceCode.WriteString("\n")
|
||
|
|
||
|
buf := []byte(fieldSourceCode.String())
|
||
|
|
||
|
pass.Report(analysis.Diagnostic{
|
||
|
Pos: expr.Lbrace,
|
||
|
End: expr.Rbrace,
|
||
|
SuggestedFixes: []analysis.SuggestedFix{{
|
||
|
Message: "Fill struct with empty values",
|
||
|
TextEdits: []analysis.TextEdit{{
|
||
|
Pos: expr.Lbrace + 1,
|
||
|
End: expr.Rbrace,
|
||
|
NewText: buf,
|
||
|
}},
|
||
|
}},
|
||
|
})
|
||
|
}
|
||
|
})
|
||
|
return nil, nil
|
||
|
}
|