typescript-transform-macros
Version:
Typescript Transform Macros
139 lines (138 loc) • 6.21 kB
JavaScript
"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;