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 }