-
Notifications
You must be signed in to change notification settings - Fork 28
/
Copy pathreference_processor.go
209 lines (174 loc) · 6.46 KB
/
reference_processor.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
/*
Copyright 2021 The Crossplane Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package method
import (
"go/types"
"regexp"
"strings"
"github.com/dave/jennifer/jen"
"github.com/pkg/errors"
"github.com/crossplane/crossplane-tools/internal/comments"
)
// Comment markers used by ReferenceProcessor
const (
ReferenceTypeMarker = "crossplane:generate:reference:type"
ReferenceExtractorMarker = "crossplane:generate:reference:extractor"
ReferenceReferenceFieldNameMarker = "crossplane:generate:reference:refFieldName"
ReferenceSelectorFieldNameMarker = "crossplane:generate:reference:selectorFieldName"
)
var (
regexFunctionCall = regexp.MustCompile(`((.+)\.)?([^.]+\(.*\))`)
)
// Reference is the internal representation that has enough information to let
// us generate the resolver.
type Reference struct {
// RemoteType represents the type whose reference we're holding.
RemoteType *jen.Statement
// Extractor is the function call of the function that will take referenced
// instance and return a string or []string to be set as value.
Extractor *jen.Statement
// RemoteListType is the list type of the type whose reference we're holding.
RemoteListType *jen.Statement
// GoValueFieldPath is the list of fields that needs to be traveled to access
// the current value field. It may include prefixes like [] for array fields,
// * for pointer fields or []* for array of pointer fields.
GoValueFieldPath []string
// GoRefFieldName is the name of the field whose type is *xpv1.Reference or
// []xpv1.Reference.
GoRefFieldName string
// GoSelectorFieldName is the name of the field whose type is *xpv1.Selector
GoSelectorFieldName string
// IsSlice tells whether the current value type is a slice kind.
IsSlice bool
// IsPointer tells whether the current value type is a pointer kind.
IsPointer bool
// IsFloatPointer tells whether the current value pointer is of type float64
IsFloatPointer bool
// IsIntPointer tells whether the current value pointer is of type int
IsIntPointer bool
}
// ReferenceProcessorOption is used to configure ReferenceProcessor.
type ReferenceProcessorOption func(*ReferenceProcessor)
// WithDefaultExtractor returns an option that sets the extractor to given
// call.
func WithDefaultExtractor(ext *jen.Statement) ReferenceProcessorOption {
return func(rp *ReferenceProcessor) {
rp.DefaultExtractor = ext
}
}
// NewReferenceProcessor returns a new *ReferenceProcessor .
func NewReferenceProcessor(receiver string, opts ...ReferenceProcessorOption) *ReferenceProcessor {
rp := &ReferenceProcessor{
Receiver: receiver,
}
for _, f := range opts {
f(rp)
}
return rp
}
// ReferenceProcessor detects whether the field is marked as referencer and
// composes the internal representation of that reference.
type ReferenceProcessor struct {
// DefaultExtractor is used when the extractor is not overridden.
DefaultExtractor *jen.Statement
// Receiver is prepended to all field paths.
Receiver string
refs []Reference
}
// Process stores the reference information of the given field, if any.
func (rp *ReferenceProcessor) Process(_ *types.Named, f *types.Var, _, comment string, parentFields ...string) error {
markers := comments.ParseMarkers(comment)
refTypeValues := markers[ReferenceTypeMarker]
if len(refTypeValues) == 0 {
return nil
}
refType := refTypeValues[0]
isPointer := false
isList := false
refFieldName := f.Name() + "Ref"
// We don't support *[]string.
switch t := f.Type().(type) {
// *string|*float64
case *types.Pointer:
isPointer = true
// []string|[]float64.
case *types.Slice:
isList = true
refFieldName = f.Name() + "Refs"
// []*string|[]*float64
if _, ok := t.Elem().(*types.Pointer); ok {
isPointer = true
}
}
isFloatPointer := strings.HasSuffix(f.Type().String(), "*float64")
isIntPointer := strings.HasSuffix(f.Type().String(), "*int64")
extractorPath := rp.DefaultExtractor
if values, ok := markers[ReferenceExtractorMarker]; ok {
var err error
extractorPath, err = getFuncCodeFromPath(values[0])
if err != nil {
return errors.Wrapf(err, "cannot get extractor function")
}
}
if values, ok := markers[ReferenceReferenceFieldNameMarker]; ok {
refFieldName = values[0]
}
selectorFieldName := f.Name() + "Selector"
if values, ok := markers[ReferenceSelectorFieldNameMarker]; ok {
selectorFieldName = values[0]
}
path := append([]string{rp.Receiver}, parentFields...)
rp.refs = append(rp.refs, Reference{
RemoteType: getTypeCodeFromPath(refType),
RemoteListType: getTypeCodeFromPath(refType, "List"),
Extractor: extractorPath,
GoValueFieldPath: append(path, f.Name()),
GoRefFieldName: refFieldName,
GoSelectorFieldName: selectorFieldName,
IsPointer: isPointer,
IsSlice: isList,
IsFloatPointer: isFloatPointer,
IsIntPointer: isIntPointer,
})
return nil
}
// GetReferences returns all the references accumulated so far from processing.
func (rp *ReferenceProcessor) GetReferences() []Reference {
return rp.refs
}
func getTypeCodeFromPath(path string, nameSuffix ...string) *jen.Statement {
words := strings.Split(path, ".")
if len(words) == 1 {
return jen.Op("&").Id(path + strings.Join(nameSuffix, "")).Values()
}
name := words[len(words)-1] + strings.Join(nameSuffix, "")
pkg := strings.TrimSuffix(path, "."+words[len(words)-1])
return jen.Op("&").Qual(pkg, name).Values()
}
func getFuncCodeFromPath(path string) (*jen.Statement, error) {
parts := regexFunctionCall.FindStringSubmatch(path)
// we have a total of four groups in the regular expression so if
// we do not have four parts, then we cannot handle the reference expression
// Examples paths are:
// github.com/upbound/upjet/pkg/resource.ExtractParamPath("a.b.c",true)
// ExtractParamPath("a.b.c",true)
// ExtractParamPath("a", false)
// ExtractParamPath()
if len(parts) != 4 {
return nil, errors.Errorf("path %q is not a valid function code", path)
}
if len(parts[1]) == 0 {
return jen.Id(parts[3]), nil
}
return jen.Qual(parts[2], parts[3]), nil
}