blob: d700a00a53ea3280f4f3007e21288230fc33f529 [file] [log] [blame]
Matthias Andreas Benkard832a54e2019-01-29 09:27:38 +01001/*
2Copyright 2015 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package namer
18
19import (
20 "path/filepath"
21 "strings"
22
23 "k8s.io/gengo/types"
24)
25
26const (
27 // GoSeperator is used to split go import paths.
28 // Forward slash is used instead of filepath.Seperator because it is the
29 // only universally-accepted path delimiter and the only delimiter not
30 // potentially forbidden by Go compilers. (In particular gc does not allow
31 // the use of backslashes in import paths.)
32 // See https://golang.org/ref/spec#Import_declarations.
33 // See also https://github.com/kubernetes/gengo/issues/83#issuecomment-367040772.
34 GoSeperator = "/"
35)
36
37// Returns whether a name is a private Go name.
38func IsPrivateGoName(name string) bool {
39 return len(name) == 0 || strings.ToLower(name[:1]) == name[:1]
40}
41
42// NewPublicNamer is a helper function that returns a namer that makes
43// CamelCase names. See the NameStrategy struct for an explanation of the
44// arguments to this constructor.
45func NewPublicNamer(prependPackageNames int, ignoreWords ...string) *NameStrategy {
46 n := &NameStrategy{
47 Join: Joiner(IC, IC),
48 IgnoreWords: map[string]bool{},
49 PrependPackageNames: prependPackageNames,
50 }
51 for _, w := range ignoreWords {
52 n.IgnoreWords[w] = true
53 }
54 return n
55}
56
57// NewPrivateNamer is a helper function that returns a namer that makes
58// camelCase names. See the NameStrategy struct for an explanation of the
59// arguments to this constructor.
60func NewPrivateNamer(prependPackageNames int, ignoreWords ...string) *NameStrategy {
61 n := &NameStrategy{
62 Join: Joiner(IL, IC),
63 IgnoreWords: map[string]bool{},
64 PrependPackageNames: prependPackageNames,
65 }
66 for _, w := range ignoreWords {
67 n.IgnoreWords[w] = true
68 }
69 return n
70}
71
72// NewRawNamer will return a Namer that makes a name by which you would
73// directly refer to a type, optionally keeping track of the import paths
74// necessary to reference the names it provides. Tracker may be nil.
75// The 'pkg' is the full package name, in which the Namer is used - all
76// types from that package will be referenced by just type name without
77// referencing the package.
78//
79// For example, if the type is map[string]int, a raw namer will literally
80// return "map[string]int".
81//
82// Or if the type, in package foo, is "type Bar struct { ... }", then the raw
83// namer will return "foo.Bar" as the name of the type, and if 'tracker' was
84// not nil, will record that package foo needs to be imported.
85func NewRawNamer(pkg string, tracker ImportTracker) *rawNamer {
86 return &rawNamer{pkg: pkg, tracker: tracker}
87}
88
89// Names is a map from Type to name, as defined by some Namer.
90type Names map[*types.Type]string
91
92// Namer takes a type, and assigns a name.
93//
94// The purpose of this complexity is so that you can assign coherent
95// side-by-side systems of names for the types. For example, you might want a
96// public interface, a private implementation struct, and also to reference
97// literally the type name.
98//
99// Note that it is safe to call your own Name() function recursively to find
100// the names of keys, elements, etc. This is because anonymous types can't have
101// cycles in their names, and named types don't require the sort of recursion
102// that would be problematic.
103type Namer interface {
104 Name(*types.Type) string
105}
106
107// NameSystems is a map of a system name to a namer for that system.
108type NameSystems map[string]Namer
109
110// NameStrategy is a general Namer. The easiest way to use it is to copy the
111// Public/PrivateNamer variables, and modify the members you wish to change.
112//
113// The Name method produces a name for the given type, of the forms:
114// Anonymous types: <Prefix><Type description><Suffix>
115// Named types: <Prefix><Optional Prepended Package name(s)><Original name><Suffix>
116//
117// In all cases, every part of the name is run through the capitalization
118// functions.
119//
120// The IgnoreWords map can be set if you have directory names that are
121// semantically meaningless for naming purposes, e.g. "proto".
122//
123// Prefix and Suffix can be used to disambiguate parallel systems of type
124// names. For example, if you want to generate an interface and an
125// implementation, you might want to suffix one with "Interface" and the other
126// with "Implementation". Another common use-- if you want to generate private
127// types, and one of your source types could be "string", you can't use the
128// default lowercase private namer. You'll have to add a suffix or prefix.
129type NameStrategy struct {
130 Prefix, Suffix string
131 Join func(pre string, parts []string, post string) string
132
133 // Add non-meaningful package directory names here (e.g. "proto") and
134 // they will be ignored.
135 IgnoreWords map[string]bool
136
137 // If > 0, prepend exactly that many package directory names (or as
138 // many as there are). Package names listed in "IgnoreWords" will be
139 // ignored.
140 //
141 // For example, if Ignore words lists "proto" and type Foo is in
142 // pkg/server/frobbing/proto, then a value of 1 will give a type name
143 // of FrobbingFoo, 2 gives ServerFrobbingFoo, etc.
144 PrependPackageNames int
145
146 // A cache of names thus far assigned by this namer.
147 Names
148}
149
150// IC ensures the first character is uppercase.
151func IC(in string) string {
152 if in == "" {
153 return in
154 }
155 return strings.ToUpper(in[:1]) + in[1:]
156}
157
158// IL ensures the first character is lowercase.
159func IL(in string) string {
160 if in == "" {
161 return in
162 }
163 return strings.ToLower(in[:1]) + in[1:]
164}
165
166// Joiner lets you specify functions that preprocess the various components of
167// a name before joining them. You can construct e.g. camelCase or CamelCase or
168// any other way of joining words. (See the IC and IL convenience functions.)
169func Joiner(first, others func(string) string) func(pre string, in []string, post string) string {
170 return func(pre string, in []string, post string) string {
171 tmp := []string{others(pre)}
172 for i := range in {
173 tmp = append(tmp, others(in[i]))
174 }
175 tmp = append(tmp, others(post))
176 return first(strings.Join(tmp, ""))
177 }
178}
179
180func (ns *NameStrategy) removePrefixAndSuffix(s string) string {
181 // The join function may have changed capitalization.
182 lowerIn := strings.ToLower(s)
183 lowerP := strings.ToLower(ns.Prefix)
184 lowerS := strings.ToLower(ns.Suffix)
185 b, e := 0, len(s)
186 if strings.HasPrefix(lowerIn, lowerP) {
187 b = len(ns.Prefix)
188 }
189 if strings.HasSuffix(lowerIn, lowerS) {
190 e -= len(ns.Suffix)
191 }
192 return s[b:e]
193}
194
195var (
196 importPathNameSanitizer = strings.NewReplacer("-", "_", ".", "")
197)
198
199// filters out unwanted directory names and sanitizes remaining names.
200func (ns *NameStrategy) filterDirs(path string) []string {
201 allDirs := strings.Split(path, GoSeperator)
202 dirs := make([]string, 0, len(allDirs))
203 for _, p := range allDirs {
204 if ns.IgnoreWords == nil || !ns.IgnoreWords[p] {
205 dirs = append(dirs, importPathNameSanitizer.Replace(p))
206 }
207 }
208 return dirs
209}
210
211// See the comment on NameStrategy.
212func (ns *NameStrategy) Name(t *types.Type) string {
213 if ns.Names == nil {
214 ns.Names = Names{}
215 }
216 if s, ok := ns.Names[t]; ok {
217 return s
218 }
219
220 if t.Name.Package != "" {
221 dirs := append(ns.filterDirs(t.Name.Package), t.Name.Name)
222 i := ns.PrependPackageNames + 1
223 dn := len(dirs)
224 if i > dn {
225 i = dn
226 }
227 name := ns.Join(ns.Prefix, dirs[dn-i:], ns.Suffix)
228 ns.Names[t] = name
229 return name
230 }
231
232 // Only anonymous types remain.
233 var name string
234 switch t.Kind {
235 case types.Builtin:
236 name = ns.Join(ns.Prefix, []string{t.Name.Name}, ns.Suffix)
237 case types.Map:
238 name = ns.Join(ns.Prefix, []string{
239 "Map",
240 ns.removePrefixAndSuffix(ns.Name(t.Key)),
241 "To",
242 ns.removePrefixAndSuffix(ns.Name(t.Elem)),
243 }, ns.Suffix)
244 case types.Slice:
245 name = ns.Join(ns.Prefix, []string{
246 "Slice",
247 ns.removePrefixAndSuffix(ns.Name(t.Elem)),
248 }, ns.Suffix)
249 case types.Pointer:
250 name = ns.Join(ns.Prefix, []string{
251 "Pointer",
252 ns.removePrefixAndSuffix(ns.Name(t.Elem)),
253 }, ns.Suffix)
254 case types.Struct:
255 names := []string{"Struct"}
256 for _, m := range t.Members {
257 names = append(names, ns.removePrefixAndSuffix(ns.Name(m.Type)))
258 }
259 name = ns.Join(ns.Prefix, names, ns.Suffix)
260 case types.Chan:
261 name = ns.Join(ns.Prefix, []string{
262 "Chan",
263 ns.removePrefixAndSuffix(ns.Name(t.Elem)),
264 }, ns.Suffix)
265 case types.Interface:
266 // TODO: add to name test
267 names := []string{"Interface"}
268 for _, m := range t.Methods {
269 // TODO: include function signature
270 names = append(names, m.Name.Name)
271 }
272 name = ns.Join(ns.Prefix, names, ns.Suffix)
273 case types.Func:
274 // TODO: add to name test
275 parts := []string{"Func"}
276 for _, pt := range t.Signature.Parameters {
277 parts = append(parts, ns.removePrefixAndSuffix(ns.Name(pt)))
278 }
279 parts = append(parts, "Returns")
280 for _, rt := range t.Signature.Results {
281 parts = append(parts, ns.removePrefixAndSuffix(ns.Name(rt)))
282 }
283 name = ns.Join(ns.Prefix, parts, ns.Suffix)
284 default:
285 name = "unnameable_" + string(t.Kind)
286 }
287 ns.Names[t] = name
288 return name
289}
290
291// ImportTracker allows a raw namer to keep track of the packages needed for
292// import. You can implement yourself or use the one in the generation package.
293type ImportTracker interface {
294 AddType(*types.Type)
295 LocalNameOf(packagePath string) string
296 PathOf(localName string) (string, bool)
297 ImportLines() []string
298}
299
300type rawNamer struct {
301 pkg string
302 tracker ImportTracker
303 Names
304}
305
306// Name makes a name the way you'd write it to literally refer to type t,
307// making ordinary assumptions about how you've imported t's package (or using
308// r.tracker to specifically track the package imports).
309func (r *rawNamer) Name(t *types.Type) string {
310 if r.Names == nil {
311 r.Names = Names{}
312 }
313 if name, ok := r.Names[t]; ok {
314 return name
315 }
316 if t.Name.Package != "" {
317 var name string
318 if r.tracker != nil {
319 r.tracker.AddType(t)
320 if t.Name.Package == r.pkg {
321 name = t.Name.Name
322 } else {
323 name = r.tracker.LocalNameOf(t.Name.Package) + "." + t.Name.Name
324 }
325 } else {
326 if t.Name.Package == r.pkg {
327 name = t.Name.Name
328 } else {
329 name = filepath.Base(t.Name.Package) + "." + t.Name.Name
330 }
331 }
332 r.Names[t] = name
333 return name
334 }
335 var name string
336 switch t.Kind {
337 case types.Builtin:
338 name = t.Name.Name
339 case types.Map:
340 name = "map[" + r.Name(t.Key) + "]" + r.Name(t.Elem)
341 case types.Slice:
342 name = "[]" + r.Name(t.Elem)
343 case types.Pointer:
344 name = "*" + r.Name(t.Elem)
345 case types.Struct:
346 elems := []string{}
347 for _, m := range t.Members {
348 elems = append(elems, m.Name+" "+r.Name(m.Type))
349 }
350 name = "struct{" + strings.Join(elems, "; ") + "}"
351 case types.Chan:
352 // TODO: include directionality
353 name = "chan " + r.Name(t.Elem)
354 case types.Interface:
355 // TODO: add to name test
356 elems := []string{}
357 for _, m := range t.Methods {
358 // TODO: include function signature
359 elems = append(elems, m.Name.Name)
360 }
361 name = "interface{" + strings.Join(elems, "; ") + "}"
362 case types.Func:
363 // TODO: add to name test
364 params := []string{}
365 for _, pt := range t.Signature.Parameters {
366 params = append(params, r.Name(pt))
367 }
368 results := []string{}
369 for _, rt := range t.Signature.Results {
370 results = append(results, r.Name(rt))
371 }
372 name = "func(" + strings.Join(params, ",") + ")"
373 if len(results) == 1 {
374 name += " " + results[0]
375 } else if len(results) > 1 {
376 name += " (" + strings.Join(results, ",") + ")"
377 }
378 default:
379 name = "unnameable_" + string(t.Kind)
380 }
381 r.Names[t] = name
382 return name
383}