k8s.io/kube-openapi@v0.0.0-20240228011516-70dd3763d340/pkg/generators/enum.go (about) 1 /* 2 Copyright 2021 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package generators 18 19 import ( 20 "fmt" 21 "regexp" 22 "sort" 23 "strings" 24 25 "k8s.io/gengo/v2" 26 "k8s.io/gengo/v2/generator" 27 "k8s.io/gengo/v2/types" 28 ) 29 30 const tagEnumType = "enum" 31 const enumTypeDescriptionHeader = "Possible enum values:" 32 33 type enumValue struct { 34 Name string 35 Value string 36 Comment string 37 } 38 39 type enumType struct { 40 Name types.Name 41 Values []*enumValue 42 } 43 44 // enumMap is a map from the name to the matching enum type. 45 type enumMap map[types.Name]*enumType 46 47 type enumContext struct { 48 enumTypes enumMap 49 } 50 51 func newEnumContext(c *generator.Context) *enumContext { 52 return &enumContext{enumTypes: parseEnums(c)} 53 } 54 55 // EnumType checks and finds the enumType for a given type. 56 // If the given type is a known enum type, returns the enumType, true 57 // Otherwise, returns nil, false 58 func (ec *enumContext) EnumType(t *types.Type) (enum *enumType, isEnum bool) { 59 // if t is a pointer, use its underlying type instead 60 if t.Kind == types.Pointer { 61 t = t.Elem 62 } 63 enum, ok := ec.enumTypes[t.Name] 64 return enum, ok 65 } 66 67 // ValueStrings returns all possible values of the enum type as strings 68 // the results are sorted and quoted as Go literals. 69 func (et *enumType) ValueStrings() []string { 70 var values []string 71 for _, value := range et.Values { 72 // use "%q" format to generate a Go literal of the string const value 73 values = append(values, fmt.Sprintf("%q", value.Value)) 74 } 75 sort.Strings(values) 76 return values 77 } 78 79 // DescriptionLines returns a description of the enum in this format: 80 // 81 // Possible enum values: 82 // - `"value1"` description 1 83 // - `"value2"` description 2 84 func (et *enumType) DescriptionLines() []string { 85 if len(et.Values) == 0 { 86 return nil 87 } 88 var lines []string 89 for _, value := range et.Values { 90 lines = append(lines, value.Description()) 91 } 92 sort.Strings(lines) 93 // Prepend an empty string to initiate a new paragraph. 94 return append([]string{"", enumTypeDescriptionHeader}, lines...) 95 } 96 97 func parseEnums(c *generator.Context) enumMap { 98 // First, find the builtin "string" type 99 stringType := c.Universe.Type(types.Name{Name: "string"}) 100 101 // find all enum types. 102 enumTypes := make(enumMap) 103 for _, p := range c.Universe { 104 for _, t := range p.Types { 105 if isEnumType(stringType, t) { 106 if _, ok := enumTypes[t.Name]; !ok { 107 enumTypes[t.Name] = &enumType{ 108 Name: t.Name, 109 } 110 } 111 } 112 } 113 } 114 115 // find all enum values from constants, and try to match each with its type. 116 for _, p := range c.Universe { 117 for _, c := range p.Constants { 118 enumType := c.Underlying 119 if _, ok := enumTypes[enumType.Name]; ok { 120 value := &enumValue{ 121 Name: c.Name.Name, 122 Value: *c.ConstValue, 123 Comment: strings.Join(c.CommentLines, " "), 124 } 125 enumTypes[enumType.Name].addIfNotPresent(value) 126 } 127 } 128 } 129 130 return enumTypes 131 } 132 133 func (et *enumType) addIfNotPresent(value *enumValue) { 134 // If we already have an enum case with the same value, then ignore this new 135 // one. This can happen if an enum aliases one from another package and 136 // re-exports the cases. 137 for i, existing := range et.Values { 138 if existing.Value == value.Value { 139 140 // Take the value of the longer comment (or some other deterministic tie breaker) 141 if len(existing.Comment) < len(value.Comment) || (len(existing.Comment) == len(value.Comment) && existing.Comment > value.Comment) { 142 et.Values[i] = value 143 } 144 145 return 146 } 147 } 148 et.Values = append(et.Values, value) 149 } 150 151 // Description returns the description line for the enumValue 152 // with the format: 153 // - `"FooValue"` is the Foo value 154 func (ev *enumValue) Description() string { 155 comment := strings.TrimSpace(ev.Comment) 156 // The comment should starts with the type name, trim it first. 157 comment = strings.TrimPrefix(comment, ev.Name) 158 // Trim the possible space after previous step. 159 comment = strings.TrimSpace(comment) 160 // The comment may be multiline, cascade all consecutive whitespaces. 161 comment = whitespaceRegex.ReplaceAllString(comment, " ") 162 return fmt.Sprintf(" - `%q` %s", ev.Value, comment) 163 } 164 165 // isEnumType checks if a given type is an enum by the definition 166 // An enum type should be an alias of string and has tag '+enum' in its comment. 167 // Additionally, pass the type of builtin 'string' to check against. 168 func isEnumType(stringType *types.Type, t *types.Type) bool { 169 return t.Kind == types.Alias && t.Underlying == stringType && hasEnumTag(t) 170 } 171 172 func hasEnumTag(t *types.Type) bool { 173 return gengo.ExtractCommentTags("+", t.CommentLines)[tagEnumType] != nil 174 } 175 176 // whitespaceRegex is the regex for consecutive whitespaces. 177 var whitespaceRegex = regexp.MustCompile(`\s+`)