github.com/unionj-cloud/go-doudou/v2@v2.3.5/toolkit/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  				switch specType := typeSpec.Type.(type) {
    60  				case *ast.StructType:
    61  					structmeta := NewStructMeta(specType, sc.exprString)
    62  					structmeta.Name = typeName
    63  					structmeta.Comments = comments
    64  					structmeta.IsExport = unicode.IsUpper(rune(typeName[0]))
    65  					sc.Structs = append(sc.Structs, structmeta)
    66  				default:
    67  					sc.NonStructTypeMap[typeName] = typeSpec.Type
    68  				}
    69  			}
    70  		}
    71  	}
    72  	return nil
    73  }
    74  
    75  // DocFlatEmbed flatten embed struct fields
    76  func (sc *StructCollector) DocFlatEmbed() []StructMeta {
    77  	structMap := make(map[string]StructMeta)
    78  	for _, structMeta := range sc.Structs {
    79  		if _, exists := structMap[structMeta.Name]; !exists {
    80  			structMap[structMeta.Name] = structMeta
    81  		}
    82  	}
    83  
    84  	var exStructs []StructMeta
    85  	for _, structMeta := range sc.Structs {
    86  		if !structMeta.IsExport {
    87  			continue
    88  		}
    89  		exStructs = append(exStructs, structMeta)
    90  	}
    91  
    92  	re := regexp.MustCompile(`json:"(.*?)"`)
    93  	var result []StructMeta
    94  	for _, structMeta := range exStructs {
    95  		_structMeta := StructMeta{
    96  			Name:     structMeta.Name,
    97  			Fields:   make([]FieldMeta, 0),
    98  			Comments: make([]string, len(structMeta.Comments)),
    99  			IsExport: true,
   100  		}
   101  		copy(_structMeta.Comments, structMeta.Comments)
   102  		fieldMap := make(map[string]FieldMeta)
   103  		embedFieldMap := make(map[string]FieldMeta)
   104  		for _, fieldMeta := range structMeta.Fields {
   105  			if strings.HasPrefix(fieldMeta.Type, "embed") {
   106  				if re.MatchString(fieldMeta.Tag) {
   107  					fieldMeta.Type = strings.TrimPrefix(fieldMeta.Type, "embed:")
   108  					_structMeta.Fields = append(_structMeta.Fields, fieldMeta)
   109  					fieldMap[fieldMeta.Name] = fieldMeta
   110  				} else {
   111  					if embedded, exists := structMap[fieldMeta.Name]; exists {
   112  						for _, field := range embedded.Fields {
   113  							if !field.IsExport {
   114  								continue
   115  							}
   116  							embedFieldMap[field.Name] = field
   117  						}
   118  					}
   119  				}
   120  			} else if fieldMeta.IsExport {
   121  				_structMeta.Fields = append(_structMeta.Fields, fieldMeta)
   122  				fieldMap[fieldMeta.Name] = fieldMeta
   123  			}
   124  		}
   125  
   126  		for key, field := range embedFieldMap {
   127  			if _, exists := fieldMap[key]; !exists {
   128  				_structMeta.Fields = append(_structMeta.Fields, field)
   129  			}
   130  		}
   131  		result = append(result, _structMeta)
   132  	}
   133  	return result
   134  }
   135  
   136  type StructCollectorOption func(collector *StructCollector)
   137  
   138  func WithEnums(enums map[string]EnumMeta) StructCollectorOption {
   139  	return func(collector *StructCollector) {
   140  		collector.enums = enums
   141  	}
   142  }
   143  
   144  // NewStructCollector initializes an StructCollector
   145  func NewStructCollector(exprString func(ast.Expr) string, opts ...StructCollectorOption) *StructCollector {
   146  	sc := &StructCollector{
   147  		Structs:          nil,
   148  		Methods:          make(map[string][]MethodMeta),
   149  		Package:          PackageMeta{},
   150  		NonStructTypeMap: make(map[string]ast.Expr),
   151  		exprString:       exprString,
   152  	}
   153  	for _, opt := range opts {
   154  		opt(sc)
   155  	}
   156  	return sc
   157  }
   158  
   159  // BuildStructCollector initializes an StructCollector and collects structs
   160  func BuildStructCollector(file string, exprString func(ast.Expr) string) StructCollector {
   161  	sc := NewStructCollector(exprString)
   162  	fset := token.NewFileSet()
   163  	root, err := parser.ParseFile(fset, file, nil, parser.ParseComments)
   164  	if err != nil {
   165  		logrus.Panicln(err)
   166  	}
   167  	ast.Walk(sc, root)
   168  	return *sc
   169  }