Skip to content

Commit

Permalink
process code with implicit return statement
Browse files Browse the repository at this point in the history
Bookmarklet for instance implicitedly assumes a "completion value" without using `return`.
The `expression` option now supports such use cases.
Optimisations on IIFEs also enhanced.

fixes mishoo#354
fixes mishoo#543
fixes mishoo#625
fixes mishoo#628
fixes mishoo#640
closes mishoo#1293
  • Loading branch information
alexlamsl committed Mar 3, 2017
1 parent 18059cc commit f01c0c4
Show file tree
Hide file tree
Showing 8 changed files with 485 additions and 32 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,9 @@ to set `true`; it's effectively a shortcut for `foo=true`).
such as `console.info` and/or retain side effects from function arguments
after dropping the function call then use `pure_funcs` instead.

- `expression` -- default `false`. Pass `true` to preserve completion values
from terminal statements without `return`, e.g. in bookmarklets.

- `keep_fargs` -- default `true`. Prevents the
compressor from discarding unused function arguments. You need this
for code which relies on `Function.length`.
Expand Down
90 changes: 70 additions & 20 deletions lib/compress.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ function Compressor(options, false_by_default) {
screw_ie8 : true,
drop_console : false,
angular : false,
expression : false,
warnings : true,
global_defs : {},
passes : 1,
Expand Down Expand Up @@ -116,12 +117,18 @@ Compressor.prototype = new TreeTransformer;
merge(Compressor.prototype, {
option: function(key) { return this.options[key] },
compress: function(node) {
if (this.option("expression")) {
node = node.process_expression(true);
}
var passes = +this.options.passes || 1;
for (var pass = 0; pass < passes && pass < 3; ++pass) {
if (pass > 0 || this.option("reduce_vars"))
node.reset_opt_flags(this, true);
node = node.transform(this);
}
if (this.option("expression")) {
node = node.process_expression(false);
}
return node;
},
warn: function(text, props) {
Expand Down Expand Up @@ -178,6 +185,42 @@ merge(Compressor.prototype, {
return this.print_to_string() == node.print_to_string();
});

AST_Node.DEFMETHOD("process_expression", function(insert) {
var self = this;
var tt = new TreeTransformer(function(node) {
if (insert && node instanceof AST_SimpleStatement) {
return make_node(AST_Return, node, {
value: node.body
});
}
if (!insert && node instanceof AST_Return) {
return make_node(AST_SimpleStatement, node, {
body: node.value || make_node(AST_Undefined, node)
});
}
if (node instanceof AST_Lambda && node !== self) {
return node;
}
if (node instanceof AST_Block) {
var index = node.body.length - 1;
if (index >= 0) {
node.body[index] = node.body[index].transform(tt);
}
}
if (node instanceof AST_If) {
node.body = node.body.transform(tt);
if (node.alternative) {
node.alternative = node.alternative.transform(tt);
}
}
if (node instanceof AST_With) {
node.body = node.body.transform(tt);
}
return node;
});
return self.transform(tt);
});

AST_Node.DEFMETHOD("reset_opt_flags", function(compressor, rescan){
var reduce_vars = rescan && compressor.option("reduce_vars");
var safe_ids = [];
Expand Down Expand Up @@ -2030,7 +2073,14 @@ merge(Compressor.prototype, {
def(AST_Constant, return_null);
def(AST_This, return_null);
def(AST_Call, function(compressor, first_in_statement){
if (!this.has_pure_annotation(compressor) && compressor.pure_funcs(this)) return this;
if (!this.has_pure_annotation(compressor) && compressor.pure_funcs(this)) {
if (this.expression instanceof AST_Function) {
var node = this.clone();
node.expression = node.expression.process_expression(false);
return node;
}
return this;
}
if (this.pure) {
compressor.warn("Dropping __PURE__ call [{file}:{line},{col}]", this.start);
this.pure.value = this.pure.value.replace(/[@#]__PURE__/g, ' ');
Expand Down Expand Up @@ -2522,12 +2572,13 @@ merge(Compressor.prototype, {
});

OPT(AST_Call, function(self, compressor){
var exp = self.expression;
if (compressor.option("unused")
&& self.expression instanceof AST_Function
&& !self.expression.uses_arguments
&& !self.expression.uses_eval
&& self.args.length > self.expression.argnames.length) {
var end = self.expression.argnames.length;
&& exp instanceof AST_Function
&& !exp.uses_arguments
&& !exp.uses_eval
&& self.args.length > exp.argnames.length) {
var end = exp.argnames.length;
for (var i = end, len = self.args.length; i < len; i++) {
var node = self.args[i].drop_side_effect_free(compressor);
if (node) {
Expand All @@ -2537,7 +2588,6 @@ merge(Compressor.prototype, {
self.args.length = end;
}
if (compressor.option("unsafe")) {
var exp = self.expression;
if (exp instanceof AST_SymbolRef && exp.undeclared()) {
switch (exp.name) {
case "Array":
Expand Down Expand Up @@ -2711,16 +2761,22 @@ merge(Compressor.prototype, {
return best_of(self, node);
}
}
if (compressor.option("side_effects")) {
if (self.expression instanceof AST_Function
&& self.args.length == 0
&& !AST_Block.prototype.has_side_effects.call(self.expression, compressor)) {
return make_node(AST_Undefined, self).transform(compressor);
if (exp instanceof AST_Function) {
if (exp.body[0] instanceof AST_Return
&& exp.body[0].value.is_constant()) {
var args = self.args.concat(exp.body[0].value);
return AST_Seq.from_array(args).transform(compressor);
}
if (compressor.option("side_effects")) {
if (!AST_Block.prototype.has_side_effects.call(exp, compressor)) {
var args = self.args.concat(make_node(AST_Undefined, self));
return AST_Seq.from_array(args).transform(compressor);
}
}
}
if (compressor.option("drop_console")) {
if (self.expression instanceof AST_PropAccess) {
var name = self.expression.expression;
if (exp instanceof AST_PropAccess) {
var name = exp.expression;
while (name.expression) {
name = name.expression;
}
Expand All @@ -2731,12 +2787,6 @@ merge(Compressor.prototype, {
}
}
}
if (self.args.length == 0
&& self.expression instanceof AST_Function
&& self.expression.body[0] instanceof AST_Return
&& self.expression.body[0].value.is_constant()) {
return self.expression.body[0].value;
}
if (compressor.option("negate_iife")
&& compressor.parent() instanceof AST_SimpleStatement
&& is_iife_call(self)) {
Expand Down
2 changes: 1 addition & 1 deletion test/compress/drop-unused.js
Original file line number Diff line number Diff line change
Expand Up @@ -632,7 +632,7 @@ iife: {
}
expect: {
function f() {
~function() {}(b);
b;
}
}
}
Expand Down
8 changes: 2 additions & 6 deletions test/compress/evaluate.js
Original file line number Diff line number Diff line change
Expand Up @@ -640,9 +640,7 @@ call_args: {
expect: {
const a = 1;
console.log(1);
+function(a) {
return 1;
}(1);
+(1, 1);
}
}

Expand All @@ -663,9 +661,7 @@ call_args_drop_param: {
expect: {
const a = 1;
console.log(1);
+function() {
return 1;
}(b);
+(b, 1);
}
}

Expand Down
8 changes: 4 additions & 4 deletions test/compress/functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ iifes_returning_constants_keep_fargs_true: {
console.log("okay");
console.log(123);
console.log(void 0);
console.log(function(x,y,z){return 2}(1,2,3));
console.log(function(x,y){return 6}(2,3));
console.log(function(x, y){return 6}(2,3,a(),b()));
console.log(2);
console.log(6);
console.log((a(), b(), 6));
}
}

Expand Down Expand Up @@ -71,6 +71,6 @@ iifes_returning_constants_keep_fargs_false: {
console.log(void 0);
console.log(2);
console.log(6);
console.log(function(){return 6}(a(),b()));
console.log((a(), b(), 6));
}
}
Loading

0 comments on commit f01c0c4

Please sign in to comment.