- fun gen [ts|go]:AST 扫描 BindService* 调用,临时生成 go test 辅助采集, 经 fun v1.3.7 公开 API 生成,产物与内置 GenCode 字节级一致(回归测试锁定) - fun run:热更新开发循环,先编译成功再切换,失败保留旧进程 - fun docs:浅色标准文档站——参数表/State 添加字段/请求 JSON 可编辑/ 响应格式化/NDJSON 流式逐行展示,反向代理 /cell 同源无 CORS - 元数据:TS 产物两遍解析重建(枚举/可空/流式工厂/嵌套引用全支持) - 纯标准库实现,零第三方依赖
226 lines
6.8 KiB
Go
226 lines
6.8 KiB
Go
package main
|
||
|
||
// TS 渲染器:fun v1.3.7 gen_ts.go 的等价移植,输入从 reflect.Type 换成元数据类型树
|
||
|
||
import "strings"
|
||
|
||
type renderTs struct{}
|
||
|
||
// typeName 等价旧 typeToTemplateType:指针 → " | null" 后缀,切片 → "[]",
|
||
// 整数 → number(枚举除外),struct/string → 类型名
|
||
func (renderTs) typeName(t *MetaType) string {
|
||
suffix := ""
|
||
if t.Optional {
|
||
suffix = " | null"
|
||
}
|
||
switch t.Kind {
|
||
case "enum":
|
||
return t.Name + suffix
|
||
case "int":
|
||
return "number" + suffix
|
||
case "bool":
|
||
return "boolean" + suffix
|
||
case "string", "struct":
|
||
return t.Name + suffix
|
||
default: // slice
|
||
return renderTs{}.typeName(t.Elem) + "[]" + suffix
|
||
}
|
||
}
|
||
|
||
func (r renderTs) genService(svc MetaService, serviceContext *genServiceType, files fileSet) {
|
||
var nestedImports []*genImportType
|
||
|
||
for _, gm := range svc.Methods {
|
||
var returnValueText string
|
||
var dtoText string
|
||
var argsText string
|
||
var genericTypeText string
|
||
|
||
// () error:无数据返回,客户端类型为 void
|
||
if gm.Void {
|
||
genericTypeText = "void"
|
||
returnValueText = "result<void>"
|
||
if gm.DTO != nil {
|
||
v := firstLetterToLower(r.typeName(gm.DTO))
|
||
if !strings.Contains(v, "[]") && strings.Contains(v, "[") {
|
||
dtoText += "dto:" + getGenericTypeName(v) + parseGenericTypeParams(v)
|
||
} else {
|
||
dtoText += "dto:" + v
|
||
}
|
||
argsText += ",dto"
|
||
nestedImports = append(nestedImports, r.genStruct(gm.DTO, files))
|
||
}
|
||
serviceContext.IsIncludeRequest = true
|
||
serviceContext.GenMethodTypeList = append(serviceContext.GenMethodTypeList, &genMethodType{
|
||
MethodName: firstLetterToLower(gm.Name),
|
||
ReturnValueText: returnValueText,
|
||
DtoText: dtoText,
|
||
ArgsText: argsText,
|
||
GenericTypeText: genericTypeText,
|
||
})
|
||
continue
|
||
}
|
||
|
||
if gm.IsStream {
|
||
serviceContext.IsIncludeStream = true
|
||
if gm.Returns == nil {
|
||
genericTypeText = "any"
|
||
returnValueText = "void"
|
||
} else {
|
||
t := firstLetterToLower(r.typeName(gm.Returns))
|
||
if !strings.Contains(t, "[]") && strings.Contains(t, "[") {
|
||
genericTypeText = getGenericTypeName(t) + parseGenericTypeParams(t)
|
||
} else {
|
||
genericTypeText = t
|
||
}
|
||
returnValueText = "void"
|
||
nestedImports = r.genReturnTypes(gm.Returns, nestedImports, files)
|
||
}
|
||
} else {
|
||
serviceContext.IsIncludeRequest = true
|
||
t := firstLetterToLower(r.typeName(gm.Returns))
|
||
if !strings.Contains(t, "[]") && strings.Contains(t, "[") {
|
||
returnValueText = getGenericTypeName(t) + parseGenericTypeParams(t)
|
||
} else {
|
||
returnValueText = t
|
||
}
|
||
genericTypeText = returnValueText
|
||
nestedImports = r.genReturnTypes(gm.Returns, nestedImports, files)
|
||
returnValueText = "result<" + returnValueText + ">"
|
||
}
|
||
|
||
if gm.DTO != nil {
|
||
v := firstLetterToLower(r.typeName(gm.DTO))
|
||
if !strings.Contains(v, "[]") && strings.Contains(v, "[") {
|
||
dtoText += "dto:" + getGenericTypeName(v) + parseGenericTypeParams(v)
|
||
} else {
|
||
dtoText += "dto:" + v
|
||
}
|
||
argsText += ",dto"
|
||
nestedImports = append(nestedImports, r.genStruct(gm.DTO, files))
|
||
}
|
||
|
||
serviceContext.GenMethodTypeList = append(serviceContext.GenMethodTypeList, &genMethodType{
|
||
MethodName: firstLetterToLower(gm.Name),
|
||
ReturnValueText: returnValueText,
|
||
DtoText: dtoText,
|
||
ArgsText: argsText,
|
||
GenericTypeText: firstLetterToLower(genericTypeText),
|
||
IsStream: gm.IsStream,
|
||
})
|
||
}
|
||
serviceContext.GenImport = deduplicateServiceImports(nestedImports)
|
||
|
||
renderCode(
|
||
templateTs{}.genServiceTemplate(),
|
||
firstLetterToLower(svc.Name),
|
||
serviceContext,
|
||
"ts",
|
||
files,
|
||
)
|
||
}
|
||
|
||
// genReturnTypes 递归生成返回类型涉及的 struct/enum 导入
|
||
func (r renderTs) genReturnTypes(returnType *MetaType, nestedImports []*genImportType, files fileSet) []*genImportType {
|
||
if returnType.Kind == "struct" {
|
||
nestedImports = append(nestedImports, r.genStruct(returnType, files))
|
||
}
|
||
if returnType.Kind == "slice" && returnType.Elem.Kind == "struct" {
|
||
nestedImports = append(nestedImports, r.genStruct(returnType.Elem, files))
|
||
}
|
||
if returnType.Kind == "enum" {
|
||
nestedImports = append(nestedImports, r.getEnum(returnType, files))
|
||
}
|
||
return nestedImports
|
||
}
|
||
|
||
func (r renderTs) renderAll(meta *FunMeta, files fileSet) {
|
||
genContext := genType{GenServiceList: []*genServiceType{}}
|
||
|
||
for _, svc := range meta.Services {
|
||
serviceContext := &genServiceType{
|
||
ServiceName: firstLetterToLower(svc.Name),
|
||
GenMethodTypeList: []*genMethodType{},
|
||
}
|
||
genContext.GenServiceList = append(genContext.GenServiceList, serviceContext)
|
||
r.genService(svc, serviceContext, files)
|
||
}
|
||
renderCode(templateTs{}.genClientTemplate(), "client", nil, "ts", files)
|
||
renderCode(templateTs{}.genDefaultServiceTemplate(), "fun", genContext, "ts", files)
|
||
}
|
||
|
||
func (r renderTs) genStruct(t *MetaType, files fileSet) *genImportType {
|
||
var structTemplate genClassType
|
||
if strings.Contains(t.Name, "[]") == false && strings.Contains(t.Name, "[") {
|
||
structTemplate = genClassType{
|
||
Name: firstLetterToLower(getGenericTypeName(t.Name)) + parseGenericTypeParams(t.Name),
|
||
}
|
||
} else {
|
||
structTemplate = genClassType{
|
||
Name: firstLetterToLower(t.Name),
|
||
}
|
||
}
|
||
var nestedImports []*genImportType
|
||
|
||
for _, field := range t.Fields {
|
||
fieldType := field.Type
|
||
jsType := r.typeName(fieldType)
|
||
name := field.Name
|
||
if fieldType.Optional {
|
||
name += "?"
|
||
}
|
||
if !strings.Contains(jsType, "[]") && strings.Contains(jsType, "[") {
|
||
structTemplate.GenClassFieldType = append(structTemplate.GenClassFieldType, &genClassFieldType{
|
||
Name: firstLetterToLower(name),
|
||
Type: firstLetterToLower(getGenericTypeName(jsType)) + parseGenericTypeParams(jsType),
|
||
})
|
||
} else {
|
||
structTemplate.GenClassFieldType = append(structTemplate.GenClassFieldType, &genClassFieldType{
|
||
Name: firstLetterToLower(name),
|
||
Type: firstLetterToLower(jsType),
|
||
})
|
||
}
|
||
|
||
if fieldType.Kind == "struct" {
|
||
nestedImports = append(nestedImports, r.genStruct(fieldType, files))
|
||
}
|
||
if fieldType.Kind == "slice" && fieldType.Elem.Kind == "struct" {
|
||
nestedImports = append(nestedImports, r.genStruct(fieldType.Elem, files))
|
||
}
|
||
if fieldType.Kind == "enum" {
|
||
nestedImports = append(nestedImports, r.getEnum(fieldType, files))
|
||
}
|
||
}
|
||
|
||
structTemplate.GenImport = deduplicateServiceImports(nestedImports)
|
||
|
||
renderCode(
|
||
templateTs{}.genStructTemplate(),
|
||
structTemplate.Name,
|
||
structTemplate,
|
||
"ts",
|
||
files,
|
||
)
|
||
|
||
if strings.Contains(t.Name, "[]") == false && strings.Contains(t.Name, "[") {
|
||
return &genImportType{Name: structTemplate.Name}
|
||
}
|
||
return &genImportType{Name: firstLetterToLower(t.Name)}
|
||
}
|
||
|
||
func (r renderTs) getEnum(t *MetaType, files fileSet) *genImportType {
|
||
var enumTemplate genEnumType
|
||
enumTemplate.Names = t.Names
|
||
enumTemplate.DisplayNames = t.DisplayNames
|
||
enumTemplate.Name = firstLetterToLower(t.Name)
|
||
|
||
renderCode(
|
||
templateTs{}.genEnumTemplate(),
|
||
firstLetterToLower(t.Name),
|
||
enumTemplate,
|
||
"ts",
|
||
files,
|
||
)
|
||
return &genImportType{Name: firstLetterToLower(t.Name)}
|
||
}
|