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