UNPKG

typescript-transform-macros

Version:
139 lines (138 loc) 6.21 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const ts = require("typescript"); class Transformer { constructor(context) { this.context = context; this.rootMacros = new Map(); this.extractMacros = (node) => { if (ts.isVariableStatement(node)) { const firstDeclaration = node.declarationList.declarations[0]; if (firstDeclaration.initializer && ts.isCallExpression(firstDeclaration.initializer) && ts.isIdentifier(firstDeclaration.initializer.expression) && firstDeclaration.initializer.expression.text === "MACRO") { const name = firstDeclaration.name; if (!ts.isIdentifier(name)) { throw new Error("Expected name to be Identifier for macro declaration"); } const value = firstDeclaration.initializer.arguments[0]; this.rootMacros.set(name.text, value); return undefined; } } return ts.visitEachChild(node, this.extractMacros, this.context); }; this.resolveMacros = (node) => { if (ts.isBlock(node) || ts.isSourceFile(node)) { const newBlock = this.replaceMacros(node.statements, this.rootMacros); if (ts.isBlock(node)) { return ts.visitEachChild(ts.updateBlock(node, newBlock), this.resolveMacros, this.context); } else { return ts.visitEachChild(ts.updateSourceFileNode(node, newBlock), this.resolveMacros, this.context); } } return ts.visitEachChild(node, this.resolveMacros, this.context); }; this.cleanMacro = (node) => { const visit = (node) => { if (ts.isReturnStatement(node)) { if (!node.expression) throw new Error("Expected macro to return value"); result = ts.visitNode(node.expression, visit); return undefined; } if (ts.isPropertyAccessExpression(node)) { return ts.createPropertyAccess(ts.visitNode(node.expression, visit), node.name); } if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name)) { variableMap.set(node.name.text, ts.createUniqueName(node.name.text)); } if (ts.isIdentifier(node) && variableMap.has(node.text)) { return variableMap.get(node.text); } return ts.visitEachChild(node, visit, this.context); }; const variableMap = new Map(); let result = undefined; const resultNode = ts.visitNode(node, visit); return [result, resultNode]; }; this.replaceMacros = (statements, macros) => { const visit = (node) => { if ([ ts.SyntaxKind.InterfaceDeclaration, ts.SyntaxKind.PropertySignature ].includes(node.kind)) { return node; } if (ts.isBlock(node)) { return ts.createBlock(this.replaceMacros(node.statements, macros)); } if (ts.isIdentifier(node) && macros.has(node.text)) { return macros.get(node.text); } if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && macros.has(node.expression.name.text)) { return ts.visitNode(ts.updateCall(node, node.expression.name, node.typeArguments, [ node.expression.expression, ...node.arguments ]), visit); } if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && macros.has(node.expression.text)) { const value = macros.get(node.expression.text); if (!ts.isArrowFunction(value) && !ts.isFunctionExpression(value)) { throw new Error("Expected function expression for macro value"); } const newMacros = new Map([ ...macros.entries(), ...getNameValueMap(node.arguments, value.parameters).entries() ]); const [resultName, resultBlock] = this.cleanMacro(ts.visitNode(ts.createBlock(this.replaceMacros(getStatements(value), newMacros)), visit)); result = result.concat(resultBlock.statements); if (!resultName) return ts.createEmptyStatement(); return resultName; } return ts.visitEachChild(node, visit, this.context); }; let result = []; for (const statement of statements) { const newStatement = ts.visitNode(statement, visit); result.push(newStatement); } return result; }; } transform(node) { return ts.visitNode(ts.visitNode(node, this.extractMacros), this.resolveMacros); } } const transformer = (_program) => context => { return node => { return new Transformer(context).transform(node); }; }; function getStatements(node) { if (ts.isBlock(node.body)) { return node.body.statements; } return ts.createNodeArray([ts.createReturn(node.body)]); } function getNameValueMap(values, args) { const map = new Map(); for (let i = 0; i < values.length && i < args.length; i++) { const argName = args[i].name; if (!ts.isIdentifier(argName)) { throw new Error("Expected identifier in macro function definition"); } const argValue = values[i]; map.set(argName.text, argValue); } return map; } exports.default = transformer;