// 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) } } } }