kittenipc/kitcom/tsparser.go
2025-10-21 10:50:42 +03:00

143 lines
3.3 KiB
Go

package main
import (
"fmt"
"io"
"os"
"strings"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/parser"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath"
)
const TagName = "kittenipc"
const TagComment = "api"
type TypescriptApiParser struct {
}
type apiClass struct {
methods []Method
}
func (t *TypescriptApiParser) Parse(sourceFilePath string) (*Api, error) {
f, err := os.Open(sourceFilePath)
if err != nil {
return nil, fmt.Errorf("failed to open file: %w", err)
}
defer f.Close()
fileContents, err := io.ReadAll(f)
if err != nil {
return nil, fmt.Errorf("failed to read file: %w", err)
}
sourceFile := parser.ParseSourceFile(ast.SourceFileParseOptions{
FileName: sourceFilePath,
Path: tspath.Path(sourceFilePath),
CompilerOptions: core.SourceFileAffectingCompilerOptions{},
ExternalModuleIndicatorOptions: ast.ExternalModuleIndicatorOptions{},
JSDocParsingMode: ast.JSDocParsingModeParseAll,
}, string(fileContents), core.ScriptKindTS)
_ = sourceFile
var apiClasses []apiClass
sourceFile.ForEachChild(func(node *ast.Node) bool {
if node.Kind != ast.KindClassDeclaration {
return false
}
cls := node.AsClassDeclaration()
jsDocNodes := cls.JSDoc(nil)
if len(jsDocNodes) == 0 {
return false
}
var isApi bool
outer:
for _, jsDocNode := range jsDocNodes {
jsDoc := jsDocNode.AsJSDoc()
for _, tag := range jsDoc.Tags.Nodes {
if tag.TagName().Text() == TagName {
for _, com := range tag.Comments() {
if strings.TrimSpace(com.Text()) == TagComment {
isApi = true
break outer
}
}
}
}
}
if !isApi {
return false
}
var apiCls apiClass
for _, member := range cls.MemberList().Nodes {
if member.Kind != ast.KindMethodDeclaration {
continue
}
method := member.AsMethodDeclaration()
var apiMethod Method
apiMethod.Name = method.Name().Text()
for _, parNode := range method.ParameterList().Nodes {
par := parNode.AsParameterDeclaration()
var apiPar Val
apiPar.Name = par.Name().Text()
switch par.Type.Kind {
case ast.KindNumberKeyword:
apiPar.Type = TInt
case ast.KindStringKeyword:
apiPar.Type = TString
case ast.KindBooleanKeyword:
apiPar.Type = TBool
default:
err = fmt.Errorf("parameter type %s is not supported yet", par.Type.Kind)
return false
}
apiMethod.Pars = append(apiMethod.Pars, apiPar)
}
var apiRet Val
switch method.Type.Kind {
case ast.KindNumberKeyword:
apiRet.Type = TInt
case ast.KindStringKeyword:
apiRet.Type = TString
case ast.KindBooleanKeyword:
apiRet.Type = TBool
default:
err = fmt.Errorf("return type %s is not supported yet", method.Type.Kind)
return false
}
apiMethod.Ret = []Val{apiRet}
apiCls.methods = append(apiCls.methods, apiMethod)
}
apiClasses = append(apiClasses, apiCls)
return false
})
if err != nil {
return nil, err
}
if len(apiClasses) == 0 {
return nil, fmt.Errorf("no api class found")
}
if len(apiClasses) > 1 {
return nil, fmt.Errorf("multiple api classes found")
}
return &Api{Methods: apiClasses[0].methods}, nil
}