gojs/ts.go
2024-06-26 12:04:45 +08:00

463 lines
14 KiB
Go

package gojs
import (
"apigo.cc/apigo/plugin"
"github.com/ssgo/u"
"path"
"reflect"
"regexp"
"runtime"
"strings"
)
type ArgInfo struct {
index int
isVariadic bool
isOutArg bool
isFunc bool
isSkip bool
isOptional bool
funcInArgs []ArgInfo
funcOutArgs []ArgInfo
Name string
Type string
}
var numberMatcher = regexp.MustCompile("[a-z0-9]*(int|float)[a-z0-9]*")
var mapMatcher = regexp.MustCompile("map\\[(\\w+)](\\w+)")
func makeArgTypeString(argType string) string {
if strings.Contains(argType, "interface {}") {
argType = strings.ReplaceAll(argType, "interface {}", "any")
}
if strings.HasPrefix(argType, "*") {
argType = argType[1:]
}
if strings.HasPrefix(argType, "[]") {
argType = argType[2:] + "[]"
}
if strings.Contains(argType, "int") || strings.Contains(argType, "float") {
argType = numberMatcher.ReplaceAllString(argType, "number")
}
if strings.Contains(argType, "bool") {
argType = strings.ReplaceAll(argType, "bool", "boolean")
}
if strings.Contains(argType, "booleanean") {
argType = strings.ReplaceAll(argType, "booleanean", "boolean")
}
if strings.HasPrefix(argType, "map[") {
argType = mapMatcher.ReplaceAllString(argType, "Map<$1, $2>")
}
//if strings.ContainsRune(argType, '.') {
// argType = argType[strings.LastIndexByte(argType, '.')+1:]
//}
if strings.HasPrefix(argType, "<-") {
argType = "any"
}
if argType == "" {
argType = "void"
}
return argType
}
func (argInfo *ArgInfo) String() string {
argType := argInfo.Type
if argInfo.isFunc {
argType = "(" + makeInArgsString(argInfo.funcInArgs) + ") => " + makeOutArgsString(argInfo.funcOutArgs)
} else {
argType = makeArgTypeString(argType)
}
if argInfo.isOutArg {
return argType
} else {
argName := argInfo.Name
if argName == "" {
argName = "arg" + u.String(argInfo.index+1)
}
if argInfo.isVariadic {
argName = "..." + argName
}
return argName + u.StringIf(argInfo.isOptional, "?: ", ": ") + argType
}
}
func makeInArgs(args []ArgInfo) []string {
arr := make([]string, 0)
for _, arg := range args {
if !arg.isSkip {
arr = append(arr, arg.String())
}
}
return arr
}
func makeInArgsString(args []ArgInfo) string {
return strings.Join(makeInArgs(args), ", ")
}
func makeOutArgsString(args []ArgInfo) string {
arr := makeInArgs(args)
if len(arr) == 0 {
return "void"
} else if len(arr) == 1 {
return arr[0]
} else {
return "[" + strings.Join(arr, ", ") + "]"
}
}
func makeFuncArgsNames(v reflect.Value, inArgs []ArgInfo, isMethod bool) {
var fp *runtime.Func
if v.IsValid() {
fp = runtime.FuncForPC(v.Pointer())
} else {
// no name for Interface
return
}
file, lineNo := fp.FileLine(fp.Entry())
if file != "<autogenerated>" && u.FileExists(file) {
lines, _ := u.ReadFileLines(file)
if len(lines) >= lineNo {
line := lines[lineNo-1]
if !strings.Contains(line, "func") && lineNo-2 >= 0 && strings.Contains(lines[lineNo-2], "func") {
line = lines[lineNo-2]
}
line = strings.ReplaceAll(line, "interface{}", "any")
pos := strings.Index(line, "func")
if pos != -1 {
line = strings.TrimSpace(line[pos+4:])
if isMethod {
// skip method this arg
pos = strings.Index(line, "(")
if pos != -1 {
line = strings.TrimSpace(line[pos+1:])
makeFuncArgsName(line, inArgs[1:])
}
} else {
makeFuncArgsName(line, inArgs)
}
}
}
}
}
func makeFuncArgsName(line string, inArgs []ArgInfo) {
pos := strings.Index(line, "(")
if pos != -1 {
line = strings.TrimSpace(line[pos+1:])
for i := 0; i < len(inArgs); i++ {
// find param name
pos = strings.Index(line, " ")
// support combined args a, b string
pos1 := strings.Index(line, ",")
if pos1 != -1 && pos1 < pos {
inArgs[i].Name = line[0:pos1]
line = strings.TrimSpace(line[pos1+1:])
continue
}
if pos != -1 {
inArgs[i].Name = line[0:pos]
line = strings.TrimSpace(line[pos+1:])
}
// skip inline func
if strings.HasPrefix(line, "func") {
line = strings.TrimSpace(line[4:])
leftQuotes := 0
quoteStarted := false
for pos = 0; pos < len(line); pos++ {
if line[pos] == '(' {
leftQuotes++
quoteStarted = true
continue
}
if quoteStarted && line[pos] == ')' {
leftQuotes--
}
if quoteStarted && leftQuotes == 0 {
break
}
}
makeFuncArgsName(line, inArgs[i].funcInArgs)
line = strings.TrimSpace(line[pos+1:])
}
// skip ,
pos = strings.Index(line, ",")
if pos != -1 {
line = strings.TrimSpace(line[pos+1:])
}
}
}
}
func fixFieldElemType(argType string) string {
if strings.HasPrefix(argType, "Map<") {
argType = "Object/*" + argType + "*/"
}
return argType
}
func makeFieldElemType(t reflect.Type, existsClasses *map[string]string, sameClassIndex *map[string]int, classes *[]string, isSkip bool) string {
originT := t
if t.Kind() == reflect.Pointer {
t = t.Elem()
}
if t.Kind() == reflect.Map {
if t.Elem().Kind() == reflect.Struct && !isSkip {
makeClass(t.Elem(), existsClasses, sameClassIndex, classes)
}
et := t.Elem()
if et.Kind() == reflect.Pointer {
et = et.Elem()
}
if et.Kind() == reflect.Struct || et.Kind() == reflect.Map || (et.Kind() == reflect.Slice && et.Elem().Kind() != reflect.Uint8) {
return "Map<" + makeArgTypeString(t.Key().String()) + ", " + makeFieldElemType(t.Elem(), existsClasses, sameClassIndex, classes, isSkip) + ">"
}
return "Object"
} else if t.Kind() == reflect.Slice && t.Elem().Kind() != reflect.Uint8 {
if t.Elem().Kind() == reflect.Struct && !isSkip {
makeClass(t.Elem(), existsClasses, sameClassIndex, classes)
}
return "Array<" + makeFieldElemType(t.Elem(), existsClasses, sameClassIndex, classes, isSkip) + ">"
} else if t.Kind() == reflect.Struct {
if !isSkip {
makeClass(originT, existsClasses, sameClassIndex, classes)
}
return makeArgTypeString(getFixedClassName(t.String(), existsClasses))
//return makeArgTypeString(strings.ReplaceAll(t.String(), ".", "_"))
} else if t.Kind() == reflect.Interface {
if !isSkip {
makeClass(originT, existsClasses, sameClassIndex, classes)
}
return makeArgTypeString(getFixedClassName(t.String(), existsClasses))
} else if t.Kind() == reflect.Func {
inArgs, outArgs := makeFuncInOutArgs(t, existsClasses, sameClassIndex, classes)
//makeFuncArgsNames(reflect.MakeFunc(t, func(args []reflect.Value) (results []reflect.Value) {
// return nil
//}), inArgs, false)
return "(" + makeInArgsString(inArgs) + ") => " + makeOutArgsString(outArgs)
} else {
return makeArgTypeString(getFixedTypeName(t))
}
}
func getFixedClassName(className string, existsClasses *map[string]string) string {
if (*existsClasses)[className] != "" {
return (*existsClasses)[className]
}
return strings.ReplaceAll(className, ".", "_")
}
func getFixedTypeName(t reflect.Type) string {
if t.String() != t.Kind().String() && !strings.HasPrefix(t.String(), "[]") {
if t.Kind().String() == "slice" {
return "[]any"
}
if t.Kind().String() == "chan" {
return "any"
}
if t.Kind().String() == "func" {
return "()=>void"
}
//fmt.Println(">>>>>", u.BYellow(t.String()), u.BYellow(t.String()), u.BYellow(t.Kind().String()))
return makeArgTypeString(t.Kind().String())
} else {
return makeArgTypeString(t.String())
}
}
func makeClass(t reflect.Type, existsClasses *map[string]string, sameClassIndex *map[string]int, classes *[]string) {
originT := t
if t.Kind() == reflect.Pointer {
t = t.Elem()
}
if t.Name() == "" {
return
}
fullTypeName := t.String()
useTypeName := t.Name()
//fmt.Println(fullTypeName, useTypeName)
if (*existsClasses)[fullTypeName] == "" {
(*sameClassIndex)[useTypeName]++
if (*sameClassIndex)[useTypeName] > 1 {
useTypeName += u.String((*sameClassIndex)[useTypeName])
}
(*existsClasses)[fullTypeName] = useTypeName
classItems := make([]string, 0)
implements := make([]string, 0)
if t.Kind() != reflect.Interface {
for i := 0; i < t.NumField(); i++ {
if !t.Field(i).IsExported() {
continue
}
ft := t.Field(i).Type
if ft.Kind() == reflect.Pointer {
ft = ft.Elem()
}
ftStr := fixFieldElemType(makeFieldElemType(ft, existsClasses, sameClassIndex, classes, false))
if t.Field(i).Anonymous {
implements = append(implements, ftStr)
continue
}
classItems = append(classItems, " "+u.GetLowerName(t.Field(i).Name)+": "+ftStr)
}
}
for i := 0; i < originT.NumMethod(); i++ {
if !originT.Method(i).IsExported() {
continue
}
inArgs, outArgs := makeFuncInOutArgs(originT.Method(i).Type, existsClasses, sameClassIndex, classes)
if len(inArgs) > 0 {
inArgs[0].isSkip = true
}
//fmt.Println(">>>>>", u.BYellow(originT.Method(i)))
makeFuncArgsNames(originT.Method(i).Func, inArgs, true)
classItems = append(classItems, " "+u.GetLowerName(originT.Method(i).Name)+"("+makeInArgsString(inArgs)+"): "+makeOutArgsString(outArgs))
}
implementsStr := ""
if len(implements) > 0 {
implementsStr = " extends " + strings.Join(implements, ", ")
}
newClass := append([]string{}, "export interface "+useTypeName+implementsStr+" {")
newClass = append(newClass, classItems...)
newClass = append(newClass, "}")
*classes = append(*classes, strings.Join(newClass, "\n"))
}
}
func makeFuncInOutArgs(t reflect.Type, existsClasses *map[string]string, sameClassIndex *map[string]int, classes *[]string) (inArgs []ArgInfo, outArgs []ArgInfo) {
inArgs = make([]ArgInfo, t.NumIn())
outArgs = make([]ArgInfo, t.NumOut())
isOptional := true
// *plugin.Context
for i := t.NumIn() - 1; i >= 0; i-- {
arg := t.In(i)
isSkip := false
if arg.String() == "*plugin.Context" {
isSkip = true
}
if isOptional && arg.Kind() != reflect.Pointer {
isOptional = false
}
argInfo := ArgInfo{
index: i,
isOutArg: false,
isFunc: false,
isSkip: isSkip,
isOptional: isOptional,
isVariadic: t.IsVariadic() && i == t.NumIn()-1,
Name: "",
Type: fixFieldElemType(makeFieldElemType(arg, existsClasses, sameClassIndex, classes, isSkip)),
}
//originArg := arg
//if arg.Kind() == reflect.Pointer {
// arg = arg.Elem()
//}
if arg.Kind() == reflect.Func {
argInfo.isFunc = true
argInfo.funcInArgs, argInfo.funcOutArgs = makeFuncInOutArgs(arg, existsClasses, sameClassIndex, classes)
}
//if !argInfo.isSkip && arg.Kind() == reflect.Struct {
// makeClass(originArg, existsClasses, sameClassIndex, classes)
//}
inArgs[i] = argInfo
}
for i := t.NumOut() - 1; i >= 0; i-- {
arg := t.Out(i)
isSkip := false
if arg.String() == "error" {
isSkip = true
}
argInfo := ArgInfo{
index: i,
isOutArg: true,
isFunc: false,
isSkip: isSkip,
isVariadic: false,
Name: "",
Type: fixFieldElemType(makeFieldElemType(arg, existsClasses, sameClassIndex, classes, isSkip)),
}
//originArg := arg
//if arg.Kind() == reflect.Pointer {
// arg = arg.Elem()
//}
//if !argInfo.isSkip && arg.Kind() == reflect.Struct {
// makeClass(originArg, existsClasses, sameClassIndex, classes)
//}
outArgs[i] = argInfo
}
return inArgs, outArgs
}
func findObject(v reflect.Value, level int, existsClasses *map[string]string, sameClassIndex *map[string]int) (codes []string, classes []string) {
codes = make([]string, 0)
classes = make([]string, 0)
if v.Kind() == reflect.Interface {
v = v.Elem()
}
originT := v.Type()
v = u.FinalValue(v)
t := originT
if t.Kind() == reflect.Pointer {
t = t.Elem()
}
indent := strings.Repeat(" ", level)
if t.Kind() == reflect.Map {
codes = append(codes, "{")
for _, k := range v.MapKeys() {
codes2, classes2 := findObject(v.MapIndex(k), level+1, existsClasses, sameClassIndex)
classes = append(classes, classes2...)
codes = append(codes, indent+" \""+k.String()+"\": "+strings.Join(codes2, "\n")+",")
}
codes = append(codes, indent+"}")
} else if t.Kind() == reflect.Struct {
makeClass(originT, existsClasses, sameClassIndex, &classes)
codes = append(codes, "null as "+getFixedClassName(t.String(), existsClasses))
} else if t.Kind() == reflect.Slice && t.Elem().Kind() != reflect.Uint8 {
codes = append(codes, "[")
for i := 0; i < v.Len(); i++ {
codes2, classes2 := findObject(v.Index(i), level+1, existsClasses, sameClassIndex)
classes = append(classes, classes2...)
codes = append(codes, indent+" "+strings.Join(codes2, "\n")+",")
}
codes = append(codes, indent+"]")
} else if t.Kind() == reflect.Func {
inArgs, outArgs := makeFuncInOutArgs(t, existsClasses, sameClassIndex, &classes)
makeFuncArgsNames(v, inArgs, false)
codes = append(codes, "function ("+makeInArgsString(inArgs)+"): "+makeOutArgsString(outArgs)+" {return}")
} else {
codes = append(codes, u.Json(v.Interface()))
}
return codes, classes
}
func MakeAllPluginCode() {
for _, plg := range plugin.List() {
exportCode, interfaceCode := MakePluginCode(&plg)
if interfaceCode != "" {
_ = u.WriteFile(path.Join("plugins", plg.Id+"Type.ts"), interfaceCode)
}
_ = u.WriteFile(path.Join("plugins", plg.Id+".ts"), exportCode)
}
}
func MakePluginCode(plg *plugin.Plugin) (exportCode, interfaceCode string) {
if plg == nil {
return "", ""
}
existsClasses := make(map[string]string)
sameClassIndex := make(map[string]int)
codes, classes := findObject(reflect.ValueOf(plg.Objects), 0, &existsClasses, &sameClassIndex)
interfaceExports := make([]string, 0)
for _, existsClassName := range existsClasses {
interfaceExports = append(interfaceExports, existsClassName)
}
exportCode = "export default " + strings.Join(codes, "\n")
interfaceCode = ""
if len(interfaceExports) > 0 {
exportCode = "import {" + strings.Join(interfaceExports, ", ") + "} from '" + plg.Id + "Type'\n\n" + exportCode
interfaceCode = strings.Join(classes, "\n\n") // + "\n\nexport default {" + strings.Join(interfaceExports, ", ") + ","
}
return
}