424 lines
10 KiB
Go
424 lines
10 KiB
Go
// exportable typescript generated from golang
|
|
// Copyright (C) 2022 Fabio Prada
|
|
|
|
package tsrpc
|
|
|
|
import (
|
|
"fmt"
|
|
"go/ast"
|
|
"go/doc"
|
|
"go/parser"
|
|
"go/token"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
type TSInfoPakage struct {
|
|
structs map[string]TSStruct
|
|
types map[string]TSType
|
|
enums map[string]TSEnum
|
|
consts map[string]TSConst
|
|
decs map[string]TSDec
|
|
endpoints map[string]TSEndpoint
|
|
}
|
|
|
|
type TSDec struct {
|
|
Name string
|
|
Value string
|
|
SourceInfo string
|
|
}
|
|
|
|
type TSType struct {
|
|
Name string
|
|
Type string
|
|
TsType string
|
|
Typescript bool
|
|
dependOn bool
|
|
SourceInfo string
|
|
}
|
|
|
|
type TSInfo struct {
|
|
Packages map[string]TSInfoPakage
|
|
}
|
|
|
|
type TSConst struct {
|
|
Name string
|
|
Value string
|
|
}
|
|
|
|
type TSEnum struct {
|
|
Name string
|
|
Info []TSEnumInfo
|
|
}
|
|
|
|
type TSEnumInfo struct {
|
|
Key string
|
|
Value string
|
|
}
|
|
|
|
// / exporter
|
|
type TSModuleInfo struct {
|
|
structs map[string]string
|
|
types map[string]string
|
|
}
|
|
|
|
type TSSourceLine struct {
|
|
Pos int
|
|
End int
|
|
Line int
|
|
Source string
|
|
}
|
|
|
|
type TSSourceFile struct {
|
|
Source string
|
|
Name string
|
|
Lines []TSSourceLine
|
|
Len int
|
|
}
|
|
|
|
func (ts *TSInfo) findStruct(p string, n string) bool {
|
|
|
|
if _, ok := ts.Packages[p]; ok {
|
|
if n == "" || IsNativeType(n) {
|
|
return true
|
|
}
|
|
n = strings.TrimPrefix(n, "[]")
|
|
n = strings.TrimPrefix(n, "*")
|
|
if _, ok := ts.Packages[p].structs[n]; ok {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (ts *TSInfo) findType(p string, n string) bool {
|
|
if _, ok := ts.Packages[p]; ok {
|
|
if n == "" || IsNativeType(n) {
|
|
return true
|
|
}
|
|
n = strings.TrimPrefix(n, "[]")
|
|
n = strings.TrimPrefix(n, "*")
|
|
if _, ok := ts.Packages[p].types[n]; ok {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (ts TSInfo) setTypescript(p string, n string, v bool) bool {
|
|
if !ts.findStruct(p, n) {
|
|
return false
|
|
}
|
|
if s, ok := ts.Packages[p].structs[n]; ok {
|
|
s.Typescript = v
|
|
ts.Packages[p].structs[n] = s // write back
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (ts TSInfo) find(p string, n string) bool {
|
|
return ts.findType(p, n) || ts.findStruct(p, n)
|
|
}
|
|
|
|
// popola TsInfo con tutte le definizioni dei tipi
|
|
|
|
func (i *TSInfo) getConst(p string, c *doc.Value, src []TSSourceFile) {
|
|
var isTypescript = strings.HasPrefix(c.Doc, "Typescript:")
|
|
if isTypescript {
|
|
command := strings.TrimPrefix(c.Doc, "Typescript:")
|
|
command = strings.TrimSpace(command)
|
|
command = strings.Trim(command, "\n")
|
|
|
|
if strings.Contains(command, "enum=") {
|
|
enumName := strings.TrimPrefix(command, "enum=")
|
|
enum := TSEnum{
|
|
Name: enumName,
|
|
Info: []TSEnumInfo{},
|
|
}
|
|
d := c.Decl
|
|
iota := false
|
|
iotaValue := 0
|
|
for _, s := range d.Specs {
|
|
v := s.(*ast.ValueSpec) // safe because decl.Tok == token.CONST
|
|
if len(v.Values) > 0 {
|
|
be, ok := v.Values[0].(*ast.BinaryExpr)
|
|
if ok {
|
|
x := be.X.(*ast.BasicLit)
|
|
exitOnError(fmt.Errorf("enum binary expression not implemented %s %s %s AT: %s", x.Value, be.Op.String(), be.Y, getSourceInfo(int(x.ValuePos), src)))
|
|
}
|
|
ident, ok := v.Values[0].(*ast.Ident)
|
|
if ok {
|
|
if ident.Name == "iota" {
|
|
iota = true
|
|
iotaValue = v.Names[0].Obj.Data.(int)
|
|
enum.Info = append(enum.Info, TSEnumInfo{Key: v.Names[0].Name, Value: fmt.Sprintf("%d", iotaValue)})
|
|
}
|
|
}
|
|
list, ok := v.Values[0].(*ast.BasicLit)
|
|
if ok {
|
|
enum.Info = append(enum.Info, TSEnumInfo{Key: v.Names[0].Name, Value: list.Value})
|
|
}
|
|
} else {
|
|
for _, name := range v.Names {
|
|
if iota {
|
|
iotaValue++
|
|
enum.Info = append(enum.Info, TSEnumInfo{Key: name.Name, Value: fmt.Sprintf("%d", iotaValue)})
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
i.Packages[p].enums[enumName] = enum
|
|
t1 := TSType{
|
|
Name: enumName,
|
|
Typescript: true,
|
|
Type: "",
|
|
TsType: fmt.Sprintf("typeof Enum%s[keyof typeof Enum%s] ", enumName, enumName), //getFieldTsInfo(expr.Type),
|
|
dependOn: false,
|
|
SourceInfo: "",
|
|
}
|
|
i.Packages[p].types[enumName] = t1
|
|
}
|
|
|
|
if strings.Contains(command, "const") {
|
|
d := c.Decl
|
|
for _, s := range d.Specs {
|
|
v := s.(*ast.ValueSpec)
|
|
if len(v.Names) == 0 || len(v.Values) == 0 {
|
|
continue
|
|
}
|
|
c := TSConst{
|
|
Name: v.Names[0].Name,
|
|
Value: v.Values[0].(*ast.BasicLit).Value,
|
|
}
|
|
i.Packages[p].consts[c.Name] = c
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
func (i *TSInfo) getType(p string, t *doc.Type, src []TSSourceFile) {
|
|
var isType = t.Decl.Tok == token.TYPE && t.Doc == ""
|
|
var isTypescript = strings.HasPrefix(t.Doc, "Typescript:")
|
|
command := ""
|
|
param := ""
|
|
if isTypescript {
|
|
//fmt.Println(t.Doc)
|
|
command = strings.TrimPrefix(t.Doc, "Typescript:")
|
|
command = strings.TrimSpace(command)
|
|
command = strings.Trim(command, "\n")
|
|
|
|
if strings.Contains(command, "=") {
|
|
a := strings.Split(command, "=")
|
|
if len(a) == 2 {
|
|
param = a[1]
|
|
}
|
|
command = strings.Trim(a[0], " ")
|
|
}
|
|
}
|
|
if isType {
|
|
//fmt.Println(t.Doc)
|
|
command = "interface"
|
|
}
|
|
for _, spec := range t.Decl.Specs {
|
|
if len(t.Consts) > 0 {
|
|
i.getConst(p, t.Consts[0], src)
|
|
continue
|
|
}
|
|
switch spec.(type) {
|
|
case *ast.TypeSpec:
|
|
typeSpec := spec.(*ast.TypeSpec)
|
|
switch typeSpec.Type.(type) {
|
|
case *ast.StructType:
|
|
if (isTypescript || isType) && command != "interface" {
|
|
exitOnError(fmt.Errorf("mismatch delaration for interface %s AT: %s", t.Doc, getSourceInfo(int(typeSpec.Name.NamePos), src)))
|
|
}
|
|
v := TSStruct{
|
|
Name: typeSpec.Name.Name,
|
|
Typescript: isTypescript,
|
|
Fields: []TSSField{},
|
|
SourceInfo: getSourceInfo(int(typeSpec.Name.NamePos), src),
|
|
}
|
|
v.getStruct(typeSpec, src)
|
|
i.Packages[p].structs[typeSpec.Name.Name] = v
|
|
default:
|
|
if isTypescript && command != "type" {
|
|
exitOnError(fmt.Errorf("mismatch delaration for type %s AT: %s", t.Doc, getSourceInfo(int(typeSpec.Name.NamePos), src)))
|
|
}
|
|
tsInfo := getFieldTsInfo(typeSpec.Type)
|
|
if command == "type" && param != "" {
|
|
tsInfo = param
|
|
}
|
|
t := TSType{
|
|
Name: typeSpec.Name.Name,
|
|
Typescript: isTypescript,
|
|
Type: getFieldInfo(typeSpec.Type),
|
|
TsType: tsInfo,
|
|
dependOn: toBeImported(typeSpec.Type),
|
|
SourceInfo: getSourceInfo(int(typeSpec.Name.NamePos), src),
|
|
}
|
|
//fmt.Printf("Type found: %s Type: %s TsType: %s AT: %s\n", t.Name, t.Type, t.TsType, t.SourceInfo)
|
|
i.Packages[p].types[typeSpec.Name.Name] = t
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (i *TSInfo) Populate(path string) {
|
|
i.Packages = make(map[string]TSInfoPakage)
|
|
err := filepath.Walk(path,
|
|
func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if info.IsDir() {
|
|
fset := token.NewFileSet()
|
|
packages, err := parser.ParseDir(fset, path, nil, parser.ParseComments)
|
|
|
|
if err != nil {
|
|
exitOnError(err)
|
|
}
|
|
|
|
for pkg, f := range packages {
|
|
if _, ok := i.Packages[pkg]; !ok {
|
|
i.Packages[pkg] = TSInfoPakage{structs: make(map[string]TSStruct), types: make(map[string]TSType), enums: make(map[string]TSEnum), consts: make(map[string]TSConst), decs: make(map[string]TSDec), endpoints: make(map[string]TSEndpoint)}
|
|
}
|
|
if pkg == "typescript" || pkg == "tsrpc" {
|
|
continue
|
|
}
|
|
|
|
var src = []TSSourceFile{}
|
|
for n := range f.Files {
|
|
|
|
dat, err := os.ReadFile(n)
|
|
if err == nil {
|
|
lines := []TSSourceLine{}
|
|
pos := 0
|
|
line := 1
|
|
for p, k := range dat {
|
|
if string(k) == "\n" {
|
|
l := TSSourceLine{
|
|
Pos: pos,
|
|
End: p,
|
|
Line: line,
|
|
Source: string(dat[pos:p]),
|
|
}
|
|
lines = append(lines, l)
|
|
pos = p + 1
|
|
line++
|
|
if strings.Contains(l.Source, "// Typescript:") {
|
|
|
|
if strings.Contains(l.Source, "TStype=") {
|
|
p := strings.Index(l.Source, "TStype=")
|
|
s := strings.Trim(l.Source[p+len("TStype="):], " ")
|
|
a := strings.Split(s, "=")
|
|
if len(a) == 2 {
|
|
t := TSType{
|
|
Name: strings.Trim(a[0], " "),
|
|
Typescript: true,
|
|
Type: "UserDefined",
|
|
TsType: strings.Trim(a[1], " "),
|
|
dependOn: false,
|
|
SourceInfo: getSourceInfo(int(l.Pos), src),
|
|
}
|
|
i.Packages[pkg].types[strings.Trim(a[0], " ")] = t
|
|
}
|
|
|
|
}
|
|
if strings.Contains(l.Source, "TSDeclaration=") {
|
|
p := strings.Index(l.Source, "TSDeclaration=")
|
|
s := strings.Trim(l.Source[p+len("TSDeclaration="):], " ")
|
|
a := strings.Split(s, "=")
|
|
if len(a) == 2 {
|
|
t := TSDec{
|
|
Name: strings.Trim(a[0], " "),
|
|
Value: strings.Trim(a[1], " "),
|
|
SourceInfo: getSourceInfo(int(l.Pos), src),
|
|
}
|
|
i.Packages[pkg].decs[strings.Trim(a[0], " ")] = t
|
|
}
|
|
}
|
|
|
|
if strings.Contains(l.Source, "TSEndpoint= ") {
|
|
e := ParseEndpoint(l.Source, n, l.Line)
|
|
if _, ok := i.Packages[pkg].endpoints[e.Name]; ok {
|
|
exitOnError(fmt.Errorf("enpoint name %s allready in use: %s", e.Name, l.Source))
|
|
}
|
|
|
|
i.Packages[pkg].endpoints[e.Name] = e
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
s := TSSourceFile{
|
|
Name: n,
|
|
Source: string(dat),
|
|
Len: len(dat),
|
|
Lines: lines,
|
|
}
|
|
src = append(src, s)
|
|
}
|
|
}
|
|
|
|
p := doc.New(f, "./", 0)
|
|
|
|
for _, t := range p.Types {
|
|
i.getType(pkg, t, src)
|
|
}
|
|
|
|
for _, c := range p.Consts {
|
|
i.getConst(pkg, c, src)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func (ts *TSInfo) findAvailableStruct(n string) (TSStruct, bool) {
|
|
a := strings.Split(n, ".")
|
|
if len(a) == 2 {
|
|
if n == "[]MailDebugItem" {
|
|
fmt.Printf("MailDebugItem found\n")
|
|
}
|
|
a[1] = strings.TrimPrefix(a[1], "[]")
|
|
a[1] = strings.TrimPrefix(a[1], "*")
|
|
if _, ok := ts.Packages[a[0]]; ok {
|
|
if _, ok := ts.Packages[a[0]].structs[a[1]]; ok {
|
|
return ts.Packages[a[0]].structs[a[1]], true
|
|
}
|
|
}
|
|
}
|
|
if n == "" || IsNativeType(n) {
|
|
return TSStruct{}, true
|
|
}
|
|
|
|
return TSStruct{}, false
|
|
}
|
|
|
|
func (i *TSInfo) TestEndpoints() {
|
|
for p := range i.Packages {
|
|
for _, v1 := range i.Packages[p].endpoints {
|
|
_, f := i.findAvailableStruct(v1.Request)
|
|
if !f {
|
|
fmt.Printf("??Endpoint: request %s not found\n", v1.Request)
|
|
}
|
|
_, f = i.findAvailableStruct(v1.Response)
|
|
if !f {
|
|
fmt.Printf("??Endpoint: response %s not found\n", v1.Response)
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|