459 lines
11 KiB
Go
459 lines
11 KiB
Go
// exportable typescript generated from golang
|
|
// Copyright (C) 2022 Fabio Prada
|
|
|
|
package tsrpc
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"go/ast"
|
|
"go/doc"
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"strings"
|
|
|
|
"golang.org/x/tools/go/packages"
|
|
)
|
|
|
|
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
|
|
imports map[string][]string
|
|
isUsed bool
|
|
}
|
|
|
|
type TSDec struct {
|
|
Name string
|
|
Value string
|
|
}
|
|
|
|
type TSType struct {
|
|
Name string
|
|
Type string
|
|
TsType string
|
|
Typescript bool
|
|
dependOn bool
|
|
}
|
|
|
|
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) {
|
|
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, "type") {
|
|
fmt.Printf("TypeScript type declaration found in const s, but type parsing is not implemented yet\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", x.Value, be.Op.String(), be.Y))
|
|
}
|
|
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: false,
|
|
Type: "",
|
|
TsType: fmt.Sprintf("typeof Enum%s[keyof typeof Enum%s] ", enumName, enumName), //getFieldTsInfo(expr.Type),
|
|
dependOn: false,
|
|
}
|
|
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
|
|
}
|
|
}
|
|
|
|
if strings.Contains(command, "type") {
|
|
fmt.Printf("TypeScript type declaration found in const s, but type parsing is not implemented yet\n")
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
func (i *TSInfo) getType(p string, t *doc.Type) {
|
|
var isTypescript = strings.HasPrefix(t.Doc, "Typescript:")
|
|
if isTypescript {
|
|
command := strings.TrimPrefix(t.Doc, "Typescript:")
|
|
command = strings.TrimSpace(command)
|
|
command = strings.Trim(command, "\n")
|
|
|
|
if strings.Contains(command, "interface") {
|
|
fmt.Printf("TypeScript interface declaration found in type s, but interface parsing is not implemented yet\n")
|
|
}
|
|
|
|
if strings.Contains(command, "type") {
|
|
fmt.Printf("TypeScript type declaration found in type s, but type parsing is not implemented yet\n")
|
|
}
|
|
|
|
if strings.Contains(command, "enum=") {
|
|
fmt.Printf("TypeScript enum declaration found in type s, but enum parsing is not implemented yet\n")
|
|
}
|
|
}
|
|
for _, spec := range t.Decl.Specs {
|
|
/* if len(t.Consts) > 0 {
|
|
i.getConst(p, t.Consts[0])
|
|
continue
|
|
} */
|
|
switch spec.(type) {
|
|
case *ast.TypeSpec:
|
|
typeSpec := spec.(*ast.TypeSpec)
|
|
switch typeSpec.Type.(type) {
|
|
case *ast.StructType:
|
|
// if 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: false,
|
|
Fields: []TSSField{},
|
|
SourceInfo: "",
|
|
}
|
|
v.getStruct(typeSpec)
|
|
i.Packages[p].structs[typeSpec.Name.Name] = v
|
|
default:
|
|
// if isType && command != "interface" {
|
|
// exitOnError(fmt.Errorf("mismatch delaration for interface %s AT: %s", t.Doc, getSourceInfo(int(typeSpec.Name.NamePos), src)))
|
|
// }
|
|
tsInfo := getFieldTsInfo(typeSpec.Type)
|
|
t := TSType{
|
|
Name: typeSpec.Name.Name,
|
|
Typescript: false,
|
|
Type: getFieldInfo(typeSpec.Type),
|
|
TsType: tsInfo,
|
|
dependOn: toBeImported(typeSpec.Type),
|
|
}
|
|
//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 parseTypescriptDeclarations(n string, pkg TSInfoPakage) {
|
|
f, err := os.OpenFile(n, os.O_RDONLY, os.ModePerm)
|
|
if err != nil {
|
|
log.Fatalf("open file error: %v", err)
|
|
return
|
|
}
|
|
defer f.Close()
|
|
l := 1
|
|
dat := ""
|
|
lines := []TSSourceLine{}
|
|
rd := bufio.NewReader(f)
|
|
for {
|
|
line, err := rd.ReadString('\n')
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
|
|
log.Fatalf("read file line error: %v", err)
|
|
return
|
|
}
|
|
lines = append(lines, TSSourceLine{Pos: l, End: l + len(line), Line: l, Source: line})
|
|
l++
|
|
dat += line
|
|
|
|
if strings.Contains(line, "// Typescript:") {
|
|
if strings.Contains(line, "TStype=") {
|
|
p := strings.Index(line, "TStype=")
|
|
s := strings.Trim(line[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,
|
|
}
|
|
pkg.types[strings.Trim(a[0], " ")] = t
|
|
}
|
|
|
|
}
|
|
if strings.Contains(line, "TSDeclaration=") {
|
|
p := strings.Index(line, "TSDeclaration=")
|
|
s := strings.Trim(line[p+len("TSDeclaration="):], " ")
|
|
a := strings.Split(s, "=")
|
|
if len(a) == 2 {
|
|
t := TSDec{
|
|
Name: strings.Trim(a[0], " "),
|
|
Value: strings.Trim(a[1], " "),
|
|
}
|
|
pkg.decs[strings.Trim(a[0], " ")] = t
|
|
}
|
|
}
|
|
|
|
if strings.Contains(line, "TSEndpoint= ") {
|
|
e := ParseEndpoint(line, n, l)
|
|
if _, ok := pkg.endpoints[e.Name]; ok {
|
|
exitOnError(fmt.Errorf("enpoint name %s allready in use: %s", e.Name, line))
|
|
}
|
|
pkg_info := pkg
|
|
pkg_info.endpoints[e.Name] = e
|
|
pkg_info.imports = e.Imports
|
|
pkg_info.isUsed = true
|
|
pkg = pkg_info
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
func (i *TSInfo) Populate(path string) {
|
|
i.Packages = make(map[string]TSInfoPakage)
|
|
cfg := &packages.Config{
|
|
Mode: packages.NeedName |
|
|
packages.NeedFiles |
|
|
packages.NeedCompiledGoFiles |
|
|
packages.NeedSyntax,
|
|
Dir: path,
|
|
}
|
|
|
|
pkgs, err := packages.Load(cfg, "./...")
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
if packages.PrintErrors(pkgs) > 0 {
|
|
log.Fatal("package loading failed")
|
|
}
|
|
|
|
//to get line info for endpoints and typescript declarations
|
|
for _, loadedPkg := range pkgs {
|
|
pkg := loadedPkg.Name
|
|
// skip packages
|
|
if pkg == "typescript" || pkg == "tsrpc" {
|
|
continue
|
|
}
|
|
// initialize package info if not exists
|
|
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)}
|
|
}
|
|
// start parsing files
|
|
for _, n := range loadedPkg.CompiledGoFiles {
|
|
// search for declarative // Typescript: declarations and endpoints
|
|
parseTypescriptDeclarations(n, i.Packages[pkg])
|
|
}
|
|
// parse doc to get all types and consts
|
|
docPkg, err := doc.NewFromFiles(loadedPkg.Fset, loadedPkg.Syntax, loadedPkg.PkgPath)
|
|
if err != nil {
|
|
exitOnError(err)
|
|
}
|
|
|
|
for _, t := range docPkg.Types {
|
|
i.getType(pkg, t)
|
|
}
|
|
|
|
for _, c := range docPkg.Consts {
|
|
i.getConst(pkg, c)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (ts *TSInfo) findAvailableStruct(pkg string, n string, deep int) (int, bool) {
|
|
a := strings.Split(n, ".")
|
|
if n == "" || IsNativeType(n) {
|
|
return deep, true
|
|
}
|
|
|
|
deep++
|
|
if deep > 10 {
|
|
exitOnError(fmt.Errorf("too much deep for struct %s", n))
|
|
}
|
|
|
|
if len(a) == 1 {
|
|
n = strings.TrimPrefix(n, "[]")
|
|
n = strings.TrimPrefix(n, "*")
|
|
if _, ok := ts.Packages[pkg].structs[n]; ok {
|
|
s := ts.Packages[pkg].structs[n]
|
|
s.Typescript = true
|
|
ts.Packages[pkg].structs[n] = s // write back
|
|
for _, v := range s.Fields {
|
|
deep, _ = ts.findAvailableStruct(pkg, v.Type, deep)
|
|
}
|
|
deep--
|
|
return deep, true
|
|
}
|
|
|
|
}
|
|
|
|
if len(a) == 2 {
|
|
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 {
|
|
s := ts.Packages[a[0]].structs[a[1]]
|
|
if s.Typescript {
|
|
deep--
|
|
return deep, true
|
|
}
|
|
s.Typescript = true
|
|
ts.Packages[a[0]].structs[a[1]] = s // write back
|
|
for _, v := range s.Fields {
|
|
ts.findAvailableStruct(pkg, v.Type, deep)
|
|
}
|
|
deep--
|
|
return deep, true
|
|
}
|
|
}
|
|
}
|
|
|
|
return deep, false
|
|
}
|
|
|
|
func (i *TSInfo) TestEndpoints() {
|
|
for p := range i.Packages {
|
|
for _, v1 := range i.Packages[p].endpoints {
|
|
_, f := i.findAvailableStruct(p, v1.Request, 0)
|
|
if !f {
|
|
fmt.Printf("??Endpoint: request %s not found\n", v1.Request)
|
|
}
|
|
_, f = i.findAvailableStruct(p, v1.Response, 0)
|
|
if !f {
|
|
fmt.Printf("??Endpoint: response %s not found\n", v1.Response)
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|