forked from switchupcb/copygen
-
Notifications
You must be signed in to change notification settings - Fork 0
/
field.go
216 lines (184 loc) · 6.67 KB
/
field.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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
package parser
import (
"fmt"
"go/types"
"strings"
"github.com/fatih/structtag"
"github.com/reedom/copygen/cli/models"
)
// parseField parses a types.Type into a *models.Field recursively.
func parseField(typ types.Type) *models.Field {
if cached, ok := fieldcache[typ.String()]; ok {
return cached
}
field := new(models.Field)
switch x := typ.(type) {
// Named Types (Alias)
// https://go.googlesource.com/example/+/HEAD/gotypes#named-types
case *types.Named:
// A named type is either:
// 1. an alias (i.e `Placeholder` in `type Placeholder bool`)
// 2. a struct (i.e `Account` in `type Account struct`)
// 3. an interface (i.e `error` in `type error interface`)
// 4. a collected type (i.e `domain.Account` in `[]domain.Account`)
//
// Underlying named types are only important in case 2,
// when we need to parse extra information from the field.
if xs, ok := x.Underlying().(*types.Struct); ok {
// set the cache early to prevent issues with named cyclic structs.
fieldcache[x.String()] = field
structfield := parseField(xs)
field.Fields = structfield.Fields
}
field.Definition = x.Obj().Name()
setFieldImportAndPackage(field, x.Obj().Pkg())
// Basic Types
// https://go.googlesource.com/example/+/HEAD/gotypes#basic-types
case *types.Basic:
field.Definition = x.Name()
// Simple Composite Types
// https://go.googlesource.com/example/+/HEAD/gotypes#simple-composite-types
case *types.Pointer:
elemfield := parseField(x.Elem())
// type aliases (including structs) must be deepcopied
// in order to match underlying fields.
if elemfield.IsAlias() {
deepfield := elemfield.Deepcopy(nil)
field.Fields = deepfield.Fields
}
field.Definition = models.CollectionPointer + collectedDefinition(elemfield)
field.VariableName = "." + alphastring(elemfield.Definition)
case *types.Array:
field.Definition = "[" + fmt.Sprint(x.Len()) + "]" + collectedDefinition(parseField(x.Elem()))
case *types.Slice:
field.Definition = models.CollectionSlice + collectedDefinition(parseField(x.Elem()))
case *types.Map:
field.Definition = models.CollectionMap + "[" + collectedDefinition(parseField(x.Key())) + "]" + collectedDefinition(parseField(x.Elem()))
case *types.Chan:
field.Definition = models.CollectionChan + " " + collectedDefinition(parseField(x.Elem()))
// Function (without Receivers)
// https://go.googlesource.com/example/+/HEAD/gotypes#function-and-method-types
case *types.Signature:
var definition strings.Builder
// set the parameters.
definition.WriteString(models.CollectionFunc + "(")
for i := 0; i < x.Params().Len(); i++ {
definition.WriteString(collectedDefinition(parseField(x.Params().At(i).Type())))
if i+1 != x.Params().Len() {
definition.WriteString(", ")
}
}
definition.WriteString(")")
// set the results.
if x.Results().Len() >= 1 {
definition.WriteString(" ")
}
if x.Results().Len() > 1 {
definition.WriteString("(")
}
for i := 0; i < x.Results().Len(); i++ {
definition.WriteString(collectedDefinition(parseField(x.Results().At(i).Type())))
if i+1 != x.Results().Len() {
definition.WriteString(", ")
}
}
if x.Results().Len() > 1 {
definition.WriteString(")")
}
field.Definition = definition.String()
// Interface Types
// https://go.googlesource.com/example/+/HEAD/gotypes#interface-types
case *types.Interface:
if x.Empty() {
field.Definition = x.String()
} else {
var definition strings.Builder
definition.WriteString(models.CollectionInterface + "{")
for i := 0; i < x.NumMethods(); i++ {
definition.WriteString(collectedDefinition(parseField(x.Method(i).Type())) + "; ")
}
definition.WriteString("}")
field.Definition = definition.String()
}
// Struct Types
// https://go.googlesource.com/example/+/HEAD/gotypes#struct-types
case *types.Struct:
var definition strings.Builder
definition.WriteString("struct{")
for i := 0; i < x.NumFields(); i++ {
// a deepcopy of subfield is returned and modified.
subfield := parseField(x.Field(i).Type()).Deepcopy(nil)
subfield.VariableName = "." + x.Field(i).Name()
subfield.Name = x.Field(i).Name()
setTags(subfield, x.Tag(i))
subfield.Parent = field
field.Fields = append(field.Fields, subfield)
definition.WriteString(subfield.Name + " " + subfield.FullDefinition() + "; ")
// all subfields are deepcopied with Fields[0].
//
// in order to correctly represent a deepcopied struct field,
// we must point its fields back to the cached field.Fields,
// which will eventually be filled.
//
// cachedsubfield.Fields are never modified.
if cachedsubfield, ok := fieldcache[x.Field(i).String()]; ok {
subfield.Fields = cachedsubfield.Fields
}
}
definition.WriteString("}")
field.Definition = definition.String()
default:
fmt.Printf("WARNING: could not parse type %v\n", x.String())
}
// do NOT cache collections.
if !field.IsCollection() {
fieldcache[typ.String()] = field
}
return field
}
// setFieldImportAndPackage sets the import and package of a field.
func setFieldImportAndPackage(field *models.Field, pkg *types.Package) {
if pkg == nil {
return
}
field.Import = pkg.Path()
field.Package = pkg.Name()
}
// setTags sets the tags for a field.
func setTags(field *models.Field, rawtag string) {
// rawtag represents tags as they are defined (i.e `api:"id", json:"tag"`).
tags, err := structtag.Parse(rawtag)
if err != nil {
fmt.Printf("WARNING: could not parse tag for field %v\n%v", field.FullName(), err)
}
field.Tags = make(map[string]map[string][]string, tags.Len())
for _, tag := range tags.Tags() {
field.Tags[tag.Key] = map[string][]string{
tag.Name: tag.Options,
}
}
}
// collectedDefinition determines the full definition for a collected type in a collection.
//
// collectedDefinition can be called in the parser, but ONLY because collections are NOT cached.
func collectedDefinition(collected *models.Field) string {
// a generated file's package == setup file's package.
//
// when the field is defined in the setup file (i.e `Collection`),
// it will be parsed with the setup file's package (i.e `copygen.Collection`).
//
// do NOT reference it by package in the generated file (i.e `Collection`).
if collected.Import == setupPkgPath {
return collected.Definition
}
// when a setup file imports the package it will output to,
// do NOT reference the fields defined in the output package, by package.
if outputPkgPath != "" && collected.Import == outputPkgPath {
return collected.Definition
}
// when a field's import uses an alias, reassign the package reference.
if aliasPkg, ok := aliasImportMap[collected.Import]; ok {
return aliasPkg + "." + collected.Definition
}
return collected.FullDefinition()
}