github.com/unionj-cloud/go-doudou/v2@v2.3.5/toolkit/astutils/interfacecollector.go (about) 1 package astutils 2 3 import ( 4 "fmt" 5 "github.com/iancoleman/strcase" 6 "github.com/sirupsen/logrus" 7 "github.com/unionj-cloud/go-doudou/v2/toolkit/sliceutils" 8 "github.com/unionj-cloud/go-doudou/v2/toolkit/stringutils" 9 "go/ast" 10 "go/parser" 11 "go/token" 12 "net/http" 13 "regexp" 14 "strings" 15 ) 16 17 // InterfaceCollector collect interfaces by parsing source code 18 type InterfaceCollector struct { 19 Interfaces []InterfaceMeta 20 Package PackageMeta 21 exprString func(ast.Expr) string 22 cmap ast.CommentMap 23 } 24 25 // Visit traverse each node from source code 26 func (ic *InterfaceCollector) Visit(n ast.Node) ast.Visitor { 27 return ic.Collect(n) 28 } 29 30 // Collect collects all interfaces from source code 31 func (ic *InterfaceCollector) Collect(n ast.Node) ast.Visitor { 32 switch spec := n.(type) { 33 case *ast.Package: 34 return ic 35 case *ast.File: // actually it is package name 36 ic.Package = PackageMeta{ 37 Name: spec.Name.Name, 38 } 39 return ic 40 case *ast.GenDecl: 41 if spec.Tok == token.TYPE { 42 comments := doc2Comments(spec.Doc) 43 for _, item := range spec.Specs { 44 typeSpec := item.(*ast.TypeSpec) 45 typeName := typeSpec.Name.Name 46 switch specType := typeSpec.Type.(type) { 47 case *ast.InterfaceType: 48 ic.Interfaces = append(ic.Interfaces, InterfaceMeta{ 49 Name: typeName, 50 Methods: ic.field2Methods(specType.Methods.List), 51 Comments: comments, 52 }) 53 } 54 } 55 } 56 } 57 return nil 58 } 59 60 // GetShelves_ShelfBooks_Book 61 // shelves/:shelf/books/:book 62 func Pattern(method string) (httpMethod string, endpoint string) { 63 httpMethods := []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete} 64 re1, err := regexp.Compile("_?[A-Z]") 65 if err != nil { 66 panic(err) 67 } 68 method = re1.ReplaceAllStringFunc(method, func(s string) string { 69 if strings.HasPrefix(s, "_") { 70 return "/:" + strings.ToLower(strings.TrimPrefix(s, "_")) 71 } else { 72 return "/" + strings.ToLower(s) 73 } 74 }) 75 splits := strings.Split(method, "/")[1:] 76 httpMethod = httpMethods[1] 77 head := strings.ToUpper(splits[0]) 78 if sliceutils.StringContains(httpMethods, head) { 79 httpMethod = head 80 splits = splits[1:] 81 } 82 return httpMethod, strings.Join(splits, "/") 83 } 84 85 func pathVariables(endpoint string) (ret []string) { 86 splits := strings.Split(endpoint, "/") 87 pvs := sliceutils.StringFilter(splits, func(item string) bool { 88 return stringutils.IsNotEmpty(item) && strings.HasPrefix(item, ":") 89 }) 90 for _, v := range pvs { 91 ret = append(ret, strings.TrimPrefix(v, ":")) 92 } 93 return 94 } 95 96 func (ic *InterfaceCollector) field2Methods(list []*ast.Field) []MethodMeta { 97 var methods []MethodMeta 98 for _, method := range list { 99 if len(method.Names) == 0 { 100 panic("no method name") 101 } 102 mn := method.Names[0].Name 103 104 var mComments []string 105 var annotations []Annotation 106 if method.Doc != nil { 107 for _, comment := range method.Doc.List { 108 mComments = append(mComments, strings.TrimSpace(strings.TrimPrefix(comment.Text, "//"))) 109 annotations = append(annotations, GetAnnotations(comment.Text)...) 110 } 111 } 112 113 ft, _ := method.Type.(*ast.FuncType) 114 var params []FieldMeta 115 if ft.Params != nil { 116 params = ic.field2Params(ft.Params.List) 117 } 118 119 httpMethod, endpoint := Pattern(mn) 120 pvs := pathVariables(endpoint) 121 for i := range params { 122 if sliceutils.StringContains(pvs, params[i].Name) { 123 params[i].IsPathVariable = true 124 } 125 } 126 127 var results []FieldMeta 128 if ft.Results != nil { 129 results = ic.field2Results(ft.Results.List) 130 } 131 methods = append(methods, MethodMeta{ 132 Name: mn, 133 Params: params, 134 Results: results, 135 Comments: mComments, 136 Annotations: annotations, 137 HasPathVariable: len(pvs) > 0, 138 HttpMethod: httpMethod, 139 }) 140 } 141 return methods 142 } 143 144 func (ic *InterfaceCollector) field2Params(list []*ast.Field) []FieldMeta { 145 var params []FieldMeta 146 pkeymap := make(map[string]int) 147 for _, param := range list { 148 pt := ic.exprString(param.Type) 149 if len(param.Names) > 0 { 150 for i, name := range param.Names { 151 field := FieldMeta{ 152 Name: name.Name, 153 Type: pt, 154 } 155 var cnode ast.Node 156 if i == 0 { 157 cnode = param 158 } else { 159 cnode = name 160 } 161 if cmts, exists := ic.cmap[cnode]; exists { 162 for _, comment := range cmts[0].List { 163 field.Comments = append(field.Comments, strings.TrimSpace(strings.TrimPrefix(comment.Text, "//"))) 164 field.Annotations = append(field.Annotations, GetAnnotations(comment.Text)...) 165 } 166 } 167 var validateTags []string 168 for _, item := range field.Annotations { 169 if item.Name == "@validate" { 170 validateTags = append(validateTags, item.Params...) 171 } 172 } 173 field.ValidateTag = strings.Join(validateTags, ",") 174 params = append(params, field) 175 } 176 continue 177 } 178 var pComments []string 179 var annotations []Annotation 180 if cmts, exists := ic.cmap[param]; exists { 181 for _, comment := range cmts[0].List { 182 pComments = append(pComments, strings.TrimSpace(strings.TrimPrefix(comment.Text, "//"))) 183 annotations = append(annotations, GetAnnotations(comment.Text)...) 184 } 185 } 186 var pn string 187 elemt := strings.TrimPrefix(pt, "*") 188 if stringutils.IsNotEmpty(elemt) { 189 if strings.Contains(elemt, "[") { 190 elemt = elemt[strings.Index(elemt, "]")+1:] 191 elemt = strings.TrimPrefix(elemt, "*") 192 } 193 splits := strings.Split(elemt, ".") 194 _key := "p" + strcase.ToLowerCamel(splits[len(splits)-1][0:1]) 195 if _, exists := pkeymap[_key]; exists { 196 pkeymap[_key]++ 197 pn = _key + fmt.Sprintf("%d", pkeymap[_key]) 198 } else { 199 pkeymap[_key]++ 200 pn = _key 201 } 202 } 203 var validateTags []string 204 for _, item := range annotations { 205 if item.Name == "@validate" { 206 validateTags = append(validateTags, item.Params...) 207 } 208 } 209 params = append(params, FieldMeta{ 210 Name: pn, 211 Type: pt, 212 Comments: pComments, 213 Annotations: annotations, 214 ValidateTag: strings.Join(validateTags, ","), 215 }) 216 } 217 return params 218 } 219 220 func (ic *InterfaceCollector) field2Results(list []*ast.Field) []FieldMeta { 221 var results []FieldMeta 222 rkeymap := make(map[string]int) 223 for _, result := range list { 224 var rComments []string 225 if cmts, exists := ic.cmap[result]; exists { 226 for _, comment := range cmts[0].List { 227 rComments = append(rComments, strings.TrimSpace(strings.TrimPrefix(comment.Text, "//"))) 228 } 229 } 230 rt := ic.exprString(result.Type) 231 if len(result.Names) > 0 { 232 for _, name := range result.Names { 233 results = append(results, FieldMeta{ 234 Name: name.Name, 235 Type: rt, 236 Tag: "", 237 Comments: rComments, 238 }) 239 } 240 continue 241 } 242 var rn string 243 elemt := strings.TrimPrefix(rt, "*") 244 if stringutils.IsNotEmpty(elemt) { 245 if strings.Contains(elemt, "[") { 246 elemt = elemt[strings.Index(elemt, "]")+1:] 247 elemt = strings.TrimPrefix(elemt, "*") 248 } 249 splits := strings.Split(elemt, ".") 250 _key := "r" + strcase.ToLowerCamel(splits[len(splits)-1][0:1]) 251 if _, exists := rkeymap[_key]; exists { 252 rkeymap[_key]++ 253 rn = _key + fmt.Sprintf("%d", rkeymap[_key]) 254 } else { 255 rkeymap[_key]++ 256 rn = _key 257 } 258 } 259 results = append(results, FieldMeta{ 260 Name: rn, 261 Type: rt, 262 Tag: "", 263 Comments: rComments, 264 }) 265 } 266 return results 267 } 268 269 func doc2Comments(doc *ast.CommentGroup) []string { 270 var comments []string 271 if doc != nil { 272 for _, comment := range doc.List { 273 comments = append(comments, strings.TrimSpace(strings.TrimPrefix(comment.Text, "//"))) 274 } 275 } 276 return comments 277 } 278 279 // NewInterfaceCollector initializes an InterfaceCollector 280 func NewInterfaceCollector(exprString func(ast.Expr) string) *InterfaceCollector { 281 return &InterfaceCollector{ 282 exprString: exprString, 283 } 284 } 285 286 // BuildInterfaceCollector initializes an InterfaceCollector and collects interfaces 287 func BuildInterfaceCollector(file string, exprString func(ast.Expr) string) InterfaceCollector { 288 ic := NewInterfaceCollector(exprString) 289 fset := token.NewFileSet() 290 root, err := parser.ParseFile(fset, file, nil, parser.ParseComments) 291 if err != nil { 292 logrus.Panicln(err) 293 } 294 ic.cmap = ast.NewCommentMap(fset, root, root.Comments) 295 ast.Walk(ic, root) 296 return *ic 297 }