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 }