1
0
mirror of https://github.com/S2-/minifyfromhtml.git synced 2025-08-03 20:30:04 +02:00
Files
minifyfromhtml/node_modules/babel-plugin-minify-simplify/lib/if-statement.js
2018-05-05 13:54:07 +02:00

273 lines
9.3 KiB
JavaScript

"use strict";
const REPLACED = Symbol("replaced");
const h = require("./helpers");
module.exports = t => {
function mergeNestedIfs(path) {
const consequent = path.get("consequent");
const alternate = path.get("alternate"); // not nested if
if (!consequent.isIfStatement()) return; // there are no alternate nodes in both the if statements (nested)
if (alternate.node || consequent.get("alternate").node) return;
const test = path.get("test");
test.replaceWith(t.logicalExpression("&&", test.node, consequent.get("test").node));
consequent.replaceWith(t.clone(consequent.get("consequent").node));
} // No alternate, make into a guarded expression
function toGuardedExpression(path) {
const node = path.node;
if (node.consequent && !node.alternate && node.consequent.type === "ExpressionStatement") {
let op = "&&";
if (t.isUnaryExpression(node.test, {
operator: "!"
})) {
node.test = node.test.argument;
op = "||";
}
path.replaceWith(t.expressionStatement(t.logicalExpression(op, node.test, node.consequent.expression)));
return REPLACED;
}
} // both consequent and alternate are expressions, turn into ternary
function toTernary(path) {
const node = path.node;
if (t.isExpressionStatement(node.consequent) && t.isExpressionStatement(node.alternate)) {
path.replaceWith(t.conditionalExpression(node.test, node.consequent.expression, node.alternate.expression));
return REPLACED;
}
} // consequent and alternate are return -- conditional.
function toConditional(path) {
const node = path.node;
if (t.isReturnStatement(node.consequent) && t.isReturnStatement(node.alternate)) {
if (!node.consequent.argument && !node.alternate.argument) {
path.replaceWith(t.expressionStatement(node.test));
return REPLACED;
}
path.replaceWith(t.returnStatement(t.conditionalExpression(node.test, node.consequent.argument || h.VOID_0(t), node.alternate.argument || h.VOID_0(t))));
return REPLACED;
}
} // There is nothing after this If block. And one or both
// of the consequent and alternate are either expression statment
// or return statements.
function toReturn(path) {
const node = path.node;
if (!path.getSibling(path.key + 1).node && path.parentPath && path.parentPath.parentPath && path.parentPath.parentPath.isFunction()) {
// Only the consequent is a return, void the alternate.
if (t.isReturnStatement(node.consequent) && t.isExpressionStatement(node.alternate)) {
if (!node.consequent.argument) {
path.replaceWith(t.expressionStatement(t.logicalExpression("||", node.test, node.alternate.expression)));
return REPLACED;
}
path.replaceWith(t.returnStatement(t.conditionalExpression(node.test, node.consequent.argument || h.VOID_0(t), t.unaryExpression("void", node.alternate.expression, true))));
return REPLACED;
} // Only the alternate is a return, void the consequent.
if (t.isReturnStatement(node.alternate) && t.isExpressionStatement(node.consequent)) {
if (!node.alternate.argument) {
path.replaceWith(t.expressionStatement(t.logicalExpression("&&", node.test, node.consequent.expression)));
return REPLACED;
}
path.replaceWith(t.returnStatement(t.conditionalExpression(node.test, t.unaryExpression("void", node.consequent.expression, true), node.alternate.argument || h.VOID_0(t))));
return REPLACED;
}
if (t.isReturnStatement(node.consequent) && !node.alternate) {
if (!node.consequent.argument) {
path.replaceWith(t.expressionStatement(node.test));
return REPLACED;
} // This would only be worth it if the previous statement was an if
// because then we may merge to create a conditional.
if (path.getSibling(path.key - 1).isIfStatement()) {
path.replaceWith(t.returnStatement(t.conditionalExpression(node.test, node.consequent.argument || h.VOID_0(t), h.VOID_0(t))));
return REPLACED;
}
}
if (t.isReturnStatement(node.alternate) && !node.consequent) {
if (!node.alternate.argument) {
path.replaceWith(t.expressionStatement(node.test));
return REPLACED;
} // Same as above.
if (path.getSibling(path.key - 1).isIfStatement()) {
path.replaceWith(t.returnStatement(t.conditionalExpression(node.test, node.alternate.argument || h.VOID_0(t), h.VOID_0(t))));
return REPLACED;
}
}
}
let next = path.getSibling(path.key + 1); // If the next satatement(s) is an if statement and we can simplify that
// to potentailly be an expression (or a return) then this will make it
// easier merge.
if (next.isIfStatement()) {
next.pushContext(path.context);
next.visit();
next.popContext();
next = path.getSibling(path.key + 1);
} // Some other visitor might have deleted our node. OUR NODE ;_;
if (!path.node) {
return;
} // No alternate but the next statement is a return
// also turn into a return conditional
if (t.isReturnStatement(node.consequent) && !node.alternate && next.isReturnStatement()) {
const nextArg = next.node.argument || h.VOID_0(t);
next.remove();
path.replaceWith(t.returnStatement(t.conditionalExpression(node.test, node.consequent.argument || h.VOID_0(t), nextArg)));
return REPLACED;
} // Next is the last expression, turn into a return while void'ing the exprs
if (path.parentPath && path.parentPath.parentPath && path.parentPath.parentPath.isFunction() && !path.getSibling(path.key + 2).node && t.isReturnStatement(node.consequent) && !node.alternate && next.isExpressionStatement()) {
const nextExpr = next.node.expression;
next.remove();
if (node.consequent.argument) {
path.replaceWith(t.returnStatement(t.conditionalExpression(node.test, node.consequent.argument, t.unaryExpression("void", nextExpr, true))));
return REPLACED;
}
path.replaceWith(t.logicalExpression("||", node.test, nextExpr));
return REPLACED;
}
} // Remove else for if-return
function removeUnnecessaryElse(path) {
const node = path.node;
const consequent = path.get("consequent");
const alternate = path.get("alternate");
if (consequent.node && alternate.node && (consequent.isReturnStatement() || consequent.isBlockStatement() && t.isReturnStatement(consequent.node.body[consequent.node.body.length - 1])) && ( // don't hoist declarations
// TODO: validate declarations after fixing scope issues
alternate.isBlockStatement() ? !alternate.get("body").some(stmt => stmt.isVariableDeclaration({
kind: "let"
}) || stmt.isVariableDeclaration({
kind: "const"
})) : true)) {
path.insertAfter(alternate.isBlockStatement() ? alternate.node.body.map(el => t.clone(el)) : t.clone(alternate.node));
node.alternate = null;
return REPLACED;
}
}
function runTransforms(path) {
// ordered
const transforms = [toGuardedExpression, toTernary, toConditional, toReturn, removeUnnecessaryElse]; // run each of the replacement till we replace something
// which is identified by the Symbol(REPLACED) that each of the
// functions return when they replace something
for (var _i = 0; _i < transforms.length; _i++) {
const transform = transforms[_i];
if (transform(path) === REPLACED) {
break;
}
}
} // If the consequent is if and the altenrate is not then
// switch them out. That way we know we don't have to print
// a block.x
function switchConsequent(path) {
const node = path.node;
if (!node.alternate) {
return;
}
if (!t.isIfStatement(node.consequent)) {
return;
}
if (t.isIfStatement(node.alternate)) {
return;
}
node.test = t.unaryExpression("!", node.test, true);
var _ref = [node.consequent, node.alternate];
node.alternate = _ref[0];
node.consequent = _ref[1];
} // Make if statements with conditional returns in the body into
// an if statement that guards the rest of the block.
function conditionalReturnToGuards(path) {
const node = path.node;
if (!path.inList || !path.get("consequent").isBlockStatement() || node.alternate) {
return;
}
let ret;
let test;
const exprs = [];
const statements = node.consequent.body;
for (let i = 0, statement; statement = statements[i]; i++) {
if (t.isExpressionStatement(statement)) {
exprs.push(statement.expression);
} else if (t.isIfStatement(statement)) {
if (i < statements.length - 1) {
// This isn't the last statement. Bail.
return;
}
if (statement.alternate) {
return;
}
if (!t.isReturnStatement(statement.consequent)) {
return;
}
ret = statement.consequent;
test = statement.test;
} else {
return;
}
}
if (!test || !ret) {
return;
}
exprs.push(test);
const expr = exprs.length === 1 ? exprs[0] : t.sequenceExpression(exprs);
const replacement = t.logicalExpression("&&", node.test, expr);
path.replaceWith(t.ifStatement(replacement, ret, null));
}
return {
mergeNestedIfs,
simplify: runTransforms,
switchConsequent,
conditionalReturnToGuards
};
};