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