GitRoot
Craft your forge, Build your project, Grow your community freely
1// SPDX-FileCopyrightText: 2026 Romain Maneschi <romain@gitroot.dev>
2//
3// SPDX-License-Identifier: EUPL-1.2
4
5package main
6
7import (
8 "fmt"
9 "go/ast"
10 "go/token"
11 "go/types"
12 "os"
13 "strings"
14
15 "golang.org/x/tools/go/packages"
16)
17
18func Parse(targetDir string) *Generate {
19 cfg := &packages.Config{
20 Dir: targetDir,
21 Mode: packages.NeedName | packages.NeedFiles | packages.NeedSyntax | packages.NeedTypes | packages.NeedTypesInfo,
22 Tests: false,
23 }
24
25 pkgs, err := packages.Load(cfg, "./...")
26 if err != nil {
27 fmt.Printf("🛑 Can't load dir %s : %v\n", targetDir, err)
28 os.Exit(1)
29 }
30
31 return Transform(targetDir, pkgs)
32}
33
34func Transform(targetDir string, pkgs []*packages.Package) *Generate {
35
36 generate := NewGeneration(targetDir)
37
38 for _, pkg := range pkgs {
39 if len(pkg.Errors) > 0 {
40 fmt.Printf("🛑 Pkg error %s : %v\n", pkg.PkgPath, pkg.Errors)
41 continue
42 }
43
44 fmt.Printf("🏁 Start pkg %s\n", pkg.PkgPath)
45
46 for _, fileAST := range pkg.Syntax {
47 pos := pkg.Fset.Position(fileAST.Package)
48 filename := pos.Filename
49
50 relativeSubPath := strings.TrimPrefix(filename, targetDir)
51 found := false
52 if relativeSubPath == filename {
53 fmt.Printf("⏩ Skip (outside pkg) file %s\n", filename)
54 found = true
55 } else {
56 for _, f := range fileToIgnore {
57 if f == relativeSubPath {
58 fmt.Printf("⏩ Skip (by conf) file %s\n", filename)
59 found = true
60 break
61 }
62 }
63 }
64 if found {
65 continue
66 }
67
68 fmt.Printf("🏁 Start file %s\n", filename)
69
70 currentFile := generate.getOrCreateFile(filename)
71
72 ast.Inspect(fileAST, func(n ast.Node) bool {
73 switch x := n.(type) {
74
75 case *ast.TypeSpec:
76 if _, ok := x.Type.(*ast.StructType); ok {
77 if !ast.IsExported(x.Name.Name) {
78 return true
79 }
80
81 typeNameObj := pkg.TypesInfo.Defs[x.Name].(*types.TypeName)
82 structObj := typeNameObj.Type().Underlying().(*types.Struct)
83
84 var fields []*types.Var
85 for i := 0; i < structObj.NumFields(); i++ {
86 field := structObj.Field(i)
87 if field.Exported() {
88 fields = append(fields, field)
89 }
90 }
91
92 generate.structLocations[x.Name.Name] = filename
93
94 currentFile.Structs = append(currentFile.Structs, Struct{
95 TypeName: x.Name.Name,
96 Doc: x.Doc,
97 Fields: fields,
98 })
99 }
100 case *ast.GenDecl:
101 if x.Tok == token.TYPE {
102 for _, spec := range x.Specs {
103 typeSpec, ok := spec.(*ast.TypeSpec)
104 if !ok || !ast.IsExported(typeSpec.Name.Name) {
105 continue
106 }
107 if _, isStruct := typeSpec.Type.(*ast.StructType); !isStruct {
108 typeNameObj := pkg.TypesInfo.Defs[typeSpec.Name].(*types.TypeName)
109 underlying := typeNameObj.Type().Underlying().String()
110
111 if underlying == "string" || strings.HasPrefix(underlying, "int") {
112 generate.structLocations[typeSpec.Name.Name] = filename
113 currentFile.Enums = append(currentFile.Enums, Enum{
114 TypeName: typeSpec.Name.Name,
115 TypeType: underlying,
116 Values: []string{},
117 })
118 }
119 }
120 }
121 }
122
123 if x.Tok == token.CONST {
124 for _, spec := range x.Specs {
125 valueSpec, ok := spec.(*ast.ValueSpec)
126 if !ok {
127 continue
128 }
129
130 if valueSpec.Type != nil {
131 if ident, ok := valueSpec.Type.(*ast.Ident); ok {
132 for i := range currentFile.Enums {
133 if currentFile.Enums[i].TypeName == ident.Name {
134 for _, name := range valueSpec.Names {
135 if ast.IsExported(name.Name) {
136 currentFile.Enums[i].Values = append(currentFile.Enums[i].Values, name.Name)
137 }
138 }
139 }
140 }
141 }
142 }
143 }
144 }
145 case *ast.FuncDecl:
146 if !ast.IsExported(x.Name.Name) && !strings.HasPrefix(x.Name.Name, "_") {
147 return true
148 }
149 if strings.HasSuffix(x.Name.Name, "FromString") {
150 return true
151 }
152
153 funcObj := pkg.TypesInfo.Defs[x.Name].(*types.Func)
154 sig := funcObj.Type().(*types.Signature)
155
156 wasmDir := WasmNone
157 if x.Doc != nil {
158 for _, comment := range x.Doc.List {
159 if strings.HasPrefix(comment.Text, "//go:wasmexport") {
160 wasmDir = WasmExport
161 break
162 } else if strings.HasPrefix(comment.Text, "//go:wasmimport") {
163 wasmDir = WasmImport
164 break
165 }
166 }
167 }
168
169 if x.Recv == nil {
170 currentFile.Funcs = append(currentFile.Funcs, Func{
171 Name: x.Name.Name,
172 Doc: x.Doc,
173 Sig: sig,
174 Wasm: wasmDir,
175 })
176 } else {
177 recvType := sig.Recv().Type()
178 if ptr, ok := recvType.(*types.Pointer); ok {
179 recvType = ptr.Elem()
180 }
181 structName := "Unknown"
182 if named, ok := recvType.(*types.Named); ok {
183 structName = named.Obj().Name()
184 }
185
186 currentFile.Methods = append(currentFile.Methods, Method{
187 StructName: structName,
188 Name: x.Name.Name,
189 Doc: x.Doc,
190 Sig: sig,
191 Wasm: wasmDir,
192 })
193 }
194 }
195 return true
196 })
197 }
198 }
199 return generate
200}