reverse-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
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);
|
||||
Reference in New Issue
Block a user