Files
kubevela/references/cuegen/convert.go
Chaitanyareddy0702 d627ecea2a
Some checks failed
Webhook Upgrade Validation / webhook-upgrade-check (push) Failing after 24s
Chore: Upgrade cuelang version to v0.14.1 (#6877)
* chore: updates culenag version and syntax across all files

Signed-off-by: Amit Singh <singhamitch@outlook.com>
Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>

* debuggin: reverts tf provider changes

Signed-off-by: Amit Singh <singhamitch@outlook.com>
Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>

* Refactor: Simplify provider configuration by removing 'providerBasic' and directly defining access keys and region for providers

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>

* Refactor: Consolidate provider configuration by introducing 'providerBasic' for access keys and region

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>

* chore: reorganize import statements in deepcopy files for consistency

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>

* chore: reorder import statements for consistency across deepcopy files

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>

* Refactor: Safely handle pattern parameter selectors to avoid panics in GetParameters and getStatusMap

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>

* chore: add comment to clarify test context in definition_revision_test.go

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>

* chore: remove redundant comment from test context initialization in definition_revision_test.go

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>

* Refactor: Introduce GetSelectorLabel function to safely extract labels from CUE selectors

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>

* chore: add newline at end of file in utils.go

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>

* chore: increase timeout for multi-cluster e2e

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>

---------

Signed-off-by: Amit Singh <singhamitch@outlook.com>
Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>
Co-authored-by: Amit Singh <singhamitch@outlook.com>
Co-authored-by: Ayush Kumar <ayushshyamkumar888@gmail.com>
2025-10-23 10:56:37 +01:00

493 lines
11 KiB
Go

/*
Copyright 2023 The KubeVela 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 cuegen
import (
"fmt"
goast "go/ast"
gotoken "go/token"
gotypes "go/types"
"strconv"
"strings"
cueast "cuelang.org/go/cue/ast"
cuetoken "cuelang.org/go/cue/token"
)
func (g *Generator) convertDecls(x *goast.GenDecl) (decls []Decl, _ error) {
// TODO(iyear): currently only support 'type'
if x.Tok != gotoken.TYPE {
return decls, nil
}
for _, spec := range x.Specs {
typeSpec, ok := spec.(*goast.TypeSpec)
if !ok {
continue
}
if g.opts.typeFilter != nil && !g.opts.typeFilter(typeSpec) {
continue
}
// only process struct
typ := g.pkg.TypesInfo.TypeOf(typeSpec.Name)
if err := supportedType(nil, typ); err != nil {
return nil, fmt.Errorf("unsupported type %s: %w", typeSpec.Name.Name, err)
}
named, ok := typ.(*gotypes.Named)
if !ok {
continue
}
switch t := named.Underlying().(type) {
case *gotypes.Struct:
lit, err := g.convert(t)
if err != nil {
return nil, err
}
decls = append(decls, &Struct{CommonFields: CommonFields{
Expr: lit,
Name: typeSpec.Name.Name,
Doc: x.Doc,
Pos: cuetoken.Newline.Pos(),
}})
default:
continue
}
}
return decls, nil
}
func (g *Generator) convert(typ gotypes.Type) (cueast.Expr, error) {
// if type is registered as special type, use it directly
if t, ok := g.opts.types[typ.String()]; ok {
switch t {
case TypeAny:
return Ident("_", false), nil
case TypeEllipsis:
return &cueast.StructLit{Elts: []cueast.Decl{&cueast.Ellipsis{}}}, nil
default:
return nil, fmt.Errorf("unsupported special cue type: %v", t)
}
}
switch t := typ.(type) {
case *gotypes.Basic:
return basicType(t), nil
case *gotypes.Named:
return g.convert(t.Underlying())
case *gotypes.Struct:
return g.makeStructLit(t)
case *gotypes.Pointer:
expr, err := g.convert(t.Elem())
if err != nil {
return nil, err
}
// generate null enum for pointer type
if g.opts.nullable {
return &cueast.BinaryExpr{
X: cueast.NewNull(),
Op: cuetoken.OR,
Y: expr,
}, nil
}
return expr, nil
case *gotypes.Slice:
if t.Elem().String() == "byte" {
return Ident("bytes", false), nil
}
expr, err := g.convert(t.Elem())
if err != nil {
return nil, err
}
return cueast.NewList(&cueast.Ellipsis{Type: expr}), nil
case *gotypes.Array:
if t.Elem().String() == "byte" {
// TODO: no way to constraint lengths of bytes for now, as regexps
// operate on Unicode, not bytes. So we need
// fmt.Fprint(e.w, fmt.Sprintf("=~ '^\C{%d}$'", x.Len())),
// but regexp does not support that.
// But translate to bytes, instead of [...byte] to be consistent.
return Ident("bytes", false), nil
}
expr, err := g.convert(t.Elem())
if err != nil {
return nil, err
}
return &cueast.BinaryExpr{
X: &cueast.BasicLit{
Kind: cuetoken.INT,
Value: strconv.Itoa(int(t.Len())),
},
Op: cuetoken.MUL,
Y: cueast.NewList(expr),
}, nil
case *gotypes.Map:
// cue map only support string as key
if b, ok := t.Key().Underlying().(*gotypes.Basic); !ok || b.Kind() != gotypes.String {
return nil, fmt.Errorf("unsupported map key type %s of %s", t.Key(), t)
}
expr, err := g.convert(t.Elem())
if err != nil {
return nil, err
}
f := &cueast.Field{
Label: cueast.NewList(Ident("string", false)),
Value: expr,
}
return &cueast.StructLit{
Elts: []cueast.Decl{f},
}, nil
case *gotypes.Interface:
// we don't process interface
return Ident("_", false), nil
}
return nil, fmt.Errorf("unsupported type %s", typ)
}
func (g *Generator) makeStructLit(x *gotypes.Struct) (*cueast.StructLit, error) {
st := &cueast.StructLit{
Elts: make([]cueast.Decl, 0),
}
// if num of fields is 1, we don't need braces. Keep it simple.
if x.NumFields() > 1 {
st.Lbrace = cuetoken.Blank.Pos()
st.Rbrace = cuetoken.Newline.Pos()
}
err := g.addFields(st, x, map[string]struct{}{})
if err != nil {
return nil, err
}
return st, nil
}
// addFields converts fields of go struct to CUE fields and add them to cue StructLit.
func (g *Generator) addFields(st *cueast.StructLit, x *gotypes.Struct, names map[string]struct{}) error {
comments := g.fieldComments(x)
for i := 0; i < x.NumFields(); i++ {
field := x.Field(i)
// skip unexported fields
if !field.Exported() {
continue
}
// TODO(iyear): support more complex tags and usages
opts := g.parseTag(x.Tag(i))
// skip fields with "-" tag
if opts.Name == "-" {
continue
}
// if field name tag is empty, use Go field name
if opts.Name == "" {
opts.Name = field.Name()
}
// can't decl same field in the same scope
if _, ok := names[opts.Name]; ok {
return fmt.Errorf("field '%s' already exists, can not declare duplicate field name", opts.Name)
}
names[opts.Name] = struct{}{}
// process anonymous field with inline tag
if field.Anonymous() && opts.Inline {
if t, ok := field.Type().Underlying().(*gotypes.Struct); ok {
if err := g.addFields(st, t, names); err != nil {
return err
}
}
continue
}
var (
expr cueast.Expr
err error
)
switch {
// process field with enum tag
case len(opts.Enum) > 0:
expr, err = g.enumField(field.Type(), opts)
// process normal field
default:
expr, err = g.normalField(field.Type(), opts)
}
if err != nil {
return fmt.Errorf("field '%s': %w", opts.Name, err)
}
f := &cueast.Field{
Label: Ident(opts.Name, false),
Value: expr,
}
// process field with optional tag(omitempty in json tag)
if opts.Optional {
f.Constraint = cuetoken.OPTION
}
makeComments(f, comments[i])
st.Elts = append(st.Elts, f)
}
return nil
}
func (g *Generator) enumField(typ gotypes.Type, opts *tagOptions) (cueast.Expr, error) {
tt, ok := typ.(*gotypes.Basic)
if !ok {
// TODO(iyear): support more types
return nil, fmt.Errorf("enum value only support [int, float, string, bool]")
}
expr, err := basicLabel(tt, opts.Enum[0])
if err != nil {
return nil, err
}
for _, v := range opts.Enum[1:] {
enumExpr, err := basicLabel(tt, v)
if err != nil {
return nil, err
}
// default value should be marked with *
if opts.Default != nil && *opts.Default == v {
enumExpr = &cueast.UnaryExpr{Op: cuetoken.MUL, X: enumExpr}
}
expr = &cueast.BinaryExpr{
X: expr,
Op: cuetoken.OR,
Y: enumExpr,
}
}
return expr, nil
}
func (g *Generator) normalField(typ gotypes.Type, opts *tagOptions) (cueast.Expr, error) {
expr, err := g.convert(typ)
if err != nil {
return nil, err
}
// process field with default tag
if opts.Default != nil {
tt, ok := typ.(*gotypes.Basic)
if !ok {
// TODO(iyear): support more types
return nil, fmt.Errorf("default value only support [int, float, string, bool]")
}
defaultExpr, err := basicLabel(tt, *opts.Default)
if err != nil {
return nil, err
}
expr = &cueast.BinaryExpr{
// default value should be marked with *
X: &cueast.UnaryExpr{Op: cuetoken.MUL, X: defaultExpr},
Op: cuetoken.OR,
Y: expr,
}
}
return expr, nil
}
func supportedType(stack []gotypes.Type, t gotypes.Type) error {
// we expand structures recursively, so we can't support recursive types
for _, t0 := range stack {
if t0 == t {
return fmt.Errorf("recursive type %s", t)
}
}
stack = append(stack, t)
t = t.Underlying()
switch x := t.(type) {
case *gotypes.Basic:
if x.String() != "invalid type" {
return nil
}
return fmt.Errorf("unsupported type %s", t)
case *gotypes.Named:
return nil
case *gotypes.Pointer:
return supportedType(stack, x.Elem())
case *gotypes.Slice:
return supportedType(stack, x.Elem())
case *gotypes.Array:
return supportedType(stack, x.Elem())
case *gotypes.Map:
if b, ok := x.Key().Underlying().(*gotypes.Basic); !ok || b.Kind() != gotypes.String {
return fmt.Errorf("unsupported map key type %s of %s", x.Key(), t)
}
return supportedType(stack, x.Elem())
case *gotypes.Struct:
// Eliminate structs with fields for which all fields are filtered.
if x.NumFields() == 0 {
return nil
}
for i := 0; i < x.NumFields(); i++ {
f := x.Field(i)
if f.Exported() {
if err := supportedType(stack, f.Type()); err != nil {
return err
}
}
}
return nil
case *gotypes.Interface:
return nil
}
return fmt.Errorf("unsupported type %s", t)
}
// ----------comment----------
type commentUnion struct {
comment *goast.CommentGroup
doc *goast.CommentGroup
}
// fieldComments returns the comments for each field in a go struct.
//
// The comments are same order as the fields.
func (g *Generator) fieldComments(x *gotypes.Struct) []*commentUnion {
comments := make([]*commentUnion, x.NumFields())
st, ok := g.types[x]
if !ok {
return comments
}
for i, field := range st.Fields.List {
comments[i] = &commentUnion{comment: field.Comment, doc: field.Doc}
}
return comments
}
// makeComments adds comments to a cue node.
//
// go docs/comments are converted to cue comments.
func makeComments(node cueast.Node, c *commentUnion) {
if c == nil {
return
}
cg := make([]*cueast.Comment, 0)
if comment := makeComment(c.comment); comment != nil && len(comment.List) > 0 {
cg = append(cg, comment.List...)
}
if doc := makeComment(c.doc); doc != nil && len(doc.List) > 0 {
cg = append(cg, doc.List...)
}
// avoid nil comment groups which will cause panics
if len(cg) > 0 {
cueast.AddComment(node, &cueast.CommentGroup{List: cg})
}
}
// makeComment converts a go CommentGroup to a cue CommentGroup.
//
// All /*-style comments are converted to //-style comments.
func makeComment(cg *goast.CommentGroup) *cueast.CommentGroup {
if cg == nil {
return nil
}
var comments []*cueast.Comment
for _, comment := range cg.List {
c := comment.Text
if len(c) < 2 {
continue
}
// Remove comment markers.
// The parser has given us exactly the comment text.
switch c[1] {
case '/':
// -style comment (no newline at the end)
comments = append(comments, &cueast.Comment{Text: c})
case '*':
/*-style comment */
c = c[2 : len(c)-2]
if len(c) > 0 && c[0] == '\n' {
c = c[1:]
}
lines := strings.Split(c, "\n")
// Find common space prefix
i := 0
line := lines[0]
for ; i < len(line); i++ {
if c := line[i]; c != ' ' && c != '\t' {
break
}
}
for _, l := range lines {
for j := 0; j < i && j < len(l); j++ {
if line[j] != l[j] {
i = j
break
}
}
}
// Strip last line if empty.
if n := len(lines); n > 1 && len(lines[n-1]) < i {
lines = lines[:n-1]
}
// Print lines.
for _, l := range lines {
if i >= len(l) {
comments = append(comments, &cueast.Comment{Text: "//"})
continue
}
comments = append(comments, &cueast.Comment{Text: "// " + l[i:]})
}
}
}
return &cueast.CommentGroup{List: comments}
}