aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoralexlamsl <alexlamsl@gmail.com>2017-02-20 01:46:59 +0800
committeralexlamsl <alexlamsl@gmail.com>2017-02-21 13:31:59 +0800
commit26fbeece1c385a0e63efe3a6683af8459f4e495a (patch)
tree76a89112f69c6569430999a531d914166e3b84d8
parent8898b8a0fe87f71c0ea2d35face6dfbf11db27ec (diff)
downloadtracifyjs-26fbeece1c385a0e63efe3a6683af8459f4e495a.tar.gz
tracifyjs-26fbeece1c385a0e63efe3a6683af8459f4e495a.zip
fix `pure_funcs` & improve `side_effects`
- only drops side-effect-free arguments - drop side-effect-free parts with discarded value from `AST_Seq` & `AST_SimpleStatement` closes #1494
-rw-r--r--lib/compress.js178
-rw-r--r--test/compress/drop-unused.js58
-rw-r--r--test/compress/pure_funcs.js295
3 files changed, 516 insertions, 15 deletions
diff --git a/lib/compress.js b/lib/compress.js
index 237af72c..4dfcdcfb 100644
--- a/lib/compress.js
+++ b/lib/compress.js
@@ -83,6 +83,14 @@ function Compressor(options, false_by_default) {
global_defs : {},
passes : 1,
}, true);
+ var pure_funcs = this.options["pure_funcs"];
+ if (typeof pure_funcs == "function") {
+ this.pure_funcs = pure_funcs;
+ } else {
+ this.pure_funcs = pure_funcs ? function(node) {
+ return pure_funcs.indexOf(node.expression.print_to_string()) < 0;
+ } : return_true;
+ }
var top_retain = this.options["top_retain"];
if (top_retain instanceof RegExp) {
this.top_retain = function(def) {
@@ -304,6 +312,13 @@ merge(Compressor.prototype, {
}
}
+ function is_iife_call(node) {
+ if (node instanceof AST_Call && !(node instanceof AST_New)) {
+ return node.expression instanceof AST_Function || is_iife_call(node.expression);
+ }
+ return false;
+ }
+
function tighten_body(statements, compressor) {
var CHANGED, max_iter = 10;
do {
@@ -1354,10 +1369,12 @@ merge(Compressor.prototype, {
def(AST_This, return_false);
def(AST_Call, function(compressor){
- var pure = compressor.option("pure_funcs");
- if (!pure) return true;
- if (typeof pure == "function") return pure(this);
- return pure.indexOf(this.expression.print_to_string()) < 0;
+ if (compressor.pure_funcs(this)) return true;
+ for (var i = this.args.length; --i >= 0;) {
+ if (this.args[i].has_side_effects(compressor))
+ return true;
+ }
+ return false;
});
def(AST_Block, function(compressor){
@@ -1855,12 +1872,151 @@ merge(Compressor.prototype, {
return self;
});
+ // drop_side_effect_free()
+ // remove side-effect-free parts which only affects return value
+ (function(def){
+ function return_this() {
+ return this;
+ }
+
+ function return_null() {
+ return null;
+ }
+
+ // Drop side-effect-free elements from an array of expressions.
+ // Returns an array of expressions with side-effects or null
+ // if all elements were dropped. Note: original array may be
+ // returned if nothing changed.
+ function trim(nodes, compressor, first_in_statement) {
+ var ret = [], changed = false;
+ for (var i = 0, ii = nodes.length; i < ii; i++) {
+ var node = nodes[i].drop_side_effect_free(compressor, first_in_statement);
+ changed |= node !== nodes[i];
+ if (node) {
+ ret.push(node);
+ first_in_statement = false;
+ }
+ }
+ return changed ? ret.length ? ret : null : nodes;
+ }
+
+ def(AST_Node, return_this);
+ def(AST_Constant, return_null);
+ def(AST_This, return_null);
+ def(AST_Call, function(compressor, first_in_statement){
+ if (compressor.pure_funcs(this)) return this;
+ var args = trim(this.args, compressor, first_in_statement);
+ return args && AST_Seq.from_array(args);
+ });
+ def(AST_Function, return_null);
+ def(AST_Binary, function(compressor, first_in_statement){
+ var right = this.right.drop_side_effect_free(compressor);
+ if (!right) return this.left.drop_side_effect_free(compressor, first_in_statement);
+ switch (this.operator) {
+ case "&&":
+ case "||":
+ var node = this.clone();
+ node.right = right;
+ return node;
+ default:
+ var left = this.left.drop_side_effect_free(compressor, first_in_statement);
+ if (!left) return this.right.drop_side_effect_free(compressor, first_in_statement);
+ return make_node(AST_Seq, this, {
+ car: left,
+ cdr: right
+ });
+ }
+ });
+ def(AST_Assign, return_this);
+ def(AST_Conditional, function(compressor){
+ var consequent = this.consequent.drop_side_effect_free(compressor);
+ var alternative = this.alternative.drop_side_effect_free(compressor);
+ if (consequent === this.consequent && alternative === this.alternative) return this;
+ if (!consequent) return alternative ? make_node(AST_Binary, this, {
+ operator: "||",
+ left: this.condition,
+ right: alternative
+ }) : this.condition.drop_side_effect_free(compressor);
+ if (!alternative) return make_node(AST_Binary, this, {
+ operator: "&&",
+ left: this.condition,
+ right: consequent
+ });
+ var node = this.clone();
+ node.consequent = consequent;
+ node.alternative = alternative;
+ return node;
+ });
+ def(AST_Unary, function(compressor, first_in_statement){
+ switch (this.operator) {
+ case "delete":
+ case "++":
+ case "--":
+ return this;
+ case "typeof":
+ if (this.expression instanceof AST_SymbolRef) return null;
+ default:
+ if (first_in_statement && is_iife_call(this.expression)) return this;
+ return this.expression.drop_side_effect_free(compressor, first_in_statement);
+ }
+ });
+ def(AST_SymbolRef, function() {
+ return this.undeclared() ? this : null;
+ });
+ def(AST_Object, function(compressor, first_in_statement){
+ var values = trim(this.properties, compressor, first_in_statement);
+ return values && AST_Seq.from_array(values);
+ });
+ def(AST_ObjectProperty, function(compressor, first_in_statement){
+ return this.value.drop_side_effect_free(compressor, first_in_statement);
+ });
+ def(AST_Array, function(compressor, first_in_statement){
+ var values = trim(this.elements, compressor, first_in_statement);
+ return values && AST_Seq.from_array(values);
+ });
+ def(AST_Dot, function(compressor, first_in_statement){
+ if (!compressor.option("pure_getters")) return this;
+ return this.expression.drop_side_effect_free(compressor, first_in_statement);
+ });
+ def(AST_Sub, function(compressor, first_in_statement){
+ if (!compressor.option("pure_getters")) return this;
+ var expression = this.expression.drop_side_effect_free(compressor, first_in_statement);
+ if (!expression) return this.property.drop_side_effect_free(compressor, first_in_statement);
+ var property = this.property.drop_side_effect_free(compressor);
+ if (!property) return expression;
+ return make_node(AST_Seq, this, {
+ car: expression,
+ cdr: property
+ });
+ });
+ def(AST_Seq, function(compressor){
+ var cdr = this.cdr.drop_side_effect_free(compressor);
+ if (cdr === this.cdr) return this;
+ if (!cdr) return this.car;
+ return make_node(AST_Seq, this, {
+ car: this.car,
+ cdr: cdr
+ });
+ });
+ })(function(node, func){
+ node.DEFMETHOD("drop_side_effect_free", func);
+ });
+
OPT(AST_SimpleStatement, function(self, compressor){
if (compressor.option("side_effects")) {
- if (!self.body.has_side_effects(compressor)) {
+ var body = self.body;
+ if (!body.has_side_effects(compressor)) {
compressor.warn("Dropping side-effect-free statement [{file}:{line},{col}]", self.start);
return make_node(AST_EmptyStatement, self);
}
+ var node = body.drop_side_effect_free(compressor, true);
+ if (!node) {
+ compressor.warn("Dropping side-effect-free statement [{file}:{line},{col}]", self.start);
+ return make_node(AST_EmptyStatement, self);
+ }
+ if (node !== body) {
+ return make_node(AST_SimpleStatement, self, { body: node });
+ }
}
return self;
});
@@ -2435,13 +2591,6 @@ merge(Compressor.prototype, {
return self.negate(compressor, true);
}
return self;
-
- function is_iife_call(node) {
- if (node instanceof AST_Call && !(node instanceof AST_New)) {
- return node.expression instanceof AST_Function || is_iife_call(node.expression);
- }
- return false;
- }
});
OPT(AST_New, function(self, compressor){
@@ -2464,9 +2613,8 @@ merge(Compressor.prototype, {
OPT(AST_Seq, function(self, compressor){
if (!compressor.option("side_effects"))
return self;
- if (!self.car.has_side_effects(compressor)) {
- return maintain_this_binding(compressor.parent(), self, self.cdr);
- }
+ self.car = self.car.drop_side_effect_free(compressor, first_in_statement(compressor));
+ if (!self.car) return maintain_this_binding(compressor.parent(), self, self.cdr);
if (compressor.option("cascade")) {
if (self.car instanceof AST_Assign
&& !self.car.left.has_side_effects(compressor)) {
diff --git a/test/compress/drop-unused.js b/test/compress/drop-unused.js
index 5a09c6cd..f5a88f21 100644
--- a/test/compress/drop-unused.js
+++ b/test/compress/drop-unused.js
@@ -590,3 +590,61 @@ drop_fnames: {
}
}
}
+
+global_var: {
+ options = {
+ side_effects: true,
+ unused: true,
+ }
+ input: {
+ var a;
+ function foo(b) {
+ a;
+ b;
+ c;
+ typeof c === "undefined";
+ c + b + a;
+ b && b.ar();
+ return b;
+ }
+ }
+ expect: {
+ var a;
+ function foo(b) {
+ c;
+ c;
+ b && b.ar();
+ return b;
+ }
+ }
+}
+
+iife: {
+ options = {
+ side_effects: true,
+ unused: true,
+ }
+ input: {
+ function f() {
+ var a;
+ ~function() {}(b);
+ }
+ }
+ expect: {
+ function f() {
+ ~function() {}(b);
+ }
+ }
+}
+
+drop_value: {
+ options = {
+ side_effects: true,
+ }
+ input: {
+ (1, [2, foo()], 3, {a:1, b:bar()});
+ }
+ expect: {
+ foo(), bar();
+ }
+}
diff --git a/test/compress/pure_funcs.js b/test/compress/pure_funcs.js
new file mode 100644
index 00000000..3cc529a8
--- /dev/null
+++ b/test/compress/pure_funcs.js
@@ -0,0 +1,295 @@
+array: {
+ options = {
+ pure_funcs: [ "Math.floor" ],
+ side_effects: true,
+ }
+ input: {
+ var a;
+ function f(b) {
+ Math.floor(a / b);
+ Math.floor(c / b);
+ }
+ }
+ expect: {
+ var a;
+ function f(b) {
+ c;
+ }
+ }
+}
+
+func: {
+ options = {
+ pure_funcs: function(node) {
+ return !~node.args[0].print_to_string().indexOf("a");
+ },
+ side_effects: true,
+ }
+ input: {
+ function f(a, b) {
+ Math.floor(a / b);
+ Math.floor(c / b);
+ }
+ }
+ expect: {
+ function f(a, b) {
+ Math.floor(c / b);
+ }
+ }
+}
+
+side_effects: {
+ options = {
+ pure_funcs: [ "console.log" ],
+ side_effects: true,
+ }
+ input: {
+ function f(a, b) {
+ console.log(a());
+ console.log(b);
+ }
+ }
+ expect: {
+ function f(a, b) {
+ a();
+ }
+ }
+}
+
+unused: {
+ options = {
+ pure_funcs: [ "pure" ],
+ side_effects: true,
+ unused: true,
+ }
+ input: {
+ function foo() {
+ var u = pure(1);
+ var x = pure(2);
+ var y = pure(x);
+ var z = pure(pure(side_effects()));
+ return pure(3);
+ }
+ }
+ expect: {
+ function foo() {
+ side_effects();
+ return pure(3);
+ }
+ }
+}
+
+babel: {
+ options = {
+ pure_funcs: [ "_classCallCheck" ],
+ side_effects: true,
+ unused: true,
+ }
+ input: {
+ function _classCallCheck(instance, Constructor) {
+ if (!(instance instanceof Constructor))
+ throw new TypeError("Cannot call a class as a function");
+ }
+ var Foo = function Foo() {
+ _classCallCheck(this, Foo);
+ };
+ }
+ expect: {
+ function _classCallCheck(instance, Constructor) {
+ if (!(instance instanceof Constructor))
+ throw new TypeError("Cannot call a class as a function");
+ }
+ var Foo = function() {
+ };
+ }
+}
+
+conditional: {
+ options = {
+ pure_funcs: [ "pure" ],
+ side_effects: true,
+ }
+ input: {
+ pure(1 | a() ? 2 & b() : 7 ^ c());
+ pure(1 | a() ? 2 & b() : 5);
+ pure(1 | a() ? 4 : 7 ^ c());
+ pure(1 | a() ? 4 : 5);
+ pure(3 ? 2 & b() : 7 ^ c());
+ pure(3 ? 2 & b() : 5);
+ pure(3 ? 4 : 7 ^ c());
+ pure(3 ? 4 : 5);
+ }
+ expect: {
+ 1 | a() ? b() : c();
+ 1 | a() && b();
+ 1 | a() || c();
+ a();
+ 3 ? b() : c();
+ 3 && b();
+ 3 || c();
+ }
+}
+
+relational: {
+ options = {
+ pure_funcs: [ "foo" ],
+ side_effects :true,
+ }
+ input: {
+ foo() in foo();
+ foo() instanceof bar();
+ foo() < "bar";
+ bar() > foo();
+ bar() != bar();
+ bar() !== "bar";
+ "bar" == foo();
+ "bar" === bar();
+ "bar" >= "bar";
+ }
+ expect: {
+ bar();
+ bar();
+ bar(), bar();
+ bar();
+ bar();
+ }
+}
+
+arithmetic: {
+ options = {
+ pure_funcs: [ "foo" ],
+ side_effects :true,
+ }
+ input: {
+ foo() + foo();
+ foo() - bar();
+ foo() * "bar";
+ bar() / foo();
+ bar() & bar();
+ bar() | "bar";
+ "bar" >> foo();
+ "bar" << bar();
+ "bar" >>> "bar";
+ }
+ expect: {
+ bar();
+ bar();
+ bar(), bar();
+ bar();
+ bar();
+ }
+}
+
+boolean_and: {
+ options = {
+ pure_funcs: [ "foo" ],
+ side_effects :true,
+ }
+ input: {
+ foo() && foo();
+ foo() && bar();
+ foo() && "bar";
+ bar() && foo();
+ bar() && bar();
+ bar() && "bar";
+ "bar" && foo();
+ "bar" && bar();
+ "bar" && "bar";
+ }
+ expect: {
+ foo() && bar();
+ bar();
+ bar() && bar();
+ bar();
+ "bar" && bar();
+ }
+}
+
+boolean_or: {
+ options = {
+ pure_funcs: [ "foo" ],
+ side_effects :true,
+ }
+ input: {
+ foo() || foo();
+ foo() || bar();
+ foo() || "bar";
+ bar() || foo();
+ bar() || bar();
+ bar() || "bar";
+ "bar" || foo();
+ "bar" || bar();
+ "bar" || "bar";
+ }
+ expect: {
+ foo() || bar();
+ bar();
+ bar() || bar();
+ bar();
+ "bar" || bar();
+ }
+}
+
+assign: {
+ options = {
+ pure_funcs: [ "foo" ],
+ side_effects :true,
+ }
+ input: {
+ var a;
+ function f(b) {
+ a = foo();
+ b *= 4 + foo();
+ c >>= 0 | foo();
+ }
+ }
+ expect: {
+ var a;
+ function f(b) {
+ a = foo();
+ b *= 4 + foo();
+ c >>= 0 | foo();
+ }
+ }
+}
+
+unary: {
+ options = {
+ pure_funcs: [ "foo" ],
+ side_effects :true,
+ }
+ input: {
+ typeof foo();
+ typeof bar();
+ typeof "bar";
+ void foo();
+ void bar();
+ void "bar";
+ delete a[foo()];
+ delete a[bar()];
+ delete a["bar"];
+ a[foo()]++;
+ a[bar()]++;
+ a["bar"]++;
+ --a[foo()];
+ --a[bar()];
+ --a["bar"];
+ ~foo();
+ ~bar();
+ ~"bar";
+ }
+ expect: {
+ bar();
+ bar();
+ delete a[foo()];
+ delete a[bar()];
+ delete a["bar"];
+ a[foo()]++;
+ a[bar()]++;
+ a["bar"]++;
+ --a[foo()];
+ --a[bar()];
+ --a["bar"];
+ bar();
+ }
+}