reverse-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
@@ -0,0 +1,775 @@
|
||||
const generator = require("@babel/generator").default;
|
||||
const { clone, parseArgs, parseFile, reparse, saveAst, t, traverse } = require("./shared");
|
||||
|
||||
function getCaseStatements(casePath) {
|
||||
return casePath.get("consequent").filter(stmt => !stmt.isContinueStatement() && !stmt.isBreakStatement()).map(stmt => clone(stmt.node));
|
||||
}
|
||||
|
||||
function hasLexicalBindings(nodes) {
|
||||
return nodes.some((node) => t.isVariableDeclaration(node) && node.kind !== "var");
|
||||
}
|
||||
|
||||
function isSimpleDiscriminant(node) {
|
||||
return t.isIdentifier(node) || t.isLiteral(node);
|
||||
}
|
||||
|
||||
function normalizeCaseConsequent(nodes) {
|
||||
const statements = nodes.map((node) => clone(node));
|
||||
if (statements.length === 0) {
|
||||
return [t.continueStatement()];
|
||||
}
|
||||
|
||||
const last = statements[statements.length - 1];
|
||||
if (t.isBreakStatement(last)) {
|
||||
statements.pop();
|
||||
statements.push(t.continueStatement());
|
||||
return statements;
|
||||
}
|
||||
if (t.isContinueStatement(last) || t.isReturnStatement(last) || t.isThrowStatement(last)) {
|
||||
return statements;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function buildIfChainFromSwitch(switchNode) {
|
||||
if (!isSimpleDiscriminant(switchNode.discriminant)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let root = null;
|
||||
let current = null;
|
||||
let defaultBlock = null;
|
||||
|
||||
for (const caseNode of switchNode.cases) {
|
||||
const normalized = normalizeCaseConsequent(caseNode.consequent);
|
||||
if (!normalized || hasLexicalBindings(normalized)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (caseNode.test === null) {
|
||||
defaultBlock = t.blockStatement(normalized);
|
||||
continue;
|
||||
}
|
||||
|
||||
const test = t.logicalExpression(
|
||||
"&&",
|
||||
t.binaryExpression("===", clone(switchNode.discriminant), clone(caseNode.test)),
|
||||
t.booleanLiteral(true)
|
||||
);
|
||||
const nextIf = t.ifStatement(test, t.blockStatement(normalized));
|
||||
|
||||
if (!root) {
|
||||
root = nextIf;
|
||||
} else {
|
||||
current.alternate = nextIf;
|
||||
}
|
||||
current = nextIf;
|
||||
}
|
||||
|
||||
if (!root) {
|
||||
return null;
|
||||
}
|
||||
if (current && defaultBlock) {
|
||||
current.alternate = defaultBlock;
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
function replaceLoopSwitchWithIf(path) {
|
||||
const body = path.get("body");
|
||||
if (!body.isBlockStatement()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const statements = body.get("body");
|
||||
if (statements.length !== 1 || !statements[0].isSwitchStatement()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const ifChain = buildIfChainFromSwitch(statements[0].node);
|
||||
if (!ifChain) {
|
||||
return false;
|
||||
}
|
||||
|
||||
body.replaceWith(t.blockStatement([ifChain]));
|
||||
return true;
|
||||
}
|
||||
|
||||
const UNRESOLVED = Symbol("unresolved");
|
||||
|
||||
function getResolvedFunctionCallPath(functionPath) {
|
||||
if (functionPath.parentPath.isCallExpression() && functionPath.parentKey === "callee") {
|
||||
return functionPath.parentPath;
|
||||
}
|
||||
if (
|
||||
functionPath.parentPath.isUnaryExpression() &&
|
||||
functionPath.parentPath.parentPath &&
|
||||
functionPath.parentPath.parentPath.isCallExpression() &&
|
||||
functionPath.parentPath.parentKey === "callee"
|
||||
) {
|
||||
return functionPath.parentPath.parentPath;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function resolveObjectPropertyFromBinding(objectPath, propertyValue, seenBindings = new Set()) {
|
||||
if (!objectPath || !objectPath.node || !objectPath.isIdentifier()) {
|
||||
return UNRESOLVED;
|
||||
}
|
||||
|
||||
const binding = objectPath.scope.getBinding(objectPath.node.name);
|
||||
if (!binding || seenBindings.has(binding)) {
|
||||
return UNRESOLVED;
|
||||
}
|
||||
seenBindings.add(binding);
|
||||
|
||||
const declaratorPath = getDeclaratorForBinding(binding);
|
||||
if (!declaratorPath) {
|
||||
seenBindings.delete(binding);
|
||||
return UNRESOLVED;
|
||||
}
|
||||
|
||||
const initPath = declaratorPath.get("init");
|
||||
if (!initPath.node) {
|
||||
seenBindings.delete(binding);
|
||||
return UNRESOLVED;
|
||||
}
|
||||
|
||||
if (initPath.isIdentifier()) {
|
||||
const resolved = resolveObjectPropertyFromBinding(initPath, propertyValue, seenBindings);
|
||||
seenBindings.delete(binding);
|
||||
return resolved;
|
||||
}
|
||||
|
||||
if (!initPath.isObjectExpression()) {
|
||||
seenBindings.delete(binding);
|
||||
return UNRESOLVED;
|
||||
}
|
||||
|
||||
for (const propertyPath of initPath.get("properties")) {
|
||||
if (!propertyPath.isObjectProperty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const keyPath = propertyPath.get("key");
|
||||
const key = propertyPath.node.computed ? resolveStatic(keyPath, seenBindings) : keyPath.isIdentifier() ? keyPath.node.name : resolveStatic(keyPath, seenBindings);
|
||||
if (key === UNRESOLVED || String(key) !== String(propertyValue)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const resolved = resolveStatic(propertyPath.get("value"), seenBindings);
|
||||
seenBindings.delete(binding);
|
||||
return resolved;
|
||||
}
|
||||
|
||||
seenBindings.delete(binding);
|
||||
return UNRESOLVED;
|
||||
}
|
||||
|
||||
function resolveStatic(path, seenBindings = new Set()) {
|
||||
if (!path || !path.node) {
|
||||
return UNRESOLVED;
|
||||
}
|
||||
|
||||
if (path.isStringLiteral() || path.isNumericLiteral() || path.isBooleanLiteral()) {
|
||||
return path.node.value;
|
||||
}
|
||||
if (path.isNullLiteral()) {
|
||||
return null;
|
||||
}
|
||||
if (path.isTemplateLiteral() && path.node.expressions.length === 0) {
|
||||
return path.node.quasis[0].value.cooked;
|
||||
}
|
||||
if (path.isIdentifier({ name: "undefined" })) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (path.isArrayExpression()) {
|
||||
const values = [];
|
||||
for (const elementPath of path.get("elements")) {
|
||||
if (!elementPath.node) {
|
||||
values.push(undefined);
|
||||
continue;
|
||||
}
|
||||
const value = resolveStatic(elementPath, seenBindings);
|
||||
if (value === UNRESOLVED) {
|
||||
return UNRESOLVED;
|
||||
}
|
||||
values.push(value);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
if (path.isObjectExpression()) {
|
||||
const out = Object.create(null);
|
||||
for (const propPath of path.get("properties")) {
|
||||
if (!propPath.isObjectProperty()) {
|
||||
return UNRESOLVED;
|
||||
}
|
||||
const keyPath = propPath.get("key");
|
||||
const key = propPath.node.computed ? resolveStatic(keyPath, seenBindings) : keyPath.isIdentifier() ? keyPath.node.name : resolveStatic(keyPath, seenBindings);
|
||||
if (key === UNRESOLVED) {
|
||||
return UNRESOLVED;
|
||||
}
|
||||
const value = resolveStatic(propPath.get("value"), seenBindings);
|
||||
if (value === UNRESOLVED) {
|
||||
return UNRESOLVED;
|
||||
}
|
||||
out[key] = value;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
if (path.isIdentifier()) {
|
||||
const binding = path.scope.getBinding(path.node.name);
|
||||
if (!binding || seenBindings.has(binding)) {
|
||||
return UNRESOLVED;
|
||||
}
|
||||
seenBindings.add(binding);
|
||||
|
||||
let resolved = UNRESOLVED;
|
||||
if (binding.path.parentPath && binding.path.parentPath.isVariableDeclarator()) {
|
||||
resolved = resolveStatic(binding.path.parentPath.get("init"), seenBindings);
|
||||
} else if (binding.kind === "param" && binding.path.parentPath && binding.path.parentPath.isFunction()) {
|
||||
const callPath = getResolvedFunctionCallPath(binding.path.parentPath);
|
||||
if (callPath && typeof binding.path.key === "number") {
|
||||
resolved = resolveStatic(callPath.get(`arguments.${binding.path.key}`), seenBindings);
|
||||
}
|
||||
}
|
||||
|
||||
seenBindings.delete(binding);
|
||||
if (resolved !== UNRESOLVED) {
|
||||
return resolved;
|
||||
}
|
||||
}
|
||||
|
||||
if (path.isMemberExpression()) {
|
||||
const propertyValue = path.node.computed
|
||||
? resolveStatic(path.get("property"), seenBindings)
|
||||
: path.get("property").isIdentifier()
|
||||
? path.node.property.name
|
||||
: resolveStatic(path.get("property"), seenBindings);
|
||||
if (propertyValue === UNRESOLVED) {
|
||||
return UNRESOLVED;
|
||||
}
|
||||
|
||||
const directPropertyValue = resolveObjectPropertyFromBinding(path.get("object"), propertyValue, seenBindings);
|
||||
if (directPropertyValue !== UNRESOLVED) {
|
||||
return directPropertyValue;
|
||||
}
|
||||
|
||||
const objectValue = resolveStatic(path.get("object"), seenBindings);
|
||||
if (objectValue === UNRESOLVED) {
|
||||
return UNRESOLVED;
|
||||
}
|
||||
if (Array.isArray(objectValue)) {
|
||||
const index = Number(propertyValue);
|
||||
return Number.isInteger(index) ? objectValue[index] : UNRESOLVED;
|
||||
}
|
||||
if (objectValue && typeof objectValue === "object" && propertyValue in objectValue) {
|
||||
return objectValue[propertyValue];
|
||||
}
|
||||
return UNRESOLVED;
|
||||
}
|
||||
|
||||
if (
|
||||
path.isCallExpression() &&
|
||||
path.get("callee").isMemberExpression() &&
|
||||
(
|
||||
path.get("callee.property").isIdentifier({ name: "split" }) ||
|
||||
path.get("callee.property").isStringLiteral({ value: "split" })
|
||||
) &&
|
||||
path.get("arguments.0").isStringLiteral()
|
||||
) {
|
||||
const source = resolveStatic(path.get("callee.object"), seenBindings);
|
||||
if (typeof source === "string") {
|
||||
return source.split(path.node.arguments[0].value);
|
||||
}
|
||||
}
|
||||
|
||||
const evaluated = path.evaluate();
|
||||
return evaluated.confident ? evaluated.value : UNRESOLVED;
|
||||
}
|
||||
|
||||
function getLiteralValue(path) {
|
||||
const resolved = resolveStatic(path);
|
||||
if (typeof resolved === "string" || typeof resolved === "number") {
|
||||
return resolved;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function normalizeKey(node) {
|
||||
return generator(node, {
|
||||
compact: true,
|
||||
comments: false,
|
||||
jsescOption: { minimal: true }
|
||||
}).code;
|
||||
}
|
||||
|
||||
function getCaseKey(casePath) {
|
||||
const value = getLiteralValue(casePath.get("test"));
|
||||
return value === null ? normalizeKey(casePath.node.test) : value;
|
||||
}
|
||||
|
||||
function getOrderFromInit(initPath) {
|
||||
if (initPath.isArrayExpression()) {
|
||||
const values = [];
|
||||
for (const elementPath of initPath.get("elements")) {
|
||||
if (!elementPath.node) {
|
||||
return null;
|
||||
}
|
||||
const value = getLiteralValue(elementPath);
|
||||
values.push(value === null ? normalizeKey(elementPath.node) : value);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
if (
|
||||
initPath.isCallExpression() &&
|
||||
initPath.get("callee").isMemberExpression() &&
|
||||
(
|
||||
initPath.get("callee.property").isIdentifier({ name: "split" }) ||
|
||||
initPath.get("callee.property").isStringLiteral({ value: "split" })
|
||||
) &&
|
||||
initPath.get("arguments.0").isStringLiteral()
|
||||
) {
|
||||
const source = getLiteralValue(initPath.get("callee.object"));
|
||||
if (typeof source === "string") {
|
||||
return source.split(initPath.node.arguments[0].value);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getOrderFromResolvedValue(value) {
|
||||
if (!Array.isArray(value) || value.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const order = [];
|
||||
for (const item of value) {
|
||||
if (typeof item === "string" || typeof item === "number") {
|
||||
order.push(item);
|
||||
continue;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return order;
|
||||
}
|
||||
|
||||
function getDeclaratorForBinding(binding) {
|
||||
if (!binding || !binding.path) {
|
||||
return null;
|
||||
}
|
||||
if (binding.path.isVariableDeclarator()) {
|
||||
return binding.path;
|
||||
}
|
||||
if (binding.path.parentPath && binding.path.parentPath.isVariableDeclarator()) {
|
||||
return binding.path.parentPath;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getOrderFromBinding(binding) {
|
||||
const declaratorPath = getDeclaratorForBinding(binding);
|
||||
if (!declaratorPath) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const initPath = declaratorPath.get("init");
|
||||
if (!initPath.node) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return getOrderFromInit(initPath) || getOrderFromResolvedValue(resolveStatic(initPath));
|
||||
}
|
||||
|
||||
function getOrderFromSplitCallWithObjectTable(initPath, loopPath) {
|
||||
if (
|
||||
!initPath.isCallExpression() ||
|
||||
!initPath.get("callee").isMemberExpression() ||
|
||||
!(
|
||||
initPath.get("callee.property").isIdentifier({ name: "split" }) ||
|
||||
initPath.get("callee.property").isStringLiteral({ value: "split" })
|
||||
) ||
|
||||
!initPath.get("arguments.0").isStringLiteral()
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const sourcePath = initPath.get("callee.object");
|
||||
if (!sourcePath.isMemberExpression() || !sourcePath.get("object").isIdentifier()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const objectName = sourcePath.get("object").node.name;
|
||||
const propertyKey = sourcePath.node.computed ? getStaticPropertyKey(sourcePath.node.property) : getStaticPropertyKey(sourcePath.node.property);
|
||||
if (propertyKey === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const match = collectObjectPipeStringCandidates(loopPath, objectName).find((candidate) => candidate.key === propertyKey);
|
||||
return match ? match.order : null;
|
||||
}
|
||||
|
||||
function getOrderFromDiscriminant(discriminantPath) {
|
||||
if (!discriminantPath.isMemberExpression()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const objectPath = discriminantPath.get("object");
|
||||
const directOrder = getOrderFromResolvedValue(resolveStatic(objectPath));
|
||||
if (directOrder) {
|
||||
return directOrder;
|
||||
}
|
||||
|
||||
if (!objectPath.isIdentifier()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const binding = objectPath.scope.getBinding(objectPath.node.name);
|
||||
return getOrderFromBinding(binding);
|
||||
}
|
||||
|
||||
function findNearbyOrderDeclaration(loopPath, maxSiblings = 3) {
|
||||
let siblingPath = loopPath.getPrevSibling();
|
||||
for (let seen = 0; siblingPath && siblingPath.node && seen < maxSiblings; seen += 1) {
|
||||
if (!siblingPath.isVariableDeclaration()) {
|
||||
break;
|
||||
}
|
||||
|
||||
for (const declaratorPath of siblingPath.get("declarations")) {
|
||||
if (!declaratorPath.node || !declaratorPath.get("init").node) {
|
||||
continue;
|
||||
}
|
||||
const order =
|
||||
getOrderFromInit(declaratorPath.get("init")) ||
|
||||
getOrderFromSplitCallWithObjectTable(declaratorPath.get("init"), loopPath) ||
|
||||
getOrderFromResolvedValue(resolveStatic(declaratorPath.get("init")));
|
||||
if (order) {
|
||||
return {
|
||||
order,
|
||||
declarationPath: siblingPath
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
siblingPath = siblingPath.getPrevSibling();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function buildBlocksFromSwitchOrder(switchStmt, order) {
|
||||
if (!order) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const caseMap = new Map();
|
||||
switchStmt.get("cases").forEach(casePath => {
|
||||
if (!casePath.node.test) {
|
||||
return;
|
||||
}
|
||||
caseMap.set(getCaseKey(casePath), getCaseStatements(casePath));
|
||||
});
|
||||
|
||||
const blocks = [];
|
||||
order.forEach(key => {
|
||||
const stmts = caseMap.get(key);
|
||||
if (stmts) {
|
||||
blocks.push(...stmts);
|
||||
}
|
||||
});
|
||||
|
||||
return blocks.length > 0 ? blocks : null;
|
||||
}
|
||||
|
||||
function getStaticPropertyKey(node) {
|
||||
if (t.isIdentifier(node)) {
|
||||
return node.name;
|
||||
}
|
||||
if (t.isStringLiteral(node) || t.isNumericLiteral(node)) {
|
||||
return String(node.value);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function resolveObjectAliasNames(loopPath, objectName) {
|
||||
const names = new Set();
|
||||
let currentName = objectName;
|
||||
|
||||
while (currentName) {
|
||||
if (names.has(currentName)) {
|
||||
break;
|
||||
}
|
||||
names.add(currentName);
|
||||
|
||||
const binding = loopPath.scope.getBinding(currentName);
|
||||
const declaratorPath = getDeclaratorForBinding(binding);
|
||||
if (!declaratorPath || !declaratorPath.get("init").isIdentifier()) {
|
||||
break;
|
||||
}
|
||||
|
||||
currentName = declaratorPath.get("init").node.name;
|
||||
}
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
function splitPipeString(value) {
|
||||
if (typeof value !== "string" || !value.includes("|")) {
|
||||
return null;
|
||||
}
|
||||
const parts = value.split("|");
|
||||
return parts.length > 1 ? parts : null;
|
||||
}
|
||||
|
||||
function getPipeOrderFromValuePath(valuePath) {
|
||||
if (!valuePath || !valuePath.node) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (valuePath.isStringLiteral()) {
|
||||
return splitPipeString(valuePath.node.value);
|
||||
}
|
||||
|
||||
const resolved = resolveStatic(valuePath);
|
||||
return splitPipeString(resolved);
|
||||
}
|
||||
|
||||
function getCaseKeys(switchStmt) {
|
||||
const keys = [];
|
||||
switchStmt.get("cases").forEach(casePath => {
|
||||
if (!casePath.node.test) {
|
||||
return;
|
||||
}
|
||||
keys.push(getCaseKey(casePath));
|
||||
});
|
||||
return keys;
|
||||
}
|
||||
|
||||
function sameSequenceKeySet(order, caseKeys) {
|
||||
if (!order || order.length !== caseKeys.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const counts = new Map();
|
||||
caseKeys.forEach((key) => counts.set(String(key), (counts.get(String(key)) || 0) + 1));
|
||||
for (const item of order) {
|
||||
const key = String(item);
|
||||
const count = counts.get(key);
|
||||
if (!count) {
|
||||
return false;
|
||||
}
|
||||
if (count === 1) {
|
||||
counts.delete(key);
|
||||
} else {
|
||||
counts.set(key, count - 1);
|
||||
}
|
||||
}
|
||||
return counts.size === 0;
|
||||
}
|
||||
|
||||
function getAncestorBodyStatements(loopPath) {
|
||||
const bodies = [];
|
||||
const seen = new Set();
|
||||
let current = loopPath;
|
||||
|
||||
while (current) {
|
||||
if (current.isProgram()) {
|
||||
const key = `program:${current.node.start || 0}`;
|
||||
if (!seen.has(key)) {
|
||||
bodies.push(current.get("body"));
|
||||
seen.add(key);
|
||||
}
|
||||
} else if (current.isBlockStatement() && Array.isArray(current.node.body)) {
|
||||
const key = `block:${current.node.start || 0}`;
|
||||
if (!seen.has(key)) {
|
||||
bodies.push(current.get("body"));
|
||||
seen.add(key);
|
||||
}
|
||||
}
|
||||
current = current.parentPath;
|
||||
}
|
||||
|
||||
return bodies;
|
||||
}
|
||||
|
||||
function collectObjectPipeStringCandidates(loopPath, objectName) {
|
||||
if (!objectName) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const objectNames = resolveObjectAliasNames(loopPath, objectName);
|
||||
const candidates = [];
|
||||
const loopStart = loopPath.node.start || 0;
|
||||
getAncestorBodyStatements(loopPath).forEach((statements) => {
|
||||
statements.forEach((stmtPath) => {
|
||||
if (!stmtPath.node || (stmtPath.node.start || 0) >= loopStart) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (stmtPath.isVariableDeclaration()) {
|
||||
stmtPath.get("declarations").forEach((declPath) => {
|
||||
if (!declPath.get("id").isIdentifier() || !objectNames.has(declPath.get("id").node.name) || !declPath.get("init").isObjectExpression()) {
|
||||
return;
|
||||
}
|
||||
declPath.get("init.properties").forEach((propPath) => {
|
||||
if (!propPath.isObjectProperty()) {
|
||||
return;
|
||||
}
|
||||
const keyPath = propPath.get("key");
|
||||
const key = propPath.node.computed ? getStaticPropertyKey(keyPath.node) : getStaticPropertyKey(keyPath.node);
|
||||
const valuePath = propPath.get("value");
|
||||
const pipeOrder = getPipeOrderFromValuePath(valuePath);
|
||||
if (pipeOrder) {
|
||||
candidates.push({ key, order: pipeOrder, start: stmtPath.node.start || 0 });
|
||||
}
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!stmtPath.isExpressionStatement()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const exprPath = stmtPath.get("expression");
|
||||
if (!exprPath.isAssignmentExpression({ operator: "=" })) {
|
||||
return;
|
||||
}
|
||||
|
||||
const leftPath = exprPath.get("left");
|
||||
const rightPath = exprPath.get("right");
|
||||
if (!leftPath.isMemberExpression() || !leftPath.get("object").isIdentifier() || !objectNames.has(leftPath.get("object").node.name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const key = leftPath.node.computed ? getStaticPropertyKey(leftPath.node.property) : getStaticPropertyKey(leftPath.node.property);
|
||||
if (key === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pipeOrder = getPipeOrderFromValuePath(rightPath);
|
||||
if (pipeOrder) {
|
||||
candidates.push({ key, order: pipeOrder, start: stmtPath.node.start || 0 });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
candidates.sort((a, b) => b.start - a.start);
|
||||
return candidates;
|
||||
}
|
||||
|
||||
function getOrderFromObjectStringTable(loopPath, switchStmt) {
|
||||
const discriminant = switchStmt.get("discriminant");
|
||||
if (!discriminant.isMemberExpression() || !discriminant.get("object").isIdentifier()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const candidates = collectObjectPipeStringCandidates(loopPath, discriminant.get("object").node.name);
|
||||
if (candidates.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const caseKeys = getCaseKeys(switchStmt);
|
||||
const match = candidates.find((candidate) => sameSequenceKeySet(candidate.order, caseKeys));
|
||||
return match ? match.order : null;
|
||||
}
|
||||
|
||||
function flatten(ast) {
|
||||
let changed = false;
|
||||
|
||||
traverse(ast, {
|
||||
ForStatement(path) {
|
||||
const body = path.get("body");
|
||||
if (!body.isBlockStatement() || body.get("body").length === 0) {
|
||||
return;
|
||||
}
|
||||
const firstStmt = body.get("body.0");
|
||||
if (firstStmt.isSwitchStatement() && path.get("init").isVariableDeclaration() && path.node.update === null) {
|
||||
let order = null;
|
||||
const leftovers = [];
|
||||
path.get("init.declarations").forEach(decl => {
|
||||
if (decl.node.init === null) {
|
||||
leftovers.push(t.variableDeclaration("var", [clone(decl.node)]));
|
||||
return;
|
||||
}
|
||||
const candidate =
|
||||
getOrderFromInit(decl.get("init")) ||
|
||||
getOrderFromSplitCallWithObjectTable(decl.get("init"), path) ||
|
||||
getOrderFromResolvedValue(resolveStatic(decl.get("init")));
|
||||
if (candidate) {
|
||||
order = candidate;
|
||||
}
|
||||
});
|
||||
if (order) {
|
||||
const caseMap = new Map();
|
||||
firstStmt.get("cases").forEach(casePath => {
|
||||
if (!casePath.node.test) {
|
||||
return;
|
||||
}
|
||||
caseMap.set(getCaseKey(casePath), getCaseStatements(casePath));
|
||||
});
|
||||
const blocks = [...leftovers];
|
||||
order.forEach(key => {
|
||||
const stmts = caseMap.get(key);
|
||||
if (stmts) {
|
||||
blocks.push(...stmts);
|
||||
}
|
||||
});
|
||||
if (blocks.length > 0) {
|
||||
path.replaceWithMultiple(blocks);
|
||||
changed = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (replaceLoopSwitchWithIf(path)) {
|
||||
changed = true;
|
||||
}
|
||||
},
|
||||
WhileStatement(path) {
|
||||
if (path.get("test").isUnaryExpression() && path.get("body").isBlockStatement()) {
|
||||
const switchStmt = path.get("body.body.0");
|
||||
if (switchStmt.isSwitchStatement()) {
|
||||
const nearby = findNearbyOrderDeclaration(path);
|
||||
const order =
|
||||
(nearby && nearby.order) ||
|
||||
getOrderFromDiscriminant(switchStmt.get("discriminant")) ||
|
||||
getOrderFromObjectStringTable(path, switchStmt);
|
||||
const blocks = buildBlocksFromSwitchOrder(switchStmt, order);
|
||||
if (blocks) {
|
||||
path.replaceWithMultiple(blocks);
|
||||
if (nearby && nearby.declarationPath && nearby.declarationPath.node) {
|
||||
nearby.declarationPath.remove();
|
||||
}
|
||||
changed = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (replaceLoopSwitchWithIf(path)) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return { ast, changed };
|
||||
}
|
||||
|
||||
const { inputPath, outputPath } = parseArgs();
|
||||
let ast = parseFile(inputPath);
|
||||
let changed = false;
|
||||
do {
|
||||
({ ast, changed } = flatten(ast));
|
||||
if (changed) {
|
||||
ast = reparse(ast);
|
||||
}
|
||||
} while (changed);
|
||||
saveAst(ast, outputPath);
|
||||
Reference in New Issue
Block a user