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/types"
10 "path/filepath"
11 "slices"
12 "strings"
13
14 "github.com/gobeam/stringy"
15)
16
17type Ts struct{}
18
19func (Ts) Extension() string {
20 return ".ts"
21}
22
23func (t Ts) TemplateStruct(g *Generate, f *FileToGen, st Struct) (string, []string) {
24 fieldStrings := []string{}
25 imports := []string{}
26 for _, field := range st.Fields {
27 typeStr, imp := t.TypeToLang(field.Type(), f.OriginFilePath, g.structLocations)
28 if imp != "" && !slices.Contains(imports, imp) {
29 imports = append(imports, imp)
30 }
31 fieldName := stringy.New(field.Name()).SnakeCase().CamelCase().Get()
32 fieldStrings = append(fieldStrings, fmt.Sprintf(" public %s: %s,", fieldName, typeStr))
33 }
34 structName := stringy.New(st.TypeName).PascalCase().Get()
35
36 return fmt.Sprintf(`@json
37export class %s {
38 constructor(
39%s
40 ) {}
41}
42`, structName, strings.Join(fieldStrings, "\n")), imports
43}
44
45func (Ts) TemplateEnum(en Enum, variants []string) string {
46 enumName := stringy.New(en.TypeName)
47 enumBuilder := strings.Builder{}
48
49 enumBuilder.WriteString(fmt.Sprintf("export namespace %s {\n", enumName.PascalCase().Get()))
50 for _, e := range variants {
51 estringy := stringy.New(e)
52 enumBuilder.WriteString(fmt.Sprintf(" export const %s = \"%s\";\n", estringy.PascalCase().Get(), estringy.ToLower()))
53 }
54 enumBuilder.WriteString("}\n\n")
55 enumBuilder.WriteString(fmt.Sprintf("export type %s = string;\n", enumName.PascalCase().Get()))
56
57 return enumBuilder.String()
58}
59
60func (ts Ts) FormatFuncName(fn Func) string {
61 return fn.Name
62}
63
64func (ts Ts) FormatParams(sig *types.Signature, isMethod bool, currentFile string, locations map[string]string) (string, []string) {
65 var params []string
66 var imports []string
67
68 p := sig.Params()
69 for i := 0; i < p.Len(); i++ {
70 v := p.At(i)
71 typeStr, imp := ts.TypeToLang(v.Type(), currentFile, locations)
72 if imp != "" {
73 imports = append(imports, imp)
74 }
75
76 name := v.Name()
77 if name == "" {
78 name = fmt.Sprintf("arg%d", i)
79 } else {
80 name = stringy.New(name).PascalCase().Get()
81 }
82
83 params = append(params, fmt.Sprintf("%s: %s", name, typeStr))
84 }
85
86 return strings.Join(params, ", "), imports
87}
88
89func (ts Ts) FormatReturnType(sig *types.Signature, currentFile string, locations map[string]string) string {
90 res := sig.Results()
91 if res.Len() == 0 {
92 return ""
93 } else {
94 typeStr, _ := ts.TypeToLang(res.At(0).Type(), currentFile, locations)
95 return fmt.Sprintf(": %s", typeStr)
96 }
97}
98
99func (ts Ts) FormatExportFunc(wasmName string, funcName string) string {
100 return fmt.Sprintf(`export function %s(ptr: i32, size: i32): void {
101 if (__server && __server!.plugin) {
102 const obj = JSON.parse<??>(String.UTF8.decodeUnsafe(ptr, size));
103 __server!.plugin!.%s(obj);
104 }
105}
106
107`, wasmName, funcName)
108}
109
110func (Ts) FormatImportFunc(wasmName string, funcName string, paramsList string, returnType string) string {
111 return fmt.Sprintf(`@external("gitroot", "%s")
112export declare function _%s(%s): %s
113`, wasmName, funcName, paramsList, returnType)
114}
115
116func (ts Ts) TypeToLang(t types.Type, currentFile string, locations map[string]string) (string, string) {
117 if sig, ok := t.(*types.Signature); ok && sig.Recv() == nil {
118 var cbParams []string
119 var cbImports []string
120
121 p := sig.Params()
122 for i := 0; i < p.Len(); i++ {
123 pType, pImp := ts.TypeToLang(p.At(i).Type(), currentFile, locations)
124 if pImp != "" {
125 cbImports = append(cbImports, pImp)
126 }
127 cbParams = append(cbParams, pType)
128 }
129
130 rustCallbackType := fmt.Sprintf("(%s) => void", strings.Join(cbParams, ", "))
131 return rustCallbackType, strings.Join(cbImports, "\n")
132 }
133
134 if slice, ok := t.(*types.Slice); ok {
135 elemType, imp := ts.TypeToLang(slice.Elem(), currentFile, locations)
136 return fmt.Sprintf("Array<%s>", elemType), imp
137 }
138
139 if ptr, ok := t.(*types.Pointer); ok {
140 return ts.TypeToLang(ptr.Elem(), currentFile, locations)
141 }
142
143 if named, ok := t.(*types.Named); ok {
144 obj := named.Obj()
145 typeName := stringy.New(obj.Name()).PascalCase().Get()
146
147 originFile, exists := locations[obj.Name()]
148 if exists {
149 if originFile == currentFile {
150 return typeName, ""
151 }
152
153 moduleName := strings.TrimSuffix(filepath.Base(originFile), ".go")
154 rustImport := fmt.Sprintf(`import { %s } from "./%s";`, typeName, moduleName)
155 return typeName, rustImport
156 }
157 }
158
159 switch t.String() {
160 case "string":
161 return "string", ""
162 case "map[string]string":
163 return "Map<string, string>", ""
164 case "map[string]any":
165 return "Map<string, string>", ""
166 case "uint64":
167 return "u64", ""
168 case "uint32":
169 return "u32", ""
170 case "int32":
171 return "u32", ""
172 case "int":
173 return "u32", ""
174 case "bool":
175 return "bool", ""
176 case "time.Time":
177 return "Date", ""
178 }
179
180 fmt.Printf("🐞 Type not found %s\n", t.String())
181
182 return t.String(), ""
183}