diff --git a/packages/babel-plugin-minify-simplify/__tests__/fixtures/double-negation/actual.js b/packages/babel-plugin-minify-simplify/__tests__/fixtures/double-negation/actual.js new file mode 100644 index 000000000..277da6925 --- /dev/null +++ b/packages/babel-plugin-minify-simplify/__tests__/fixtures/double-negation/actual.js @@ -0,0 +1,19 @@ +if (!!foo) { + const thisIs = aStatement; +} +if (!!foo && bar) { + const thisIs = aStatement; +} +if (!!some.complex({ expression: () => "obviously" })) { + const thisIs = aStatement; +} + +// -------------------------------------- + +!!foo && an.expression(); +(!!foo && bar) && an.expression(); +!!some.complex({ expression: () => "obviously" }) && an.expression(); + +!!foo || an.expression(); +(!!foo && bar) || an.expression(); +!!some.complex({ expression: () => "obviously" }) || an.expression(); diff --git a/packages/babel-plugin-minify-simplify/__tests__/fixtures/double-negation/expected.js b/packages/babel-plugin-minify-simplify/__tests__/fixtures/double-negation/expected.js new file mode 100644 index 000000000..34ba37980 --- /dev/null +++ b/packages/babel-plugin-minify-simplify/__tests__/fixtures/double-negation/expected.js @@ -0,0 +1,20 @@ +if (foo) { + const thisIs = aStatement; +} + +if (foo && bar) { + const thisIs = aStatement; +} + +if (some.complex({ + expression: () => "obviously" +})) { + const thisIs = aStatement; +} // -------------------------------------- + + +foo && an.expression(), !!foo && bar && an.expression(), some.complex({ + expression: () => "obviously" +}) && an.expression(), foo || an.expression(), !!foo && bar || an.expression(), some.complex({ + expression: () => "obviously" +}) || an.expression(); \ No newline at end of file diff --git a/packages/babel-plugin-minify-simplify/__tests__/fixtures/equals-minus-one/actual.js b/packages/babel-plugin-minify-simplify/__tests__/fixtures/equals-minus-one/actual.js new file mode 100644 index 000000000..e092ac6f1 --- /dev/null +++ b/packages/babel-plugin-minify-simplify/__tests__/fixtures/equals-minus-one/actual.js @@ -0,0 +1,59 @@ +if (foo.indexOf(bar) === -1) { + const thisIs = aStatement; +} +if (foo.indexOf(bar) !== -1) { + const thisIs = aStatement; +} + +if (foo.indexOf(bar) == -1) { + const thisIs = aStatement; +} +if (foo.indexOf(bar) != -1) { + const thisIs = aStatement; +} + +if (fooBar === -1) { + const thisIs = aStatement; +} +if (fooBar !== -1) { + const thisIs = aStatement; +} + +if (fooBar == -1) { + const thisIs = aStatement; +} +if (fooBar != -1) { + const thisIs = aStatement; +} + +if (foo.indexOf(bar) == 0) { + const thisIs = aStatement; +} +if (foo.indexOf(bar) != 0) { + const thisIs = aStatement; +} +if (foo.indexOf(bar) === 0) { + const thisIs = aStatement; +} +if (foo.indexOf(bar) !== 0) { + const thisIs = aStatement; +} + +// -------------------------------------- + +foo.indexOf(bar) === -1 && an.expression() +foo.indexOf(bar) !== -1 && an.expression() + +foo.indexOf(bar) == -1 && an.expression() +foo.indexOf(bar) != -1 && an.expression() + +fooBar === -1 && an.expression() +fooBar !== -1 && an.expression() + +fooBar == -1 && an.expression() +fooBar != -1 && an.expression() + +foo.indexOf(bar) == 0 && an.expression() +foo.indexOf(bar) != 0 && an.expression() +foo.indexOf(bar) === 0 && an.expression() +foo.indexOf(bar) !== 0 && an.expression() diff --git a/packages/babel-plugin-minify-simplify/__tests__/fixtures/equals-minus-one/expected.js b/packages/babel-plugin-minify-simplify/__tests__/fixtures/equals-minus-one/expected.js new file mode 100644 index 000000000..967f66132 --- /dev/null +++ b/packages/babel-plugin-minify-simplify/__tests__/fixtures/equals-minus-one/expected.js @@ -0,0 +1,50 @@ +if (!~foo.indexOf(bar)) { + const thisIs = aStatement; +} + +if (~foo.indexOf(bar)) { + const thisIs = aStatement; +} + +if (!~foo.indexOf(bar)) { + const thisIs = aStatement; +} + +if (~foo.indexOf(bar)) { + const thisIs = aStatement; +} + +if (!~fooBar) { + const thisIs = aStatement; +} + +if (~fooBar) { + const thisIs = aStatement; +} + +if (!~fooBar) { + const thisIs = aStatement; +} + +if (~fooBar) { + const thisIs = aStatement; +} + +if (foo.indexOf(bar) == 0) { + const thisIs = aStatement; +} + +if (foo.indexOf(bar) ^ 0) { + const thisIs = aStatement; +} + +if (foo.indexOf(bar) === 0) { + const thisIs = aStatement; +} + +if (foo.indexOf(bar) !== 0) { + const thisIs = aStatement; +} // -------------------------------------- + + +!~foo.indexOf(bar) && an.expression(), ~foo.indexOf(bar) && an.expression(), !~foo.indexOf(bar) && an.expression(), ~foo.indexOf(bar) && an.expression(), !~fooBar && an.expression(), ~fooBar && an.expression(), !~fooBar && an.expression(), ~fooBar && an.expression(), foo.indexOf(bar) == 0 && an.expression(), foo.indexOf(bar) ^ 0 && an.expression(), foo.indexOf(bar) === 0 && an.expression(), foo.indexOf(bar) !== 0 && an.expression(); \ No newline at end of file diff --git a/packages/babel-plugin-minify-simplify/__tests__/fixtures/negated-equal/actual.js b/packages/babel-plugin-minify-simplify/__tests__/fixtures/negated-equal/actual.js new file mode 100644 index 000000000..5e100dbee --- /dev/null +++ b/packages/babel-plugin-minify-simplify/__tests__/fixtures/negated-equal/actual.js @@ -0,0 +1,41 @@ +if (!(foo === 2)) { + const thisIs = aStatement; +} +if (!(foo !== 2)) { + const thisIs = aStatement; +} + +if (!(foo == 2)) { + const thisIs = aStatement; +} +if (!(foo != 2)) { + const thisIs = aStatement; +} + +if (!(myFoo({ bar: { baz: { quux() {} } } }) === 2)) { + const thisIs = aStatement; +} +if (!(myFoo({ bar: { baz: { quux() {} } } }) !== 2)) { + const thisIs = aStatement; +} + +if (!(myFoo({ bar: { baz: { quux() {} } } }) == 2)) { + const thisIs = aStatement; +} +if (!(myFoo({ bar: { baz: { quux() {} } } }) != 2)) { + const thisIs = aStatement; +} + +// -------------------------------------- + +!(foo === 2) && an.expression() +!(foo !== 2) && an.expression() + +!(foo == 2) && an.expression() +!(foo != 2) && an.expression() + +!(myFoo({ bar: { baz: { quux() {} } } }) === 2) && an.expression() +!(myFoo({ bar: { baz: { quux() {} } } }) !== 2) && an.expression() + +!(myFoo({ bar: { baz: { quux() {} } } }) == 2) && an.expression() +!(myFoo({ bar: { baz: { quux() {} } } }) != 2) && an.expression() diff --git a/packages/babel-plugin-minify-simplify/__tests__/fixtures/negated-equal/expected.js b/packages/babel-plugin-minify-simplify/__tests__/fixtures/negated-equal/expected.js new file mode 100644 index 000000000..62bdba441 --- /dev/null +++ b/packages/babel-plugin-minify-simplify/__tests__/fixtures/negated-equal/expected.js @@ -0,0 +1,90 @@ +if (foo !== 2) { + const thisIs = aStatement; +} + +if (foo === 2) { + const thisIs = aStatement; +} + +if (foo ^ 2) { + const thisIs = aStatement; +} + +if (foo == 2) { + const thisIs = aStatement; +} + +if (myFoo({ + bar: { + baz: { + quux() {} + + } + } +}) !== 2) { + const thisIs = aStatement; +} + +if (myFoo({ + bar: { + baz: { + quux() {} + + } + } +}) === 2) { + const thisIs = aStatement; +} + +if (myFoo({ + bar: { + baz: { + quux() {} + + } + } +}) ^ 2) { + const thisIs = aStatement; +} + +if (myFoo({ + bar: { + baz: { + quux() {} + + } + } +}) == 2) { + const thisIs = aStatement; +} // -------------------------------------- + + +foo !== 2 && an.expression(), foo === 2 && an.expression(), foo ^ 2 && an.expression(), foo == 2 && an.expression(), myFoo({ + bar: { + baz: { + quux() {} + + } + } +}) !== 2 && an.expression(), myFoo({ + bar: { + baz: { + quux() {} + + } + } +}) === 2 && an.expression(), myFoo({ + bar: { + baz: { + quux() {} + + } + } +}) ^ 2 && an.expression(), myFoo({ + bar: { + baz: { + quux() {} + + } + } +}) == 2 && an.expression(); \ No newline at end of file diff --git a/packages/babel-plugin-minify-simplify/src/boolean.js b/packages/babel-plugin-minify-simplify/src/boolean.js new file mode 100644 index 000000000..b5cd3181c --- /dev/null +++ b/packages/babel-plugin-minify-simplify/src/boolean.js @@ -0,0 +1,77 @@ +const isNumber = path => + path.isNumericLiteral() || + (path.isUnaryExpression({ operator: "-" }) && + path.get("argument").isNumericLiteral()); + +const isSpecificNumber = (path, number) => + path.isNumericLiteral({ value: number }) || + (path.isUnaryExpression({ operator: "-" }) && + path.get("argument").isNumericLiteral({ value: -number })); + +module.exports = t => path => { + const isTripleEquals = (p = path) => + p.isBinaryExpression({ operator: "!==" }) || + p.isBinaryExpression({ operator: "===" }); + + const isDoubleEquals = (p = path) => + p.isBinaryExpression({ operator: "!=" }) || + p.isBinaryExpression({ operator: "==" }); + + const isEquals = (p = path) => isTripleEquals(p) || isDoubleEquals(p); + + const isNegatedEqual = (p = path) => + p.isBinaryExpression({ operator: "!==" }) || + p.isBinaryExpression({ operator: "!=" }); + + // if (!!foo) {} >-> if (foo) {} + if ( + path.isUnaryExpression({ operator: "!" }) && + path.get("argument").isUnaryExpression({ operator: "!" }) + ) { + path.replaceWith(path.get("argument.argument")); + } + + // if (!(foo === bar)) {} >-> if (foo !== bar) {} + // if (!(foo !== bar)) {} >-> if (foo === bar) {} + if ( + path.isUnaryExpression({ operator: "!" }) && + isEquals(path.get("argument")) + ) { + const argument = path.get("argument"); + if (isNegatedEqual(argument)) { + argument.node.operator = argument.node.operator.replace("!", "="); + } else { + // String#replace() only changes the first match + argument.node.operator = argument.node.operator.replace("=", "!"); + } + path.replaceWith(argument); + } + + // if (foo === -1) {} >-> if (!~foo) {} + // if (foo !== -1) {} >-> if (~foo) {} + if (isEquals()) { + let comparator = null; + if (isSpecificNumber(path.get("left"), -1)) { + comparator = path.node.right; + } else if (isSpecificNumber(path.get("right"), -1)) { + comparator = path.node.left; + } + + if (comparator) { + const tilde = t.unaryExpression("~", comparator); + if (isNegatedEqual()) { + path.replaceWith(tilde); + } else { + path.replaceWith(t.unaryExpression("!", tilde)); + } + } + } + + // if (foo != 1) {} >-> if (foo^1) {} + if ( + path.isBinaryExpression({ operator: "!=" }) && + (isNumber(path.get("left")) || isNumber(path.get("right"))) + ) { + path.node.operator = "^"; + } +}; diff --git a/packages/babel-plugin-minify-simplify/src/index.js b/packages/babel-plugin-minify-simplify/src/index.js index 1845e1034..990349e01 100644 --- a/packages/babel-plugin-minify-simplify/src/index.js +++ b/packages/babel-plugin-minify-simplify/src/index.js @@ -5,6 +5,7 @@ module.exports = ({ types: t }) => { const toMultipleSequenceExpressions = require("babel-helper-to-multiple-sequence-expressions")( t ); + const minifyBooleanValue = require("./boolean")(t); const ifStatement = require("./if-statement")(t); const conditionalExpression = require("./conditional-expression")(t); const logicalExpression = require("./logical-expression")(t); @@ -127,13 +128,40 @@ module.exports = ({ types: t }) => { }, LogicalExpression: { - exit: logicalExpression.simplifyPatterns + exit: [ + path => { + const parent = path.parentPath; + let ok = false; + if (parent.isExpressionStatement()) { + ok = true; + } else if (path.key === "test") { + ok = true; + } else if (parent.isWhileStatement()) { + ok = true; + } else if (parent.isSequenceExpression()) { + if ( + path.parent.expressions.indexOf(path.node) !== + path.parent.expressions.length - 1 + ) { + ok = true; + } else if (parent.parentPath.isExpressionStatement()) { + ok = true; + } + } + if (ok) { + minifyBooleanValue(path.get("left")); + } + }, + logicalExpression.simplifyPatterns + ] }, AssignmentExpression: assignmentExpression.simplify, ConditionalExpression: { enter: [ + path => minifyBooleanValue(path.get("test")), + // !foo ? 'foo' : 'bar' -> foo ? 'bar' : 'foo' // foo !== 'lol' ? 'foo' : 'bar' -> foo === 'lol' ? 'bar' : 'foo' function flipIfOrConditional(path) { @@ -613,6 +641,7 @@ module.exports = ({ types: t }) => { // turn blocked ifs into single statements IfStatement: { + enter: [path => minifyBooleanValue(path.get("test"))], exit: [ ifStatement.mergeNestedIfs, ifStatement.simplify,