Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
81166 views
1
/***********************************************************************
2
3
A JavaScript tokenizer / parser / beautifier / compressor.
4
https://github.com/mishoo/UglifyJS2
5
6
-------------------------------- (C) ---------------------------------
7
8
Author: Mihai Bazon
9
<[email protected]>
10
http://mihai.bazon.net/blog
11
12
Distributed under the BSD license:
13
14
Copyright 2012 (c) Mihai Bazon <[email protected]>
15
16
Redistribution and use in source and binary forms, with or without
17
modification, are permitted provided that the following conditions
18
are met:
19
20
* Redistributions of source code must retain the above
21
copyright notice, this list of conditions and the following
22
disclaimer.
23
24
* Redistributions in binary form must reproduce the above
25
copyright notice, this list of conditions and the following
26
disclaimer in the documentation and/or other materials
27
provided with the distribution.
28
29
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
30
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
31
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
32
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
33
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
34
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
35
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
36
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
37
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
38
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
39
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
40
SUCH DAMAGE.
41
42
***********************************************************************/
43
44
"use strict";
45
46
function Compressor(options, false_by_default) {
47
if (!(this instanceof Compressor))
48
return new Compressor(options, false_by_default);
49
TreeTransformer.call(this, this.before, this.after);
50
this.options = defaults(options, {
51
sequences : !false_by_default,
52
properties : !false_by_default,
53
dead_code : !false_by_default,
54
drop_debugger : !false_by_default,
55
unsafe : !false_by_default,
56
unsafe_comps : false,
57
conditionals : !false_by_default,
58
comparisons : !false_by_default,
59
evaluate : !false_by_default,
60
booleans : !false_by_default,
61
loops : !false_by_default,
62
unused : !false_by_default,
63
hoist_funs : !false_by_default,
64
hoist_vars : false,
65
if_return : !false_by_default,
66
join_vars : !false_by_default,
67
cascade : !false_by_default,
68
side_effects : !false_by_default,
69
70
warnings : true,
71
global_defs : {}
72
}, true);
73
};
74
75
Compressor.prototype = new TreeTransformer;
76
merge(Compressor.prototype, {
77
option: function(key) { return this.options[key] },
78
warn: function() {
79
if (this.options.warnings)
80
AST_Node.warn.apply(AST_Node, arguments);
81
},
82
before: function(node, descend, in_list) {
83
if (node._squeezed) return node;
84
if (node instanceof AST_Scope) {
85
node.drop_unused(this);
86
node = node.hoist_declarations(this);
87
}
88
descend(node, this);
89
node = node.optimize(this);
90
if (node instanceof AST_Scope) {
91
// dead code removal might leave further unused declarations.
92
// this'll usually save very few bytes, but the performance
93
// hit seems negligible so I'll just drop it here.
94
95
// no point to repeat warnings.
96
var save_warnings = this.options.warnings;
97
this.options.warnings = false;
98
node.drop_unused(this);
99
this.options.warnings = save_warnings;
100
}
101
node._squeezed = true;
102
return node;
103
}
104
});
105
106
(function(){
107
108
function OPT(node, optimizer) {
109
node.DEFMETHOD("optimize", function(compressor){
110
var self = this;
111
if (self._optimized) return self;
112
var opt = optimizer(self, compressor);
113
opt._optimized = true;
114
if (opt === self) return opt;
115
return opt.transform(compressor);
116
});
117
};
118
119
OPT(AST_Node, function(self, compressor){
120
return self;
121
});
122
123
AST_Node.DEFMETHOD("equivalent_to", function(node){
124
// XXX: this is a rather expensive way to test two node's equivalence:
125
return this.print_to_string() == node.print_to_string();
126
});
127
128
function make_node(ctor, orig, props) {
129
if (!props) props = {};
130
if (orig) {
131
if (!props.start) props.start = orig.start;
132
if (!props.end) props.end = orig.end;
133
}
134
return new ctor(props);
135
};
136
137
function make_node_from_constant(compressor, val, orig) {
138
// XXX: WIP.
139
// if (val instanceof AST_Node) return val.transform(new TreeTransformer(null, function(node){
140
// if (node instanceof AST_SymbolRef) {
141
// var scope = compressor.find_parent(AST_Scope);
142
// var def = scope.find_variable(node);
143
// node.thedef = def;
144
// return node;
145
// }
146
// })).transform(compressor);
147
148
if (val instanceof AST_Node) return val.transform(compressor);
149
switch (typeof val) {
150
case "string":
151
return make_node(AST_String, orig, {
152
value: val
153
}).optimize(compressor);
154
case "number":
155
return make_node(isNaN(val) ? AST_NaN : AST_Number, orig, {
156
value: val
157
}).optimize(compressor);
158
case "boolean":
159
return make_node(val ? AST_True : AST_False, orig);
160
case "undefined":
161
return make_node(AST_Undefined, orig).optimize(compressor);
162
default:
163
if (val === null) {
164
return make_node(AST_Null, orig).optimize(compressor);
165
}
166
if (val instanceof RegExp) {
167
return make_node(AST_RegExp, orig).optimize(compressor);
168
}
169
throw new Error(string_template("Can't handle constant of type: {type}", {
170
type: typeof val
171
}));
172
}
173
};
174
175
function as_statement_array(thing) {
176
if (thing === null) return [];
177
if (thing instanceof AST_BlockStatement) return thing.body;
178
if (thing instanceof AST_EmptyStatement) return [];
179
if (thing instanceof AST_Statement) return [ thing ];
180
throw new Error("Can't convert thing to statement array");
181
};
182
183
function is_empty(thing) {
184
if (thing === null) return true;
185
if (thing instanceof AST_EmptyStatement) return true;
186
if (thing instanceof AST_BlockStatement) return thing.body.length == 0;
187
return false;
188
};
189
190
function loop_body(x) {
191
if (x instanceof AST_Switch) return x;
192
if (x instanceof AST_For || x instanceof AST_ForIn || x instanceof AST_DWLoop) {
193
return (x.body instanceof AST_BlockStatement ? x.body : x);
194
}
195
return x;
196
};
197
198
function tighten_body(statements, compressor) {
199
var CHANGED;
200
do {
201
CHANGED = false;
202
statements = eliminate_spurious_blocks(statements);
203
if (compressor.option("dead_code")) {
204
statements = eliminate_dead_code(statements, compressor);
205
}
206
if (compressor.option("if_return")) {
207
statements = handle_if_return(statements, compressor);
208
}
209
if (compressor.option("sequences")) {
210
statements = sequencesize(statements, compressor);
211
}
212
if (compressor.option("join_vars")) {
213
statements = join_consecutive_vars(statements, compressor);
214
}
215
} while (CHANGED);
216
return statements;
217
218
function eliminate_spurious_blocks(statements) {
219
var seen_dirs = [];
220
return statements.reduce(function(a, stat){
221
if (stat instanceof AST_BlockStatement) {
222
CHANGED = true;
223
a.push.apply(a, eliminate_spurious_blocks(stat.body));
224
} else if (stat instanceof AST_EmptyStatement) {
225
CHANGED = true;
226
} else if (stat instanceof AST_Directive) {
227
if (seen_dirs.indexOf(stat.value) < 0) {
228
a.push(stat);
229
seen_dirs.push(stat.value);
230
} else {
231
CHANGED = true;
232
}
233
} else {
234
a.push(stat);
235
}
236
return a;
237
}, []);
238
};
239
240
function handle_if_return(statements, compressor) {
241
var self = compressor.self();
242
var in_lambda = self instanceof AST_Lambda;
243
var ret = [];
244
loop: for (var i = statements.length; --i >= 0;) {
245
var stat = statements[i];
246
switch (true) {
247
case (in_lambda && stat instanceof AST_Return && !stat.value && ret.length == 0):
248
CHANGED = true;
249
// note, ret.length is probably always zero
250
// because we drop unreachable code before this
251
// step. nevertheless, it's good to check.
252
continue loop;
253
case stat instanceof AST_If:
254
if (stat.body instanceof AST_Return) {
255
//---
256
// pretty silly case, but:
257
// if (foo()) return; return; ==> foo(); return;
258
if (((in_lambda && ret.length == 0)
259
|| (ret[0] instanceof AST_Return && !ret[0].value))
260
&& !stat.body.value && !stat.alternative) {
261
CHANGED = true;
262
var cond = make_node(AST_SimpleStatement, stat.condition, {
263
body: stat.condition
264
});
265
ret.unshift(cond);
266
continue loop;
267
}
268
//---
269
// if (foo()) return x; return y; ==> return foo() ? x : y;
270
if (ret[0] instanceof AST_Return && stat.body.value && ret[0].value && !stat.alternative) {
271
CHANGED = true;
272
stat = stat.clone();
273
stat.alternative = ret[0];
274
ret[0] = stat.transform(compressor);
275
continue loop;
276
}
277
//---
278
// if (foo()) return x; [ return ; ] ==> return foo() ? x : undefined;
279
if ((ret.length == 0 || ret[0] instanceof AST_Return) && stat.body.value && !stat.alternative && in_lambda) {
280
CHANGED = true;
281
stat = stat.clone();
282
stat.alternative = ret[0] || make_node(AST_Return, stat, {
283
value: make_node(AST_Undefined, stat)
284
});
285
ret[0] = stat.transform(compressor);
286
continue loop;
287
}
288
//---
289
// if (foo()) return; [ else x... ]; y... ==> if (!foo()) { x...; y... }
290
if (!stat.body.value && in_lambda) {
291
CHANGED = true;
292
stat = stat.clone();
293
stat.condition = stat.condition.negate(compressor);
294
stat.body = make_node(AST_BlockStatement, stat, {
295
body: as_statement_array(stat.alternative).concat(ret)
296
});
297
stat.alternative = null;
298
ret = [ stat.transform(compressor) ];
299
continue loop;
300
}
301
//---
302
if (ret.length == 1 && in_lambda && ret[0] instanceof AST_SimpleStatement
303
&& (!stat.alternative || stat.alternative instanceof AST_SimpleStatement)) {
304
CHANGED = true;
305
ret.push(make_node(AST_Return, ret[0], {
306
value: make_node(AST_Undefined, ret[0])
307
}).transform(compressor));
308
ret = as_statement_array(stat.alternative).concat(ret);
309
ret.unshift(stat);
310
continue loop;
311
}
312
}
313
314
var ab = aborts(stat.body);
315
var lct = ab instanceof AST_LoopControl ? compressor.loopcontrol_target(ab.label) : null;
316
if (ab && ((ab instanceof AST_Return && !ab.value && in_lambda)
317
|| (ab instanceof AST_Continue && self === loop_body(lct))
318
|| (ab instanceof AST_Break && lct instanceof AST_BlockStatement && self === lct))) {
319
if (ab.label) {
320
remove(ab.label.thedef.references, ab.label);
321
}
322
CHANGED = true;
323
var body = as_statement_array(stat.body).slice(0, -1);
324
stat = stat.clone();
325
stat.condition = stat.condition.negate(compressor);
326
stat.body = make_node(AST_BlockStatement, stat, {
327
body: ret
328
});
329
stat.alternative = make_node(AST_BlockStatement, stat, {
330
body: body
331
});
332
ret = [ stat.transform(compressor) ];
333
continue loop;
334
}
335
336
var ab = aborts(stat.alternative);
337
var lct = ab instanceof AST_LoopControl ? compressor.loopcontrol_target(ab.label) : null;
338
if (ab && ((ab instanceof AST_Return && !ab.value && in_lambda)
339
|| (ab instanceof AST_Continue && self === loop_body(lct))
340
|| (ab instanceof AST_Break && lct instanceof AST_BlockStatement && self === lct))) {
341
if (ab.label) {
342
remove(ab.label.thedef.references, ab.label);
343
}
344
CHANGED = true;
345
stat = stat.clone();
346
stat.body = make_node(AST_BlockStatement, stat.body, {
347
body: as_statement_array(stat.body).concat(ret)
348
});
349
stat.alternative = make_node(AST_BlockStatement, stat.alternative, {
350
body: as_statement_array(stat.alternative).slice(0, -1)
351
});
352
ret = [ stat.transform(compressor) ];
353
continue loop;
354
}
355
356
ret.unshift(stat);
357
break;
358
default:
359
ret.unshift(stat);
360
break;
361
}
362
}
363
return ret;
364
};
365
366
function eliminate_dead_code(statements, compressor) {
367
var has_quit = false;
368
var orig = statements.length;
369
var self = compressor.self();
370
statements = statements.reduce(function(a, stat){
371
if (has_quit) {
372
extract_declarations_from_unreachable_code(compressor, stat, a);
373
} else {
374
if (stat instanceof AST_LoopControl) {
375
var lct = compressor.loopcontrol_target(stat.label);
376
if ((stat instanceof AST_Break
377
&& lct instanceof AST_BlockStatement
378
&& loop_body(lct) === self) || (stat instanceof AST_Continue
379
&& loop_body(lct) === self)) {
380
if (stat.label) {
381
remove(stat.label.thedef.references, stat.label);
382
}
383
} else {
384
a.push(stat);
385
}
386
} else {
387
a.push(stat);
388
}
389
if (aborts(stat)) has_quit = true;
390
}
391
return a;
392
}, []);
393
CHANGED = statements.length != orig;
394
return statements;
395
};
396
397
function sequencesize(statements, compressor) {
398
if (statements.length < 2) return statements;
399
var seq = [], ret = [];
400
function push_seq() {
401
seq = AST_Seq.from_array(seq);
402
if (seq) ret.push(make_node(AST_SimpleStatement, seq, {
403
body: seq
404
}));
405
seq = [];
406
};
407
statements.forEach(function(stat){
408
if (stat instanceof AST_SimpleStatement) seq.push(stat.body);
409
else push_seq(), ret.push(stat);
410
});
411
push_seq();
412
ret = sequencesize_2(ret, compressor);
413
CHANGED = ret.length != statements.length;
414
return ret;
415
};
416
417
function sequencesize_2(statements, compressor) {
418
function cons_seq(right) {
419
ret.pop();
420
var left = prev.body;
421
if (left instanceof AST_Seq) {
422
left.add(right);
423
} else {
424
left = AST_Seq.cons(left, right);
425
}
426
return left.transform(compressor);
427
};
428
var ret = [], prev = null;
429
statements.forEach(function(stat){
430
if (prev) {
431
if (stat instanceof AST_For) {
432
var opera = {};
433
try {
434
prev.body.walk(new TreeWalker(function(node){
435
if (node instanceof AST_Binary && node.operator == "in")
436
throw opera;
437
}));
438
if (stat.init && !(stat.init instanceof AST_Definitions)) {
439
stat.init = cons_seq(stat.init);
440
}
441
else if (!stat.init) {
442
stat.init = prev.body;
443
ret.pop();
444
}
445
} catch(ex) {
446
if (ex !== opera) throw ex;
447
}
448
}
449
else if (stat instanceof AST_If) {
450
stat.condition = cons_seq(stat.condition);
451
}
452
else if (stat instanceof AST_With) {
453
stat.expression = cons_seq(stat.expression);
454
}
455
else if (stat instanceof AST_Exit && stat.value) {
456
stat.value = cons_seq(stat.value);
457
}
458
else if (stat instanceof AST_Exit) {
459
stat.value = cons_seq(make_node(AST_Undefined, stat));
460
}
461
else if (stat instanceof AST_Switch) {
462
stat.expression = cons_seq(stat.expression);
463
}
464
}
465
ret.push(stat);
466
prev = stat instanceof AST_SimpleStatement ? stat : null;
467
});
468
return ret;
469
};
470
471
function join_consecutive_vars(statements, compressor) {
472
var prev = null;
473
return statements.reduce(function(a, stat){
474
if (stat instanceof AST_Definitions && prev && prev.TYPE == stat.TYPE) {
475
prev.definitions = prev.definitions.concat(stat.definitions);
476
CHANGED = true;
477
}
478
else if (stat instanceof AST_For
479
&& prev instanceof AST_Definitions
480
&& (!stat.init || stat.init.TYPE == prev.TYPE)) {
481
CHANGED = true;
482
a.pop();
483
if (stat.init) {
484
stat.init.definitions = prev.definitions.concat(stat.init.definitions);
485
} else {
486
stat.init = prev;
487
}
488
a.push(stat);
489
prev = stat;
490
}
491
else {
492
prev = stat;
493
a.push(stat);
494
}
495
return a;
496
}, []);
497
};
498
499
};
500
501
function extract_declarations_from_unreachable_code(compressor, stat, target) {
502
compressor.warn("Dropping unreachable code [{file}:{line},{col}]", stat.start);
503
stat.walk(new TreeWalker(function(node){
504
if (node instanceof AST_Definitions) {
505
compressor.warn("Declarations in unreachable code! [{file}:{line},{col}]", node.start);
506
node.remove_initializers();
507
target.push(node);
508
return true;
509
}
510
if (node instanceof AST_Defun) {
511
target.push(node);
512
return true;
513
}
514
if (node instanceof AST_Scope) {
515
return true;
516
}
517
}));
518
};
519
520
/* -----[ boolean/negation helpers ]----- */
521
522
// methods to determine whether an expression has a boolean result type
523
(function (def){
524
var unary_bool = [ "!", "delete" ];
525
var binary_bool = [ "in", "instanceof", "==", "!=", "===", "!==", "<", "<=", ">=", ">" ];
526
def(AST_Node, function(){ return false });
527
def(AST_UnaryPrefix, function(){
528
return member(this.operator, unary_bool);
529
});
530
def(AST_Binary, function(){
531
return member(this.operator, binary_bool) ||
532
( (this.operator == "&&" || this.operator == "||") &&
533
this.left.is_boolean() && this.right.is_boolean() );
534
});
535
def(AST_Conditional, function(){
536
return this.consequent.is_boolean() && this.alternative.is_boolean();
537
});
538
def(AST_Assign, function(){
539
return this.operator == "=" && this.right.is_boolean();
540
});
541
def(AST_Seq, function(){
542
return this.cdr.is_boolean();
543
});
544
def(AST_True, function(){ return true });
545
def(AST_False, function(){ return true });
546
})(function(node, func){
547
node.DEFMETHOD("is_boolean", func);
548
});
549
550
// methods to determine if an expression has a string result type
551
(function (def){
552
def(AST_Node, function(){ return false });
553
def(AST_String, function(){ return true });
554
def(AST_UnaryPrefix, function(){
555
return this.operator == "typeof";
556
});
557
def(AST_Binary, function(compressor){
558
return this.operator == "+" &&
559
(this.left.is_string(compressor) || this.right.is_string(compressor));
560
});
561
def(AST_Assign, function(compressor){
562
return (this.operator == "=" || this.operator == "+=") && this.right.is_string(compressor);
563
});
564
def(AST_Seq, function(compressor){
565
return this.cdr.is_string(compressor);
566
});
567
def(AST_Conditional, function(compressor){
568
return this.consequent.is_string(compressor) && this.alternative.is_string(compressor);
569
});
570
def(AST_Call, function(compressor){
571
return compressor.option("unsafe")
572
&& this.expression instanceof AST_SymbolRef
573
&& this.expression.name == "String"
574
&& this.expression.undeclared();
575
});
576
})(function(node, func){
577
node.DEFMETHOD("is_string", func);
578
});
579
580
function best_of(ast1, ast2) {
581
return ast1.print_to_string().length >
582
ast2.print_to_string().length
583
? ast2 : ast1;
584
};
585
586
// methods to evaluate a constant expression
587
(function (def){
588
// The evaluate method returns an array with one or two
589
// elements. If the node has been successfully reduced to a
590
// constant, then the second element tells us the value;
591
// otherwise the second element is missing. The first element
592
// of the array is always an AST_Node descendant; when
593
// evaluation was successful it's a node that represents the
594
// constant; otherwise it's the original node.
595
AST_Node.DEFMETHOD("evaluate", function(compressor){
596
if (!compressor.option("evaluate")) return [ this ];
597
try {
598
var val = this._eval(), ast = make_node_from_constant(compressor, val, this);
599
return [ best_of(ast, this), val ];
600
} catch(ex) {
601
if (ex !== def) throw ex;
602
return [ this ];
603
}
604
});
605
def(AST_Statement, function(){
606
throw new Error(string_template("Cannot evaluate a statement [{file}:{line},{col}]", this.start));
607
});
608
def(AST_Function, function(){
609
// XXX: AST_Function inherits from AST_Scope, which itself
610
// inherits from AST_Statement; however, an AST_Function
611
// isn't really a statement. This could byte in other
612
// places too. :-( Wish JS had multiple inheritance.
613
return [ this ];
614
});
615
function ev(node) {
616
return node._eval();
617
};
618
def(AST_Node, function(){
619
throw def; // not constant
620
});
621
def(AST_Constant, function(){
622
return this.getValue();
623
});
624
def(AST_UnaryPrefix, function(){
625
var e = this.expression;
626
switch (this.operator) {
627
case "!": return !ev(e);
628
case "typeof": return typeof ev(e);
629
case "void": return void ev(e);
630
case "~": return ~ev(e);
631
case "-":
632
e = ev(e);
633
if (e === 0) throw def;
634
return -e;
635
case "+": return +ev(e);
636
}
637
throw def;
638
});
639
def(AST_Binary, function(){
640
var left = this.left, right = this.right;
641
switch (this.operator) {
642
case "&&" : return ev(left) && ev(right);
643
case "||" : return ev(left) || ev(right);
644
case "|" : return ev(left) | ev(right);
645
case "&" : return ev(left) & ev(right);
646
case "^" : return ev(left) ^ ev(right);
647
case "+" : return ev(left) + ev(right);
648
case "*" : return ev(left) * ev(right);
649
case "/" : return ev(left) / ev(right);
650
case "%" : return ev(left) % ev(right);
651
case "-" : return ev(left) - ev(right);
652
case "<<" : return ev(left) << ev(right);
653
case ">>" : return ev(left) >> ev(right);
654
case ">>>" : return ev(left) >>> ev(right);
655
case "==" : return ev(left) == ev(right);
656
case "===" : return ev(left) === ev(right);
657
case "!=" : return ev(left) != ev(right);
658
case "!==" : return ev(left) !== ev(right);
659
case "<" : return ev(left) < ev(right);
660
case "<=" : return ev(left) <= ev(right);
661
case ">" : return ev(left) > ev(right);
662
case ">=" : return ev(left) >= ev(right);
663
case "in" : return ev(left) in ev(right);
664
case "instanceof" : return ev(left) instanceof ev(right);
665
}
666
throw def;
667
});
668
def(AST_Conditional, function(){
669
return ev(this.condition)
670
? ev(this.consequent)
671
: ev(this.alternative);
672
});
673
def(AST_SymbolRef, function(){
674
var d = this.definition();
675
if (d && d.constant && d.init) return ev(d.init);
676
throw def;
677
});
678
})(function(node, func){
679
node.DEFMETHOD("_eval", func);
680
});
681
682
// method to negate an expression
683
(function(def){
684
function basic_negation(exp) {
685
return make_node(AST_UnaryPrefix, exp, {
686
operator: "!",
687
expression: exp
688
});
689
};
690
def(AST_Node, function(){
691
return basic_negation(this);
692
});
693
def(AST_Statement, function(){
694
throw new Error("Cannot negate a statement");
695
});
696
def(AST_Function, function(){
697
return basic_negation(this);
698
});
699
def(AST_UnaryPrefix, function(){
700
if (this.operator == "!")
701
return this.expression;
702
return basic_negation(this);
703
});
704
def(AST_Seq, function(compressor){
705
var self = this.clone();
706
self.cdr = self.cdr.negate(compressor);
707
return self;
708
});
709
def(AST_Conditional, function(compressor){
710
var self = this.clone();
711
self.consequent = self.consequent.negate(compressor);
712
self.alternative = self.alternative.negate(compressor);
713
return best_of(basic_negation(this), self);
714
});
715
def(AST_Binary, function(compressor){
716
var self = this.clone(), op = this.operator;
717
if (compressor.option("unsafe_comps")) {
718
switch (op) {
719
case "<=" : self.operator = ">" ; return self;
720
case "<" : self.operator = ">=" ; return self;
721
case ">=" : self.operator = "<" ; return self;
722
case ">" : self.operator = "<=" ; return self;
723
}
724
}
725
switch (op) {
726
case "==" : self.operator = "!="; return self;
727
case "!=" : self.operator = "=="; return self;
728
case "===": self.operator = "!=="; return self;
729
case "!==": self.operator = "==="; return self;
730
case "&&":
731
self.operator = "||";
732
self.left = self.left.negate(compressor);
733
self.right = self.right.negate(compressor);
734
return best_of(basic_negation(this), self);
735
case "||":
736
self.operator = "&&";
737
self.left = self.left.negate(compressor);
738
self.right = self.right.negate(compressor);
739
return best_of(basic_negation(this), self);
740
}
741
return basic_negation(this);
742
});
743
})(function(node, func){
744
node.DEFMETHOD("negate", function(compressor){
745
return func.call(this, compressor);
746
});
747
});
748
749
// determine if expression has side effects
750
(function(def){
751
def(AST_Node, function(){ return true });
752
753
def(AST_EmptyStatement, function(){ return false });
754
def(AST_Constant, function(){ return false });
755
def(AST_This, function(){ return false });
756
757
def(AST_Block, function(){
758
for (var i = this.body.length; --i >= 0;) {
759
if (this.body[i].has_side_effects())
760
return true;
761
}
762
return false;
763
});
764
765
def(AST_SimpleStatement, function(){
766
return this.body.has_side_effects();
767
});
768
def(AST_Defun, function(){ return true });
769
def(AST_Function, function(){ return false });
770
def(AST_Binary, function(){
771
return this.left.has_side_effects()
772
|| this.right.has_side_effects();
773
});
774
def(AST_Assign, function(){ return true });
775
def(AST_Conditional, function(){
776
return this.condition.has_side_effects()
777
|| this.consequent.has_side_effects()
778
|| this.alternative.has_side_effects();
779
});
780
def(AST_Unary, function(){
781
return this.operator == "delete"
782
|| this.operator == "++"
783
|| this.operator == "--"
784
|| this.expression.has_side_effects();
785
});
786
def(AST_SymbolRef, function(){ return false });
787
def(AST_Object, function(){
788
for (var i = this.properties.length; --i >= 0;)
789
if (this.properties[i].has_side_effects())
790
return true;
791
return false;
792
});
793
def(AST_ObjectProperty, function(){
794
return this.value.has_side_effects();
795
});
796
def(AST_Array, function(){
797
for (var i = this.elements.length; --i >= 0;)
798
if (this.elements[i].has_side_effects())
799
return true;
800
return false;
801
});
802
// def(AST_Dot, function(){
803
// return this.expression.has_side_effects();
804
// });
805
// def(AST_Sub, function(){
806
// return this.expression.has_side_effects()
807
// || this.property.has_side_effects();
808
// });
809
def(AST_PropAccess, function(){
810
return true;
811
});
812
def(AST_Seq, function(){
813
return this.car.has_side_effects()
814
|| this.cdr.has_side_effects();
815
});
816
})(function(node, func){
817
node.DEFMETHOD("has_side_effects", func);
818
});
819
820
// tell me if a statement aborts
821
function aborts(thing) {
822
return thing && thing.aborts();
823
};
824
(function(def){
825
def(AST_Statement, function(){ return null });
826
def(AST_Jump, function(){ return this });
827
function block_aborts(){
828
var n = this.body.length;
829
return n > 0 && aborts(this.body[n - 1]);
830
};
831
def(AST_BlockStatement, block_aborts);
832
def(AST_SwitchBranch, block_aborts);
833
def(AST_If, function(){
834
return this.alternative && aborts(this.body) && aborts(this.alternative);
835
});
836
})(function(node, func){
837
node.DEFMETHOD("aborts", func);
838
});
839
840
/* -----[ optimizers ]----- */
841
842
OPT(AST_Directive, function(self, compressor){
843
if (self.scope.has_directive(self.value) !== self.scope) {
844
return make_node(AST_EmptyStatement, self);
845
}
846
return self;
847
});
848
849
OPT(AST_Debugger, function(self, compressor){
850
if (compressor.option("drop_debugger"))
851
return make_node(AST_EmptyStatement, self);
852
return self;
853
});
854
855
OPT(AST_LabeledStatement, function(self, compressor){
856
if (self.body instanceof AST_Break
857
&& compressor.loopcontrol_target(self.body.label) === self.body) {
858
return make_node(AST_EmptyStatement, self);
859
}
860
return self.label.references.length == 0 ? self.body : self;
861
});
862
863
OPT(AST_Block, function(self, compressor){
864
self.body = tighten_body(self.body, compressor);
865
return self;
866
});
867
868
OPT(AST_BlockStatement, function(self, compressor){
869
self.body = tighten_body(self.body, compressor);
870
switch (self.body.length) {
871
case 1: return self.body[0];
872
case 0: return make_node(AST_EmptyStatement, self);
873
}
874
return self;
875
});
876
877
AST_Scope.DEFMETHOD("drop_unused", function(compressor){
878
var self = this;
879
if (compressor.option("unused")
880
&& !(self instanceof AST_Toplevel)
881
&& !self.uses_eval
882
) {
883
var in_use = [];
884
var initializations = new Dictionary();
885
// pass 1: find out which symbols are directly used in
886
// this scope (not in nested scopes).
887
var scope = this;
888
var tw = new TreeWalker(function(node, descend){
889
if (node !== self) {
890
if (node instanceof AST_Defun) {
891
initializations.add(node.name.name, node);
892
return true; // don't go in nested scopes
893
}
894
if (node instanceof AST_Definitions && scope === self) {
895
node.definitions.forEach(function(def){
896
if (def.value) {
897
initializations.add(def.name.name, def.value);
898
if (def.value.has_side_effects()) {
899
def.value.walk(tw);
900
}
901
}
902
});
903
return true;
904
}
905
if (node instanceof AST_SymbolRef) {
906
push_uniq(in_use, node.definition());
907
return true;
908
}
909
if (node instanceof AST_Scope) {
910
var save_scope = scope;
911
scope = node;
912
descend();
913
scope = save_scope;
914
return true;
915
}
916
}
917
});
918
self.walk(tw);
919
// pass 2: for every used symbol we need to walk its
920
// initialization code to figure out if it uses other
921
// symbols (that may not be in_use).
922
for (var i = 0; i < in_use.length; ++i) {
923
in_use[i].orig.forEach(function(decl){
924
// undeclared globals will be instanceof AST_SymbolRef
925
var init = initializations.get(decl.name);
926
if (init) init.forEach(function(init){
927
var tw = new TreeWalker(function(node){
928
if (node instanceof AST_SymbolRef) {
929
push_uniq(in_use, node.definition());
930
}
931
});
932
init.walk(tw);
933
});
934
});
935
}
936
// pass 3: we should drop declarations not in_use
937
var tt = new TreeTransformer(
938
function before(node, descend, in_list) {
939
if (node instanceof AST_Lambda) {
940
for (var a = node.argnames, i = a.length; --i >= 0;) {
941
var sym = a[i];
942
if (sym.unreferenced()) {
943
a.pop();
944
compressor.warn("Dropping unused function argument {name} [{file}:{line},{col}]", {
945
name : sym.name,
946
file : sym.start.file,
947
line : sym.start.line,
948
col : sym.start.col
949
});
950
}
951
else break;
952
}
953
}
954
if (node instanceof AST_Defun && node !== self) {
955
if (!member(node.name.definition(), in_use)) {
956
compressor.warn("Dropping unused function {name} [{file}:{line},{col}]", {
957
name : node.name.name,
958
file : node.name.start.file,
959
line : node.name.start.line,
960
col : node.name.start.col
961
});
962
return make_node(AST_EmptyStatement, node);
963
}
964
return node;
965
}
966
if (node instanceof AST_Definitions && !(tt.parent() instanceof AST_ForIn)) {
967
var def = node.definitions.filter(function(def){
968
if (member(def.name.definition(), in_use)) return true;
969
var w = {
970
name : def.name.name,
971
file : def.name.start.file,
972
line : def.name.start.line,
973
col : def.name.start.col
974
};
975
if (def.value && def.value.has_side_effects()) {
976
def._unused_side_effects = true;
977
compressor.warn("Side effects in initialization of unused variable {name} [{file}:{line},{col}]", w);
978
return true;
979
}
980
compressor.warn("Dropping unused variable {name} [{file}:{line},{col}]", w);
981
return false;
982
});
983
// place uninitialized names at the start
984
def = mergeSort(def, function(a, b){
985
if (!a.value && b.value) return -1;
986
if (!b.value && a.value) return 1;
987
return 0;
988
});
989
// for unused names whose initialization has
990
// side effects, we can cascade the init. code
991
// into the next one, or next statement.
992
var side_effects = [];
993
for (var i = 0; i < def.length;) {
994
var x = def[i];
995
if (x._unused_side_effects) {
996
side_effects.push(x.value);
997
def.splice(i, 1);
998
} else {
999
if (side_effects.length > 0) {
1000
side_effects.push(x.value);
1001
x.value = AST_Seq.from_array(side_effects);
1002
side_effects = [];
1003
}
1004
++i;
1005
}
1006
}
1007
if (side_effects.length > 0) {
1008
side_effects = make_node(AST_BlockStatement, node, {
1009
body: [ make_node(AST_SimpleStatement, node, {
1010
body: AST_Seq.from_array(side_effects)
1011
}) ]
1012
});
1013
} else {
1014
side_effects = null;
1015
}
1016
if (def.length == 0 && !side_effects) {
1017
return make_node(AST_EmptyStatement, node);
1018
}
1019
if (def.length == 0) {
1020
return side_effects;
1021
}
1022
node.definitions = def;
1023
if (side_effects) {
1024
side_effects.body.unshift(node);
1025
node = side_effects;
1026
}
1027
return node;
1028
}
1029
if (node instanceof AST_For && node.init instanceof AST_BlockStatement) {
1030
descend(node, this);
1031
// certain combination of unused name + side effect leads to:
1032
// https://github.com/mishoo/UglifyJS2/issues/44
1033
// that's an invalid AST.
1034
// We fix it at this stage by moving the `var` outside the `for`.
1035
var body = node.init.body.slice(0, -1);
1036
node.init = node.init.body.slice(-1)[0].body;
1037
body.push(node);
1038
return in_list ? MAP.splice(body) : make_node(AST_BlockStatement, node, {
1039
body: body
1040
});
1041
}
1042
if (node instanceof AST_Scope && node !== self)
1043
return node;
1044
}
1045
);
1046
self.transform(tt);
1047
}
1048
});
1049
1050
AST_Scope.DEFMETHOD("hoist_declarations", function(compressor){
1051
var hoist_funs = compressor.option("hoist_funs");
1052
var hoist_vars = compressor.option("hoist_vars");
1053
var self = this;
1054
if (hoist_funs || hoist_vars) {
1055
var dirs = [];
1056
var hoisted = [];
1057
var vars = new Dictionary(), vars_found = 0, var_decl = 0;
1058
// let's count var_decl first, we seem to waste a lot of
1059
// space if we hoist `var` when there's only one.
1060
self.walk(new TreeWalker(function(node){
1061
if (node instanceof AST_Scope && node !== self)
1062
return true;
1063
if (node instanceof AST_Var) {
1064
++var_decl;
1065
return true;
1066
}
1067
}));
1068
hoist_vars = hoist_vars && var_decl > 1;
1069
var tt = new TreeTransformer(
1070
function before(node) {
1071
if (node !== self) {
1072
if (node instanceof AST_Directive) {
1073
dirs.push(node);
1074
return make_node(AST_EmptyStatement, node);
1075
}
1076
if (node instanceof AST_Defun && hoist_funs) {
1077
hoisted.push(node);
1078
return make_node(AST_EmptyStatement, node);
1079
}
1080
if (node instanceof AST_Var && hoist_vars) {
1081
node.definitions.forEach(function(def){
1082
vars.set(def.name.name, def);
1083
++vars_found;
1084
});
1085
var seq = node.to_assignments();
1086
var p = tt.parent();
1087
if (p instanceof AST_ForIn && p.init === node) {
1088
if (seq == null) return node.definitions[0].name;
1089
return seq;
1090
}
1091
if (p instanceof AST_For && p.init === node) {
1092
return seq;
1093
}
1094
if (!seq) return make_node(AST_EmptyStatement, node);
1095
return make_node(AST_SimpleStatement, node, {
1096
body: seq
1097
});
1098
}
1099
if (node instanceof AST_Scope)
1100
return node; // to avoid descending in nested scopes
1101
}
1102
}
1103
);
1104
self = self.transform(tt);
1105
if (vars_found > 0) {
1106
// collect only vars which don't show up in self's arguments list
1107
var defs = [];
1108
vars.each(function(def, name){
1109
if (self instanceof AST_Lambda
1110
&& find_if(function(x){ return x.name == def.name.name },
1111
self.argnames)) {
1112
vars.del(name);
1113
} else {
1114
def = def.clone();
1115
def.value = null;
1116
defs.push(def);
1117
vars.set(name, def);
1118
}
1119
});
1120
if (defs.length > 0) {
1121
// try to merge in assignments
1122
for (var i = 0; i < self.body.length;) {
1123
if (self.body[i] instanceof AST_SimpleStatement) {
1124
var expr = self.body[i].body, sym, assign;
1125
if (expr instanceof AST_Assign
1126
&& expr.operator == "="
1127
&& (sym = expr.left) instanceof AST_Symbol
1128
&& vars.has(sym.name))
1129
{
1130
var def = vars.get(sym.name);
1131
if (def.value) break;
1132
def.value = expr.right;
1133
remove(defs, def);
1134
defs.push(def);
1135
self.body.splice(i, 1);
1136
continue;
1137
}
1138
if (expr instanceof AST_Seq
1139
&& (assign = expr.car) instanceof AST_Assign
1140
&& assign.operator == "="
1141
&& (sym = assign.left) instanceof AST_Symbol
1142
&& vars.has(sym.name))
1143
{
1144
var def = vars.get(sym.name);
1145
if (def.value) break;
1146
def.value = assign.right;
1147
remove(defs, def);
1148
defs.push(def);
1149
self.body[i].body = expr.cdr;
1150
continue;
1151
}
1152
}
1153
if (self.body[i] instanceof AST_EmptyStatement) {
1154
self.body.splice(i, 1);
1155
continue;
1156
}
1157
if (self.body[i] instanceof AST_BlockStatement) {
1158
var tmp = [ i, 1 ].concat(self.body[i].body);
1159
self.body.splice.apply(self.body, tmp);
1160
continue;
1161
}
1162
break;
1163
}
1164
defs = make_node(AST_Var, self, {
1165
definitions: defs
1166
});
1167
hoisted.push(defs);
1168
};
1169
}
1170
self.body = dirs.concat(hoisted, self.body);
1171
}
1172
return self;
1173
});
1174
1175
OPT(AST_SimpleStatement, function(self, compressor){
1176
if (compressor.option("side_effects")) {
1177
if (!self.body.has_side_effects()) {
1178
compressor.warn("Dropping side-effect-free statement [{file}:{line},{col}]", self.start);
1179
return make_node(AST_EmptyStatement, self);
1180
}
1181
}
1182
return self;
1183
});
1184
1185
OPT(AST_DWLoop, function(self, compressor){
1186
var cond = self.condition.evaluate(compressor);
1187
self.condition = cond[0];
1188
if (!compressor.option("loops")) return self;
1189
if (cond.length > 1) {
1190
if (cond[1]) {
1191
return make_node(AST_For, self, {
1192
body: self.body
1193
});
1194
} else if (self instanceof AST_While) {
1195
if (compressor.option("dead_code")) {
1196
var a = [];
1197
extract_declarations_from_unreachable_code(compressor, self.body, a);
1198
return make_node(AST_BlockStatement, self, { body: a });
1199
}
1200
} else {
1201
return self.body;
1202
}
1203
}
1204
return self;
1205
});
1206
1207
function if_break_in_loop(self, compressor) {
1208
function drop_it(rest) {
1209
rest = as_statement_array(rest);
1210
if (self.body instanceof AST_BlockStatement) {
1211
self.body = self.body.clone();
1212
self.body.body = rest.concat(self.body.body.slice(1));
1213
self.body = self.body.transform(compressor);
1214
} else {
1215
self.body = make_node(AST_BlockStatement, self.body, {
1216
body: rest
1217
}).transform(compressor);
1218
}
1219
if_break_in_loop(self, compressor);
1220
}
1221
var first = self.body instanceof AST_BlockStatement ? self.body.body[0] : self.body;
1222
if (first instanceof AST_If) {
1223
if (first.body instanceof AST_Break
1224
&& compressor.loopcontrol_target(first.body.label) === self) {
1225
if (self.condition) {
1226
self.condition = make_node(AST_Binary, self.condition, {
1227
left: self.condition,
1228
operator: "&&",
1229
right: first.condition.negate(compressor),
1230
});
1231
} else {
1232
self.condition = first.condition.negate(compressor);
1233
}
1234
drop_it(first.alternative);
1235
}
1236
else if (first.alternative instanceof AST_Break
1237
&& compressor.loopcontrol_target(first.alternative.label) === self) {
1238
if (self.condition) {
1239
self.condition = make_node(AST_Binary, self.condition, {
1240
left: self.condition,
1241
operator: "&&",
1242
right: first.condition,
1243
});
1244
} else {
1245
self.condition = first.condition;
1246
}
1247
drop_it(first.body);
1248
}
1249
}
1250
};
1251
1252
OPT(AST_While, function(self, compressor) {
1253
if (!compressor.option("loops")) return self;
1254
self = AST_DWLoop.prototype.optimize.call(self, compressor);
1255
if (self instanceof AST_While) {
1256
if_break_in_loop(self, compressor);
1257
self = make_node(AST_For, self, self).transform(compressor);
1258
}
1259
return self;
1260
});
1261
1262
OPT(AST_For, function(self, compressor){
1263
var cond = self.condition;
1264
if (cond) {
1265
cond = cond.evaluate(compressor);
1266
self.condition = cond[0];
1267
}
1268
if (!compressor.option("loops")) return self;
1269
if (cond) {
1270
if (cond.length > 1 && !cond[1]) {
1271
if (compressor.option("dead_code")) {
1272
var a = [];
1273
if (self.init instanceof AST_Statement) {
1274
a.push(self.init);
1275
}
1276
else if (self.init) {
1277
a.push(make_node(AST_SimpleStatement, self.init, {
1278
body: self.init
1279
}));
1280
}
1281
extract_declarations_from_unreachable_code(compressor, self.body, a);
1282
return make_node(AST_BlockStatement, self, { body: a });
1283
}
1284
}
1285
}
1286
if_break_in_loop(self, compressor);
1287
return self;
1288
});
1289
1290
OPT(AST_If, function(self, compressor){
1291
if (!compressor.option("conditionals")) return self;
1292
// if condition can be statically determined, warn and drop
1293
// one of the blocks. note, statically determined implies
1294
// “has no side effects”; also it doesn't work for cases like
1295
// `x && true`, though it probably should.
1296
var cond = self.condition.evaluate(compressor);
1297
self.condition = cond[0];
1298
if (cond.length > 1) {
1299
if (cond[1]) {
1300
compressor.warn("Condition always true [{file}:{line},{col}]", self.condition.start);
1301
if (compressor.option("dead_code")) {
1302
var a = [];
1303
if (self.alternative) {
1304
extract_declarations_from_unreachable_code(compressor, self.alternative, a);
1305
}
1306
a.push(self.body);
1307
return make_node(AST_BlockStatement, self, { body: a }).transform(compressor);
1308
}
1309
} else {
1310
compressor.warn("Condition always false [{file}:{line},{col}]", self.condition.start);
1311
if (compressor.option("dead_code")) {
1312
var a = [];
1313
extract_declarations_from_unreachable_code(compressor, self.body, a);
1314
if (self.alternative) a.push(self.alternative);
1315
return make_node(AST_BlockStatement, self, { body: a }).transform(compressor);
1316
}
1317
}
1318
}
1319
if (is_empty(self.alternative)) self.alternative = null;
1320
var negated = self.condition.negate(compressor);
1321
var negated_is_best = best_of(self.condition, negated) === negated;
1322
if (self.alternative && negated_is_best) {
1323
negated_is_best = false; // because we already do the switch here.
1324
self.condition = negated;
1325
var tmp = self.body;
1326
self.body = self.alternative || make_node(AST_EmptyStatement);
1327
self.alternative = tmp;
1328
}
1329
if (is_empty(self.body) && is_empty(self.alternative)) {
1330
return make_node(AST_SimpleStatement, self.condition, {
1331
body: self.condition
1332
}).transform(compressor);
1333
}
1334
if (self.body instanceof AST_SimpleStatement
1335
&& self.alternative instanceof AST_SimpleStatement) {
1336
return make_node(AST_SimpleStatement, self, {
1337
body: make_node(AST_Conditional, self, {
1338
condition : self.condition,
1339
consequent : self.body.body,
1340
alternative : self.alternative.body
1341
})
1342
}).transform(compressor);
1343
}
1344
if (is_empty(self.alternative) && self.body instanceof AST_SimpleStatement) {
1345
if (negated_is_best) return make_node(AST_SimpleStatement, self, {
1346
body: make_node(AST_Binary, self, {
1347
operator : "||",
1348
left : negated,
1349
right : self.body.body
1350
})
1351
}).transform(compressor);
1352
return make_node(AST_SimpleStatement, self, {
1353
body: make_node(AST_Binary, self, {
1354
operator : "&&",
1355
left : self.condition,
1356
right : self.body.body
1357
})
1358
}).transform(compressor);
1359
}
1360
if (self.body instanceof AST_EmptyStatement
1361
&& self.alternative
1362
&& self.alternative instanceof AST_SimpleStatement) {
1363
return make_node(AST_SimpleStatement, self, {
1364
body: make_node(AST_Binary, self, {
1365
operator : "||",
1366
left : self.condition,
1367
right : self.alternative.body
1368
})
1369
}).transform(compressor);
1370
}
1371
if (self.body instanceof AST_Exit
1372
&& self.alternative instanceof AST_Exit
1373
&& self.body.TYPE == self.alternative.TYPE) {
1374
return make_node(self.body.CTOR, self, {
1375
value: make_node(AST_Conditional, self, {
1376
condition : self.condition,
1377
consequent : self.body.value || make_node(AST_Undefined, self.body).optimize(compressor),
1378
alternative : self.alternative.value || make_node(AST_Undefined, self.alternative).optimize(compressor)
1379
})
1380
}).transform(compressor);
1381
}
1382
if (self.body instanceof AST_If
1383
&& !self.body.alternative
1384
&& !self.alternative) {
1385
self.condition = make_node(AST_Binary, self.condition, {
1386
operator: "&&",
1387
left: self.condition,
1388
right: self.body.condition
1389
}).transform(compressor);
1390
self.body = self.body.body;
1391
}
1392
if (aborts(self.body)) {
1393
if (self.alternative) {
1394
var alt = self.alternative;
1395
self.alternative = null;
1396
return make_node(AST_BlockStatement, self, {
1397
body: [ self, alt ]
1398
}).transform(compressor);
1399
}
1400
}
1401
if (aborts(self.alternative)) {
1402
var body = self.body;
1403
self.body = self.alternative;
1404
self.condition = negated_is_best ? negated : self.condition.negate(compressor);
1405
self.alternative = null;
1406
return make_node(AST_BlockStatement, self, {
1407
body: [ self, body ]
1408
}).transform(compressor);
1409
}
1410
return self;
1411
});
1412
1413
OPT(AST_Switch, function(self, compressor){
1414
if (self.body.length == 0 && compressor.option("conditionals")) {
1415
return make_node(AST_SimpleStatement, self, {
1416
body: self.expression
1417
}).transform(compressor);
1418
}
1419
var last_branch = self.body[self.body.length - 1];
1420
if (last_branch) {
1421
var stat = last_branch.body[last_branch.body.length - 1]; // last statement
1422
if (stat instanceof AST_Break && loop_body(compressor.loopcontrol_target(stat.label)) === self)
1423
last_branch.body.pop();
1424
}
1425
var exp = self.expression.evaluate(compressor);
1426
out: if (exp.length == 2) try {
1427
// constant expression
1428
self.expression = exp[0];
1429
if (!compressor.option("dead_code")) break out;
1430
var value = exp[1];
1431
var in_if = false;
1432
var in_block = false;
1433
var started = false;
1434
var stopped = false;
1435
var ruined = false;
1436
var tt = new TreeTransformer(function(node, descend, in_list){
1437
if (node instanceof AST_Lambda || node instanceof AST_SimpleStatement) {
1438
// no need to descend these node types
1439
return node;
1440
}
1441
else if (node instanceof AST_Switch && node === self) {
1442
node = node.clone();
1443
descend(node, this);
1444
return ruined ? node : make_node(AST_BlockStatement, node, {
1445
body: node.body.reduce(function(a, branch){
1446
return a.concat(branch.body);
1447
}, [])
1448
}).transform(compressor);
1449
}
1450
else if (node instanceof AST_If || node instanceof AST_Try) {
1451
var save = in_if;
1452
in_if = !in_block;
1453
descend(node, this);
1454
in_if = save;
1455
return node;
1456
}
1457
else if (node instanceof AST_StatementWithBody || node instanceof AST_Switch) {
1458
var save = in_block;
1459
in_block = true;
1460
descend(node, this);
1461
in_block = save;
1462
return node;
1463
}
1464
else if (node instanceof AST_Break && this.loopcontrol_target(node.label) === self) {
1465
if (in_if) {
1466
ruined = true;
1467
return node;
1468
}
1469
if (in_block) return node;
1470
stopped = true;
1471
return in_list ? MAP.skip : make_node(AST_EmptyStatement, node);
1472
}
1473
else if (node instanceof AST_SwitchBranch && this.parent() === self) {
1474
if (stopped) return MAP.skip;
1475
if (node instanceof AST_Case) {
1476
var exp = node.expression.evaluate(compressor);
1477
if (exp.length < 2) {
1478
// got a case with non-constant expression, baling out
1479
throw self;
1480
}
1481
if (exp[1] === value || started) {
1482
started = true;
1483
if (aborts(node)) stopped = true;
1484
descend(node, this);
1485
return node;
1486
}
1487
return MAP.skip;
1488
}
1489
descend(node, this);
1490
return node;
1491
}
1492
});
1493
tt.stack = compressor.stack.slice(); // so that's able to see parent nodes
1494
self = self.transform(tt);
1495
} catch(ex) {
1496
if (ex !== self) throw ex;
1497
}
1498
return self;
1499
});
1500
1501
OPT(AST_Case, function(self, compressor){
1502
self.body = tighten_body(self.body, compressor);
1503
return self;
1504
});
1505
1506
OPT(AST_Try, function(self, compressor){
1507
self.body = tighten_body(self.body, compressor);
1508
return self;
1509
});
1510
1511
AST_Definitions.DEFMETHOD("remove_initializers", function(){
1512
this.definitions.forEach(function(def){ def.value = null });
1513
});
1514
1515
AST_Definitions.DEFMETHOD("to_assignments", function(){
1516
var assignments = this.definitions.reduce(function(a, def){
1517
if (def.value) {
1518
var name = make_node(AST_SymbolRef, def.name, def.name);
1519
a.push(make_node(AST_Assign, def, {
1520
operator : "=",
1521
left : name,
1522
right : def.value
1523
}));
1524
}
1525
return a;
1526
}, []);
1527
if (assignments.length == 0) return null;
1528
return AST_Seq.from_array(assignments);
1529
});
1530
1531
OPT(AST_Definitions, function(self, compressor){
1532
if (self.definitions.length == 0)
1533
return make_node(AST_EmptyStatement, self);
1534
return self;
1535
});
1536
1537
OPT(AST_Function, function(self, compressor){
1538
self = AST_Lambda.prototype.optimize.call(self, compressor);
1539
if (compressor.option("unused")) {
1540
if (self.name && self.name.unreferenced()) {
1541
self.name = null;
1542
}
1543
}
1544
return self;
1545
});
1546
1547
OPT(AST_Call, function(self, compressor){
1548
if (compressor.option("unsafe")) {
1549
var exp = self.expression;
1550
if (exp instanceof AST_SymbolRef && exp.undeclared()) {
1551
switch (exp.name) {
1552
case "Array":
1553
if (self.args.length != 1) {
1554
return make_node(AST_Array, self, {
1555
elements: self.args
1556
});
1557
}
1558
break;
1559
case "Object":
1560
if (self.args.length == 0) {
1561
return make_node(AST_Object, self, {
1562
properties: []
1563
});
1564
}
1565
break;
1566
case "String":
1567
if (self.args.length == 0) return make_node(AST_String, self, {
1568
value: ""
1569
});
1570
return make_node(AST_Binary, self, {
1571
left: self.args[0],
1572
operator: "+",
1573
right: make_node(AST_String, self, { value: "" })
1574
});
1575
}
1576
}
1577
else if (exp instanceof AST_Dot && exp.property == "toString" && self.args.length == 0) {
1578
return make_node(AST_Binary, self, {
1579
left: make_node(AST_String, self, { value: "" }),
1580
operator: "+",
1581
right: exp.expression
1582
}).transform(compressor);
1583
}
1584
}
1585
if (compressor.option("side_effects")) {
1586
if (self.expression instanceof AST_Function
1587
&& self.args.length == 0
1588
&& !AST_Block.prototype.has_side_effects.call(self.expression)) {
1589
return make_node(AST_Undefined, self).transform(compressor);
1590
}
1591
}
1592
return self;
1593
});
1594
1595
OPT(AST_New, function(self, compressor){
1596
if (compressor.option("unsafe")) {
1597
var exp = self.expression;
1598
if (exp instanceof AST_SymbolRef && exp.undeclared()) {
1599
switch (exp.name) {
1600
case "Object":
1601
case "RegExp":
1602
case "Function":
1603
case "Error":
1604
case "Array":
1605
return make_node(AST_Call, self, self).transform(compressor);
1606
}
1607
}
1608
}
1609
return self;
1610
});
1611
1612
OPT(AST_Seq, function(self, compressor){
1613
if (!compressor.option("side_effects"))
1614
return self;
1615
if (!self.car.has_side_effects()) {
1616
// we shouldn't compress (1,eval)(something) to
1617
// eval(something) because that changes the meaning of
1618
// eval (becomes lexical instead of global).
1619
var p;
1620
if (!(self.cdr instanceof AST_SymbolRef
1621
&& self.cdr.name == "eval"
1622
&& self.cdr.undeclared()
1623
&& (p = compressor.parent()) instanceof AST_Call
1624
&& p.expression === self)) {
1625
return self.cdr;
1626
}
1627
}
1628
if (compressor.option("cascade")) {
1629
if (self.car instanceof AST_Assign
1630
&& !self.car.left.has_side_effects()
1631
&& self.car.left.equivalent_to(self.cdr)) {
1632
return self.car;
1633
}
1634
if (!self.car.has_side_effects()
1635
&& !self.cdr.has_side_effects()
1636
&& self.car.equivalent_to(self.cdr)) {
1637
return self.car;
1638
}
1639
}
1640
return self;
1641
});
1642
1643
AST_Unary.DEFMETHOD("lift_sequences", function(compressor){
1644
if (compressor.option("sequences")) {
1645
if (this.expression instanceof AST_Seq) {
1646
var seq = this.expression;
1647
var x = seq.to_array();
1648
this.expression = x.pop();
1649
x.push(this);
1650
seq = AST_Seq.from_array(x).transform(compressor);
1651
return seq;
1652
}
1653
}
1654
return this;
1655
});
1656
1657
OPT(AST_UnaryPostfix, function(self, compressor){
1658
return self.lift_sequences(compressor);
1659
});
1660
1661
OPT(AST_UnaryPrefix, function(self, compressor){
1662
self = self.lift_sequences(compressor);
1663
var e = self.expression;
1664
if (compressor.option("booleans") && compressor.in_boolean_context()) {
1665
switch (self.operator) {
1666
case "!":
1667
if (e instanceof AST_UnaryPrefix && e.operator == "!") {
1668
// !!foo ==> foo, if we're in boolean context
1669
return e.expression;
1670
}
1671
break;
1672
case "typeof":
1673
// typeof always returns a non-empty string, thus it's
1674
// always true in booleans
1675
compressor.warn("Boolean expression always true [{file}:{line},{col}]", self.start);
1676
return make_node(AST_True, self);
1677
}
1678
if (e instanceof AST_Binary && self.operator == "!") {
1679
self = best_of(self, e.negate(compressor));
1680
}
1681
}
1682
return self.evaluate(compressor)[0];
1683
});
1684
1685
AST_Binary.DEFMETHOD("lift_sequences", function(compressor){
1686
if (compressor.option("sequences")) {
1687
if (this.left instanceof AST_Seq) {
1688
var seq = this.left;
1689
var x = seq.to_array();
1690
this.left = x.pop();
1691
x.push(this);
1692
seq = AST_Seq.from_array(x).transform(compressor);
1693
return seq;
1694
}
1695
if (this.right instanceof AST_Seq
1696
&& !(this.operator == "||" || this.operator == "&&")
1697
&& !this.left.has_side_effects()) {
1698
var seq = this.right;
1699
var x = seq.to_array();
1700
this.right = x.pop();
1701
x.push(this);
1702
seq = AST_Seq.from_array(x).transform(compressor);
1703
return seq;
1704
}
1705
}
1706
return this;
1707
});
1708
1709
var commutativeOperators = makePredicate("== === != !== * & | ^");
1710
1711
OPT(AST_Binary, function(self, compressor){
1712
function reverse(op) {
1713
if (!(self.left.has_side_effects() && self.right.has_side_effects())) {
1714
if (op) self.operator = op;
1715
var tmp = self.left;
1716
self.left = self.right;
1717
self.right = tmp;
1718
}
1719
};
1720
if (commutativeOperators(self.operator)) {
1721
if (self.right instanceof AST_Constant
1722
&& !(self.left instanceof AST_Constant)) {
1723
reverse();
1724
}
1725
}
1726
self = self.lift_sequences(compressor);
1727
if (compressor.option("comparisons")) switch (self.operator) {
1728
case "===":
1729
case "!==":
1730
if ((self.left.is_string(compressor) && self.right.is_string(compressor)) ||
1731
(self.left.is_boolean() && self.right.is_boolean())) {
1732
self.operator = self.operator.substr(0, 2);
1733
}
1734
// XXX: intentionally falling down to the next case
1735
case "==":
1736
case "!=":
1737
if (self.left instanceof AST_String
1738
&& self.left.value == "undefined"
1739
&& self.right instanceof AST_UnaryPrefix
1740
&& self.right.operator == "typeof"
1741
&& compressor.option("unsafe")) {
1742
if (!(self.right.expression instanceof AST_SymbolRef)
1743
|| !self.right.expression.undeclared()) {
1744
self.left = self.right.expression;
1745
self.right = make_node(AST_Undefined, self.left).optimize(compressor);
1746
if (self.operator.length == 2) self.operator += "=";
1747
}
1748
}
1749
break;
1750
}
1751
if (compressor.option("booleans") && compressor.in_boolean_context()) switch (self.operator) {
1752
case "&&":
1753
var ll = self.left.evaluate(compressor);
1754
var rr = self.right.evaluate(compressor);
1755
if ((ll.length > 1 && !ll[1]) || (rr.length > 1 && !rr[1])) {
1756
compressor.warn("Boolean && always false [{file}:{line},{col}]", self.start);
1757
return make_node(AST_False, self);
1758
}
1759
if (ll.length > 1 && ll[1]) {
1760
return rr[0];
1761
}
1762
if (rr.length > 1 && rr[1]) {
1763
return ll[0];
1764
}
1765
break;
1766
case "||":
1767
var ll = self.left.evaluate(compressor);
1768
var rr = self.right.evaluate(compressor);
1769
if ((ll.length > 1 && ll[1]) || (rr.length > 1 && rr[1])) {
1770
compressor.warn("Boolean || always true [{file}:{line},{col}]", self.start);
1771
return make_node(AST_True, self);
1772
}
1773
if (ll.length > 1 && !ll[1]) {
1774
return rr[0];
1775
}
1776
if (rr.length > 1 && !rr[1]) {
1777
return ll[0];
1778
}
1779
break;
1780
case "+":
1781
var ll = self.left.evaluate(compressor);
1782
var rr = self.right.evaluate(compressor);
1783
if ((ll.length > 1 && ll[0] instanceof AST_String && ll[1]) ||
1784
(rr.length > 1 && rr[0] instanceof AST_String && rr[1])) {
1785
compressor.warn("+ in boolean context always true [{file}:{line},{col}]", self.start);
1786
return make_node(AST_True, self);
1787
}
1788
break;
1789
}
1790
var exp = self.evaluate(compressor);
1791
if (exp.length > 1) {
1792
if (best_of(exp[0], self) !== self)
1793
return exp[0];
1794
}
1795
if (compressor.option("comparisons")) {
1796
if (!(compressor.parent() instanceof AST_Binary)
1797
|| compressor.parent() instanceof AST_Assign) {
1798
var negated = make_node(AST_UnaryPrefix, self, {
1799
operator: "!",
1800
expression: self.negate(compressor)
1801
});
1802
self = best_of(self, negated);
1803
}
1804
switch (self.operator) {
1805
case "<": reverse(">"); break;
1806
case "<=": reverse(">="); break;
1807
}
1808
}
1809
if (self.operator == "+" && self.right instanceof AST_String
1810
&& self.right.getValue() === "" && self.left instanceof AST_Binary
1811
&& self.left.operator == "+" && self.left.is_string(compressor)) {
1812
return self.left;
1813
}
1814
return self;
1815
});
1816
1817
OPT(AST_SymbolRef, function(self, compressor){
1818
if (self.undeclared()) {
1819
var defines = compressor.option("global_defs");
1820
if (defines && defines.hasOwnProperty(self.name)) {
1821
return make_node_from_constant(compressor, defines[self.name], self);
1822
}
1823
switch (self.name) {
1824
case "undefined":
1825
return make_node(AST_Undefined, self);
1826
case "NaN":
1827
return make_node(AST_NaN, self);
1828
case "Infinity":
1829
return make_node(AST_Infinity, self);
1830
}
1831
}
1832
return self;
1833
});
1834
1835
OPT(AST_Undefined, function(self, compressor){
1836
if (compressor.option("unsafe")) {
1837
var scope = compressor.find_parent(AST_Scope);
1838
var undef = scope.find_variable("undefined");
1839
if (undef) {
1840
var ref = make_node(AST_SymbolRef, self, {
1841
name : "undefined",
1842
scope : scope,
1843
thedef : undef
1844
});
1845
ref.reference();
1846
return ref;
1847
}
1848
}
1849
return self;
1850
});
1851
1852
var ASSIGN_OPS = [ '+', '-', '/', '*', '%', '>>', '<<', '>>>', '|', '^', '&' ];
1853
OPT(AST_Assign, function(self, compressor){
1854
self = self.lift_sequences(compressor);
1855
if (self.operator == "="
1856
&& self.left instanceof AST_SymbolRef
1857
&& self.right instanceof AST_Binary
1858
&& self.right.left instanceof AST_SymbolRef
1859
&& self.right.left.name == self.left.name
1860
&& member(self.right.operator, ASSIGN_OPS)) {
1861
self.operator = self.right.operator + "=";
1862
self.right = self.right.right;
1863
}
1864
return self;
1865
});
1866
1867
OPT(AST_Conditional, function(self, compressor){
1868
if (!compressor.option("conditionals")) return self;
1869
if (self.condition instanceof AST_Seq) {
1870
var car = self.condition.car;
1871
self.condition = self.condition.cdr;
1872
return AST_Seq.cons(car, self);
1873
}
1874
var cond = self.condition.evaluate(compressor);
1875
if (cond.length > 1) {
1876
if (cond[1]) {
1877
compressor.warn("Condition always true [{file}:{line},{col}]", self.start);
1878
return self.consequent;
1879
} else {
1880
compressor.warn("Condition always false [{file}:{line},{col}]", self.start);
1881
return self.alternative;
1882
}
1883
}
1884
var negated = cond[0].negate(compressor);
1885
if (best_of(cond[0], negated) === negated) {
1886
self = make_node(AST_Conditional, self, {
1887
condition: negated,
1888
consequent: self.alternative,
1889
alternative: self.consequent
1890
});
1891
}
1892
var consequent = self.consequent;
1893
var alternative = self.alternative;
1894
if (consequent instanceof AST_Assign
1895
&& alternative instanceof AST_Assign
1896
&& consequent.operator == alternative.operator
1897
&& consequent.left.equivalent_to(alternative.left)
1898
) {
1899
/*
1900
* Stuff like this:
1901
* if (foo) exp = something; else exp = something_else;
1902
* ==>
1903
* exp = foo ? something : something_else;
1904
*/
1905
self = make_node(AST_Assign, self, {
1906
operator: consequent.operator,
1907
left: consequent.left,
1908
right: make_node(AST_Conditional, self, {
1909
condition: self.condition,
1910
consequent: consequent.right,
1911
alternative: alternative.right
1912
})
1913
});
1914
}
1915
return self;
1916
});
1917
1918
OPT(AST_Boolean, function(self, compressor){
1919
if (compressor.option("booleans")) {
1920
var p = compressor.parent();
1921
if (p instanceof AST_Binary && (p.operator == "=="
1922
|| p.operator == "!=")) {
1923
compressor.warn("Non-strict equality against boolean: {operator} {value} [{file}:{line},{col}]", {
1924
operator : p.operator,
1925
value : self.value,
1926
file : p.start.file,
1927
line : p.start.line,
1928
col : p.start.col,
1929
});
1930
return make_node(AST_Number, self, {
1931
value: +self.value
1932
});
1933
}
1934
return make_node(AST_UnaryPrefix, self, {
1935
operator: "!",
1936
expression: make_node(AST_Number, self, {
1937
value: 1 - self.value
1938
})
1939
});
1940
}
1941
return self;
1942
});
1943
1944
OPT(AST_Sub, function(self, compressor){
1945
var prop = self.property;
1946
if (prop instanceof AST_String && compressor.option("properties")) {
1947
prop = prop.getValue();
1948
if (is_identifier(prop)) {
1949
return make_node(AST_Dot, self, {
1950
expression : self.expression,
1951
property : prop
1952
});
1953
}
1954
}
1955
return self;
1956
});
1957
1958
function literals_in_boolean_context(self, compressor) {
1959
if (compressor.option("booleans") && compressor.in_boolean_context()) {
1960
return make_node(AST_True, self);
1961
}
1962
return self;
1963
};
1964
OPT(AST_Array, literals_in_boolean_context);
1965
OPT(AST_Object, literals_in_boolean_context);
1966
OPT(AST_RegExp, literals_in_boolean_context);
1967
1968
})();
1969
1970