Skip to content

Commit 07accd2

Browse files
authoredMar 3, 2017
process code with implicit return statement (#1522)
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
1 parent 18059cc commit 07accd2

8 files changed

+485
-32
lines changed
 

‎README.md

+3
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,9 @@ to set `true`; it's effectively a shortcut for `foo=true`).
426426
such as `console.info` and/or retain side effects from function arguments
427427
after dropping the function call then use `pure_funcs` instead.
428428

429+
- `expression` -- default `false`. Pass `true` to preserve completion values
430+
from terminal statements without `return`, e.g. in bookmarklets.
431+
429432
- `keep_fargs` -- default `true`. Prevents the
430433
compressor from discarding unused function arguments. You need this
431434
for code which relies on `Function.length`.

‎lib/compress.js

+70-20
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ function Compressor(options, false_by_default) {
8080
screw_ie8 : true,
8181
drop_console : false,
8282
angular : false,
83+
expression : false,
8384
warnings : true,
8485
global_defs : {},
8586
passes : 1,
@@ -116,12 +117,18 @@ Compressor.prototype = new TreeTransformer;
116117
merge(Compressor.prototype, {
117118
option: function(key) { return this.options[key] },
118119
compress: function(node) {
120+
if (this.option("expression")) {
121+
node = node.process_expression(true);
122+
}
119123
var passes = +this.options.passes || 1;
120124
for (var pass = 0; pass < passes && pass < 3; ++pass) {
121125
if (pass > 0 || this.option("reduce_vars"))
122126
node.reset_opt_flags(this, true);
123127
node = node.transform(this);
124128
}
129+
if (this.option("expression")) {
130+
node = node.process_expression(false);
131+
}
125132
return node;
126133
},
127134
warn: function(text, props) {
@@ -178,6 +185,42 @@ merge(Compressor.prototype, {
178185
return this.print_to_string() == node.print_to_string();
179186
});
180187

188+
AST_Node.DEFMETHOD("process_expression", function(insert) {
189+
var self = this;
190+
var tt = new TreeTransformer(function(node) {
191+
if (insert && node instanceof AST_SimpleStatement) {
192+
return make_node(AST_Return, node, {
193+
value: node.body
194+
});
195+
}
196+
if (!insert && node instanceof AST_Return) {
197+
return make_node(AST_SimpleStatement, node, {
198+
body: node.value || make_node(AST_Undefined, node)
199+
});
200+
}
201+
if (node instanceof AST_Lambda && node !== self) {
202+
return node;
203+
}
204+
if (node instanceof AST_Block) {
205+
var index = node.body.length - 1;
206+
if (index >= 0) {
207+
node.body[index] = node.body[index].transform(tt);
208+
}
209+
}
210+
if (node instanceof AST_If) {
211+
node.body = node.body.transform(tt);
212+
if (node.alternative) {
213+
node.alternative = node.alternative.transform(tt);
214+
}
215+
}
216+
if (node instanceof AST_With) {
217+
node.body = node.body.transform(tt);
218+
}
219+
return node;
220+
});
221+
return self.transform(tt);
222+
});
223+
181224
AST_Node.DEFMETHOD("reset_opt_flags", function(compressor, rescan){
182225
var reduce_vars = rescan && compressor.option("reduce_vars");
183226
var safe_ids = [];
@@ -2030,7 +2073,14 @@ merge(Compressor.prototype, {
20302073
def(AST_Constant, return_null);
20312074
def(AST_This, return_null);
20322075
def(AST_Call, function(compressor, first_in_statement){
2033-
if (!this.has_pure_annotation(compressor) && compressor.pure_funcs(this)) return this;
2076+
if (!this.has_pure_annotation(compressor) && compressor.pure_funcs(this)) {
2077+
if (this.expression instanceof AST_Function) {
2078+
var node = this.clone();
2079+
node.expression = node.expression.process_expression(false);
2080+
return node;
2081+
}
2082+
return this;
2083+
}
20342084
if (this.pure) {
20352085
compressor.warn("Dropping __PURE__ call [{file}:{line},{col}]", this.start);
20362086
this.pure.value = this.pure.value.replace(/[@#]__PURE__/g, ' ');
@@ -2522,12 +2572,13 @@ merge(Compressor.prototype, {
25222572
});
25232573

25242574
OPT(AST_Call, function(self, compressor){
2575+
var exp = self.expression;
25252576
if (compressor.option("unused")
2526-
&& self.expression instanceof AST_Function
2527-
&& !self.expression.uses_arguments
2528-
&& !self.expression.uses_eval
2529-
&& self.args.length > self.expression.argnames.length) {
2530-
var end = self.expression.argnames.length;
2577+
&& exp instanceof AST_Function
2578+
&& !exp.uses_arguments
2579+
&& !exp.uses_eval
2580+
&& self.args.length > exp.argnames.length) {
2581+
var end = exp.argnames.length;
25312582
for (var i = end, len = self.args.length; i < len; i++) {
25322583
var node = self.args[i].drop_side_effect_free(compressor);
25332584
if (node) {
@@ -2537,7 +2588,6 @@ merge(Compressor.prototype, {
25372588
self.args.length = end;
25382589
}
25392590
if (compressor.option("unsafe")) {
2540-
var exp = self.expression;
25412591
if (exp instanceof AST_SymbolRef && exp.undeclared()) {
25422592
switch (exp.name) {
25432593
case "Array":
@@ -2711,16 +2761,22 @@ merge(Compressor.prototype, {
27112761
return best_of(self, node);
27122762
}
27132763
}
2714-
if (compressor.option("side_effects")) {
2715-
if (self.expression instanceof AST_Function
2716-
&& self.args.length == 0
2717-
&& !AST_Block.prototype.has_side_effects.call(self.expression, compressor)) {
2718-
return make_node(AST_Undefined, self).transform(compressor);
2764+
if (exp instanceof AST_Function) {
2765+
if (exp.body[0] instanceof AST_Return
2766+
&& exp.body[0].value.is_constant()) {
2767+
var args = self.args.concat(exp.body[0].value);
2768+
return AST_Seq.from_array(args).transform(compressor);
2769+
}
2770+
if (compressor.option("side_effects")) {
2771+
if (!AST_Block.prototype.has_side_effects.call(exp, compressor)) {
2772+
var args = self.args.concat(make_node(AST_Undefined, self));
2773+
return AST_Seq.from_array(args).transform(compressor);
2774+
}
27192775
}
27202776
}
27212777
if (compressor.option("drop_console")) {
2722-
if (self.expression instanceof AST_PropAccess) {
2723-
var name = self.expression.expression;
2778+
if (exp instanceof AST_PropAccess) {
2779+
var name = exp.expression;
27242780
while (name.expression) {
27252781
name = name.expression;
27262782
}
@@ -2731,12 +2787,6 @@ merge(Compressor.prototype, {
27312787
}
27322788
}
27332789
}
2734-
if (self.args.length == 0
2735-
&& self.expression instanceof AST_Function
2736-
&& self.expression.body[0] instanceof AST_Return
2737-
&& self.expression.body[0].value.is_constant()) {
2738-
return self.expression.body[0].value;
2739-
}
27402790
if (compressor.option("negate_iife")
27412791
&& compressor.parent() instanceof AST_SimpleStatement
27422792
&& is_iife_call(self)) {

‎test/compress/drop-unused.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -632,7 +632,7 @@ iife: {
632632
}
633633
expect: {
634634
function f() {
635-
~function() {}(b);
635+
b;
636636
}
637637
}
638638
}

‎test/compress/evaluate.js

+2-6
Original file line numberDiff line numberDiff line change
@@ -640,9 +640,7 @@ call_args: {
640640
expect: {
641641
const a = 1;
642642
console.log(1);
643-
+function(a) {
644-
return 1;
645-
}(1);
643+
+(1, 1);
646644
}
647645
}
648646

@@ -663,9 +661,7 @@ call_args_drop_param: {
663661
expect: {
664662
const a = 1;
665663
console.log(1);
666-
+function() {
667-
return 1;
668-
}(b);
664+
+(b, 1);
669665
}
670666
}
671667

‎test/compress/functions.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@ iifes_returning_constants_keep_fargs_true: {
3535
console.log("okay");
3636
console.log(123);
3737
console.log(void 0);
38-
console.log(function(x,y,z){return 2}(1,2,3));
39-
console.log(function(x,y){return 6}(2,3));
40-
console.log(function(x, y){return 6}(2,3,a(),b()));
38+
console.log(2);
39+
console.log(6);
40+
console.log((a(), b(), 6));
4141
}
4242
}
4343

@@ -71,6 +71,6 @@ iifes_returning_constants_keep_fargs_false: {
7171
console.log(void 0);
7272
console.log(2);
7373
console.log(6);
74-
console.log(function(){return 6}(a(),b()));
74+
console.log((a(), b(), 6));
7575
}
7676
}

‎test/compress/issue-640.js

+317
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
1+
cond_5: {
2+
options = {
3+
conditionals: true,
4+
expression: true,
5+
}
6+
input: {
7+
if (some_condition()) {
8+
if (some_other_condition()) {
9+
do_something();
10+
} else {
11+
alternate();
12+
}
13+
} else {
14+
alternate();
15+
}
16+
17+
if (some_condition()) {
18+
if (some_other_condition()) {
19+
do_something();
20+
}
21+
}
22+
}
23+
expect: {
24+
some_condition() && some_other_condition() ? do_something() : alternate();
25+
if (some_condition() && some_other_condition()) do_something();
26+
}
27+
}
28+
29+
dead_code_const_annotation_regex: {
30+
options = {
31+
booleans : true,
32+
conditionals : true,
33+
dead_code : true,
34+
evaluate : true,
35+
expression : true,
36+
loops : true,
37+
}
38+
input: {
39+
var unused;
40+
// @constraint this shouldn't be a constant
41+
var CONST_FOO_ANN = false;
42+
if (CONST_FOO_ANN) {
43+
console.log("reachable");
44+
}
45+
}
46+
expect: {
47+
var unused;
48+
var CONST_FOO_ANN = !1;
49+
if (CONST_FOO_ANN) console.log('reachable');
50+
}
51+
}
52+
53+
drop_console_2: {
54+
options = {
55+
drop_console: true,
56+
expression: true,
57+
}
58+
input: {
59+
console.log('foo');
60+
console.log.apply(console, arguments);
61+
}
62+
expect: {
63+
// with regular compression these will be stripped out as well
64+
void 0;
65+
void 0;
66+
}
67+
}
68+
69+
drop_value: {
70+
options = {
71+
expression: true,
72+
side_effects: true,
73+
}
74+
input: {
75+
(1, [2, foo()], 3, {a:1, b:bar()});
76+
}
77+
expect: {
78+
foo(), {a:1, b:bar()};
79+
}
80+
}
81+
82+
wrongly_optimized: {
83+
options = {
84+
conditionals: true,
85+
booleans: true,
86+
evaluate: true,
87+
expression: true,
88+
}
89+
input: {
90+
function func() {
91+
foo();
92+
}
93+
if (func() || true) {
94+
bar();
95+
}
96+
}
97+
expect: {
98+
function func() {
99+
foo();
100+
}
101+
// TODO: optimize to `func(), bar()`
102+
if (func(), !0) bar();
103+
}
104+
}
105+
106+
negate_iife_1: {
107+
options = {
108+
expression: true,
109+
negate_iife: true,
110+
}
111+
input: {
112+
(function(){ stuff() })();
113+
}
114+
expect: {
115+
(function(){ stuff() })();
116+
}
117+
}
118+
119+
negate_iife_3: {
120+
options = {
121+
conditionals: true,
122+
expression: true,
123+
negate_iife: true,
124+
}
125+
input: {
126+
(function(){ return t })() ? console.log(true) : console.log(false);
127+
}
128+
expect: {
129+
(function(){ return t })() ? console.log(true) : console.log(false);
130+
}
131+
}
132+
133+
negate_iife_3_off: {
134+
options = {
135+
conditionals: true,
136+
expression: true,
137+
negate_iife: false,
138+
}
139+
input: {
140+
(function(){ return t })() ? console.log(true) : console.log(false);
141+
}
142+
expect: {
143+
(function(){ return t })() ? console.log(true) : console.log(false);
144+
}
145+
}
146+
147+
negate_iife_4: {
148+
options = {
149+
conditionals: true,
150+
expression: true,
151+
negate_iife: true,
152+
sequences: true,
153+
}
154+
input: {
155+
(function(){ return t })() ? console.log(true) : console.log(false);
156+
(function(){
157+
console.log("something");
158+
})();
159+
}
160+
expect: {
161+
(function(){ return t })() ? console.log(true) : console.log(false), function(){
162+
console.log("something");
163+
}();
164+
}
165+
}
166+
167+
negate_iife_5: {
168+
options = {
169+
conditionals: true,
170+
expression: true,
171+
negate_iife: true,
172+
sequences: true,
173+
}
174+
input: {
175+
if ((function(){ return t })()) {
176+
foo(true);
177+
} else {
178+
bar(false);
179+
}
180+
(function(){
181+
console.log("something");
182+
})();
183+
}
184+
expect: {
185+
(function(){ return t })() ? foo(true) : bar(false), function(){
186+
console.log("something");
187+
}();
188+
}
189+
}
190+
191+
negate_iife_5_off: {
192+
options = {
193+
conditionals: true,
194+
expression: true,
195+
negate_iife: false,
196+
sequences: true,
197+
};
198+
input: {
199+
if ((function(){ return t })()) {
200+
foo(true);
201+
} else {
202+
bar(false);
203+
}
204+
(function(){
205+
console.log("something");
206+
})();
207+
}
208+
expect: {
209+
(function(){ return t })() ? foo(true) : bar(false), function(){
210+
console.log("something");
211+
}();
212+
}
213+
}
214+
215+
issue_1254_negate_iife_true: {
216+
options = {
217+
expression: true,
218+
negate_iife: true,
219+
}
220+
input: {
221+
(function() {
222+
return function() {
223+
console.log('test')
224+
};
225+
})()();
226+
}
227+
expect_exact: '(function(){return function(){console.log("test")}})()();'
228+
}
229+
230+
issue_1254_negate_iife_nested: {
231+
options = {
232+
expression: true,
233+
negate_iife: true,
234+
}
235+
input: {
236+
(function() {
237+
return function() {
238+
console.log('test')
239+
};
240+
})()()()()();
241+
}
242+
expect_exact: '(function(){return function(){console.log("test")}})()()()()();'
243+
}
244+
245+
conditional: {
246+
options = {
247+
expression: true,
248+
pure_funcs: [ "pure" ],
249+
side_effects: true,
250+
}
251+
input: {
252+
pure(1 | a() ? 2 & b() : 7 ^ c());
253+
pure(1 | a() ? 2 & b() : 5);
254+
pure(1 | a() ? 4 : 7 ^ c());
255+
pure(1 | a() ? 4 : 5);
256+
pure(3 ? 2 & b() : 7 ^ c());
257+
pure(3 ? 2 & b() : 5);
258+
pure(3 ? 4 : 7 ^ c());
259+
pure(3 ? 4 : 5);
260+
}
261+
expect: {
262+
1 | a() ? b() : c();
263+
1 | a() && b();
264+
1 | a() || c();
265+
a();
266+
3 ? b() : c();
267+
3 && b();
268+
3 || c();
269+
pure(3 ? 4 : 5);
270+
}
271+
}
272+
273+
limit_1: {
274+
options = {
275+
expression: true,
276+
sequences: 3,
277+
}
278+
input: {
279+
a;
280+
b;
281+
c;
282+
d;
283+
e;
284+
f;
285+
g;
286+
h;
287+
i;
288+
j;
289+
k;
290+
}
291+
expect: {
292+
// Turned into a single return statement
293+
// so it can no longer be split into lines
294+
a,b,c,d,e,f,g,h,i,j,k;
295+
}
296+
}
297+
298+
iife: {
299+
options = {
300+
expression: true,
301+
sequences: true,
302+
}
303+
input: {
304+
x = 42;
305+
(function a() {})();
306+
!function b() {}();
307+
~function c() {}();
308+
+function d() {}();
309+
-function e() {}();
310+
void function f() {}();
311+
typeof function g() {}();
312+
}
313+
expect: {
314+
x = 42, function a() {}(), function b() {}(), function c() {}(),
315+
function d() {}(), function e() {}(), function f() {}(), typeof function g() {}();
316+
}
317+
}

‎test/compress/negate-iife.js

+87
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,19 @@ negate_iife_2: {
3232
}
3333
}
3434

35+
negate_iife_2_side_effects: {
36+
options = {
37+
negate_iife: true,
38+
side_effects: true,
39+
}
40+
input: {
41+
(function(){ return {} })().x = 10; // should not transform this one
42+
}
43+
expect: {
44+
(function(){ return {} })().x = 10;
45+
}
46+
}
47+
3548
negate_iife_3: {
3649
options = {
3750
negate_iife: true,
@@ -45,6 +58,34 @@ negate_iife_3: {
4558
}
4659
}
4760

61+
negate_iife_3_evaluate: {
62+
options = {
63+
conditionals: true,
64+
evaluate: true,
65+
negate_iife: true,
66+
}
67+
input: {
68+
(function(){ return true })() ? console.log(true) : console.log(false);
69+
}
70+
expect: {
71+
console.log(true);
72+
}
73+
}
74+
75+
negate_iife_3_side_effects: {
76+
options = {
77+
conditionals: true,
78+
negate_iife: true,
79+
side_effects: true,
80+
}
81+
input: {
82+
(function(){ return t })() ? console.log(true) : console.log(false);
83+
}
84+
expect: {
85+
!function(){ return t }() ? console.log(false) : console.log(true);
86+
}
87+
}
88+
4889
negate_iife_3_off: {
4990
options = {
5091
negate_iife: false,
@@ -58,6 +99,20 @@ negate_iife_3_off: {
5899
}
59100
}
60101

102+
negate_iife_3_off_evaluate: {
103+
options = {
104+
conditionals: true,
105+
evaluate: true,
106+
negate_iife: false,
107+
}
108+
input: {
109+
(function(){ return true })() ? console.log(true) : console.log(false);
110+
}
111+
expect: {
112+
console.log(true);
113+
}
114+
}
115+
61116
negate_iife_4: {
62117
options = {
63118
negate_iife: true,
@@ -320,3 +375,35 @@ issue_1288: {
320375
}(0);
321376
}
322377
}
378+
379+
issue_1288_side_effects: {
380+
options = {
381+
conditionals: true,
382+
negate_iife: true,
383+
side_effects: true,
384+
}
385+
input: {
386+
if (w) ;
387+
else {
388+
(function f() {})();
389+
}
390+
if (!x) {
391+
(function() {
392+
x = {};
393+
})();
394+
}
395+
if (y)
396+
(function() {})();
397+
else
398+
(function(z) {
399+
return z;
400+
})(0);
401+
}
402+
expect: {
403+
w;
404+
x || function() {
405+
x = {};
406+
}();
407+
y;
408+
}
409+
}

‎test/compress/sequences.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,6 @@ iife: {
248248
}
249249
expect: {
250250
x = 42, function a() {}(), function b() {}(), function c() {}(),
251-
function d() {}(), function e() {}(), function f() {}(), function g() {}()
251+
function d() {}(), function e() {}(), function f() {}(), function g() {}();
252252
}
253253
}

0 commit comments

Comments
 (0)
Please sign in to comment.