go-quasar-partial-ssr/backend/pkg/ts-rpc/tsInfo.go

476 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"
"slices"
"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 (i *TSInfo) Populate(path string, excludedPackages ExcudedPackages) {
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
skip := false
for _, excluded := range excludedPackages {
if pkg == excluded {
skip = true
break
}
}
if skip {
continue
}
// initialize package info if not exists
if _, ok := i.Packages[pkg]; !ok {
ip := TSInfoPakage{}
ip.MakeMaps()
i.Packages[pkg] = ip
}
// start parsing files
for _, n := range loadedPkg.CompiledGoFiles {
// search for declarative // Typescript: declarations and endpoints
i.parseTypescriptDeclarations(pkg, n)
}
// 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)
}
}
}
// estrae le definizioni typescript // Typescript: ...
func (ts *TSInfo) parseTypescriptDeclarations(p string, n string) {
pkg := ts.Packages[p]
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)
// scansione del file per trovare le dichiarazioni typescript
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
// è una definizione typescript?
if strings.Contains(line, "// Typescript:") {
// typescript type, ad esempio: // Typescript: TStype=MyType=string | number
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
}
}
// dicchiarazione typescript, ad esempio: // Typescript: TSDeclaration=MyType=string | number
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
}
}
// dichiarazione endpoint, ad esempio: // Typescript: TSEndpoint= path=/api/auth/login; name=login; method=POST; request=users.LoginRequest; response=tokens.TokenPair
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) 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")
}
for _, spec := range t.Decl.Specs {
if len(t.Consts) > 0 {
i.getConst(p, t.Consts[0])
}
switch spec.(type) {
case *ast.TypeSpec:
typeSpec := spec.(*ast.TypeSpec)
switch typeSpec.Type.(type) {
case *ast.StructType:
v := TSStruct{
Name: typeSpec.Name.Name,
Typescript: false,
Fields: []TSSField{},
}
v.getStruct(typeSpec)
if len(v.Fields) == 0 {
continue
}
i.Packages[p].structs[typeSpec.Name.Name] = v
for _, imp := range v.Imports {
a := strings.Split(imp, ".")
if len(a) == 2 {
if _, ok := i.Packages[p].imports[a[0]]; !ok {
i.Packages[p].imports[a[0]] = []string{}
}
if !slices.Contains(i.Packages[p].imports[a[0]], a[1]) {
i.Packages[p].imports[a[0]] = append(i.Packages[p].imports[a[0]], a[1])
}
}
}
case *ast.Ident:
tsInfo := getFieldTsInfo(typeSpec.Type)
t := TSType{
Name: typeSpec.Name.Name,
Typescript: true,
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
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: true,
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 (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, "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: true,
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 (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 (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("\n??Endpoint: request %s not found\n", v1.Request)
}
_, f = i.findAvailableStruct(p, v1.Response, 0)
if !f {
fmt.Printf("\n??Endpoint: response %s not found\n", v1.Response)
}
}
}
}