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}