UNPKG

50.7 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 // https://github.com/mishoo/UglifyJS2/issues/115
787 // https://github.com/mishoo/UglifyJS2/pull/1009
788 if (value < 0 || /^0/.test(make_num(value))) {
789 return true;
790 }
791 }
792 });
793
794 PARENS([ AST_Assign, AST_Conditional ], function(output) {
795 var p = output.parent();
796 // !(a = false) → true
797 if (p instanceof AST_Unary)
798 return true;
799 // 1 + (a = 2) + 3 → 6, side effect setting a = 2
800 if (p instanceof AST_Binary && !(p instanceof AST_Assign))
801 return true;
802 // (a = func)() —or— new (a = Object)()
803 if (p instanceof AST_Call && p.expression === this)
804 return true;
805 // (a = foo) ? bar : baz
806 if (p instanceof AST_Conditional && p.condition === this)
807 return true;
808 // (a = foo)["prop"] —or— (a = foo).prop
809 if (p instanceof AST_PropAccess && p.expression === this)
810 return true;
811 });
812
813 /* -----[ PRINTERS ]----- */
814
815 DEFPRINT(AST_Directive, function(self, output) {
816 var quote = self.quote;
817 var value = self.value;
818 switch (output.option("quote_style")) {
819 case 0:
820 case 2:
821 if (value.indexOf('"') == -1) quote = '"';
822 break;
823 case 1:
824 if (value.indexOf("'") == -1) quote = "'";
825 break;
826 }
827 output.print(quote + value + quote);
828 output.semicolon();
829 });
830 DEFPRINT(AST_Debugger, function(self, output) {
831 output.print("debugger");
832 output.semicolon();
833 });
834
835 /* -----[ statements ]----- */
836
837 function display_body(body, is_toplevel, output, allow_directives) {
838 var last = body.length - 1;
839 var in_directive = allow_directives;
840 var was_asm = use_asm;
841 body.forEach(function(stmt, i) {
842 if (in_directive) {
843 if (stmt instanceof AST_Directive) {
844 if (stmt.value == "use asm") use_asm = true;
845 } else if (!(stmt instanceof AST_EmptyStatement)) {
846 if (stmt instanceof AST_SimpleStatement && stmt.body instanceof AST_String) {
847 output.force_semicolon();
848 }
849 in_directive = false;
850 }
851 }
852 if (stmt instanceof AST_EmptyStatement) return;
853 output.indent();
854 stmt.print(output);
855 if (i == last && is_toplevel) return;
856 output.newline();
857 if (is_toplevel) output.newline();
858 });
859 use_asm = was_asm;
860 }
861
862 AST_StatementWithBody.DEFMETHOD("_do_print_body", function(output) {
863 force_statement(this.body, output);
864 });
865
866 DEFPRINT(AST_Statement, function(self, output) {
867 self.body.print(output);
868 output.semicolon();
869 });
870 DEFPRINT(AST_Toplevel, function(self, output) {
871 display_body(self.body, true, output, true);
872 output.print("");
873 });
874 DEFPRINT(AST_LabeledStatement, function(self, output) {
875 self.label.print(output);
876 output.colon();
877 self.body.print(output);
878 });
879 DEFPRINT(AST_SimpleStatement, function(self, output) {
880 self.body.print(output);
881 output.semicolon();
882 });
883 function print_braced_empty(self, output) {
884 output.print("{");
885 output.with_indent(output.next_indent(), function() {
886 output.append_comments(self, true);
887 });
888 output.print("}");
889 }
890 function print_braced(self, output, allow_directives) {
891 if (self.body.length > 0) {
892 output.with_block(function() {
893 display_body(self.body, false, output, allow_directives);
894 });
895 } else print_braced_empty(self, output);
896 }
897 DEFPRINT(AST_BlockStatement, function(self, output) {
898 print_braced(self, output);
899 });
900 DEFPRINT(AST_EmptyStatement, function(self, output) {
901 output.semicolon();
902 });
903 DEFPRINT(AST_Do, function(self, output) {
904 output.print("do");
905 output.space();
906 make_block(self.body, output);
907 output.space();
908 output.print("while");
909 output.space();
910 output.with_parens(function() {
911 self.condition.print(output);
912 });
913 output.semicolon();
914 });
915 DEFPRINT(AST_While, function(self, output) {
916 output.print("while");
917 output.space();
918 output.with_parens(function() {
919 self.condition.print(output);
920 });
921 output.space();
922 self._do_print_body(output);
923 });
924 DEFPRINT(AST_For, function(self, output) {
925 output.print("for");
926 output.space();
927 output.with_parens(function() {
928 if (self.init) {
929 if (self.init instanceof AST_Definitions) {
930 self.init.print(output);
931 } else {
932 parenthesize_for_noin(self.init, output, true);
933 }
934 output.print(";");
935 output.space();
936 } else {
937 output.print(";");
938 }
939 if (self.condition) {
940 self.condition.print(output);
941 output.print(";");
942 output.space();
943 } else {
944 output.print(";");
945 }
946 if (self.step) {
947 self.step.print(output);
948 }
949 });
950 output.space();
951 self._do_print_body(output);
952 });
953 DEFPRINT(AST_ForIn, function(self, output) {
954 output.print("for");
955 output.space();
956 output.with_parens(function() {
957 self.init.print(output);
958 output.space();
959 output.print("in");
960 output.space();
961 self.object.print(output);
962 });
963 output.space();
964 self._do_print_body(output);
965 });
966 DEFPRINT(AST_With, function(self, output) {
967 output.print("with");
968 output.space();
969 output.with_parens(function() {
970 self.expression.print(output);
971 });
972 output.space();
973 self._do_print_body(output);
974 });
975
976 /* -----[ functions ]----- */
977 AST_Lambda.DEFMETHOD("_do_print", function(output, nokeyword) {
978 var self = this;
979 if (!nokeyword) {
980 output.print("function");
981 }
982 if (self.name) {
983 output.space();
984 self.name.print(output);
985 }
986 output.with_parens(function() {
987 self.argnames.forEach(function(arg, i) {
988 if (i) output.comma();
989 arg.print(output);
990 });
991 });
992 output.space();
993 print_braced(self, output, true);
994 });
995 DEFPRINT(AST_Lambda, function(self, output) {
996 self._do_print(output);
997 });
998
999 /* -----[ jumps ]----- */
1000 function print_jump(output, kind, target) {
1001 output.print(kind);
1002 if (target) {
1003 output.space();
1004 target.print(output);
1005 }
1006 output.semicolon();
1007 }
1008
1009 DEFPRINT(AST_Return, function(self, output) {
1010 print_jump(output, "return", self.value);
1011 });
1012 DEFPRINT(AST_Throw, function(self, output) {
1013 print_jump(output, "throw", self.value);
1014 });
1015 DEFPRINT(AST_Break, function(self, output) {
1016 print_jump(output, "break", self.label);
1017 });
1018 DEFPRINT(AST_Continue, function(self, output) {
1019 print_jump(output, "continue", self.label);
1020 });
1021
1022 /* -----[ if ]----- */
1023 function make_then(self, output) {
1024 var b = self.body;
1025 if (output.option("braces")
1026 || output.option("ie8") && b instanceof AST_Do)
1027 return make_block(b, output);
1028 // The squeezer replaces "block"-s that contain only a single
1029 // statement with the statement itself; technically, the AST
1030 // is correct, but this can create problems when we output an
1031 // IF having an ELSE clause where the THEN clause ends in an
1032 // IF *without* an ELSE block (then the outer ELSE would refer
1033 // to the inner IF). This function checks for this case and
1034 // adds the block braces if needed.
1035 if (!b) return output.force_semicolon();
1036 while (true) {
1037 if (b instanceof AST_If) {
1038 if (!b.alternative) {
1039 make_block(self.body, output);
1040 return;
1041 }
1042 b = b.alternative;
1043 } else if (b instanceof AST_StatementWithBody) {
1044 b = b.body;
1045 } else break;
1046 }
1047 force_statement(self.body, output);
1048 }
1049 DEFPRINT(AST_If, function(self, output) {
1050 output.print("if");
1051 output.space();
1052 output.with_parens(function() {
1053 self.condition.print(output);
1054 });
1055 output.space();
1056 if (self.alternative) {
1057 make_then(self, output);
1058 output.space();
1059 output.print("else");
1060 output.space();
1061 if (self.alternative instanceof AST_If)
1062 self.alternative.print(output);
1063 else
1064 force_statement(self.alternative, output);
1065 } else {
1066 self._do_print_body(output);
1067 }
1068 });
1069
1070 /* -----[ switch ]----- */
1071 DEFPRINT(AST_Switch, function(self, output) {
1072 output.print("switch");
1073 output.space();
1074 output.with_parens(function() {
1075 self.expression.print(output);
1076 });
1077 output.space();
1078 var last = self.body.length - 1;
1079 if (last < 0) print_braced_empty(self, output);
1080 else output.with_block(function() {
1081 self.body.forEach(function(branch, i) {
1082 output.indent(true);
1083 branch.print(output);
1084 if (i < last && branch.body.length > 0)
1085 output.newline();
1086 });
1087 });
1088 });
1089 AST_SwitchBranch.DEFMETHOD("_do_print_body", function(output) {
1090 output.newline();
1091 this.body.forEach(function(stmt) {
1092 output.indent();
1093 stmt.print(output);
1094 output.newline();
1095 });
1096 });
1097 DEFPRINT(AST_Default, function(self, output) {
1098 output.print("default:");
1099 self._do_print_body(output);
1100 });
1101 DEFPRINT(AST_Case, function(self, output) {
1102 output.print("case");
1103 output.space();
1104 self.expression.print(output);
1105 output.print(":");
1106 self._do_print_body(output);
1107 });
1108
1109 /* -----[ exceptions ]----- */
1110 DEFPRINT(AST_Try, function(self, output) {
1111 output.print("try");
1112 output.space();
1113 print_braced(self, output);
1114 if (self.bcatch) {
1115 output.space();
1116 self.bcatch.print(output);
1117 }
1118 if (self.bfinally) {
1119 output.space();
1120 self.bfinally.print(output);
1121 }
1122 });
1123 DEFPRINT(AST_Catch, function(self, output) {
1124 output.print("catch");
1125 output.space();
1126 output.with_parens(function() {
1127 self.argname.print(output);
1128 });
1129 output.space();
1130 print_braced(self, output);
1131 });
1132 DEFPRINT(AST_Finally, function(self, output) {
1133 output.print("finally");
1134 output.space();
1135 print_braced(self, output);
1136 });
1137
1138 DEFPRINT(AST_Var, function(self, output) {
1139 output.print("var");
1140 output.space();
1141 self.definitions.forEach(function(def, i) {
1142 if (i) output.comma();
1143 def.print(output);
1144 });
1145 var p = output.parent();
1146 if (p && p.init !== self || !(p instanceof AST_For || p instanceof AST_ForIn)) output.semicolon();
1147 });
1148
1149 function parenthesize_for_noin(node, output, noin) {
1150 var parens = false;
1151 // need to take some precautions here:
1152 // https://github.com/mishoo/UglifyJS2/issues/60
1153 if (noin) node.walk(new TreeWalker(function(node) {
1154 if (parens || node instanceof AST_Scope) return true;
1155 if (node instanceof AST_Binary && node.operator == "in") {
1156 parens = true;
1157 return true;
1158 }
1159 }));
1160 node.print(output, parens);
1161 }
1162
1163 DEFPRINT(AST_VarDef, function(self, output) {
1164 self.name.print(output);
1165 if (self.value) {
1166 output.space();
1167 output.print("=");
1168 output.space();
1169 var p = output.parent(1);
1170 var noin = p instanceof AST_For || p instanceof AST_ForIn;
1171 parenthesize_for_noin(self.value, output, noin);
1172 }
1173 });
1174
1175 /* -----[ other expressions ]----- */
1176 DEFPRINT(AST_Call, function(self, output) {
1177 self.expression.print(output);
1178 if (self instanceof AST_New && !need_constructor_parens(self, output))
1179 return;
1180 if (self.expression instanceof AST_Call || self.expression instanceof AST_Lambda) {
1181 output.add_mapping(self.start);
1182 }
1183 output.with_parens(function() {
1184 self.args.forEach(function(expr, i) {
1185 if (i) output.comma();
1186 expr.print(output);
1187 });
1188 });
1189 });
1190 DEFPRINT(AST_New, function(self, output) {
1191 output.print("new");
1192 output.space();
1193 AST_Call.prototype._codegen(self, output);
1194 });
1195 DEFPRINT(AST_Sequence, function(self, output) {
1196 self.expressions.forEach(function(node, index) {
1197 if (index > 0) {
1198 output.comma();
1199 if (output.should_break()) {
1200 output.newline();
1201 output.indent();
1202 }
1203 }
1204 node.print(output);
1205 });
1206 });
1207 DEFPRINT(AST_Dot, function(self, output) {
1208 var expr = self.expression;
1209 expr.print(output);
1210 var prop = self.property;
1211 if (output.option("ie8") && RESERVED_WORDS[prop]) {
1212 output.print("[");
1213 output.add_mapping(self.end);
1214 output.print_string(prop);
1215 output.print("]");
1216 } else {
1217 if (expr instanceof AST_Number && expr.value >= 0) {
1218 if (!/[xa-f.)]/i.test(output.last())) {
1219 output.print(".");
1220 }
1221 }
1222 output.print(".");
1223 // the name after dot would be mapped about here.
1224 output.add_mapping(self.end);
1225 output.print_name(prop);
1226 }
1227 });
1228 DEFPRINT(AST_Sub, function(self, output) {
1229 self.expression.print(output);
1230 output.print("[");
1231 self.property.print(output);
1232 output.print("]");
1233 });
1234 DEFPRINT(AST_UnaryPrefix, function(self, output) {
1235 var op = self.operator;
1236 output.print(op);
1237 if (/^[a-z]/i.test(op)
1238 || (/[+-]$/.test(op)
1239 && self.expression instanceof AST_UnaryPrefix
1240 && /^[+-]/.test(self.expression.operator))) {
1241 output.space();
1242 }
1243 self.expression.print(output);
1244 });
1245 DEFPRINT(AST_UnaryPostfix, function(self, output) {
1246 self.expression.print(output);
1247 output.print(self.operator);
1248 });
1249 DEFPRINT(AST_Binary, function(self, output) {
1250 self.left.print(output);
1251 output.space();
1252 output.print(self.operator);
1253 output.space();
1254 self.right.print(output);
1255 });
1256 DEFPRINT(AST_Conditional, function(self, output) {
1257 self.condition.print(output);
1258 output.space();
1259 output.print("?");
1260 output.space();
1261 self.consequent.print(output);
1262 output.space();
1263 output.colon();
1264 self.alternative.print(output);
1265 });
1266
1267 /* -----[ literals ]----- */
1268 DEFPRINT(AST_Array, function(self, output) {
1269 output.with_square(function() {
1270 var a = self.elements, len = a.length;
1271 if (len > 0) output.space();
1272 a.forEach(function(exp, i) {
1273 if (i) output.comma();
1274 exp.print(output);
1275 // If the final element is a hole, we need to make sure it
1276 // doesn't look like a trailing comma, by inserting an actual
1277 // trailing comma.
1278 if (i === len - 1 && exp instanceof AST_Hole)
1279 output.comma();
1280 });
1281 if (len > 0) output.space();
1282 });
1283 });
1284 DEFPRINT(AST_Object, function(self, output) {
1285 if (self.properties.length > 0) output.with_block(function() {
1286 self.properties.forEach(function(prop, i) {
1287 if (i) {
1288 output.print(",");
1289 output.newline();
1290 }
1291 output.indent();
1292 prop.print(output);
1293 });
1294 output.newline();
1295 });
1296 else print_braced_empty(self, output);
1297 });
1298
1299 function print_property_name(key, quote, output) {
1300 if (output.option("quote_keys")) {
1301 output.print_string(key);
1302 } else if ("" + +key == key && key >= 0) {
1303 output.print(make_num(key));
1304 } else if (RESERVED_WORDS[key] ? !output.option("ie8") : is_identifier_string(key)) {
1305 if (quote && output.option("keep_quoted_props")) {
1306 output.print_string(key, quote);
1307 } else {
1308 output.print_name(key);
1309 }
1310 } else {
1311 output.print_string(key, quote);
1312 }
1313 }
1314
1315 DEFPRINT(AST_ObjectKeyVal, function(self, output) {
1316 print_property_name(self.key, self.quote, output);
1317 output.colon();
1318 self.value.print(output);
1319 });
1320 AST_ObjectProperty.DEFMETHOD("_print_getter_setter", function(type, output) {
1321 output.print(type);
1322 output.space();
1323 print_property_name(this.key.name, this.quote, output);
1324 this.value._do_print(output, true);
1325 });
1326 DEFPRINT(AST_ObjectSetter, function(self, output) {
1327 self._print_getter_setter("set", output);
1328 });
1329 DEFPRINT(AST_ObjectGetter, function(self, output) {
1330 self._print_getter_setter("get", output);
1331 });
1332 DEFPRINT(AST_Symbol, function(self, output) {
1333 var def = self.definition();
1334 output.print_name(def && def.mangled_name || self.name);
1335 });
1336 DEFPRINT(AST_Hole, noop);
1337 DEFPRINT(AST_This, function(self, output) {
1338 output.print("this");
1339 });
1340 DEFPRINT(AST_Constant, function(self, output) {
1341 output.print(self.value);
1342 });
1343 DEFPRINT(AST_String, function(self, output) {
1344 output.print_string(self.value, self.quote);
1345 });
1346 DEFPRINT(AST_Number, function(self, output) {
1347 if (use_asm && self.start && self.start.raw != null) {
1348 output.print(self.start.raw);
1349 } else {
1350 output.print(make_num(self.value));
1351 }
1352 });
1353
1354 DEFPRINT(AST_RegExp, function(self, output) {
1355 var regexp = self.value;
1356 var str = regexp.toString();
1357 var end = str.lastIndexOf("/");
1358 if (regexp.raw_source) {
1359 str = "/" + regexp.raw_source + str.slice(end);
1360 } else if (end == 1) {
1361 str = "/(?:)" + str.slice(end);
1362 } else if (str.indexOf("/", 1) < end) {
1363 str = "/" + str.slice(1, end).replace(/\\\\|[^/]?\//g, function(match) {
1364 return match[0] == "\\" ? match : match.slice(0, -1) + "\\/";
1365 }) + str.slice(end);
1366 }
1367 output.print(output.to_utf8(str).replace(/\\(?:\0(?![0-9])|[^\0])/g, function(match) {
1368 switch (match[1]) {
1369 case "\n": return "\\n";
1370 case "\r": return "\\r";
1371 case "\t": return "\t";
1372 case "\b": return "\b";
1373 case "\f": return "\f";
1374 case "\0": return "\0";
1375 case "\x0B": return "\v";
1376 case "\u2028": return "\\u2028";
1377 case "\u2029": return "\\u2029";
1378 default: return match;
1379 }
1380 }).replace(/[\n\r\u2028\u2029]/g, function(c) {
1381 switch (c) {
1382 case "\n": return "\\n";
1383 case "\r": return "\\r";
1384 case "\u2028": return "\\u2028";
1385 case "\u2029": return "\\u2029";
1386 }
1387 }));
1388 var p = output.parent();
1389 if (p instanceof AST_Binary && /^in/.test(p.operator) && p.left === self)
1390 output.print(" ");
1391 });
1392
1393 function force_statement(stat, output) {
1394 if (output.option("braces")) {
1395 make_block(stat, output);
1396 } else {
1397 if (!stat || stat instanceof AST_EmptyStatement)
1398 output.force_semicolon();
1399 else
1400 stat.print(output);
1401 }
1402 }
1403
1404 // self should be AST_New. decide if we want to show parens or not.
1405 function need_constructor_parens(self, output) {
1406 // Always print parentheses with arguments
1407 if (self.args.length > 0) return true;
1408
1409 return output.option("beautify");
1410 }
1411
1412 function best_of(a) {
1413 var best = a[0], len = best.length;
1414 for (var i = 1; i < a.length; ++i) {
1415 if (a[i].length < len) {
1416 best = a[i];
1417 len = best.length;
1418 }
1419 }
1420 return best;
1421 }
1422
1423 function make_num(num) {
1424 var str = num.toString(10).replace(/^0\./, ".").replace("e+", "e");
1425 var candidates = [ str ];
1426 if (Math.floor(num) === num) {
1427 if (num < 0) {
1428 candidates.push("-0x" + (-num).toString(16).toLowerCase());
1429 } else {
1430 candidates.push("0x" + num.toString(16).toLowerCase());
1431 }
1432 }
1433 var match, len, digits;
1434 if (match = /^\.0+/.exec(str)) {
1435 len = match[0].length;
1436 digits = str.slice(len);
1437 candidates.push(digits + "e-" + (digits.length + len - 1));
1438 } else if (match = /0+$/.exec(str)) {
1439 len = match[0].length;
1440 candidates.push(str.slice(0, -len) + "e" + len);
1441 } else if (match = /^(\d)\.(\d+)e(-?\d+)$/.exec(str)) {
1442 candidates.push(match[1] + match[2] + "e" + (match[3] - match[2].length));
1443 }
1444 return best_of(candidates);
1445 }
1446
1447 function make_block(stmt, output) {
1448 if (!stmt || stmt instanceof AST_EmptyStatement)
1449 output.print("{}");
1450 else if (stmt instanceof AST_BlockStatement)
1451 stmt.print(output);
1452 else output.with_block(function() {
1453 output.indent();
1454 stmt.print(output);
1455 output.newline();
1456 });
1457 }
1458
1459 /* -----[ source map generators ]----- */
1460
1461 function DEFMAP(nodetype, generator) {
1462 nodetype.forEach(function(nodetype) {
1463 nodetype.DEFMETHOD("add_source_map", generator);
1464 });
1465 }
1466
1467 DEFMAP([
1468 // We could easily add info for ALL nodes, but it seems to me that
1469 // would be quite wasteful, hence this noop in the base class.
1470 AST_Node,
1471 // since the label symbol will mark it
1472 AST_LabeledStatement,
1473 AST_Toplevel,
1474 ], noop);
1475
1476 // XXX: I'm not exactly sure if we need it for all of these nodes,
1477 // or if we should add even more.
1478 DEFMAP([
1479 AST_Array,
1480 AST_BlockStatement,
1481 AST_Catch,
1482 AST_Constant,
1483 AST_Debugger,
1484 AST_Definitions,
1485 AST_Directive,
1486 AST_Finally,
1487 AST_Jump,
1488 AST_Lambda,
1489 AST_New,
1490 AST_Object,
1491 AST_StatementWithBody,
1492 AST_Symbol,
1493 AST_Switch,
1494 AST_SwitchBranch,
1495 AST_Try,
1496 ], function(output) {
1497 output.add_mapping(this.start);
1498 });
1499
1500 DEFMAP([
1501 AST_ObjectGetter,
1502 AST_ObjectSetter,
1503 ], function(output) {
1504 output.add_mapping(this.start, this.key.name);
1505 });
1506
1507 DEFMAP([ AST_ObjectProperty ], function(output) {
1508 output.add_mapping(this.start, this.key);
1509 });
1510})();