forked from goadesign/goa
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathimport.go
157 lines (143 loc) · 4.01 KB
/
import.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
package codegen
import (
"fmt"
"strconv"
"goa.design/goa/v3/expr"
goa "goa.design/goa/v3/pkg"
)
// DesignVersion contains the major component of the version of Goa used to
// author the design - either 2 or 3. This value is initialized when the
// generated tool is invoked by retrieving the information passed on the
// command line by the goa tool.
var DesignVersion = goa.Major
type (
// ImportSpec defines a generated import statement.
ImportSpec struct {
// Name of imported package if needed.
Name string
// Go import path of package.
Path string
}
)
// NewImport creates an import spec.
func NewImport(name, path string) *ImportSpec {
return &ImportSpec{Name: name, Path: path}
}
// SimpleImport creates an import with no explicit path component.
func SimpleImport(path string) *ImportSpec {
return &ImportSpec{Path: path}
}
// GoaImport creates an import for a Goa package.
func GoaImport(rel string) *ImportSpec {
name := ""
if rel == "" {
name = "goa"
rel = "pkg"
}
return GoaNamedImport(rel, name)
}
// GoaNamedImport creates an import for a Goa package with the given name.
func GoaNamedImport(rel, name string) *ImportSpec {
root := "goa.design/goa"
if DesignVersion > 2 {
root += "/v" + strconv.Itoa(DesignVersion)
}
if rel != "" {
rel = "/" + rel
}
return &ImportSpec{Name: name, Path: root + rel}
}
// Code returns the Go import statement for the ImportSpec.
func (s *ImportSpec) Code() string {
if len(s.Name) > 0 {
return fmt.Sprintf(`%s "%s"`, s.Name, s.Path)
}
return fmt.Sprintf(`"%s"`, s.Path)
}
// GetMetaType retrieves the type and package defined by the struct:field:type
// metadata if any.
func GetMetaType(att *expr.AttributeExpr) (typeName string, importS *ImportSpec) {
if att == nil {
return typeName, importS
}
if args, ok := att.Meta["struct:field:type"]; ok {
if len(args) > 0 {
typeName = args[0]
}
if len(args) > 1 {
importS = &ImportSpec{Path: args[1]}
}
if len(args) > 2 {
importS.Name = args[2]
}
}
return typeName, importS
}
// GetMetaTypeImports parses the attribute for all user defined imports
func GetMetaTypeImports(att *expr.AttributeExpr) []*ImportSpec {
return safelyGetMetaTypeImports(att, nil)
}
// safelyGetMetaTypeImports parses attributes while keeping track of previous usertypes to avoid infinite recursion
func safelyGetMetaTypeImports(att *expr.AttributeExpr, seen map[string]struct{}) []*ImportSpec {
if att == nil {
return nil
}
if seen == nil {
seen = make(map[string]struct{})
}
uniqueImports := make(map[ImportSpec]struct{})
imports := make([]*ImportSpec, 0)
switch t := att.Type.(type) {
case expr.UserType:
if _, wasSeen := seen[t.ID()]; wasSeen {
return imports
}
seen[t.ID()] = struct{}{}
for _, im := range safelyGetMetaTypeImports(t.Attribute(), seen) {
if im != nil {
uniqueImports[*im] = struct{}{}
}
}
case *expr.Array:
_, im := GetMetaType(t.ElemType)
if im != nil {
uniqueImports[*im] = struct{}{}
}
case *expr.Map:
_, im := GetMetaType(t.ElemType)
if im != nil {
uniqueImports[*im] = struct{}{}
}
_, im = GetMetaType(t.KeyType)
if im != nil {
uniqueImports[*im] = struct{}{}
}
case *expr.Object:
for _, key := range *t {
if key != nil {
_, im := GetMetaType(key.Attribute)
if im != nil {
uniqueImports[*im] = struct{}{}
}
}
}
}
_, im := GetMetaType(att)
if im != nil {
uniqueImports[*im] = struct{}{}
}
for imp := range uniqueImports {
// Copy loop variable into body so next iteration doesnt overwrite its address https://stackoverflow.com/questions/27610039/golang-appending-leaves-only-last-element
copy := imp
imports = append(imports, ©)
}
return imports
}
// AddServiceMetaTypeImports adds meta type imports for each method of the service expr
func AddServiceMetaTypeImports(header *SectionTemplate, svc *expr.ServiceExpr) {
for _, m := range svc.Methods {
AddImport(header, GetMetaTypeImports(m.Payload)...)
AddImport(header, GetMetaTypeImports(m.StreamingPayload)...)
AddImport(header, GetMetaTypeImports(m.Result)...)
}
}