import * as ts from "typescript" import * as fs from "fs" export class TSAPIParser { constructor() { } fileParsers: TSFileParser[] = [] parse(pathes: string[]) { for (let file of pathes) { const content = fs.readFileSync(file, "utf8") const source = ts.createSourceFile(file, content, ts.ScriptTarget.ESNext, true) this.fileParsers.push(new TSFileParser(source)) } } generate() { let apis = this.fileParsers.map(item => item.generate()).join("\n") return ` try { var Leaf = window["Leaf"] var _d = Leaf.Util } catch (e) { var Leaf = require("leaf-ts") } ${apis} ` } } export class TSFileParser { constructor(public readonly source: ts.SourceFile) { } generate() { let services: APIServiceParser[] = [] ts.forEachChild(this.source, (child) => { if (child.kind === ts.SyntaxKind.ModuleDeclaration) { let children = getEachChild(child).slice(-2) let id = children[0] as ts.Identifier let content = children[1] console.assert(content.kind === ts.SyntaxKind.ModuleBlock) let vars = this.parseModuleBlock(content as ts.ModuleBlock) let isServiceSpec = vars.some((v) => { if (v.name === "API") return true }) if (isServiceSpec) { services.push(new APIServiceParser(id.text, vars)) } } }) return services.map(item => item.toFrontentAPIGenerator()).join("\n") } private childOf(node: ts.Node, kind: ts.SyntaxKind) { } parseModuleBlock(node: ts.ModuleBlock) { let vars = getEachChild(node).filter(item => item.kind === ts.SyntaxKind.VariableStatement).map(item => this.parseVairableStatement(item as ts.VariableStatement)) return vars } parseVairableStatement(node: ts.VariableStatement) { let name = node.declarationList.declarations[0].name.getText() let value = node.declarationList.declarations[0].initializer return { name, value } } } export class APIServiceParser { constructor(public serviceName: string, vars: { name: string value: ts.Expression }[]) { for (let value of vars) { if (value.name === "API") { this.parseAPIs(value.value) } else if (value.name === "prefix") { this.parsePrefix(value.value) } else if (value.name === "name") { this.parseName(value.value) } } } toFrontentAPIGenerator() { let apis = Object.keys(this.API).map(key => { let api = this.API[key] let requestTypes: string[] = [] if (api.params) { requestTypes.push(this.stringifyType(api.params)) } if (api.query) { requestTypes.push(this.stringifyType(api.query)) } if (api.body) { requestTypes.push(this.stringifyType(api.body)) } return ` ${key} = factory.createAPIFunction<${requestTypes.join("&") || "{}"} ,${this.stringifyType(api.return) || "null"}>({ method:"${api.method}", path:"${api.path}" }) ` }) let result = ` export function Generate${this.serviceName} {}) = new (...args: any[]) => {}>(factory: Leaf.APIFactory = new Leaf.APIFactory({prefix:"${this.prefix}",bodyType:"json",serializer:Leaf.Serializer.getDefault()}),Base:T = class { } as any){ return class extends Base{ public factory = factory ${apis.join("\n")} } }` return result } public name: string public prefix: string = "" public API: { [name: string]: { body?: RecursiveType params?: RecursiveType query?: RecursiveType path: string method: string return: RecursiveType } } = {} parseAPIs(node: ts.Expression) { let apis = getEachChild(node) for (let child of apis) { let assign: ts.PropertyAssignment = child as any let name = assign.name.getText() let value = assign.initializer as ts.NewExpression console.assert(value.kind === ts.SyntaxKind.NewExpression) console.assert(value.expression.kind === ts.SyntaxKind.ClassExpression) let classInfo = this.parseClass(value.expression as ts.ClassExpression) let fields = classInfo.fields || {} as any let body = fields.body || {} as any let params = fields.params || {} as any let query = fields.query || {} as any let returnValue = fields.return || {} as any let path = fields.path || {} as any let method = fields.method || {} as any this.API[name] = { body: body.type, params: params.type, query: query.type, return: returnValue.type, path: path.stringLiteral || "", method: method.stringLiteral || "GET" } } } parseName(node: ts.Expression) { console.assert(node.kind == ts.SyntaxKind.StringLiteral) let s = node as ts.StringLiteral this.name = s.text } parsePrefix(node: ts.Expression) { console.assert(node.kind == ts.SyntaxKind.StringLiteral) let s = node as ts.StringLiteral this.prefix = s.text } parseClass(exp: ts.ClassExpression): ClassInfo { let info: ClassInfo = { name: null, extends: null, methods: {}, fields: {} } if (exp.name) { info.name = exp.name.text } for (let member of exp.members) { // we skip method here if (member.kind == ts.SyntaxKind.MethodDeclaration) continue console.assert(member.kind === ts.SyntaxKind.PropertyDeclaration) let name = member.name.getText() let prop = member as ts.PropertyDeclaration let optional = !!prop.questionToken let stringLiteral: string if (prop.initializer && prop.initializer.kind == ts.SyntaxKind.StringLiteral) { let literal = prop.initializer as ts.StringLiteral stringLiteral = literal.text } let type: RecursiveType if (prop.type) { type = this.parseType(prop.type) } info.fields[name] = { name, stringLiteral, type, optional, initializer: prop.initializer } } return info } parseType(node: ts.Node): RecursiveType { if (node.kind !== ts.SyntaxKind.TypeLiteral) return node.getText() let literal = node as ts.TypeLiteralNode let result: RecursiveType = {} for (let member of literal.members) { let name = member.name.getText() if (member.kind !== ts.SyntaxKind.PropertySignature) continue let sig = member as ts.PropertySignature if (sig.questionToken) { name += "?" } if (!sig.type) { throw new Error("Untyped property signature: " + sig.getText()) } result[name] = this.parseType(sig.type) } return result } stringifyType(type: RecursiveType) { if (typeof type === "string") return type let result = ["{"] for (let name in type as any) { result.push(name + ":" + this.stringifyType(type[name]).trim()) } result.push("}") return result.join("\n") } } interface ClassInfo { name: string extends?: string methods: { [key: string]: { name: string params: { name: string type?: string optional?: boolean }[] } } fields: { [key: string]: { name: string stringLiteral?: string type?: RecursiveType optional?: boolean initializer: ts.Expression } } } type RecursiveType = { [key: string]: RecursiveType } | string function getSyntaxKindName(kind) { for (let name in ts.SyntaxKind) { if (ts.SyntaxKind[name] === kind) { return name } } return "Unknown" } interface KindTree { name: string children: KindTree[] } function getKindTree(node: ts.Node, depth: number = 1): KindTree { let tree: KindTree = { name: getSyntaxKindName(node.kind) , children: [] } if (depth === 0) { tree.children = null return tree } ts.forEachChild(node, (child) => { tree.children.push(getKindTree(child, depth - 1)) }) return tree } function inspectKind(node: ts.Node, depth: number = 1) { let tree = getKindTree(node, depth) } function getEachChild(node: ts.Node) { let result: ts.Node[] = [] node.forEachChild((child) => { result.push(child) }) return result }