github.com/oam-dev/kubevela@v1.9.11/references/cuegen/convert.go (about)

     1  /*
     2  Copyright 2023 The KubeVela 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 cuegen
    18  
    19  import (
    20  	"fmt"
    21  	goast "go/ast"
    22  	gotoken "go/token"
    23  	gotypes "go/types"
    24  	"strconv"
    25  	"strings"
    26  
    27  	cueast "cuelang.org/go/cue/ast"
    28  	cuetoken "cuelang.org/go/cue/token"
    29  )
    30  
    31  func (g *Generator) convertDecls(x *goast.GenDecl) (decls []Decl, _ error) {
    32  	// TODO(iyear): currently only support 'type'
    33  	if x.Tok != gotoken.TYPE {
    34  		return decls, nil
    35  	}
    36  
    37  	for _, spec := range x.Specs {
    38  		typeSpec, ok := spec.(*goast.TypeSpec)
    39  		if !ok {
    40  			continue
    41  		}
    42  
    43  		if g.opts.typeFilter != nil && !g.opts.typeFilter(typeSpec) {
    44  			continue
    45  		}
    46  
    47  		// only process struct
    48  		typ := g.pkg.TypesInfo.TypeOf(typeSpec.Name)
    49  
    50  		if err := supportedType(nil, typ); err != nil {
    51  			return nil, fmt.Errorf("unsupported type %s: %w", typeSpec.Name.Name, err)
    52  		}
    53  
    54  		named, ok := typ.(*gotypes.Named)
    55  		if !ok {
    56  			continue
    57  		}
    58  
    59  		switch t := named.Underlying().(type) {
    60  		case *gotypes.Struct:
    61  			lit, err := g.convert(t)
    62  			if err != nil {
    63  				return nil, err
    64  			}
    65  
    66  			decls = append(decls, &Struct{CommonFields: CommonFields{
    67  				Expr: lit,
    68  				Name: typeSpec.Name.Name,
    69  				Doc:  x.Doc,
    70  				Pos:  cuetoken.Newline.Pos(),
    71  			}})
    72  		default:
    73  			continue
    74  		}
    75  	}
    76  
    77  	return decls, nil
    78  }
    79  
    80  func (g *Generator) convert(typ gotypes.Type) (cueast.Expr, error) {
    81  	// if type is registered as special type, use it directly
    82  	if t, ok := g.opts.types[typ.String()]; ok {
    83  		switch t {
    84  		case TypeAny:
    85  			return Ident("_", false), nil
    86  		case TypeEllipsis:
    87  			return &cueast.StructLit{Elts: []cueast.Decl{&cueast.Ellipsis{}}}, nil
    88  		default:
    89  			return nil, fmt.Errorf("unsupported special cue type: %v", t)
    90  		}
    91  	}
    92  
    93  	switch t := typ.(type) {
    94  	case *gotypes.Basic:
    95  		return basicType(t), nil
    96  	case *gotypes.Named:
    97  		return g.convert(t.Underlying())
    98  	case *gotypes.Struct:
    99  		return g.makeStructLit(t)
   100  	case *gotypes.Pointer:
   101  		expr, err := g.convert(t.Elem())
   102  		if err != nil {
   103  			return nil, err
   104  		}
   105  
   106  		// generate null enum for pointer type
   107  		if g.opts.nullable {
   108  			return &cueast.BinaryExpr{
   109  				X:  cueast.NewNull(),
   110  				Op: cuetoken.OR,
   111  				Y:  expr,
   112  			}, nil
   113  		}
   114  		return expr, nil
   115  	case *gotypes.Slice:
   116  		if t.Elem().String() == "byte" {
   117  			return Ident("bytes", false), nil
   118  		}
   119  		expr, err := g.convert(t.Elem())
   120  		if err != nil {
   121  			return nil, err
   122  		}
   123  		return cueast.NewList(&cueast.Ellipsis{Type: expr}), nil
   124  	case *gotypes.Array:
   125  		if t.Elem().String() == "byte" {
   126  			// TODO: no way to constraint lengths of bytes for now, as regexps
   127  			// operate on Unicode, not bytes. So we need
   128  			//     fmt.Fprint(e.w, fmt.Sprintf("=~ '^\C{%d}$'", x.Len())),
   129  			// but regexp does not support that.
   130  			// But translate to bytes, instead of [...byte] to be consistent.
   131  			return Ident("bytes", false), nil
   132  		}
   133  
   134  		expr, err := g.convert(t.Elem())
   135  		if err != nil {
   136  			return nil, err
   137  		}
   138  		return &cueast.BinaryExpr{
   139  			X: &cueast.BasicLit{
   140  				Kind:  cuetoken.INT,
   141  				Value: strconv.Itoa(int(t.Len())),
   142  			},
   143  			Op: cuetoken.MUL,
   144  			Y:  cueast.NewList(expr),
   145  		}, nil
   146  	case *gotypes.Map:
   147  		// cue map only support string as key
   148  		if b, ok := t.Key().Underlying().(*gotypes.Basic); !ok || b.Kind() != gotypes.String {
   149  			return nil, fmt.Errorf("unsupported map key type %s of %s", t.Key(), t)
   150  		}
   151  
   152  		expr, err := g.convert(t.Elem())
   153  		if err != nil {
   154  			return nil, err
   155  		}
   156  
   157  		f := &cueast.Field{
   158  			Label: cueast.NewList(Ident("string", false)),
   159  			Value: expr,
   160  		}
   161  		return &cueast.StructLit{
   162  			Elts: []cueast.Decl{f},
   163  		}, nil
   164  	case *gotypes.Interface:
   165  		// we don't process interface
   166  		return Ident("_", false), nil
   167  	}
   168  
   169  	return nil, fmt.Errorf("unsupported type %s", typ)
   170  }
   171  
   172  func (g *Generator) makeStructLit(x *gotypes.Struct) (*cueast.StructLit, error) {
   173  	st := &cueast.StructLit{
   174  		Elts: make([]cueast.Decl, 0),
   175  	}
   176  
   177  	// if num of fields is 1, we don't need braces. Keep it simple.
   178  	if x.NumFields() > 1 {
   179  		st.Lbrace = cuetoken.Blank.Pos()
   180  		st.Rbrace = cuetoken.Newline.Pos()
   181  	}
   182  
   183  	err := g.addFields(st, x, map[string]struct{}{})
   184  	if err != nil {
   185  		return nil, err
   186  	}
   187  
   188  	return st, nil
   189  }
   190  
   191  // addFields converts fields of go struct to CUE fields and add them to cue StructLit.
   192  func (g *Generator) addFields(st *cueast.StructLit, x *gotypes.Struct, names map[string]struct{}) error {
   193  	comments := g.fieldComments(x)
   194  
   195  	for i := 0; i < x.NumFields(); i++ {
   196  		field := x.Field(i)
   197  
   198  		// skip unexported fields
   199  		if !field.Exported() {
   200  			continue
   201  		}
   202  
   203  		// TODO(iyear): support more complex tags and usages
   204  		opts := g.parseTag(x.Tag(i))
   205  
   206  		// skip fields with "-" tag
   207  		if opts.Name == "-" {
   208  			continue
   209  		}
   210  
   211  		// if field name tag is empty, use Go field name
   212  		if opts.Name == "" {
   213  			opts.Name = field.Name()
   214  		}
   215  
   216  		// can't decl same field in the same scope
   217  		if _, ok := names[opts.Name]; ok {
   218  			return fmt.Errorf("field '%s' already exists, can not declare duplicate field name", opts.Name)
   219  		}
   220  		names[opts.Name] = struct{}{}
   221  
   222  		// process anonymous field with inline tag
   223  		if field.Anonymous() && opts.Inline {
   224  			if t, ok := field.Type().Underlying().(*gotypes.Struct); ok {
   225  				if err := g.addFields(st, t, names); err != nil {
   226  					return err
   227  				}
   228  			}
   229  			continue
   230  		}
   231  
   232  		var (
   233  			expr cueast.Expr
   234  			err  error
   235  		)
   236  		switch {
   237  		// process field with enum tag
   238  		case opts.Enum != nil && len(opts.Enum) > 0:
   239  			expr, err = g.enumField(field.Type(), opts)
   240  		// process normal field
   241  		default:
   242  			expr, err = g.normalField(field.Type(), opts)
   243  		}
   244  		if err != nil {
   245  			return fmt.Errorf("field '%s': %w", opts.Name, err)
   246  		}
   247  
   248  		f := &cueast.Field{
   249  			Label: Ident(opts.Name, false),
   250  			Value: expr,
   251  		}
   252  
   253  		// process field with optional tag(omitempty in json tag)
   254  		if opts.Optional {
   255  			f.Token = cuetoken.COLON
   256  			f.Optional = cuetoken.Blank.Pos()
   257  		}
   258  
   259  		makeComments(f, comments[i])
   260  
   261  		st.Elts = append(st.Elts, f)
   262  	}
   263  
   264  	return nil
   265  }
   266  
   267  func (g *Generator) enumField(typ gotypes.Type, opts *tagOptions) (cueast.Expr, error) {
   268  	tt, ok := typ.(*gotypes.Basic)
   269  	if !ok {
   270  		// TODO(iyear): support more types
   271  		return nil, fmt.Errorf("enum value only support [int, float, string, bool]")
   272  	}
   273  
   274  	expr, err := basicLabel(tt, opts.Enum[0])
   275  	if err != nil {
   276  		return nil, err
   277  	}
   278  
   279  	for _, v := range opts.Enum[1:] {
   280  		enumExpr, err := basicLabel(tt, v)
   281  		if err != nil {
   282  			return nil, err
   283  		}
   284  
   285  		// default value should be marked with *
   286  		if opts.Default != nil && *opts.Default == v {
   287  			enumExpr = &cueast.UnaryExpr{Op: cuetoken.MUL, X: enumExpr}
   288  		}
   289  
   290  		expr = &cueast.BinaryExpr{
   291  			X:  expr,
   292  			Op: cuetoken.OR,
   293  			Y:  enumExpr,
   294  		}
   295  	}
   296  
   297  	return expr, nil
   298  }
   299  
   300  func (g *Generator) normalField(typ gotypes.Type, opts *tagOptions) (cueast.Expr, error) {
   301  	expr, err := g.convert(typ)
   302  	if err != nil {
   303  		return nil, err
   304  	}
   305  
   306  	// process field with default tag
   307  	if opts.Default != nil {
   308  		tt, ok := typ.(*gotypes.Basic)
   309  		if !ok {
   310  			// TODO(iyear): support more types
   311  			return nil, fmt.Errorf("default value only support [int, float, string, bool]")
   312  		}
   313  
   314  		defaultExpr, err := basicLabel(tt, *opts.Default)
   315  		if err != nil {
   316  			return nil, err
   317  		}
   318  		expr = &cueast.BinaryExpr{
   319  			// default value should be marked with *
   320  			X:  &cueast.UnaryExpr{Op: cuetoken.MUL, X: defaultExpr},
   321  			Op: cuetoken.OR,
   322  			Y:  expr,
   323  		}
   324  	}
   325  
   326  	return expr, nil
   327  }
   328  
   329  func supportedType(stack []gotypes.Type, t gotypes.Type) error {
   330  	// we expand structures recursively, so we can't support recursive types
   331  	for _, t0 := range stack {
   332  		if t0 == t {
   333  			return fmt.Errorf("recursive type %s", t)
   334  		}
   335  	}
   336  	stack = append(stack, t)
   337  
   338  	t = t.Underlying()
   339  	switch x := t.(type) {
   340  	case *gotypes.Basic:
   341  		if x.String() != "invalid type" {
   342  			return nil
   343  		}
   344  		return fmt.Errorf("unsupported type %s", t)
   345  	case *gotypes.Named:
   346  		return nil
   347  	case *gotypes.Pointer:
   348  		return supportedType(stack, x.Elem())
   349  	case *gotypes.Slice:
   350  		return supportedType(stack, x.Elem())
   351  	case *gotypes.Array:
   352  		return supportedType(stack, x.Elem())
   353  	case *gotypes.Map:
   354  		if b, ok := x.Key().Underlying().(*gotypes.Basic); !ok || b.Kind() != gotypes.String {
   355  			return fmt.Errorf("unsupported map key type %s of %s", x.Key(), t)
   356  		}
   357  		return supportedType(stack, x.Elem())
   358  	case *gotypes.Struct:
   359  		// Eliminate structs with fields for which all fields are filtered.
   360  		if x.NumFields() == 0 {
   361  			return nil
   362  		}
   363  		for i := 0; i < x.NumFields(); i++ {
   364  			f := x.Field(i)
   365  			if f.Exported() {
   366  				if err := supportedType(stack, f.Type()); err != nil {
   367  					return err
   368  				}
   369  			}
   370  		}
   371  		return nil
   372  	case *gotypes.Interface:
   373  		return nil
   374  	}
   375  	return fmt.Errorf("unsupported type %s", t)
   376  }
   377  
   378  // ----------comment----------
   379  
   380  type commentUnion struct {
   381  	comment *goast.CommentGroup
   382  	doc     *goast.CommentGroup
   383  }
   384  
   385  // fieldComments returns the comments for each field in a go struct.
   386  //
   387  // The comments are same order as the fields.
   388  func (g *Generator) fieldComments(x *gotypes.Struct) []*commentUnion {
   389  	comments := make([]*commentUnion, x.NumFields())
   390  
   391  	st, ok := g.types[x]
   392  	if !ok {
   393  		return comments
   394  	}
   395  
   396  	for i, field := range st.Fields.List {
   397  		comments[i] = &commentUnion{comment: field.Comment, doc: field.Doc}
   398  	}
   399  
   400  	return comments
   401  }
   402  
   403  // makeComments adds comments to a cue node.
   404  //
   405  // go docs/comments are converted to cue comments.
   406  func makeComments(node cueast.Node, c *commentUnion) {
   407  	if c == nil {
   408  		return
   409  	}
   410  	cg := make([]*cueast.Comment, 0)
   411  
   412  	if comment := makeComment(c.comment); comment != nil && len(comment.List) > 0 {
   413  		cg = append(cg, comment.List...)
   414  	}
   415  	if doc := makeComment(c.doc); doc != nil && len(doc.List) > 0 {
   416  		cg = append(cg, doc.List...)
   417  	}
   418  
   419  	// avoid nil comment groups which will cause panics
   420  	if len(cg) > 0 {
   421  		cueast.AddComment(node, &cueast.CommentGroup{List: cg})
   422  	}
   423  }
   424  
   425  // makeComment converts a go CommentGroup to a cue CommentGroup.
   426  //
   427  // All /*-style comments are converted to //-style comments.
   428  func makeComment(cg *goast.CommentGroup) *cueast.CommentGroup {
   429  	if cg == nil {
   430  		return nil
   431  	}
   432  
   433  	var comments []*cueast.Comment
   434  
   435  	for _, comment := range cg.List {
   436  		c := comment.Text
   437  
   438  		if len(c) < 2 {
   439  			continue
   440  		}
   441  
   442  		// Remove comment markers.
   443  		// The parser has given us exactly the comment text.
   444  		switch c[1] {
   445  		case '/':
   446  			// -style comment (no newline at the end)
   447  			comments = append(comments, &cueast.Comment{Text: c})
   448  
   449  		case '*':
   450  			/*-style comment */
   451  			c = c[2 : len(c)-2]
   452  			if len(c) > 0 && c[0] == '\n' {
   453  				c = c[1:]
   454  			}
   455  
   456  			lines := strings.Split(c, "\n")
   457  
   458  			// Find common space prefix
   459  			i := 0
   460  			line := lines[0]
   461  			for ; i < len(line); i++ {
   462  				if c := line[i]; c != ' ' && c != '\t' {
   463  					break
   464  				}
   465  			}
   466  
   467  			for _, l := range lines {
   468  				for j := 0; j < i && j < len(l); j++ {
   469  					if line[j] != l[j] {
   470  						i = j
   471  						break
   472  					}
   473  				}
   474  			}
   475  
   476  			// Strip last line if empty.
   477  			if n := len(lines); n > 1 && len(lines[n-1]) < i {
   478  				lines = lines[:n-1]
   479  			}
   480  
   481  			// Print lines.
   482  			for _, l := range lines {
   483  				if i >= len(l) {
   484  					comments = append(comments, &cueast.Comment{Text: "//"})
   485  					continue
   486  				}
   487  				comments = append(comments, &cueast.Comment{Text: "// " + l[i:]})
   488  			}
   489  		}
   490  	}
   491  
   492  	return &cueast.CommentGroup{List: comments}
   493  }