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+`)