212 lines
6.4 KiB
JavaScript
212 lines
6.4 KiB
JavaScript
const { parseArgs, parseFile, reparse, saveAst, t, traverse } = require("./shared");
|
|
|
|
function toStatements(expressions) {
|
|
return expressions.map(expr => t.expressionStatement(expr));
|
|
}
|
|
|
|
function hasUnsafeContinue(bodyPath) {
|
|
let found = false;
|
|
bodyPath.traverse({
|
|
Function(path) {
|
|
path.skip();
|
|
},
|
|
ContinueStatement(path) {
|
|
found = true;
|
|
path.stop();
|
|
}
|
|
});
|
|
return found;
|
|
}
|
|
|
|
function ensureBlock(path) {
|
|
if (path.isBlockStatement()) {
|
|
return path;
|
|
}
|
|
path.replaceWith(t.blockStatement([path.node]));
|
|
return path.parentPath.get(path.key);
|
|
}
|
|
|
|
function replaceStatement(path, node) {
|
|
if (path.inList) {
|
|
path.replaceWith(node);
|
|
return;
|
|
}
|
|
path.replaceWith(t.blockStatement([node]));
|
|
}
|
|
|
|
function replaceStatementWithStatements(path, statements) {
|
|
if (statements.length === 0) {
|
|
path.remove();
|
|
return;
|
|
}
|
|
if (path.inList) {
|
|
path.replaceWithMultiple(statements);
|
|
return;
|
|
}
|
|
if (statements.length === 1) {
|
|
path.replaceWith(statements[0]);
|
|
return;
|
|
}
|
|
path.replaceWith(t.blockStatement(statements));
|
|
}
|
|
|
|
function normalize(ast) {
|
|
let changed = false;
|
|
|
|
traverse(ast, {
|
|
UnaryExpression(path) {
|
|
if (path.node.operator === "void" && path.parentPath.isExpressionStatement()) {
|
|
path.replaceWith(path.node.argument);
|
|
changed = true;
|
|
}
|
|
},
|
|
SequenceExpression(path) {
|
|
if (path.parentPath.isExpressionStatement()) {
|
|
replaceStatementWithStatements(path.parentPath, toStatements(path.node.expressions));
|
|
changed = true;
|
|
return;
|
|
}
|
|
|
|
if (path.parentPath.isReturnStatement()) {
|
|
if (!path.parentPath.inList) {
|
|
return;
|
|
}
|
|
const exprs = path.node.expressions.slice();
|
|
const returnExpr = exprs.pop();
|
|
path.parentPath.insertBefore(toStatements(exprs));
|
|
path.replaceWith(returnExpr);
|
|
changed = true;
|
|
return;
|
|
}
|
|
|
|
if (path.parentPath.isIfStatement() && path.key === "test") {
|
|
if (!path.parentPath.inList) {
|
|
return;
|
|
}
|
|
const exprs = path.node.expressions.slice();
|
|
const testExpr = exprs.pop();
|
|
path.parentPath.insertBefore(toStatements(exprs));
|
|
path.replaceWith(testExpr);
|
|
changed = true;
|
|
return;
|
|
}
|
|
|
|
if (path.parentPath.isForStatement() && path.key === "init") {
|
|
if (!path.parentPath.inList) {
|
|
return;
|
|
}
|
|
path.parentPath.insertBefore(toStatements(path.node.expressions));
|
|
path.remove();
|
|
changed = true;
|
|
return;
|
|
}
|
|
|
|
if (path.parentPath.isForStatement() && path.key === "test") {
|
|
const exprs = path.node.expressions.slice();
|
|
const testExpr = exprs.pop();
|
|
const forPath = path.parentPath;
|
|
const bodyPath = ensureBlock(forPath.get("body"));
|
|
bodyPath.unshiftContainer(
|
|
"body",
|
|
t.ifStatement(t.unaryExpression("!", testExpr, true), t.blockStatement([t.breakStatement()]))
|
|
);
|
|
bodyPath.unshiftContainer("body", toStatements(exprs));
|
|
path.remove();
|
|
changed = true;
|
|
return;
|
|
}
|
|
|
|
if (path.parentPath.isForStatement() && path.key === "update") {
|
|
const forPath = path.parentPath;
|
|
const bodyPath = ensureBlock(forPath.get("body"));
|
|
if (hasUnsafeContinue(bodyPath)) {
|
|
return;
|
|
}
|
|
bodyPath.pushContainer("body", toStatements(path.node.expressions));
|
|
path.remove();
|
|
changed = true;
|
|
}
|
|
},
|
|
ConditionalExpression(path) {
|
|
if (path.parentPath.isExpressionStatement()) {
|
|
replaceStatement(
|
|
path.parentPath,
|
|
t.ifStatement(
|
|
path.node.test,
|
|
t.blockStatement([t.expressionStatement(path.node.consequent)]),
|
|
t.blockStatement([t.expressionStatement(path.node.alternate)])
|
|
)
|
|
);
|
|
changed = true;
|
|
}
|
|
},
|
|
LogicalExpression(path) {
|
|
if (!path.parentPath.isExpressionStatement()) {
|
|
return;
|
|
}
|
|
const body = t.blockStatement([t.expressionStatement(path.node.right)]);
|
|
if (path.node.operator === "&&") {
|
|
replaceStatement(path.parentPath, t.ifStatement(path.node.left, body, null));
|
|
} else if (path.node.operator === "||") {
|
|
replaceStatement(path.parentPath, t.ifStatement(t.unaryExpression("!", path.node.left, true), body, null));
|
|
} else {
|
|
return;
|
|
}
|
|
changed = true;
|
|
},
|
|
AssignmentExpression(path) {
|
|
if (!path.parentPath.isExpressionStatement() || !t.isConditionalExpression(path.node.right)) {
|
|
return;
|
|
}
|
|
const test = path.node.right.test;
|
|
const consequent = t.expressionStatement(
|
|
t.assignmentExpression(path.node.operator, t.cloneNode(path.node.left, true), path.node.right.consequent)
|
|
);
|
|
const alternate = t.expressionStatement(
|
|
t.assignmentExpression(path.node.operator, t.cloneNode(path.node.left, true), path.node.right.alternate)
|
|
);
|
|
replaceStatement(path.parentPath, t.ifStatement(test, t.blockStatement([consequent]), t.blockStatement([alternate])));
|
|
changed = true;
|
|
},
|
|
CallExpression(path) {
|
|
if (!path.parentPath.isExpressionStatement()) {
|
|
return;
|
|
}
|
|
if (path.get("callee").isFunctionExpression() && path.node.arguments.length === 0) {
|
|
replaceStatementWithStatements(path.parentPath, path.node.callee.body.body);
|
|
changed = true;
|
|
return;
|
|
}
|
|
if (
|
|
path.get("callee").isMemberExpression() &&
|
|
path.get("callee.object").isFunctionExpression() &&
|
|
path.get("callee.property").isIdentifier({ name: "call" }) &&
|
|
path.node.arguments.length === 1 &&
|
|
path.get("arguments.0").isThisExpression()
|
|
) {
|
|
replaceStatementWithStatements(path.parentPath, path.node.callee.object.body.body);
|
|
changed = true;
|
|
}
|
|
},
|
|
BlockStatement(path) {
|
|
if (path.parentPath.isSwitchCase()) {
|
|
path.replaceWithMultiple(path.node.body);
|
|
changed = true;
|
|
}
|
|
}
|
|
});
|
|
|
|
return { ast, changed };
|
|
}
|
|
|
|
const { inputPath, outputPath } = parseArgs();
|
|
let ast = parseFile(inputPath);
|
|
let changed = false;
|
|
do {
|
|
({ ast, changed } = normalize(ast));
|
|
if (changed) {
|
|
ast = reparse(ast);
|
|
}
|
|
} while (changed);
|
|
saveAst(ast, outputPath);
|