github.com/unionj-cloud/go-doudou@v1.3.8-0.20221011095552-0088008e5b31/cmd/internal/astutils/structcollector.go (about)

     1  package astutils
     2  
     3  import (
     4  	"github.com/sirupsen/logrus"
     5  	"go/ast"
     6  	"go/parser"
     7  	"go/token"
     8  	"regexp"
     9  	"strings"
    10  	"unicode"
    11  )
    12  
    13  // StructCollector collect structs by parsing source code
    14  type StructCollector struct {
    15  	Structs          []StructMeta
    16  	Methods          map[string][]MethodMeta
    17  	Package          PackageMeta
    18  	NonStructTypeMap map[string]ast.Expr
    19  	exprString       func(ast.Expr) string
    20  	enums            map[string]EnumMeta
    21  }
    22  
    23  // Visit traverse each node from source code
    24  func (sc *StructCollector) Visit(n ast.Node) ast.Visitor {
    25  	return sc.Collect(n)
    26  }
    27  
    28  // Collect collects all structs from source code
    29  func (sc *StructCollector) Collect(n ast.Node) ast.Visitor {
    30  	switch spec := n.(type) {
    31  	case *ast.Package:
    32  		return sc
    33  	case *ast.File: // actually it is package name
    34  		sc.Package = PackageMeta{
    35  			Name: spec.Name.Name,
    36  		}
    37  		return sc
    38  	case *ast.FuncDecl:
    39  		if spec.Recv != nil {
    40  			typeName := strings.TrimPrefix(sc.exprString(spec.Recv.List[0].Type), "*")
    41  			methods, _ := sc.Methods[typeName]
    42  			methods = append(methods, GetMethodMeta(spec))
    43  			if sc.Methods == nil {
    44  				sc.Methods = make(map[string][]MethodMeta)
    45  			}
    46  			sc.Methods[typeName] = methods
    47  		}
    48  	case *ast.GenDecl:
    49  		if spec.Tok == token.TYPE {
    50  			var comments []string
    51  			if spec.Doc != nil {
    52  				for _, comment := range spec.Doc.List {
    53  					comments = append(comments, strings.TrimSpace(strings.TrimPrefix(comment.Text, "//")))
    54  				}
    55  			}
    56  			for _, item := range spec.Specs {
    57  				typeSpec := item.(*ast.TypeSpec)
    58  				typeName := typeSpec.Name.Name
    59  				logrus.Printf("Type: name=%s\n", typeName)
    60  				switch specType := typeSpec.Type.(type) {
    61  				case *ast.StructType:
    62  					structmeta := NewStructMeta(specType, sc.exprString)
    63  					structmeta.Name = typeName
    64  					structmeta.Comments = comments
    65  					structmeta.IsExport = unicode.IsUpper(rune(typeName[0]))
    66  					sc.Structs = append(sc.Structs, structmeta)
    67  				default:
    68  					sc.NonStructTypeMap[typeName] = typeSpec.Type
    69  				}
    70  			}
    71  		}
    72  	}
    73  	return nil
    74  }
    75  
    76  // DocFlatEmbed flatten embed struct fields
    77  func (sc *StructCollector) DocFlatEmbed() []StructMeta {
    78  	structMap := make(map[string]StructMeta)
    79  	for _, structMeta := range sc.Structs {
    80  		if _, exists := structMap[structMeta.Name]; !exists {
    81  			structMap[structMeta.Name] = structMeta
    82  		}
    83  	}
    84  
    85  	var exStructs []StructMeta
    86  	for _, structMeta := range sc.Structs {
    87  		if !structMeta.IsExport {
    88  			continue
    89  		}
    90  		exStructs = append(exStructs, structMeta)
    91  	}
    92  
    93  	re := regexp.MustCompile(`json:"(.*?)"`)
    94  	var result []StructMeta
    95  	for _, structMeta := range exStructs {
    96  		_structMeta := StructMeta{
    97  			Name:     structMeta.Name,
    98  			Fields:   make([]FieldMeta, 0),
    99  			Comments: make([]string, len(structMeta.Comments)),
   100  			IsExport: true,
   101  		}
   102  		copy(_structMeta.Comments, structMeta.Comments)
   103  		fieldMap := make(map[string]FieldMeta)
   104  		embedFieldMap := make(map[string]FieldMeta)
   105  		for _, fieldMeta := range structMeta.Fields {
   106  			if strings.HasPrefix(fieldMeta.Type, "embed") {
   107  				if re.MatchString(fieldMeta.Tag) {
   108  					fieldMeta.Type = strings.TrimPrefix(fieldMeta.Type, "embed:")
   109  					_structMeta.Fields = append(_structMeta.Fields, fieldMeta)
   110  					fieldMap[fieldMeta.Name] = fieldMeta
   111  				} else {
   112  					if embedded, exists := structMap[fieldMeta.Name]; exists {
   113  						for _, field := range embedded.Fields {
   114  							if !field.IsExport {
   115  								continue
   116  							}
   117  							embedFieldMap[field.Name] = field
   118  						}
   119  					}
   120  				}
   121  			} else if fieldMeta.IsExport {
   122  				_structMeta.Fields = append(_structMeta.Fields, fieldMeta)
   123  				fieldMap[fieldMeta.Name] = fieldMeta
   124  			}
   125  		}
   126  
   127  		for key, field := range embedFieldMap {
   128  			if _, exists := fieldMap[key]; !exists {
   129  				_structMeta.Fields = append(_structMeta.Fields, field)
   130  			}
   131  		}
   132  		result = append(result, _structMeta)
   133  	}
   134  	return result
   135  }
   136  
   137  type StructCollectorOption func(collector *StructCollector)
   138  
   139  func WithEnums(enums map[string]EnumMeta) StructCollectorOption {
   140  	return func(collector *StructCollector) {
   141  		collector.enums = enums
   142  	}
   143  }
   144  
   145  // NewStructCollector initializes an StructCollector
   146  func NewStructCollector(exprString func(ast.Expr) string, opts ...StructCollectorOption) *StructCollector {
   147  	sc := &StructCollector{
   148  		Structs:          nil,
   149  		Methods:          make(map[string][]MethodMeta),
   150  		Package:          PackageMeta{},
   151  		NonStructTypeMap: make(map[string]ast.Expr),
   152  		exprString:       exprString,
   153  	}
   154  	for _, opt := range opts {
   155  		opt(sc)
   156  	}
   157  	return sc
   158  }
   159  
   160  // BuildStructCollector initializes an StructCollector and collects structs
   161  func BuildStructCollector(file string, exprString func(ast.Expr) string) StructCollector {
   162  	sc := NewStructCollector(exprString)
   163  	fset := token.NewFileSet()
   164  	root, err := parser.ParseFile(fset, file, nil, parser.ParseComments)
   165  	if err != nil {
   166  		logrus.Panicln(err)
   167  	}
   168  	ast.Walk(sc, root)
   169  	return *sc
   170  }