UNPKG

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