Skip to content

preserve implicit return values #1522

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 3, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
process code with implicit return statement
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 #354
fixes #543
fixes #625
fixes #628
fixes #640
closes #1293
  • Loading branch information
alexlamsl committed Mar 3, 2017
commit f01c0c4a8961a214aa440fc95ccc98258481493b
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));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice!

}
}

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