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 OutputStream(options) {
47
48
options = defaults(options, {
49
indent_start : 0,
50
indent_level : 4,
51
quote_keys : false,
52
space_colon : true,
53
ascii_only : false,
54
unescape_regexps : false,
55
inline_script : false,
56
width : 80,
57
max_line_len : 32000,
58
beautify : false,
59
source_map : null,
60
bracketize : false,
61
semicolons : true,
62
comments : false,
63
preserve_line : false,
64
screw_ie8 : false,
65
preamble : null,
66
quote_style : 0
67
}, true);
68
69
var indentation = 0;
70
var current_col = 0;
71
var current_line = 1;
72
var current_pos = 0;
73
var OUTPUT = "";
74
75
function to_ascii(str, identifier) {
76
return str.replace(/[\u0080-\uffff]/g, function(ch) {
77
var code = ch.charCodeAt(0).toString(16);
78
if (code.length <= 2 && !identifier) {
79
while (code.length < 2) code = "0" + code;
80
return "\\x" + code;
81
} else {
82
while (code.length < 4) code = "0" + code;
83
return "\\u" + code;
84
}
85
});
86
};
87
88
function make_string(str, quote) {
89
var dq = 0, sq = 0;
90
str = str.replace(/[\\\b\f\n\r\t\x22\x27\u2028\u2029\0\ufeff]/g, function(s){
91
switch (s) {
92
case "\\": return "\\\\";
93
case "\b": return "\\b";
94
case "\f": return "\\f";
95
case "\n": return "\\n";
96
case "\r": return "\\r";
97
case "\u2028": return "\\u2028";
98
case "\u2029": return "\\u2029";
99
case '"': ++dq; return '"';
100
case "'": ++sq; return "'";
101
case "\0": return "\\x00";
102
case "\ufeff": return "\\ufeff";
103
}
104
return s;
105
});
106
function quote_single() {
107
return "'" + str.replace(/\x27/g, "\\'") + "'";
108
}
109
function quote_double() {
110
return '"' + str.replace(/\x22/g, '\\"') + '"';
111
}
112
if (options.ascii_only) str = to_ascii(str);
113
switch (options.quote_style) {
114
case 1:
115
return quote_single();
116
case 2:
117
return quote_double();
118
case 3:
119
return quote == "'" ? quote_single() : quote_double();
120
default:
121
return dq > sq ? quote_single() : quote_double();
122
}
123
};
124
125
function encode_string(str, quote) {
126
var ret = make_string(str, quote);
127
if (options.inline_script)
128
ret = ret.replace(/<\x2fscript([>\/\t\n\f\r ])/gi, "<\\/script$1");
129
return ret;
130
};
131
132
function make_name(name) {
133
name = name.toString();
134
if (options.ascii_only)
135
name = to_ascii(name, true);
136
return name;
137
};
138
139
function make_indent(back) {
140
return repeat_string(" ", options.indent_start + indentation - back * options.indent_level);
141
};
142
143
/* -----[ beautification/minification ]----- */
144
145
var might_need_space = false;
146
var might_need_semicolon = false;
147
var last = null;
148
149
function last_char() {
150
return last.charAt(last.length - 1);
151
};
152
153
function maybe_newline() {
154
if (options.max_line_len && current_col > options.max_line_len)
155
print("\n");
156
};
157
158
var requireSemicolonChars = makePredicate("( [ + * / - , .");
159
160
function print(str) {
161
str = String(str);
162
var ch = str.charAt(0);
163
if (might_need_semicolon) {
164
if ((!ch || ";}".indexOf(ch) < 0) && !/[;]$/.test(last)) {
165
if (options.semicolons || requireSemicolonChars(ch)) {
166
OUTPUT += ";";
167
current_col++;
168
current_pos++;
169
} else {
170
OUTPUT += "\n";
171
current_pos++;
172
current_line++;
173
current_col = 0;
174
}
175
if (!options.beautify)
176
might_need_space = false;
177
}
178
might_need_semicolon = false;
179
}
180
181
if (!options.beautify && options.preserve_line && stack[stack.length - 1]) {
182
var target_line = stack[stack.length - 1].start.line;
183
while (current_line < target_line) {
184
OUTPUT += "\n";
185
current_pos++;
186
current_line++;
187
current_col = 0;
188
might_need_space = false;
189
}
190
}
191
192
if (might_need_space) {
193
var prev = last_char();
194
if ((is_identifier_char(prev)
195
&& (is_identifier_char(ch) || ch == "\\"))
196
|| (/^[\+\-\/]$/.test(ch) && ch == prev))
197
{
198
OUTPUT += " ";
199
current_col++;
200
current_pos++;
201
}
202
might_need_space = false;
203
}
204
var a = str.split(/\r?\n/), n = a.length - 1;
205
current_line += n;
206
if (n == 0) {
207
current_col += a[n].length;
208
} else {
209
current_col = a[n].length;
210
}
211
current_pos += str.length;
212
last = str;
213
OUTPUT += str;
214
};
215
216
var space = options.beautify ? function() {
217
print(" ");
218
} : function() {
219
might_need_space = true;
220
};
221
222
var indent = options.beautify ? function(half) {
223
if (options.beautify) {
224
print(make_indent(half ? 0.5 : 0));
225
}
226
} : noop;
227
228
var with_indent = options.beautify ? function(col, cont) {
229
if (col === true) col = next_indent();
230
var save_indentation = indentation;
231
indentation = col;
232
var ret = cont();
233
indentation = save_indentation;
234
return ret;
235
} : function(col, cont) { return cont() };
236
237
var newline = options.beautify ? function() {
238
print("\n");
239
} : maybe_newline;
240
241
var semicolon = options.beautify ? function() {
242
print(";");
243
} : function() {
244
might_need_semicolon = true;
245
};
246
247
function force_semicolon() {
248
might_need_semicolon = false;
249
print(";");
250
};
251
252
function next_indent() {
253
return indentation + options.indent_level;
254
};
255
256
function with_block(cont) {
257
var ret;
258
print("{");
259
newline();
260
with_indent(next_indent(), function(){
261
ret = cont();
262
});
263
indent();
264
print("}");
265
return ret;
266
};
267
268
function with_parens(cont) {
269
print("(");
270
//XXX: still nice to have that for argument lists
271
//var ret = with_indent(current_col, cont);
272
var ret = cont();
273
print(")");
274
return ret;
275
};
276
277
function with_square(cont) {
278
print("[");
279
//var ret = with_indent(current_col, cont);
280
var ret = cont();
281
print("]");
282
return ret;
283
};
284
285
function comma() {
286
print(",");
287
space();
288
};
289
290
function colon() {
291
print(":");
292
if (options.space_colon) space();
293
};
294
295
var add_mapping = options.source_map ? function(token, name) {
296
try {
297
if (token) options.source_map.add(
298
token.file || "?",
299
current_line, current_col,
300
token.line, token.col,
301
(!name && token.type == "name") ? token.value : name
302
);
303
} catch(ex) {
304
AST_Node.warn("Couldn't figure out mapping for {file}:{line},{col} → {cline},{ccol} [{name}]", {
305
file: token.file,
306
line: token.line,
307
col: token.col,
308
cline: current_line,
309
ccol: current_col,
310
name: name || ""
311
})
312
}
313
} : noop;
314
315
function get() {
316
return OUTPUT;
317
};
318
319
if (options.preamble) {
320
print(options.preamble.replace(/\r\n?|[\n\u2028\u2029]|\s*$/g, "\n"));
321
}
322
323
var stack = [];
324
return {
325
get : get,
326
toString : get,
327
indent : indent,
328
indentation : function() { return indentation },
329
current_width : function() { return current_col - indentation },
330
should_break : function() { return options.width && this.current_width() >= options.width },
331
newline : newline,
332
print : print,
333
space : space,
334
comma : comma,
335
colon : colon,
336
last : function() { return last },
337
semicolon : semicolon,
338
force_semicolon : force_semicolon,
339
to_ascii : to_ascii,
340
print_name : function(name) { print(make_name(name)) },
341
print_string : function(str, quote) { print(encode_string(str, quote)) },
342
next_indent : next_indent,
343
with_indent : with_indent,
344
with_block : with_block,
345
with_parens : with_parens,
346
with_square : with_square,
347
add_mapping : add_mapping,
348
option : function(opt) { return options[opt] },
349
line : function() { return current_line },
350
col : function() { return current_col },
351
pos : function() { return current_pos },
352
push_node : function(node) { stack.push(node) },
353
pop_node : function() { return stack.pop() },
354
stack : function() { return stack },
355
parent : function(n) {
356
return stack[stack.length - 2 - (n || 0)];
357
}
358
};
359
360
};
361
362
/* -----[ code generators ]----- */
363
364
(function(){
365
366
/* -----[ utils ]----- */
367
368
function DEFPRINT(nodetype, generator) {
369
nodetype.DEFMETHOD("_codegen", generator);
370
};
371
372
AST_Node.DEFMETHOD("print", function(stream, force_parens){
373
var self = this, generator = self._codegen;
374
function doit() {
375
self.add_comments(stream);
376
self.add_source_map(stream);
377
generator(self, stream);
378
}
379
stream.push_node(self);
380
if (force_parens || self.needs_parens(stream)) {
381
stream.with_parens(doit);
382
} else {
383
doit();
384
}
385
stream.pop_node();
386
});
387
388
AST_Node.DEFMETHOD("print_to_string", function(options){
389
var s = OutputStream(options);
390
this.print(s);
391
return s.get();
392
});
393
394
/* -----[ comments ]----- */
395
396
AST_Node.DEFMETHOD("add_comments", function(output){
397
var c = output.option("comments"), self = this;
398
if (c) {
399
var start = self.start;
400
if (start && !start._comments_dumped) {
401
start._comments_dumped = true;
402
var comments = start.comments_before || [];
403
404
// XXX: ugly fix for https://github.com/mishoo/UglifyJS2/issues/112
405
// and https://github.com/mishoo/UglifyJS2/issues/372
406
if (self instanceof AST_Exit && self.value) {
407
self.value.walk(new TreeWalker(function(node){
408
if (node.start && node.start.comments_before) {
409
comments = comments.concat(node.start.comments_before);
410
node.start.comments_before = [];
411
}
412
if (node instanceof AST_Function ||
413
node instanceof AST_Array ||
414
node instanceof AST_Object)
415
{
416
return true; // don't go inside.
417
}
418
}));
419
}
420
421
if (c.test) {
422
comments = comments.filter(function(comment){
423
return c.test(comment.value);
424
});
425
} else if (typeof c == "function") {
426
comments = comments.filter(function(comment){
427
return c(self, comment);
428
});
429
}
430
431
// Keep single line comments after nlb, after nlb
432
if (!output.option("beautify") && comments.length > 0 &&
433
/comment[134]/.test(comments[0].type) &&
434
output.col() !== 0 && comments[0].nlb)
435
{
436
output.print("\n");
437
}
438
439
comments.forEach(function(c){
440
if (/comment[134]/.test(c.type)) {
441
output.print("//" + c.value + "\n");
442
output.indent();
443
}
444
else if (c.type == "comment2") {
445
output.print("/*" + c.value + "*/");
446
if (start.nlb) {
447
output.print("\n");
448
output.indent();
449
} else {
450
output.space();
451
}
452
}
453
});
454
}
455
}
456
});
457
458
/* -----[ PARENTHESES ]----- */
459
460
function PARENS(nodetype, func) {
461
if (Array.isArray(nodetype)) {
462
nodetype.forEach(function(nodetype){
463
PARENS(nodetype, func);
464
});
465
} else {
466
nodetype.DEFMETHOD("needs_parens", func);
467
}
468
};
469
470
PARENS(AST_Node, function(){
471
return false;
472
});
473
474
// a function expression needs parens around it when it's provably
475
// the first token to appear in a statement.
476
PARENS(AST_Function, function(output){
477
return first_in_statement(output);
478
});
479
480
// same goes for an object literal, because otherwise it would be
481
// interpreted as a block of code.
482
PARENS(AST_Object, function(output){
483
return first_in_statement(output);
484
});
485
486
PARENS([ AST_Unary, AST_Undefined ], function(output){
487
var p = output.parent();
488
return p instanceof AST_PropAccess && p.expression === this;
489
});
490
491
PARENS(AST_Seq, function(output){
492
var p = output.parent();
493
return p instanceof AST_Call // (foo, bar)() or foo(1, (2, 3), 4)
494
|| p instanceof AST_Unary // !(foo, bar, baz)
495
|| p instanceof AST_Binary // 1 + (2, 3) + 4 ==> 8
496
|| p instanceof AST_VarDef // var a = (1, 2), b = a + a; ==> b == 4
497
|| p instanceof AST_PropAccess // (1, {foo:2}).foo or (1, {foo:2})["foo"] ==> 2
498
|| p instanceof AST_Array // [ 1, (2, 3), 4 ] ==> [ 1, 3, 4 ]
499
|| p instanceof AST_ObjectProperty // { foo: (1, 2) }.foo ==> 2
500
|| p instanceof AST_Conditional /* (false, true) ? (a = 10, b = 20) : (c = 30)
501
* ==> 20 (side effect, set a := 10 and b := 20) */
502
;
503
});
504
505
PARENS(AST_Binary, function(output){
506
var p = output.parent();
507
// (foo && bar)()
508
if (p instanceof AST_Call && p.expression === this)
509
return true;
510
// typeof (foo && bar)
511
if (p instanceof AST_Unary)
512
return true;
513
// (foo && bar)["prop"], (foo && bar).prop
514
if (p instanceof AST_PropAccess && p.expression === this)
515
return true;
516
// this deals with precedence: 3 * (2 + 1)
517
if (p instanceof AST_Binary) {
518
var po = p.operator, pp = PRECEDENCE[po];
519
var so = this.operator, sp = PRECEDENCE[so];
520
if (pp > sp
521
|| (pp == sp
522
&& this === p.right)) {
523
return true;
524
}
525
}
526
});
527
528
PARENS(AST_PropAccess, function(output){
529
var p = output.parent();
530
if (p instanceof AST_New && p.expression === this) {
531
// i.e. new (foo.bar().baz)
532
//
533
// if there's one call into this subtree, then we need
534
// parens around it too, otherwise the call will be
535
// interpreted as passing the arguments to the upper New
536
// expression.
537
try {
538
this.walk(new TreeWalker(function(node){
539
if (node instanceof AST_Call) throw p;
540
}));
541
} catch(ex) {
542
if (ex !== p) throw ex;
543
return true;
544
}
545
}
546
});
547
548
PARENS(AST_Call, function(output){
549
var p = output.parent(), p1;
550
if (p instanceof AST_New && p.expression === this)
551
return true;
552
553
// workaround for Safari bug.
554
// https://bugs.webkit.org/show_bug.cgi?id=123506
555
return this.expression instanceof AST_Function
556
&& p instanceof AST_PropAccess
557
&& p.expression === this
558
&& (p1 = output.parent(1)) instanceof AST_Assign
559
&& p1.left === p;
560
});
561
562
PARENS(AST_New, function(output){
563
var p = output.parent();
564
if (no_constructor_parens(this, output)
565
&& (p instanceof AST_PropAccess // (new Date).getTime(), (new Date)["getTime"]()
566
|| p instanceof AST_Call && p.expression === this)) // (new foo)(bar)
567
return true;
568
});
569
570
PARENS(AST_Number, function(output){
571
var p = output.parent();
572
if (this.getValue() < 0 && p instanceof AST_PropAccess && p.expression === this)
573
return true;
574
});
575
576
PARENS([ AST_Assign, AST_Conditional ], function (output){
577
var p = output.parent();
578
// !(a = false) → true
579
if (p instanceof AST_Unary)
580
return true;
581
// 1 + (a = 2) + 3 → 6, side effect setting a = 2
582
if (p instanceof AST_Binary && !(p instanceof AST_Assign))
583
return true;
584
// (a = func)() —or— new (a = Object)()
585
if (p instanceof AST_Call && p.expression === this)
586
return true;
587
// (a = foo) ? bar : baz
588
if (p instanceof AST_Conditional && p.condition === this)
589
return true;
590
// (a = foo)["prop"] —or— (a = foo).prop
591
if (p instanceof AST_PropAccess && p.expression === this)
592
return true;
593
});
594
595
/* -----[ PRINTERS ]----- */
596
597
DEFPRINT(AST_Directive, function(self, output){
598
output.print_string(self.value, self.quote);
599
output.semicolon();
600
});
601
DEFPRINT(AST_Debugger, function(self, output){
602
output.print("debugger");
603
output.semicolon();
604
});
605
606
/* -----[ statements ]----- */
607
608
function display_body(body, is_toplevel, output) {
609
var last = body.length - 1;
610
body.forEach(function(stmt, i){
611
if (!(stmt instanceof AST_EmptyStatement)) {
612
output.indent();
613
stmt.print(output);
614
if (!(i == last && is_toplevel)) {
615
output.newline();
616
if (is_toplevel) output.newline();
617
}
618
}
619
});
620
};
621
622
AST_StatementWithBody.DEFMETHOD("_do_print_body", function(output){
623
force_statement(this.body, output);
624
});
625
626
DEFPRINT(AST_Statement, function(self, output){
627
self.body.print(output);
628
output.semicolon();
629
});
630
DEFPRINT(AST_Toplevel, function(self, output){
631
display_body(self.body, true, output);
632
output.print("");
633
});
634
DEFPRINT(AST_LabeledStatement, function(self, output){
635
self.label.print(output);
636
output.colon();
637
self.body.print(output);
638
});
639
DEFPRINT(AST_SimpleStatement, function(self, output){
640
self.body.print(output);
641
output.semicolon();
642
});
643
function print_bracketed(body, output) {
644
if (body.length > 0) output.with_block(function(){
645
display_body(body, false, output);
646
});
647
else output.print("{}");
648
};
649
DEFPRINT(AST_BlockStatement, function(self, output){
650
print_bracketed(self.body, output);
651
});
652
DEFPRINT(AST_EmptyStatement, function(self, output){
653
output.semicolon();
654
});
655
DEFPRINT(AST_Do, function(self, output){
656
output.print("do");
657
output.space();
658
self._do_print_body(output);
659
output.space();
660
output.print("while");
661
output.space();
662
output.with_parens(function(){
663
self.condition.print(output);
664
});
665
output.semicolon();
666
});
667
DEFPRINT(AST_While, function(self, output){
668
output.print("while");
669
output.space();
670
output.with_parens(function(){
671
self.condition.print(output);
672
});
673
output.space();
674
self._do_print_body(output);
675
});
676
DEFPRINT(AST_For, function(self, output){
677
output.print("for");
678
output.space();
679
output.with_parens(function(){
680
if (self.init && !(self.init instanceof AST_EmptyStatement)) {
681
if (self.init instanceof AST_Definitions) {
682
self.init.print(output);
683
} else {
684
parenthesize_for_noin(self.init, output, true);
685
}
686
output.print(";");
687
output.space();
688
} else {
689
output.print(";");
690
}
691
if (self.condition) {
692
self.condition.print(output);
693
output.print(";");
694
output.space();
695
} else {
696
output.print(";");
697
}
698
if (self.step) {
699
self.step.print(output);
700
}
701
});
702
output.space();
703
self._do_print_body(output);
704
});
705
DEFPRINT(AST_ForIn, function(self, output){
706
output.print("for");
707
output.space();
708
output.with_parens(function(){
709
self.init.print(output);
710
output.space();
711
output.print("in");
712
output.space();
713
self.object.print(output);
714
});
715
output.space();
716
self._do_print_body(output);
717
});
718
DEFPRINT(AST_With, function(self, output){
719
output.print("with");
720
output.space();
721
output.with_parens(function(){
722
self.expression.print(output);
723
});
724
output.space();
725
self._do_print_body(output);
726
});
727
728
/* -----[ functions ]----- */
729
AST_Lambda.DEFMETHOD("_do_print", function(output, nokeyword){
730
var self = this;
731
if (!nokeyword) {
732
output.print("function");
733
}
734
if (self.name) {
735
output.space();
736
self.name.print(output);
737
}
738
output.with_parens(function(){
739
self.argnames.forEach(function(arg, i){
740
if (i) output.comma();
741
arg.print(output);
742
});
743
});
744
output.space();
745
print_bracketed(self.body, output);
746
});
747
DEFPRINT(AST_Lambda, function(self, output){
748
self._do_print(output);
749
});
750
751
/* -----[ exits ]----- */
752
AST_Exit.DEFMETHOD("_do_print", function(output, kind){
753
output.print(kind);
754
if (this.value) {
755
output.space();
756
this.value.print(output);
757
}
758
output.semicolon();
759
});
760
DEFPRINT(AST_Return, function(self, output){
761
self._do_print(output, "return");
762
});
763
DEFPRINT(AST_Throw, function(self, output){
764
self._do_print(output, "throw");
765
});
766
767
/* -----[ loop control ]----- */
768
AST_LoopControl.DEFMETHOD("_do_print", function(output, kind){
769
output.print(kind);
770
if (this.label) {
771
output.space();
772
this.label.print(output);
773
}
774
output.semicolon();
775
});
776
DEFPRINT(AST_Break, function(self, output){
777
self._do_print(output, "break");
778
});
779
DEFPRINT(AST_Continue, function(self, output){
780
self._do_print(output, "continue");
781
});
782
783
/* -----[ if ]----- */
784
function make_then(self, output) {
785
if (output.option("bracketize")) {
786
make_block(self.body, output);
787
return;
788
}
789
// The squeezer replaces "block"-s that contain only a single
790
// statement with the statement itself; technically, the AST
791
// is correct, but this can create problems when we output an
792
// IF having an ELSE clause where the THEN clause ends in an
793
// IF *without* an ELSE block (then the outer ELSE would refer
794
// to the inner IF). This function checks for this case and
795
// adds the block brackets if needed.
796
if (!self.body)
797
return output.force_semicolon();
798
if (self.body instanceof AST_Do
799
&& !output.option("screw_ie8")) {
800
// https://github.com/mishoo/UglifyJS/issues/#issue/57 IE
801
// croaks with "syntax error" on code like this: if (foo)
802
// do ... while(cond); else ... we need block brackets
803
// around do/while
804
make_block(self.body, output);
805
return;
806
}
807
var b = self.body;
808
while (true) {
809
if (b instanceof AST_If) {
810
if (!b.alternative) {
811
make_block(self.body, output);
812
return;
813
}
814
b = b.alternative;
815
}
816
else if (b instanceof AST_StatementWithBody) {
817
b = b.body;
818
}
819
else break;
820
}
821
force_statement(self.body, output);
822
};
823
DEFPRINT(AST_If, function(self, output){
824
output.print("if");
825
output.space();
826
output.with_parens(function(){
827
self.condition.print(output);
828
});
829
output.space();
830
if (self.alternative) {
831
make_then(self, output);
832
output.space();
833
output.print("else");
834
output.space();
835
force_statement(self.alternative, output);
836
} else {
837
self._do_print_body(output);
838
}
839
});
840
841
/* -----[ switch ]----- */
842
DEFPRINT(AST_Switch, function(self, output){
843
output.print("switch");
844
output.space();
845
output.with_parens(function(){
846
self.expression.print(output);
847
});
848
output.space();
849
if (self.body.length > 0) output.with_block(function(){
850
self.body.forEach(function(stmt, i){
851
if (i) output.newline();
852
output.indent(true);
853
stmt.print(output);
854
});
855
});
856
else output.print("{}");
857
});
858
AST_SwitchBranch.DEFMETHOD("_do_print_body", function(output){
859
if (this.body.length > 0) {
860
output.newline();
861
this.body.forEach(function(stmt){
862
output.indent();
863
stmt.print(output);
864
output.newline();
865
});
866
}
867
});
868
DEFPRINT(AST_Default, function(self, output){
869
output.print("default:");
870
self._do_print_body(output);
871
});
872
DEFPRINT(AST_Case, function(self, output){
873
output.print("case");
874
output.space();
875
self.expression.print(output);
876
output.print(":");
877
self._do_print_body(output);
878
});
879
880
/* -----[ exceptions ]----- */
881
DEFPRINT(AST_Try, function(self, output){
882
output.print("try");
883
output.space();
884
print_bracketed(self.body, output);
885
if (self.bcatch) {
886
output.space();
887
self.bcatch.print(output);
888
}
889
if (self.bfinally) {
890
output.space();
891
self.bfinally.print(output);
892
}
893
});
894
DEFPRINT(AST_Catch, function(self, output){
895
output.print("catch");
896
output.space();
897
output.with_parens(function(){
898
self.argname.print(output);
899
});
900
output.space();
901
print_bracketed(self.body, output);
902
});
903
DEFPRINT(AST_Finally, function(self, output){
904
output.print("finally");
905
output.space();
906
print_bracketed(self.body, output);
907
});
908
909
/* -----[ var/const ]----- */
910
AST_Definitions.DEFMETHOD("_do_print", function(output, kind){
911
output.print(kind);
912
output.space();
913
this.definitions.forEach(function(def, i){
914
if (i) output.comma();
915
def.print(output);
916
});
917
var p = output.parent();
918
var in_for = p instanceof AST_For || p instanceof AST_ForIn;
919
var avoid_semicolon = in_for && p.init === this;
920
if (!avoid_semicolon)
921
output.semicolon();
922
});
923
DEFPRINT(AST_Var, function(self, output){
924
self._do_print(output, "var");
925
});
926
DEFPRINT(AST_Const, function(self, output){
927
self._do_print(output, "const");
928
});
929
930
function parenthesize_for_noin(node, output, noin) {
931
if (!noin) node.print(output);
932
else try {
933
// need to take some precautions here:
934
// https://github.com/mishoo/UglifyJS2/issues/60
935
node.walk(new TreeWalker(function(node){
936
if (node instanceof AST_Binary && node.operator == "in")
937
throw output;
938
}));
939
node.print(output);
940
} catch(ex) {
941
if (ex !== output) throw ex;
942
node.print(output, true);
943
}
944
};
945
946
DEFPRINT(AST_VarDef, function(self, output){
947
self.name.print(output);
948
if (self.value) {
949
output.space();
950
output.print("=");
951
output.space();
952
var p = output.parent(1);
953
var noin = p instanceof AST_For || p instanceof AST_ForIn;
954
parenthesize_for_noin(self.value, output, noin);
955
}
956
});
957
958
/* -----[ other expressions ]----- */
959
DEFPRINT(AST_Call, function(self, output){
960
self.expression.print(output);
961
if (self instanceof AST_New && no_constructor_parens(self, output))
962
return;
963
output.with_parens(function(){
964
self.args.forEach(function(expr, i){
965
if (i) output.comma();
966
expr.print(output);
967
});
968
});
969
});
970
DEFPRINT(AST_New, function(self, output){
971
output.print("new");
972
output.space();
973
AST_Call.prototype._codegen(self, output);
974
});
975
976
AST_Seq.DEFMETHOD("_do_print", function(output){
977
this.car.print(output);
978
if (this.cdr) {
979
output.comma();
980
if (output.should_break()) {
981
output.newline();
982
output.indent();
983
}
984
this.cdr.print(output);
985
}
986
});
987
DEFPRINT(AST_Seq, function(self, output){
988
self._do_print(output);
989
// var p = output.parent();
990
// if (p instanceof AST_Statement) {
991
// output.with_indent(output.next_indent(), function(){
992
// self._do_print(output);
993
// });
994
// } else {
995
// self._do_print(output);
996
// }
997
});
998
DEFPRINT(AST_Dot, function(self, output){
999
var expr = self.expression;
1000
expr.print(output);
1001
if (expr instanceof AST_Number && expr.getValue() >= 0) {
1002
if (!/[xa-f.]/i.test(output.last())) {
1003
output.print(".");
1004
}
1005
}
1006
output.print(".");
1007
// the name after dot would be mapped about here.
1008
output.add_mapping(self.end);
1009
output.print_name(self.property);
1010
});
1011
DEFPRINT(AST_Sub, function(self, output){
1012
self.expression.print(output);
1013
output.print("[");
1014
self.property.print(output);
1015
output.print("]");
1016
});
1017
DEFPRINT(AST_UnaryPrefix, function(self, output){
1018
var op = self.operator;
1019
output.print(op);
1020
if (/^[a-z]/i.test(op)
1021
|| (/[+-]$/.test(op)
1022
&& self.expression instanceof AST_UnaryPrefix
1023
&& /^[+-]/.test(self.expression.operator))) {
1024
output.space();
1025
}
1026
self.expression.print(output);
1027
});
1028
DEFPRINT(AST_UnaryPostfix, function(self, output){
1029
self.expression.print(output);
1030
output.print(self.operator);
1031
});
1032
DEFPRINT(AST_Binary, function(self, output){
1033
self.left.print(output);
1034
output.space();
1035
output.print(self.operator);
1036
if (self.operator == "<"
1037
&& self.right instanceof AST_UnaryPrefix
1038
&& self.right.operator == "!"
1039
&& self.right.expression instanceof AST_UnaryPrefix
1040
&& self.right.expression.operator == "--") {
1041
// space is mandatory to avoid outputting <!--
1042
// http://javascript.spec.whatwg.org/#comment-syntax
1043
output.print(" ");
1044
} else {
1045
// the space is optional depending on "beautify"
1046
output.space();
1047
}
1048
self.right.print(output);
1049
});
1050
DEFPRINT(AST_Conditional, function(self, output){
1051
self.condition.print(output);
1052
output.space();
1053
output.print("?");
1054
output.space();
1055
self.consequent.print(output);
1056
output.space();
1057
output.colon();
1058
self.alternative.print(output);
1059
});
1060
1061
/* -----[ literals ]----- */
1062
DEFPRINT(AST_Array, function(self, output){
1063
output.with_square(function(){
1064
var a = self.elements, len = a.length;
1065
if (len > 0) output.space();
1066
a.forEach(function(exp, i){
1067
if (i) output.comma();
1068
exp.print(output);
1069
// If the final element is a hole, we need to make sure it
1070
// doesn't look like a trailing comma, by inserting an actual
1071
// trailing comma.
1072
if (i === len - 1 && exp instanceof AST_Hole)
1073
output.comma();
1074
});
1075
if (len > 0) output.space();
1076
});
1077
});
1078
DEFPRINT(AST_Object, function(self, output){
1079
if (self.properties.length > 0) output.with_block(function(){
1080
self.properties.forEach(function(prop, i){
1081
if (i) {
1082
output.print(",");
1083
output.newline();
1084
}
1085
output.indent();
1086
prop.print(output);
1087
});
1088
output.newline();
1089
});
1090
else output.print("{}");
1091
});
1092
DEFPRINT(AST_ObjectKeyVal, function(self, output){
1093
var key = self.key;
1094
var quote = self.quote;
1095
if (output.option("quote_keys")) {
1096
output.print_string(key + "");
1097
} else if ((typeof key == "number"
1098
|| !output.option("beautify")
1099
&& +key + "" == key)
1100
&& parseFloat(key) >= 0) {
1101
output.print(make_num(key));
1102
} else if (RESERVED_WORDS(key) ? output.option("screw_ie8") : is_identifier_string(key)) {
1103
output.print_name(key);
1104
} else {
1105
output.print_string(key, quote);
1106
}
1107
output.colon();
1108
self.value.print(output);
1109
});
1110
DEFPRINT(AST_ObjectSetter, function(self, output){
1111
output.print("set");
1112
output.space();
1113
self.key.print(output);
1114
self.value._do_print(output, true);
1115
});
1116
DEFPRINT(AST_ObjectGetter, function(self, output){
1117
output.print("get");
1118
output.space();
1119
self.key.print(output);
1120
self.value._do_print(output, true);
1121
});
1122
DEFPRINT(AST_Symbol, function(self, output){
1123
var def = self.definition();
1124
output.print_name(def ? def.mangled_name || def.name : self.name);
1125
});
1126
DEFPRINT(AST_Undefined, function(self, output){
1127
output.print("void 0");
1128
});
1129
DEFPRINT(AST_Hole, noop);
1130
DEFPRINT(AST_Infinity, function(self, output){
1131
output.print("Infinity");
1132
});
1133
DEFPRINT(AST_NaN, function(self, output){
1134
output.print("NaN");
1135
});
1136
DEFPRINT(AST_This, function(self, output){
1137
output.print("this");
1138
});
1139
DEFPRINT(AST_Constant, function(self, output){
1140
output.print(self.getValue());
1141
});
1142
DEFPRINT(AST_String, function(self, output){
1143
output.print_string(self.getValue(), self.quote);
1144
});
1145
DEFPRINT(AST_Number, function(self, output){
1146
output.print(make_num(self.getValue()));
1147
});
1148
1149
function regexp_safe_literal(code) {
1150
return [
1151
0x5c , // \
1152
0x2f , // /
1153
0x2e , // .
1154
0x2b , // +
1155
0x2a , // *
1156
0x3f , // ?
1157
0x28 , // (
1158
0x29 , // )
1159
0x5b , // [
1160
0x5d , // ]
1161
0x7b , // {
1162
0x7d , // }
1163
0x24 , // $
1164
0x5e , // ^
1165
0x3a , // :
1166
0x7c , // |
1167
0x21 , // !
1168
0x0a , // \n
1169
0x0d , // \r
1170
0x00 , // \0
1171
0xfeff , // Unicode BOM
1172
0x2028 , // unicode "line separator"
1173
0x2029 , // unicode "paragraph separator"
1174
].indexOf(code) < 0;
1175
};
1176
1177
DEFPRINT(AST_RegExp, function(self, output){
1178
var str = self.getValue().toString();
1179
if (output.option("ascii_only")) {
1180
str = output.to_ascii(str);
1181
} else if (output.option("unescape_regexps")) {
1182
str = str.split("\\\\").map(function(str){
1183
return str.replace(/\\u[0-9a-fA-F]{4}|\\x[0-9a-fA-F]{2}/g, function(s){
1184
var code = parseInt(s.substr(2), 16);
1185
return regexp_safe_literal(code) ? String.fromCharCode(code) : s;
1186
});
1187
}).join("\\\\");
1188
}
1189
output.print(str);
1190
var p = output.parent();
1191
if (p instanceof AST_Binary && /^in/.test(p.operator) && p.left === self)
1192
output.print(" ");
1193
});
1194
1195
function force_statement(stat, output) {
1196
if (output.option("bracketize")) {
1197
if (!stat || stat instanceof AST_EmptyStatement)
1198
output.print("{}");
1199
else if (stat instanceof AST_BlockStatement)
1200
stat.print(output);
1201
else output.with_block(function(){
1202
output.indent();
1203
stat.print(output);
1204
output.newline();
1205
});
1206
} else {
1207
if (!stat || stat instanceof AST_EmptyStatement)
1208
output.force_semicolon();
1209
else
1210
stat.print(output);
1211
}
1212
};
1213
1214
// return true if the node at the top of the stack (that means the
1215
// innermost node in the current output) is lexically the first in
1216
// a statement.
1217
function first_in_statement(output) {
1218
var a = output.stack(), i = a.length, node = a[--i], p = a[--i];
1219
while (i > 0) {
1220
if (p instanceof AST_Statement && p.body === node)
1221
return true;
1222
if ((p instanceof AST_Seq && p.car === node ) ||
1223
(p instanceof AST_Call && p.expression === node && !(p instanceof AST_New) ) ||
1224
(p instanceof AST_Dot && p.expression === node ) ||
1225
(p instanceof AST_Sub && p.expression === node ) ||
1226
(p instanceof AST_Conditional && p.condition === node ) ||
1227
(p instanceof AST_Binary && p.left === node ) ||
1228
(p instanceof AST_UnaryPostfix && p.expression === node ))
1229
{
1230
node = p;
1231
p = a[--i];
1232
} else {
1233
return false;
1234
}
1235
}
1236
};
1237
1238
// self should be AST_New. decide if we want to show parens or not.
1239
function no_constructor_parens(self, output) {
1240
return self.args.length == 0 && !output.option("beautify");
1241
};
1242
1243
function best_of(a) {
1244
var best = a[0], len = best.length;
1245
for (var i = 1; i < a.length; ++i) {
1246
if (a[i].length < len) {
1247
best = a[i];
1248
len = best.length;
1249
}
1250
}
1251
return best;
1252
};
1253
1254
function make_num(num) {
1255
var str = num.toString(10), a = [ str.replace(/^0\./, ".").replace('e+', 'e') ], m;
1256
if (Math.floor(num) === num) {
1257
if (num >= 0) {
1258
a.push("0x" + num.toString(16).toLowerCase(), // probably pointless
1259
"0" + num.toString(8)); // same.
1260
} else {
1261
a.push("-0x" + (-num).toString(16).toLowerCase(), // probably pointless
1262
"-0" + (-num).toString(8)); // same.
1263
}
1264
if ((m = /^(.*?)(0+)$/.exec(num))) {
1265
a.push(m[1] + "e" + m[2].length);
1266
}
1267
} else if ((m = /^0?\.(0+)(.*)$/.exec(num))) {
1268
a.push(m[2] + "e-" + (m[1].length + m[2].length),
1269
str.substr(str.indexOf(".")));
1270
}
1271
return best_of(a);
1272
};
1273
1274
function make_block(stmt, output) {
1275
if (stmt instanceof AST_BlockStatement) {
1276
stmt.print(output);
1277
return;
1278
}
1279
output.with_block(function(){
1280
output.indent();
1281
stmt.print(output);
1282
output.newline();
1283
});
1284
};
1285
1286
/* -----[ source map generators ]----- */
1287
1288
function DEFMAP(nodetype, generator) {
1289
nodetype.DEFMETHOD("add_source_map", function(stream){
1290
generator(this, stream);
1291
});
1292
};
1293
1294
// We could easily add info for ALL nodes, but it seems to me that
1295
// would be quite wasteful, hence this noop in the base class.
1296
DEFMAP(AST_Node, noop);
1297
1298
function basic_sourcemap_gen(self, output) {
1299
output.add_mapping(self.start);
1300
};
1301
1302
// XXX: I'm not exactly sure if we need it for all of these nodes,
1303
// or if we should add even more.
1304
1305
DEFMAP(AST_Directive, basic_sourcemap_gen);
1306
DEFMAP(AST_Debugger, basic_sourcemap_gen);
1307
DEFMAP(AST_Symbol, basic_sourcemap_gen);
1308
DEFMAP(AST_Jump, basic_sourcemap_gen);
1309
DEFMAP(AST_StatementWithBody, basic_sourcemap_gen);
1310
DEFMAP(AST_LabeledStatement, noop); // since the label symbol will mark it
1311
DEFMAP(AST_Lambda, basic_sourcemap_gen);
1312
DEFMAP(AST_Switch, basic_sourcemap_gen);
1313
DEFMAP(AST_SwitchBranch, basic_sourcemap_gen);
1314
DEFMAP(AST_BlockStatement, basic_sourcemap_gen);
1315
DEFMAP(AST_Toplevel, noop);
1316
DEFMAP(AST_New, basic_sourcemap_gen);
1317
DEFMAP(AST_Try, basic_sourcemap_gen);
1318
DEFMAP(AST_Catch, basic_sourcemap_gen);
1319
DEFMAP(AST_Finally, basic_sourcemap_gen);
1320
DEFMAP(AST_Definitions, basic_sourcemap_gen);
1321
DEFMAP(AST_Constant, basic_sourcemap_gen);
1322
DEFMAP(AST_ObjectProperty, function(self, output){
1323
output.add_mapping(self.start, self.key);
1324
});
1325
1326
})();
1327
1328