131 lines
4.7 KiB
JavaScript
131 lines
4.7 KiB
JavaScript
const { clone, parseArgs, parseFile, reparse, saveAst, t, traverse } = require("./shared");
|
|
|
|
function collectDispatcherValue(node, dispatcherMap) {
|
|
if (t.isStringLiteral(node) || t.isNumericLiteral(node) || t.isBooleanLiteral(node)) {
|
|
return { type: "literal", value: clone(node) };
|
|
}
|
|
if (t.isMemberExpression(node) && t.isIdentifier(node.object) && !node.computed && t.isIdentifier(node.property)) {
|
|
return { type: "member", objectName: node.object.name, key: node.property.name };
|
|
}
|
|
if (t.isFunctionExpression(node) && node.body.body.length === 1 && t.isReturnStatement(node.body.body[0])) {
|
|
const arg = node.body.body[0].argument;
|
|
if (t.isBinaryExpression(arg) && t.isIdentifier(arg.left) && t.isIdentifier(arg.right)) {
|
|
return { type: "binary", operator: arg.operator };
|
|
}
|
|
if (t.isLogicalExpression(arg) && t.isIdentifier(arg.left) && t.isIdentifier(arg.right)) {
|
|
return { type: "logical", operator: arg.operator };
|
|
}
|
|
if (t.isCallExpression(arg) && t.isIdentifier(arg.callee)) {
|
|
return { type: "call" };
|
|
}
|
|
if (t.isCallExpression(arg) && t.isMemberExpression(arg.callee) && t.isIdentifier(arg.callee.object) && !arg.callee.computed && t.isIdentifier(arg.callee.property)) {
|
|
const target = dispatcherMap[arg.callee.object.name] && dispatcherMap[arg.callee.object.name][arg.callee.property.name];
|
|
if (target && (target.type === "binary" || target.type === "logical" || target.type === "call")) {
|
|
return target;
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function inlineDispatchers(ast) {
|
|
let changed = false;
|
|
const dispatcherMap = Object.create(null);
|
|
const removableEntries = [];
|
|
|
|
traverse(ast, {
|
|
VariableDeclarator(path) {
|
|
if (!path.get("id").isIdentifier() || !path.get("init").isObjectExpression()) {
|
|
return;
|
|
}
|
|
const name = path.node.id.name;
|
|
const collected = dispatcherMap[name] || Object.create(null);
|
|
let localChanged = false;
|
|
let supportedPropertyCount = 0;
|
|
let collectedPropertyCount = 0;
|
|
path.get("init.properties").forEach(propertyPath => {
|
|
if (!propertyPath.isObjectProperty()) {
|
|
return;
|
|
}
|
|
supportedPropertyCount += 1;
|
|
const keyNode = propertyPath.node.key;
|
|
const key = t.isIdentifier(keyNode) ? keyNode.name : keyNode.value;
|
|
const value = collectDispatcherValue(propertyPath.node.value, dispatcherMap);
|
|
if (value) {
|
|
collected[key] = value;
|
|
localChanged = true;
|
|
collectedPropertyCount += 1;
|
|
}
|
|
});
|
|
if (localChanged) {
|
|
dispatcherMap[name] = collected;
|
|
removableEntries.push({
|
|
path,
|
|
removable: supportedPropertyCount > 0 && supportedPropertyCount === collectedPropertyCount
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
traverse(ast, {
|
|
CallExpression(path) {
|
|
if (!path.get("callee").isMemberExpression()) {
|
|
return;
|
|
}
|
|
const objectName = path.node.callee.object.name;
|
|
const key = path.node.callee.computed ? path.node.callee.property.value : path.node.callee.property.name;
|
|
const entry = dispatcherMap[objectName] && dispatcherMap[objectName][key];
|
|
if (!entry) {
|
|
return;
|
|
}
|
|
const args = path.node.arguments.map(arg => clone(arg));
|
|
if (entry.type === "binary" && args.length >= 2) {
|
|
path.replaceWith(t.binaryExpression(entry.operator, args[0], args[1]));
|
|
changed = true;
|
|
} else if (entry.type === "logical" && args.length >= 2) {
|
|
path.replaceWith(t.logicalExpression(entry.operator, args[0], args[1]));
|
|
changed = true;
|
|
} else if (entry.type === "call" && args.length >= 1) {
|
|
path.replaceWith(t.callExpression(args[0], args.slice(1)));
|
|
changed = true;
|
|
}
|
|
},
|
|
MemberExpression(path) {
|
|
if (!path.get("object").isIdentifier()) {
|
|
return;
|
|
}
|
|
const objectName = path.node.object.name;
|
|
const key = path.node.computed ? path.node.property.value : path.node.property.name;
|
|
const entry = dispatcherMap[objectName] && dispatcherMap[objectName][key];
|
|
if (!entry) {
|
|
return;
|
|
}
|
|
if (entry.type === "literal") {
|
|
path.replaceWith(clone(entry.value));
|
|
changed = true;
|
|
}
|
|
}
|
|
});
|
|
|
|
if (changed) {
|
|
removableEntries.forEach(({ path, removable }) => {
|
|
if (removable && !path.removed) {
|
|
path.remove();
|
|
}
|
|
});
|
|
}
|
|
|
|
return { ast, changed };
|
|
}
|
|
|
|
const { inputPath, outputPath } = parseArgs();
|
|
let ast = parseFile(inputPath);
|
|
let changed = false;
|
|
do {
|
|
({ ast, changed } = inlineDispatchers(ast));
|
|
if (changed) {
|
|
ast = reparse(ast);
|
|
}
|
|
} while (changed);
|
|
saveAst(ast, outputPath);
|