/
struct_info.go
163 lines (136 loc) · 4.58 KB
/
struct_info.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
package distiller
import (
"fmt"
"go/ast"
"strings"
"github.com/modulo-srl/mu-config/go2cfg/ordered"
"golang.org/x/tools/go/packages"
)
// StructInfo holds information about a struct.
type StructInfo struct {
Package *packages.Package // Package of which the structure is part
Name string // Struct name.
Doc string // Documentation content if present.
Fields []*FieldInfo // Struct fields.
// If a function with following signature:
//
// func <StructName>Defaults() *StructName
//
// is found on the same package where the struct is declared
// it will be parsed to extract default values for all fields.
Defaults map[string]interface{} // Map of defaults values for struct fields.
}
func (s *StructInfo) String() string {
return fmt.Sprintf(
"Package: %s\nName: \"%s\"\nFieldCount: %v\nFields:\n%s\nDoc: \"%v\"\nDefaults: %+v",
s.Package.PkgPath, s.Name, len(s.Fields), s.Fields,
strings.ReplaceAll(s.Doc, "\n", "\\n"), s.Defaults,
)
}
// NewStructInfo creates a new struct information object from given abstract syntax tree type spec
// and types info read on package loading.
func NewStructInfo(genDecl *ast.GenDecl, pkg *packages.Package) *StructInfo {
typeSpec := genDecl.Specs[0].(*ast.TypeSpec)
structType := typeSpec.Type.(*ast.StructType)
info := &StructInfo{
Package: pkg,
Name: typeSpec.Name.Name,
Doc: genDecl.Doc.Text() + typeSpec.Doc.Text() + typeSpec.Comment.Text(),
}
for _, field := range structType.Fields.List {
f := NewFieldInfo(field, info.Package)
info.Fields = append(info.Fields, f)
}
return info
}
// FormatDoc formats the struct documentation indenting it with passed indent string.
func (s *StructInfo) FormatDoc(indent string) string {
commentPrefix := indent + "// "
d := strings.ReplaceAll(s.Doc, "\n", "\n"+commentPrefix)
return commentPrefix + d[:len(d)-len(commentPrefix)]
}
// ParseDefaultsMethod parses the Defaults method of this struct populating Defaults map.
func (s *StructInfo) ParseDefaultsMethod() error {
typePath := s.Package.PkgPath + "." + s.Name
for _, astFile := range s.Package.Syntax {
for _, decl := range astFile.Decls {
funcDecl, ok := decl.(*ast.FuncDecl)
if !ok || funcDecl.Name.Name != s.Name+"Defaults" {
continue
}
if funcDecl.Recv != nil || funcDecl.Type.Results.NumFields() != 1 ||
s.Package.TypesInfo.Types[funcDecl.Type.Results.List[0].Type].Type.String() != "*"+typePath {
return fmt.Errorf("invalid defaults method signature.\n"+
"expected: func %sDefaults() *%s\n"+
"got: func %s() %s",
s.Name, typePath, funcDecl.Name.Name,
s.Package.TypesInfo.Types[funcDecl.Type.Results.List[0].Type].Type.String())
}
var defaults interface{}
ast.Inspect(funcDecl, func(node ast.Node) bool {
switch n := node.(type) {
case *ast.CompositeLit:
var ident *ast.Ident
if ident, ok = n.Type.(*ast.Ident); ok && ident.Name == s.Name {
defaults = s.parseDefaultsMethodBody(n)
// Stop traversing.
return false
}
}
// Continue traversing.
return true
})
s.Defaults = defaults.(map[string]interface{})
}
}
return nil
}
// ParseDefaultsMethod parses recursively the composite literals of Defaults method. It returns a map of
// fields names-values or an array of values.
func (s *StructInfo) parseDefaultsMethodBody(lit *ast.CompositeLit) interface{} {
var values interface{}
isOrdered := false
switch lit.Type.(type) {
case *ast.ArrayType:
values = make([]interface{}, 0)
case *ast.MapType:
// Uses an ordered map to ensure consistent and sorted output according
// to the order in which the default values are defined.
isOrdered = true
values = ordered.NewMap()
default:
values = make(map[string]interface{})
}
for _, elt := range lit.Elts {
switch el := elt.(type) {
case *ast.KeyValueExpr:
// values is a key-value pair.
var key string
switch k := el.Key.(type) {
case *ast.Ident:
key = k.Name
case *ast.BasicLit:
key = k.Value
}
var value interface{}
switch val := el.Value.(type) {
case *ast.CompositeLit:
value = s.parseDefaultsMethodBody(val)
default:
value = s.Package.TypesInfo.Types[val].Value
}
if isOrdered {
values.(*ordered.Map).Append(key, value)
} else {
values.(map[string]interface{})[key] = value
}
case *ast.CompositeLit:
// values is an array of interfaces.
values = append(values.([]interface{}), s.parseDefaultsMethodBody(el))
default:
// values is an array of interfaces.
values = append(values.([]interface{}), s.Package.TypesInfo.Types[el].Value)
}
}
return values
}