1
0

walker.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. package main
  2. import (
  3. "fmt"
  4. "go/ast"
  5. "go/parser"
  6. "go/token"
  7. "io/fs"
  8. "path/filepath"
  9. "strings"
  10. )
  11. type walkOverride struct {
  12. Field string
  13. Kind TypeKind
  14. }
  15. type packageRequest struct {
  16. Path string
  17. StructAllow map[string]bool
  18. AliasAllow map[string]bool
  19. Overrides map[string][]walkOverride
  20. }
  21. func walkPackages(requests []packageRequest) ([]Schema, []Alias, error) {
  22. fset := token.NewFileSet()
  23. var schemas []Schema
  24. var aliases []Alias
  25. for _, req := range requests {
  26. dir := req.Path
  27. pkgs, err := parser.ParseDir(fset, dir, func(fi fs.FileInfo) bool {
  28. return !strings.HasSuffix(fi.Name(), "_test.go")
  29. }, parser.ParseComments)
  30. if err != nil {
  31. return nil, nil, fmt.Errorf("parse %s: %w", dir, err)
  32. }
  33. for _, pkg := range pkgs {
  34. for _, file := range pkg.Files {
  35. for _, decl := range file.Decls {
  36. gen, ok := decl.(*ast.GenDecl)
  37. if !ok || gen.Tok != token.TYPE {
  38. continue
  39. }
  40. for _, spec := range gen.Specs {
  41. ts, ok := spec.(*ast.TypeSpec)
  42. if !ok {
  43. continue
  44. }
  45. if strct, ok := ts.Type.(*ast.StructType); ok {
  46. if req.StructAllow != nil && !req.StructAllow[ts.Name.Name] {
  47. continue
  48. }
  49. s := Schema{
  50. Name: ts.Name.Name,
  51. Package: pkg.Name,
  52. Doc: collectDoc(gen.Doc, ts.Doc),
  53. }
  54. overrides := req.Overrides[ts.Name.Name]
  55. for _, fld := range strct.Fields.List {
  56. for _, f := range buildFields(fld, overrides) {
  57. s.Fields = append(s.Fields, f)
  58. }
  59. }
  60. schemas = append(schemas, s)
  61. continue
  62. }
  63. if req.AliasAllow != nil && !req.AliasAllow[ts.Name.Name] {
  64. continue
  65. }
  66. aliases = append(aliases, Alias{
  67. Name: ts.Name.Name,
  68. Package: pkg.Name,
  69. Underlying: exprToType(ts.Type),
  70. })
  71. }
  72. }
  73. }
  74. }
  75. }
  76. return schemas, aliases, nil
  77. }
  78. func collectDoc(group ...*ast.CommentGroup) string {
  79. var b strings.Builder
  80. for _, g := range group {
  81. if g == nil {
  82. continue
  83. }
  84. for _, c := range g.List {
  85. line := strings.TrimPrefix(c.Text, "// ")
  86. line = strings.TrimPrefix(line, "//")
  87. b.WriteString(strings.TrimSpace(line))
  88. b.WriteByte('\n')
  89. }
  90. }
  91. return strings.TrimSpace(b.String())
  92. }
  93. func buildFields(fld *ast.Field, overrides []walkOverride) []Field {
  94. var fields []Field
  95. tag := ""
  96. if fld.Tag != nil {
  97. tag = fld.Tag.Value
  98. }
  99. jsonTag, validateTag, exampleTag, gormDash := parseStructTag(tag)
  100. if gormDash && jsonTag == "" {
  101. return nil
  102. }
  103. jsonName, omit, omitempty := parseJSONTag(jsonTag)
  104. if omit {
  105. return nil
  106. }
  107. validate := parseValidateTag(validateTag)
  108. doc := collectDoc(fld.Doc, fld.Comment)
  109. for _, n := range fld.Names {
  110. fname := jsonName
  111. if fname == "" {
  112. fname = lowerFirst(n.Name)
  113. }
  114. t := exprToType(fld.Type)
  115. for _, o := range overrides {
  116. if o.Field == n.Name || o.Field == jsonName {
  117. t = TypeRef{Kind: o.Kind}
  118. break
  119. }
  120. }
  121. fields = append(fields, Field{
  122. JSONName: fname,
  123. GoName: n.Name,
  124. Type: t,
  125. Optional: omitempty || isPointer(fld.Type),
  126. Validate: validate,
  127. Doc: doc,
  128. Example: exampleTag,
  129. })
  130. }
  131. if len(fld.Names) == 0 {
  132. fname := jsonName
  133. if fname == "" {
  134. fname = lowerFirst(exprIdentName(fld.Type))
  135. }
  136. t := exprToType(fld.Type)
  137. for _, o := range overrides {
  138. if o.Field == exprIdentName(fld.Type) || o.Field == jsonName {
  139. t = TypeRef{Kind: o.Kind}
  140. break
  141. }
  142. }
  143. fields = append(fields, Field{
  144. JSONName: fname,
  145. GoName: exprIdentName(fld.Type),
  146. Type: t,
  147. Optional: omitempty || isPointer(fld.Type),
  148. Validate: validate,
  149. Doc: doc,
  150. Example: exampleTag,
  151. })
  152. }
  153. return fields
  154. }
  155. func exprToType(expr ast.Expr) TypeRef {
  156. switch e := expr.(type) {
  157. case *ast.Ident:
  158. return identType(e.Name)
  159. case *ast.StarExpr:
  160. inner := exprToType(e.X)
  161. return TypeRef{Kind: KindRef, Name: "nullable", Inner: &inner}
  162. case *ast.ArrayType:
  163. elem := exprToType(e.Elt)
  164. return TypeRef{Kind: KindArray, Element: &elem}
  165. case *ast.MapType:
  166. k := exprToType(e.Key)
  167. v := exprToType(e.Value)
  168. return TypeRef{Kind: KindMap, Key: &k, Value: &v}
  169. case *ast.SelectorExpr:
  170. pkg := exprIdentName(e.X)
  171. name := e.Sel.Name
  172. if pkg == "json" && name == "RawMessage" {
  173. return TypeRef{Kind: KindAny}
  174. }
  175. if pkg == "time" && name == "Time" {
  176. return TypeRef{Kind: KindString, Name: "datetime"}
  177. }
  178. return TypeRef{Kind: KindRef, Name: name}
  179. case *ast.InterfaceType:
  180. return TypeRef{Kind: KindAny}
  181. default:
  182. return TypeRef{Kind: KindUnknown}
  183. }
  184. }
  185. func identType(name string) TypeRef {
  186. switch name {
  187. case "string":
  188. return TypeRef{Kind: KindString}
  189. case "bool":
  190. return TypeRef{Kind: KindBool}
  191. case "int", "int8", "int16", "int32", "int64",
  192. "uint", "uint8", "uint16", "uint32", "uint64":
  193. return TypeRef{Kind: KindInt}
  194. case "float32", "float64":
  195. return TypeRef{Kind: KindNumber}
  196. case "byte", "rune":
  197. return TypeRef{Kind: KindInt}
  198. case "any":
  199. return TypeRef{Kind: KindAny}
  200. default:
  201. return TypeRef{Kind: KindRef, Name: name}
  202. }
  203. }
  204. func isPointer(expr ast.Expr) bool {
  205. _, ok := expr.(*ast.StarExpr)
  206. return ok
  207. }
  208. func exprIdentName(expr ast.Expr) string {
  209. switch e := expr.(type) {
  210. case *ast.Ident:
  211. return e.Name
  212. case *ast.SelectorExpr:
  213. return e.Sel.Name
  214. case *ast.StarExpr:
  215. return exprIdentName(e.X)
  216. default:
  217. return ""
  218. }
  219. }
  220. func lowerFirst(s string) string {
  221. if s == "" {
  222. return s
  223. }
  224. return strings.ToLower(s[:1]) + s[1:]
  225. }
  226. func resolveRel(base, rel string) string {
  227. if filepath.IsAbs(rel) {
  228. return rel
  229. }
  230. return filepath.Clean(filepath.Join(base, rel))
  231. }