UNPKG

rokot-notification

Version:

Rokot - [Rocketmakers](http://www.rocketmakers.com/) TypeScript NodeJs Platform

161 lines (159 loc) 7.42 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const ts = require("typescript"); const _ = require("underscore"); const fs = require("fs"); const path = require("path"); class ApiReflector { constructor(logger, sourcePaths) { this.logger = logger; this.sourcePaths = sourcePaths; this.collected = {}; const options = {}; const host = ts.createCompilerHost(options); this.program = ts.createProgram(sourcePaths, options, host); this.checker = this.program.getTypeChecker(); } getNotificationInheritance(type) { const types = []; if (this.getNotificationInheritanceRecursive(type, types)) { return types.reverse(); } } getNotificationInheritanceRecursive(type, types) { if (type.symbol.name === "INotification") { types.push(type); return true; } const bts = type.getBaseTypes(); for (let bt of bts) { if (this.getNotificationInheritanceRecursive(bt, types)) { types.push(type); return true; } } return false; } processClass(cls, code, relativePath, options) { const notificationType = this.getNotificationType(cls); if (!notificationType || !cls.heritageClauses) { return; } cls.heritageClauses.forEach(hc => { hc.types.forEach(t => { if (t.typeArguments && t.typeArguments.length === 1) { const tr = t.typeArguments[0]; const type = this.checker.getTypeAtLocation(tr); const nt = this.getNotificationInheritance(type); if (nt && nt.length) { const msg = nt[0]; const id = msg["id"]; if (this.collected[id]) { this.logger.trace("Already collected", msg.symbol.name); return; } this.logger.trace("Collecting", msg.symbol.name); this.collected[id] = true; const customProps = this.checker.getPropertiesOfType(msg).filter(p => p.name !== "type" && p.name !== "recipient"); let interfaceProps = _.flatten(customProps.map(cp => { return cp.declarations.map(d => { const ddd = d; return { name: ddd.name.getText(), optional: !!ddd.questionToken, type: ddd.type.getText() }; }); })); const propNames = interfaceProps.length ? ", ...payload" : ""; const sf = type.symbol.declarations[0].getSourceFile().fileName; const relativePath1 = "./" + path.relative(path.resolve("."), sf); code.imports.push({ name: type.symbol.name, path: relativePath1 }); const methodName = options && options.functionName ? options.functionName(type.symbol.name, notificationType) : `dispatch${type.symbol.name}`; const interfaceName = `${type.symbol.name}Payload`; code.interfaces.push(`export type ${interfaceName} = Omit<${type.symbol.name}, OmitProps>\n`); const methodParams = interfaceProps.length ? `, payload: ${interfaceName}` : ""; const sig = `${methodName}(recipient: NotificationRecipients${methodParams}): Promise<INotificationDispatchResult[]>`; code.functions.push(` ${sig} { const notify: ${type.symbol.name} = { type: ${notificationType}, recipient${propNames} } return this.dispatcher.dispatch(notify) } `); code.interfaceSignatures.push(` ${sig}`); } } }); }); } getNotificationType(cls) { if (!cls.decorators) { return; } for (let d of cls.decorators) { if (d.expression.kind !== ts.SyntaxKind.CallExpression) { continue; } let de = d.expression; if (de.expression.getText().trim() !== "notifications.handler" || de.arguments.length < 1) { continue; } let dea = de.arguments[0]; if (dea.kind == ts.SyntaxKind.Identifier) { let ident = dea; const ttt = this.checker.getSymbolAtLocation(ident); const vd = ttt.valueDeclaration; return vd.initializer.getText(); } return dea.getText(); } } writeNotifications(writeTo, options) { const code = { imports: [], functions: [], interfaceSignatures: [], interfaces: [] }; this.program.getSourceFiles().forEach(sourceFile => { const relativePath = "./" + path.relative(path.resolve("."), sourceFile.fileName); if (this.sourcePaths.indexOf(relativePath) > -1) { this.logger.trace("PROCESSING: ", sourceFile.fileName); ts.forEachChild(sourceFile, c => { if (c.kind === ts.SyntaxKind.ClassDeclaration) { this.processClass(c, code, relativePath, options); } }); } }); if (code.functions.length) { let src = ""; const groups = _.groupBy(code.imports, i => i.path); for (let groupKey of _.keys(groups)) { let ext = path.extname(groupKey); let relPath = path.relative(path.dirname(writeTo), path.dirname(groupKey)) + "/" + path.basename(groupKey, ext); let importPath = relPath.indexOf(".") === 0 ? relPath : (relPath.indexOf("/") === 0 ? "." + relPath : "./" + relPath); src += `import { ${groups[groupKey].map(g => g.name).join(", ")} } from "${importPath}"\n`; } src += `import { NotificationRecipients, INotificationDispatcher, INotificationDispatchResult } from "${options && options.notificationImport || "rokot-notification"}"\n`; src += `${options.additionalImports ? options.additionalImports.join("\n") + "\n" : ""}\n`; src += `export type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;; type OmitProps = "type" | "recipient" `; for (let func of code.interfaces) { src += func; } const className = options && options.className || "Notifications"; src += `\nexport interface I${className} {\n`; for (let func of code.interfaceSignatures) { src += func + "\n"; } src += `}\n\n`; src += `export class ${className} implements I${className}{\n`; src += ` constructor(private dispatcher: INotificationDispatcher) {}\n`; for (let func of code.functions) { src += func; } src += `}\n`; fs.writeFileSync(writeTo, src); } return code; } } exports.ApiReflector = ApiReflector;