Skip to content

Commit

Permalink
Split simplify plugin to multiple files (#624)
Browse files Browse the repository at this point in the history
* Split Simplify to multiple files - IfStatement

* Extract Conditional Expression transformations to separate file

* Move Logical Expression patterns to separate file

* Move AssignmentExpression to separate file
  • Loading branch information
boopathi authored Jul 10, 2017
1 parent 8c7b973 commit 25e57f2
Show file tree
Hide file tree
Showing 6 changed files with 710 additions and 623 deletions.
125 changes: 125 additions & 0 deletions packages/babel-plugin-minify-simplify/src/assignment-expression.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
"use strict";

const operators = new Set([
"+",
"-",
"*",
"%",
"<<",
">>",
">>>",
"&",
"|",
"^",
"/",
"**"
]);

const updateOperators = new Set(["+", "-"]);

module.exports = t => {
function simplify(path) {
const rightExpr = path.get("right");
const leftExpr = path.get("left");

if (path.node.operator !== "=") {
return;
}

const canBeUpdateExpression =
rightExpr.get("right").isNumericLiteral() &&
rightExpr.get("right").node.value === 1 &&
updateOperators.has(rightExpr.node.operator);

if (leftExpr.isMemberExpression()) {
const leftPropNames = getPropNames(leftExpr);
const rightPropNames = getPropNames(rightExpr.get("left"));

if (
!leftPropNames ||
leftPropNames.indexOf(undefined) > -1 ||
!rightPropNames ||
rightPropNames.indexOf(undefined) > -1 ||
!operators.has(rightExpr.node.operator) ||
!areArraysEqual(leftPropNames, rightPropNames)
) {
return;
}
} else {
if (
!rightExpr.isBinaryExpression() ||
!operators.has(rightExpr.node.operator) ||
leftExpr.node.name !== rightExpr.node.left.name
) {
return;
}
}

let newExpression;

// special case x=x+1 --> ++x
if (canBeUpdateExpression) {
newExpression = t.updateExpression(
rightExpr.node.operator + rightExpr.node.operator,
t.clone(leftExpr.node),
true /* prefix */
);
} else {
newExpression = t.assignmentExpression(
rightExpr.node.operator + "=",
t.clone(leftExpr.node),
t.clone(rightExpr.node.right)
);
}

path.replaceWith(newExpression);
}

return {
simplify
};
};

function areArraysEqual(arr1, arr2) {
return arr1.every((value, index) => {
return String(value) === String(arr2[index]);
});
}

function getPropNames(path) {
if (!path.isMemberExpression()) {
return;
}

let obj = path.get("object");

const prop = path.get("property");
const propNames = [getName(prop.node)];

while (obj.type === "MemberExpression") {
const node = obj.get("property").node;
if (node) {
propNames.push(getName(node));
}
obj = obj.get("object");
}
propNames.push(getName(obj.node));

return propNames;
}

function getName(node) {
if (node.type === "ThisExpression") {
return "this";
}
if (node.type === "Super") {
return "super";
}
if (node.type === "NullLiteral") {
return "null";
}
// augment identifiers so that they don't match
// string/number literals
// but still match against each other
return node.name ? node.name + "_" : node.value /* Literal */;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
const h = require("./helpers");
const PatternMatch = require("./pattern-match");

module.exports = t => {
// small abstractions
const not = node => t.unaryExpression("!", node);
const notnot = node => not(not(node));
const or = (a, b) => t.logicalExpression("||", a, b);
const and = (a, b) => t.logicalExpression("&&", a, b);

function simplifyPatterns(path) {
const test = path.get("test");
const consequent = path.get("consequent");
const alternate = path.get("alternate");

const { Expression: EX, LogicalExpression: LE } = h.typeSymbols(t);

// Convention:
// ===============
// for each pattern [test, consequent, alternate, handler(expr, cons, alt)]
const matcher = new PatternMatch([
[LE, true, false, e => e],
[EX, true, false, e => notnot(e)],

[EX, false, true, e => not(e)],

[LE, true, EX, (e, c, a) => or(e, a)],
[EX, true, EX, (e, c, a) => or(notnot(e), a)],

[EX, false, EX, (e, c, a) => and(not(e), a)],

[EX, EX, true, (e, c) => or(not(e), c)],

[LE, EX, false, (e, c) => and(e, c)],
[EX, EX, false, (e, c) => and(notnot(e), c)]
]);

const result = matcher.match(
[test, consequent, alternate],
h.isPatternMatchesPath(t)
);

if (result.match) {
path.replaceWith(
result.value(test.node, consequent.node, alternate.node)
);
}
}

return {
simplifyPatterns
};
};
51 changes: 51 additions & 0 deletions packages/babel-plugin-minify-simplify/src/helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"use strict";

const VOID_0 = t => t.unaryExpression("void", t.numericLiteral(0), true);

// Types as Symbols - for comparing types
// init must be empty object -
// computing this involves checking object.keys() to be of length 0
// skipped otherwise
const types = {};
const typeSymbols = t => {
// don't recompute
if (Object.keys(types).length < 1) {
t.TYPES.forEach(type => {
types[type] = Symbol.for(type);
});
}
return types;
};

const isNodeOfType = (t, node, typeSymbol) =>
typeof typeSymbol !== "symbol"
? false
: t["is" + Symbol.keyFor(typeSymbol)](node);

const isPatternMatchesPath = t =>
function _isPatternMatchesPath(patternValue, inputPath) {
if (Array.isArray(patternValue)) {
for (let i = 0; i < patternValue.length; i++) {
if (_isPatternMatchesPath(patternValue[i], inputPath)) {
return true;
}
}
return false;
}
if (typeof patternValue === "function") {
return patternValue(inputPath);
}
if (isNodeOfType(t, inputPath.node, patternValue)) return true;
const evalResult = inputPath.evaluate();
if (!evalResult.confident || !inputPath.isPure()) return false;
return evalResult.value === patternValue;
};

module.exports = {
VOID_0,
// Types as Symbols
typeSymbols,
// This is required for resolving type aliases
isNodeOfType,
isPatternMatchesPath
};
Loading

0 comments on commit 25e57f2

Please sign in to comment.