1 /***********************************************************************
3 A JavaScript tokenizer / parser / beautifier / compressor.
4 https://github.com/mishoo/UglifyJS2
6 -------------------------------- (C) ---------------------------------
9 <mihai.bazon@gmail.com>
10 http://mihai.bazon.net/blog
12 Distributed under the BSD license:
14 Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com>
16 Redistribution and use in source and binary forms, with or without
17 modification, are permitted provided that the following conditions
20 * Redistributions of source code must retain the above
21 copyright notice, this list of conditions and the following
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.
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
42 ***********************************************************************/
46 var EXPECT_DIRECTIVE = /^$|[;{][\s\n]*$/;
48 function is_some_comments(comment) {
50 return comment.type == "comment2" && /@preserve|@license|@cc_on/i.test(comment.value);
53 function OutputStream(options) {
55 options = defaults(options, {
61 unescape_regexps : false,
62 inline_script : false,
71 preserve_line : false,
75 keep_quoted_props: false,
79 // Convert comment option to RegExp if neccessary and set up comments filter
80 var comment_filter = return_false; // Default case, throw all comments away
81 if (options.comments) {
82 var comments = options.comments;
83 if (typeof options.comments === "string" && /^\/.*\/[a-zA-Z]*$/.test(options.comments)) {
84 var regex_pos = options.comments.lastIndexOf("/");
85 comments = new RegExp(
86 options.comments.substr(1, regex_pos - 1),
87 options.comments.substr(regex_pos + 1)
90 if (comments instanceof RegExp) {
91 comment_filter = function(comment) {
92 return comment.type != "comment5" && comments.test(comment.value);
95 else if (typeof comments === "function") {
96 comment_filter = function(comment) {
97 return comment.type != "comment5" && comments(this, comment);
100 else if (comments === "some") {
101 comment_filter = is_some_comments;
102 } else { // NOTE includes "all" option
103 comment_filter = return_true;
109 var current_line = 1;
113 function to_ascii(str, identifier) {
114 return str.replace(/[\u0000-\u001f\u007f-\uffff]/g, function(ch) {
115 var code = ch.charCodeAt(0).toString(16);
116 if (code.length <= 2 && !identifier) {
117 while (code.length < 2) code = "0" + code;
120 while (code.length < 4) code = "0" + code;
126 function make_string(str, quote) {
128 str = str.replace(/[\\\b\f\n\r\v\t\x22\x27\u2028\u2029\0\ufeff]/g,
131 case '"': ++dq; return '"';
132 case "'": ++sq; return "'";
133 case "\\": return "\\\\";
134 case "\n": return "\\n";
135 case "\r": return "\\r";
136 case "\t": return "\\t";
137 case "\b": return "\\b";
138 case "\f": return "\\f";
139 case "\x0B": return options.screw_ie8 ? "\\v" : "\\x0B";
140 case "\u2028": return "\\u2028";
141 case "\u2029": return "\\u2029";
142 case "\ufeff": return "\\ufeff";
144 return /[0-7]/.test(str.charAt(i+1)) ? "\\x00" : "\\0";
148 function quote_single() {
149 return "'" + str.replace(/\x27/g, "\\'") + "'";
151 function quote_double() {
152 return '"' + str.replace(/\x22/g, '\\"') + '"';
154 if (options.ascii_only) str = to_ascii(str);
155 switch (options.quote_style) {
157 return quote_single();
159 return quote_double();
161 return quote == "'" ? quote_single() : quote_double();
163 return dq > sq ? quote_single() : quote_double();
167 function encode_string(str, quote) {
168 var ret = make_string(str, quote);
169 if (options.inline_script) {
170 ret = ret.replace(/<\x2fscript([>\/\t\n\f\r ])/gi, "<\\/script$1");
171 ret = ret.replace(/\x3c!--/g, "\\x3c!--");
172 ret = ret.replace(/--\x3e/g, "--\\x3e");
177 function make_name(name) {
178 name = name.toString();
179 if (options.ascii_only)
180 name = to_ascii(name, true);
184 function make_indent(back) {
185 return repeat_string(" ", options.indent_start + indentation - back * options.indent_level);
188 /* -----[ beautification/minification ]----- */
190 var might_need_space = false;
191 var might_need_semicolon = false;
192 var might_add_newline = 0;
195 function last_char() {
196 return last.charAt(last.length - 1);
199 var ensure_line_len = options.max_line_len ? function() {
200 if (current_col > options.max_line_len) {
201 if (might_add_newline) {
202 var left = OUTPUT.slice(0, might_add_newline);
203 var right = OUTPUT.slice(might_add_newline);
204 OUTPUT = left + "\n" + right;
207 current_col = right.length;
209 if (current_col > options.max_line_len) {
210 AST_Node.warn("Output exceeds {max_line_len} characters", options);
213 might_add_newline = 0;
216 var requireSemicolonChars = makePredicate("( [ + * / - , .");
218 function print(str) {
220 var ch = str.charAt(0);
221 if (might_need_semicolon) {
222 might_need_semicolon = false;
224 if ((!ch || ";}".indexOf(ch) < 0) && !/[;]$/.test(last)) {
225 if (options.semicolons || requireSemicolonChars(ch)) {
236 if (/^\s+$/.test(str)) {
237 // reset the semicolon flag, since we didn't print one
238 // now and might still have to later
239 might_need_semicolon = true;
243 if (!options.beautify)
244 might_need_space = false;
248 if (!options.beautify && options.preserve_line && stack[stack.length - 1]) {
249 var target_line = stack[stack.length - 1].start.line;
250 while (current_line < target_line) {
256 might_need_space = false;
260 if (might_need_space) {
261 var prev = last_char();
262 if ((is_identifier_char(prev)
263 && (is_identifier_char(ch) || ch == "\\"))
264 || (ch == "/" && ch == prev)
265 || ((ch == "+" || ch == "-") && ch == last))
271 might_need_space = false;
274 current_pos += str.length;
275 var a = str.split(/\r?\n/), n = a.length - 1;
277 current_col += a[0].length;
280 current_col = a[n].length;
285 var space = options.beautify ? function() {
288 might_need_space = true;
291 var indent = options.beautify ? function(half) {
292 if (options.beautify) {
293 print(make_indent(half ? 0.5 : 0));
297 var with_indent = options.beautify ? function(col, cont) {
298 if (col === true) col = next_indent();
299 var save_indentation = indentation;
302 indentation = save_indentation;
304 } : function(col, cont) { return cont() };
306 var newline = options.beautify ? function() {
308 } : options.max_line_len ? function() {
310 might_add_newline = OUTPUT.length;
313 var semicolon = options.beautify ? function() {
316 might_need_semicolon = true;
319 function force_semicolon() {
320 might_need_semicolon = false;
324 function next_indent() {
325 return indentation + options.indent_level;
328 function with_block(cont) {
332 with_indent(next_indent(), function(){
340 function with_parens(cont) {
342 //XXX: still nice to have that for argument lists
343 //var ret = with_indent(current_col, cont);
349 function with_square(cont) {
351 //var ret = with_indent(current_col, cont);
364 if (options.space_colon) space();
367 var add_mapping = options.source_map ? function(token, name) {
369 if (token) options.source_map.add(
371 current_line, current_col,
372 token.line, token.col,
373 (!name && token.type == "name") ? token.value : name
376 AST_Node.warn("Couldn't figure out mapping for {file}:{line},{col} → {cline},{ccol} [{name}]", {
388 if (might_add_newline) {
399 indentation : function() { return indentation },
400 current_width : function() { return current_col - indentation },
401 should_break : function() { return options.width && this.current_width() >= options.width },
407 last : function() { return last },
408 semicolon : semicolon,
409 force_semicolon : force_semicolon,
411 print_name : function(name) { print(make_name(name)) },
412 print_string : function(str, quote, escape_directive) {
413 var encoded = encode_string(str, quote);
414 if (escape_directive === true && encoded.indexOf("\\") === -1) {
415 // Insert semicolons to break directive prologue
416 if (!EXPECT_DIRECTIVE.test(OUTPUT)) {
423 encode_string : encode_string,
424 next_indent : next_indent,
425 with_indent : with_indent,
426 with_block : with_block,
427 with_parens : with_parens,
428 with_square : with_square,
429 add_mapping : add_mapping,
430 option : function(opt) { return options[opt] },
431 comment_filter : comment_filter,
432 line : function() { return current_line },
433 col : function() { return current_col },
434 pos : function() { return current_pos },
435 push_node : function(node) { stack.push(node) },
436 pop_node : function() { return stack.pop() },
437 parent : function(n) {
438 return stack[stack.length - 2 - (n || 0)];
444 /* -----[ code generators ]----- */
448 /* -----[ utils ]----- */
450 function DEFPRINT(nodetype, generator) {
451 nodetype.DEFMETHOD("_codegen", generator);
455 var in_directive = false;
457 AST_Node.DEFMETHOD("print", function(stream, force_parens){
458 var self = this, generator = self._codegen, prev_use_asm = use_asm;
459 if (self instanceof AST_Directive && self.value == "use asm" && stream.parent() instanceof AST_Scope) {
463 self.add_comments(stream);
464 self.add_source_map(stream);
465 generator(self, stream);
467 stream.push_node(self);
468 if (force_parens || self.needs_parens(stream)) {
469 stream.with_parens(doit);
474 if (self instanceof AST_Scope) {
475 use_asm = prev_use_asm;
479 AST_Node.DEFMETHOD("print_to_string", function(options){
480 var s = OutputStream(options);
481 if (!options) s._readonly = true;
486 /* -----[ comments ]----- */
488 AST_Node.DEFMETHOD("add_comments", function(output){
489 if (output._readonly) return;
491 var start = self.start;
492 if (start && !start._comments_dumped) {
493 start._comments_dumped = true;
494 var comments = start.comments_before || [];
496 // XXX: ugly fix for https://github.com/mishoo/UglifyJS2/issues/112
497 // and https://github.com/mishoo/UglifyJS2/issues/372
498 if (self instanceof AST_Exit && self.value) {
499 self.value.walk(new TreeWalker(function(node){
500 if (node.start && node.start.comments_before) {
501 comments = comments.concat(node.start.comments_before);
502 node.start.comments_before = [];
504 if (node instanceof AST_Function ||
505 node instanceof AST_Array ||
506 node instanceof AST_Object)
508 return true; // don't go inside.
513 if (comments.length > 0 && output.pos() == 0) {
514 if (output.option("shebang") && comments[0].type == "comment5") {
515 output.print("#!" + comments.shift().value + "\n");
518 var preamble = output.option("preamble");
520 output.print(preamble.replace(/\r\n?|[\n\u2028\u2029]|\s*$/g, "\n"));
524 comments = comments.filter(output.comment_filter, self);
526 // Keep single line comments after nlb, after nlb
527 if (!output.option("beautify") && comments.length > 0 &&
528 /comment[134]/.test(comments[0].type) &&
529 output.col() !== 0 && comments[0].nlb)
534 comments.forEach(function(c){
535 if (/comment[134]/.test(c.type)) {
536 output.print("//" + c.value + "\n");
539 else if (c.type == "comment2") {
540 output.print("/*" + c.value + "*/");
552 /* -----[ PARENTHESES ]----- */
554 function PARENS(nodetype, func) {
555 if (Array.isArray(nodetype)) {
556 nodetype.forEach(function(nodetype){
557 PARENS(nodetype, func);
560 nodetype.DEFMETHOD("needs_parens", func);
564 PARENS(AST_Node, function(){
568 // a function expression needs parens around it when it's provably
569 // the first token to appear in a statement.
570 PARENS(AST_Function, function(output){
571 if (first_in_statement(output)) {
575 if (output.option('wrap_iife')) {
576 var p = output.parent();
577 return p instanceof AST_Call && p.expression === this;
583 // same goes for an object literal, because otherwise it would be
584 // interpreted as a block of code.
585 PARENS(AST_Object, function(output){
586 return first_in_statement(output);
589 PARENS([ AST_Unary, AST_Undefined ], function(output){
590 var p = output.parent();
591 return p instanceof AST_PropAccess && p.expression === this
592 || p instanceof AST_Call && p.expression === this;
595 PARENS(AST_Seq, function(output){
596 var p = output.parent();
597 return p instanceof AST_Call // (foo, bar)() or foo(1, (2, 3), 4)
598 || p instanceof AST_Unary // !(foo, bar, baz)
599 || p instanceof AST_Binary // 1 + (2, 3) + 4 ==> 8
600 || p instanceof AST_VarDef // var a = (1, 2), b = a + a; ==> b == 4
601 || p instanceof AST_PropAccess // (1, {foo:2}).foo or (1, {foo:2})["foo"] ==> 2
602 || p instanceof AST_Array // [ 1, (2, 3), 4 ] ==> [ 1, 3, 4 ]
603 || p instanceof AST_ObjectProperty // { foo: (1, 2) }.foo ==> 2
604 || p instanceof AST_Conditional /* (false, true) ? (a = 10, b = 20) : (c = 30)
605 * ==> 20 (side effect, set a := 10 and b := 20) */
609 PARENS(AST_Binary, function(output){
610 var p = output.parent();
612 if (p instanceof AST_Call && p.expression === this)
614 // typeof (foo && bar)
615 if (p instanceof AST_Unary)
617 // (foo && bar)["prop"], (foo && bar).prop
618 if (p instanceof AST_PropAccess && p.expression === this)
620 // this deals with precedence: 3 * (2 + 1)
621 if (p instanceof AST_Binary) {
622 var po = p.operator, pp = PRECEDENCE[po];
623 var so = this.operator, sp = PRECEDENCE[so];
626 && this === p.right)) {
632 PARENS(AST_PropAccess, function(output){
633 var p = output.parent();
634 if (p instanceof AST_New && p.expression === this) {
635 // i.e. new (foo.bar().baz)
637 // if there's one call into this subtree, then we need
638 // parens around it too, otherwise the call will be
639 // interpreted as passing the arguments to the upper New
642 this.walk(new TreeWalker(function(node){
643 if (node instanceof AST_Call) throw p;
646 if (ex !== p) throw ex;
652 PARENS(AST_Call, function(output){
653 var p = output.parent(), p1;
654 if (p instanceof AST_New && p.expression === this)
657 // workaround for Safari bug.
658 // https://bugs.webkit.org/show_bug.cgi?id=123506
659 return this.expression instanceof AST_Function
660 && p instanceof AST_PropAccess
661 && p.expression === this
662 && (p1 = output.parent(1)) instanceof AST_Assign
666 PARENS(AST_New, function(output){
667 var p = output.parent();
668 if (!need_constructor_parens(this, output)
669 && (p instanceof AST_PropAccess // (new Date).getTime(), (new Date)["getTime"]()
670 || p instanceof AST_Call && p.expression === this)) // (new foo)(bar)
674 PARENS(AST_Number, function(output){
675 var p = output.parent();
676 if (p instanceof AST_PropAccess && p.expression === this) {
677 var value = this.getValue();
678 if (value < 0 || /^0/.test(make_num(value))) {
684 PARENS([ AST_Assign, AST_Conditional ], function (output){
685 var p = output.parent();
686 // !(a = false) → true
687 if (p instanceof AST_Unary)
689 // 1 + (a = 2) + 3 → 6, side effect setting a = 2
690 if (p instanceof AST_Binary && !(p instanceof AST_Assign))
692 // (a = func)() —or— new (a = Object)()
693 if (p instanceof AST_Call && p.expression === this)
695 // (a = foo) ? bar : baz
696 if (p instanceof AST_Conditional && p.condition === this)
698 // (a = foo)["prop"] —or— (a = foo).prop
699 if (p instanceof AST_PropAccess && p.expression === this)
703 /* -----[ PRINTERS ]----- */
705 DEFPRINT(AST_Directive, function(self, output){
706 output.print_string(self.value, self.quote);
709 DEFPRINT(AST_Debugger, function(self, output){
710 output.print("debugger");
714 /* -----[ statements ]----- */
716 function display_body(body, is_toplevel, output, allow_directives) {
717 var last = body.length - 1;
718 in_directive = allow_directives;
719 body.forEach(function(stmt, i){
720 if (in_directive === true && !(stmt instanceof AST_Directive ||
721 stmt instanceof AST_EmptyStatement ||
722 (stmt instanceof AST_SimpleStatement && stmt.body instanceof AST_String)
724 in_directive = false;
726 if (!(stmt instanceof AST_EmptyStatement)) {
729 if (!(i == last && is_toplevel)) {
731 if (is_toplevel) output.newline();
734 if (in_directive === true &&
735 stmt instanceof AST_SimpleStatement &&
736 stmt.body instanceof AST_String
738 in_directive = false;
741 in_directive = false;
744 AST_StatementWithBody.DEFMETHOD("_do_print_body", function(output){
745 force_statement(this.body, output);
748 DEFPRINT(AST_Statement, function(self, output){
749 self.body.print(output);
752 DEFPRINT(AST_Toplevel, function(self, output){
753 display_body(self.body, true, output, true);
756 DEFPRINT(AST_LabeledStatement, function(self, output){
757 self.label.print(output);
759 self.body.print(output);
761 DEFPRINT(AST_SimpleStatement, function(self, output){
762 self.body.print(output);
765 function print_bracketed(body, output, allow_directives) {
766 if (body.length > 0) output.with_block(function(){
767 display_body(body, false, output, allow_directives);
769 else output.print("{}");
771 DEFPRINT(AST_BlockStatement, function(self, output){
772 print_bracketed(self.body, output);
774 DEFPRINT(AST_EmptyStatement, function(self, output){
777 DEFPRINT(AST_Do, function(self, output){
780 self._do_print_body(output);
782 output.print("while");
784 output.with_parens(function(){
785 self.condition.print(output);
789 DEFPRINT(AST_While, function(self, output){
790 output.print("while");
792 output.with_parens(function(){
793 self.condition.print(output);
796 self._do_print_body(output);
798 DEFPRINT(AST_For, function(self, output){
801 output.with_parens(function(){
802 if (self.init && !(self.init instanceof AST_EmptyStatement)) {
803 if (self.init instanceof AST_Definitions) {
804 self.init.print(output);
806 parenthesize_for_noin(self.init, output, true);
813 if (self.condition) {
814 self.condition.print(output);
821 self.step.print(output);
825 self._do_print_body(output);
827 DEFPRINT(AST_ForIn, function(self, output){
830 output.with_parens(function(){
831 self.init.print(output);
835 self.object.print(output);
838 self._do_print_body(output);
840 DEFPRINT(AST_With, function(self, output){
841 output.print("with");
843 output.with_parens(function(){
844 self.expression.print(output);
847 self._do_print_body(output);
850 /* -----[ functions ]----- */
851 AST_Lambda.DEFMETHOD("_do_print", function(output, nokeyword){
854 output.print("function");
858 self.name.print(output);
860 output.with_parens(function(){
861 self.argnames.forEach(function(arg, i){
862 if (i) output.comma();
867 print_bracketed(self.body, output, true);
869 DEFPRINT(AST_Lambda, function(self, output){
870 self._do_print(output);
873 /* -----[ exits ]----- */
874 AST_Exit.DEFMETHOD("_do_print", function(output, kind){
878 this.value.print(output);
882 DEFPRINT(AST_Return, function(self, output){
883 self._do_print(output, "return");
885 DEFPRINT(AST_Throw, function(self, output){
886 self._do_print(output, "throw");
889 /* -----[ loop control ]----- */
890 AST_LoopControl.DEFMETHOD("_do_print", function(output, kind){
894 this.label.print(output);
898 DEFPRINT(AST_Break, function(self, output){
899 self._do_print(output, "break");
901 DEFPRINT(AST_Continue, function(self, output){
902 self._do_print(output, "continue");
905 /* -----[ if ]----- */
906 function make_then(self, output) {
907 if (output.option("bracketize")) {
908 make_block(self.body, output);
911 // The squeezer replaces "block"-s that contain only a single
912 // statement with the statement itself; technically, the AST
913 // is correct, but this can create problems when we output an
914 // IF having an ELSE clause where the THEN clause ends in an
915 // IF *without* an ELSE block (then the outer ELSE would refer
916 // to the inner IF). This function checks for this case and
917 // adds the block brackets if needed.
919 return output.force_semicolon();
920 if (self.body instanceof AST_Do) {
921 // Unconditionally use the if/do-while workaround for all browsers.
922 // https://github.com/mishoo/UglifyJS/issues/#issue/57 IE
923 // croaks with "syntax error" on code like this: if (foo)
924 // do ... while(cond); else ... we need block brackets
926 make_block(self.body, output);
931 if (b instanceof AST_If) {
932 if (!b.alternative) {
933 make_block(self.body, output);
938 else if (b instanceof AST_StatementWithBody) {
943 force_statement(self.body, output);
945 DEFPRINT(AST_If, function(self, output){
948 output.with_parens(function(){
949 self.condition.print(output);
952 if (self.alternative) {
953 make_then(self, output);
955 output.print("else");
957 if (self.alternative instanceof AST_If)
958 self.alternative.print(output);
960 force_statement(self.alternative, output);
962 self._do_print_body(output);
966 /* -----[ switch ]----- */
967 DEFPRINT(AST_Switch, function(self, output){
968 output.print("switch");
970 output.with_parens(function(){
971 self.expression.print(output);
974 if (self.body.length > 0) output.with_block(function(){
975 self.body.forEach(function(stmt, i){
976 if (i) output.newline();
981 else output.print("{}");
983 AST_SwitchBranch.DEFMETHOD("_do_print_body", function(output){
984 if (this.body.length > 0) {
986 this.body.forEach(function(stmt){
993 DEFPRINT(AST_Default, function(self, output){
994 output.print("default:");
995 self._do_print_body(output);
997 DEFPRINT(AST_Case, function(self, output){
998 output.print("case");
1000 self.expression.print(output);
1002 self._do_print_body(output);
1005 /* -----[ exceptions ]----- */
1006 DEFPRINT(AST_Try, function(self, output){
1007 output.print("try");
1009 print_bracketed(self.body, output);
1012 self.bcatch.print(output);
1014 if (self.bfinally) {
1016 self.bfinally.print(output);
1019 DEFPRINT(AST_Catch, function(self, output){
1020 output.print("catch");
1022 output.with_parens(function(){
1023 self.argname.print(output);
1026 print_bracketed(self.body, output);
1028 DEFPRINT(AST_Finally, function(self, output){
1029 output.print("finally");
1031 print_bracketed(self.body, output);
1034 /* -----[ var/const ]----- */
1035 AST_Definitions.DEFMETHOD("_do_print", function(output, kind){
1038 this.definitions.forEach(function(def, i){
1039 if (i) output.comma();
1042 var p = output.parent();
1043 var in_for = p instanceof AST_For || p instanceof AST_ForIn;
1044 var avoid_semicolon = in_for && p.init === this;
1045 if (!avoid_semicolon)
1048 DEFPRINT(AST_Var, function(self, output){
1049 self._do_print(output, "var");
1051 DEFPRINT(AST_Const, function(self, output){
1052 self._do_print(output, "const");
1055 function parenthesize_for_noin(node, output, noin) {
1056 if (!noin) node.print(output);
1058 // need to take some precautions here:
1059 // https://github.com/mishoo/UglifyJS2/issues/60
1060 node.walk(new TreeWalker(function(node){
1061 if (node instanceof AST_Binary && node.operator == "in")
1066 if (ex !== output) throw ex;
1067 node.print(output, true);
1071 DEFPRINT(AST_VarDef, function(self, output){
1072 self.name.print(output);
1077 var p = output.parent(1);
1078 var noin = p instanceof AST_For || p instanceof AST_ForIn;
1079 parenthesize_for_noin(self.value, output, noin);
1083 /* -----[ other expressions ]----- */
1084 DEFPRINT(AST_Call, function(self, output){
1085 self.expression.print(output);
1086 if (self instanceof AST_New && !need_constructor_parens(self, output))
1088 output.with_parens(function(){
1089 self.args.forEach(function(expr, i){
1090 if (i) output.comma();
1095 DEFPRINT(AST_New, function(self, output){
1096 output.print("new");
1098 AST_Call.prototype._codegen(self, output);
1101 AST_Seq.DEFMETHOD("_do_print", function(output){
1102 this.car.print(output);
1105 if (output.should_break()) {
1109 this.cdr.print(output);
1112 DEFPRINT(AST_Seq, function(self, output){
1113 self._do_print(output);
1114 // var p = output.parent();
1115 // if (p instanceof AST_Statement) {
1116 // output.with_indent(output.next_indent(), function(){
1117 // self._do_print(output);
1120 // self._do_print(output);
1123 DEFPRINT(AST_Dot, function(self, output){
1124 var expr = self.expression;
1126 if (expr instanceof AST_Number && expr.getValue() >= 0) {
1127 if (!/[xa-f.)]/i.test(output.last())) {
1132 // the name after dot would be mapped about here.
1133 output.add_mapping(self.end);
1134 output.print_name(self.property);
1136 DEFPRINT(AST_Sub, function(self, output){
1137 self.expression.print(output);
1139 self.property.print(output);
1142 DEFPRINT(AST_UnaryPrefix, function(self, output){
1143 var op = self.operator;
1145 if (/^[a-z]/i.test(op)
1146 || (/[+-]$/.test(op)
1147 && self.expression instanceof AST_UnaryPrefix
1148 && /^[+-]/.test(self.expression.operator))) {
1151 self.expression.print(output);
1153 DEFPRINT(AST_UnaryPostfix, function(self, output){
1154 self.expression.print(output);
1155 output.print(self.operator);
1157 DEFPRINT(AST_Binary, function(self, output){
1158 var op = self.operator;
1159 self.left.print(output);
1160 if (op[0] == ">" /* ">>" ">>>" ">" ">=" */
1161 && self.left instanceof AST_UnaryPostfix
1162 && self.left.operator == "--") {
1163 // space is mandatory to avoid outputting -->
1166 // the space is optional depending on "beautify"
1170 if ((op == "<" || op == "<<")
1171 && self.right instanceof AST_UnaryPrefix
1172 && self.right.operator == "!"
1173 && self.right.expression instanceof AST_UnaryPrefix
1174 && self.right.expression.operator == "--") {
1175 // space is mandatory to avoid outputting <!--
1178 // the space is optional depending on "beautify"
1181 self.right.print(output);
1183 DEFPRINT(AST_Conditional, function(self, output){
1184 self.condition.print(output);
1188 self.consequent.print(output);
1191 self.alternative.print(output);
1194 /* -----[ literals ]----- */
1195 DEFPRINT(AST_Array, function(self, output){
1196 output.with_square(function(){
1197 var a = self.elements, len = a.length;
1198 if (len > 0) output.space();
1199 a.forEach(function(exp, i){
1200 if (i) output.comma();
1202 // If the final element is a hole, we need to make sure it
1203 // doesn't look like a trailing comma, by inserting an actual
1205 if (i === len - 1 && exp instanceof AST_Hole)
1208 if (len > 0) output.space();
1211 DEFPRINT(AST_Object, function(self, output){
1212 if (self.properties.length > 0) output.with_block(function(){
1213 self.properties.forEach(function(prop, i){
1223 else output.print("{}");
1225 DEFPRINT(AST_ObjectKeyVal, function(self, output){
1227 var quote = self.quote;
1228 if (output.option("quote_keys")) {
1229 output.print_string(key + "");
1230 } else if ((typeof key == "number"
1231 || !output.option("beautify")
1232 && +key + "" == key)
1233 && parseFloat(key) >= 0) {
1234 output.print(make_num(key));
1235 } else if (RESERVED_WORDS(key) ? output.option("screw_ie8") : is_identifier_string(key)) {
1236 if (quote && output.option("keep_quoted_props")) {
1237 output.print_string(key, quote);
1239 output.print_name(key);
1242 output.print_string(key, quote);
1245 self.value.print(output);
1247 DEFPRINT(AST_ObjectSetter, function(self, output){
1248 output.print("set");
1250 self.key.print(output);
1251 self.value._do_print(output, true);
1253 DEFPRINT(AST_ObjectGetter, function(self, output){
1254 output.print("get");
1256 self.key.print(output);
1257 self.value._do_print(output, true);
1259 DEFPRINT(AST_Symbol, function(self, output){
1260 var def = self.definition();
1261 output.print_name(def ? def.mangled_name || def.name : self.name);
1263 DEFPRINT(AST_Undefined, function(self, output){
1264 output.print("void 0");
1266 DEFPRINT(AST_Hole, noop);
1267 DEFPRINT(AST_Infinity, function(self, output){
1268 output.print("Infinity");
1270 DEFPRINT(AST_NaN, function(self, output){
1271 output.print("NaN");
1273 DEFPRINT(AST_This, function(self, output){
1274 output.print("this");
1276 DEFPRINT(AST_Constant, function(self, output){
1277 output.print(self.getValue());
1279 DEFPRINT(AST_String, function(self, output){
1280 output.print_string(self.getValue(), self.quote, in_directive);
1282 DEFPRINT(AST_Number, function(self, output){
1283 if (use_asm && self.start && self.start.raw != null) {
1284 output.print(self.start.raw);
1286 output.print(make_num(self.getValue()));
1290 function regexp_safe_literal(code) {
1312 0xfeff , // Unicode BOM
1313 0x2028 , // unicode "line separator"
1314 0x2029 , // unicode "paragraph separator"
1315 ].indexOf(code) < 0;
1318 DEFPRINT(AST_RegExp, function(self, output){
1319 var str = self.getValue().toString();
1320 if (output.option("ascii_only")) {
1321 str = output.to_ascii(str);
1322 } else if (output.option("unescape_regexps")) {
1323 str = str.split("\\\\").map(function(str){
1324 return str.replace(/\\u[0-9a-fA-F]{4}|\\x[0-9a-fA-F]{2}/g, function(s){
1325 var code = parseInt(s.substr(2), 16);
1326 return regexp_safe_literal(code) ? String.fromCharCode(code) : s;
1331 var p = output.parent();
1332 if (p instanceof AST_Binary && /^in/.test(p.operator) && p.left === self)
1336 function force_statement(stat, output) {
1337 if (output.option("bracketize")) {
1338 if (!stat || stat instanceof AST_EmptyStatement)
1340 else if (stat instanceof AST_BlockStatement)
1342 else output.with_block(function(){
1348 if (!stat || stat instanceof AST_EmptyStatement)
1349 output.force_semicolon();
1355 // self should be AST_New. decide if we want to show parens or not.
1356 function need_constructor_parens(self, output) {
1357 // Always print parentheses with arguments
1358 if (self.args.length > 0) return true;
1360 return output.option("beautify");
1363 function best_of(a) {
1364 var best = a[0], len = best.length;
1365 for (var i = 1; i < a.length; ++i) {
1366 if (a[i].length < len) {
1374 function make_num(num) {
1375 var str = num.toString(10), a = [ str.replace(/^0\./, ".").replace('e+', 'e') ], m;
1376 if (Math.floor(num) === num) {
1378 a.push("0x" + num.toString(16).toLowerCase(), // probably pointless
1379 "0" + num.toString(8)); // same.
1381 a.push("-0x" + (-num).toString(16).toLowerCase(), // probably pointless
1382 "-0" + (-num).toString(8)); // same.
1384 if ((m = /^(.*?)(0+)$/.exec(num))) {
1385 a.push(m[1] + "e" + m[2].length);
1387 } else if ((m = /^0?\.(0+)(.*)$/.exec(num))) {
1388 a.push(m[2] + "e-" + (m[1].length + m[2].length),
1389 str.substr(str.indexOf(".")));
1394 function make_block(stmt, output) {
1395 if (stmt instanceof AST_BlockStatement) {
1399 output.with_block(function(){
1406 /* -----[ source map generators ]----- */
1408 function DEFMAP(nodetype, generator) {
1409 nodetype.DEFMETHOD("add_source_map", function(stream){
1410 generator(this, stream);
1414 // We could easily add info for ALL nodes, but it seems to me that
1415 // would be quite wasteful, hence this noop in the base class.
1416 DEFMAP(AST_Node, noop);
1418 function basic_sourcemap_gen(self, output) {
1419 output.add_mapping(self.start);
1422 // XXX: I'm not exactly sure if we need it for all of these nodes,
1423 // or if we should add even more.
1425 DEFMAP(AST_Directive, basic_sourcemap_gen);
1426 DEFMAP(AST_Debugger, basic_sourcemap_gen);
1427 DEFMAP(AST_Symbol, basic_sourcemap_gen);
1428 DEFMAP(AST_Jump, basic_sourcemap_gen);
1429 DEFMAP(AST_StatementWithBody, basic_sourcemap_gen);
1430 DEFMAP(AST_LabeledStatement, noop); // since the label symbol will mark it
1431 DEFMAP(AST_Lambda, basic_sourcemap_gen);
1432 DEFMAP(AST_Switch, basic_sourcemap_gen);
1433 DEFMAP(AST_SwitchBranch, basic_sourcemap_gen);
1434 DEFMAP(AST_BlockStatement, basic_sourcemap_gen);
1435 DEFMAP(AST_Toplevel, noop);
1436 DEFMAP(AST_New, basic_sourcemap_gen);
1437 DEFMAP(AST_Try, basic_sourcemap_gen);
1438 DEFMAP(AST_Catch, basic_sourcemap_gen);
1439 DEFMAP(AST_Finally, basic_sourcemap_gen);
1440 DEFMAP(AST_Definitions, basic_sourcemap_gen);
1441 DEFMAP(AST_Constant, basic_sourcemap_gen);
1442 DEFMAP(AST_ObjectSetter, function(self, output){
1443 output.add_mapping(self.start, self.key.name);
1445 DEFMAP(AST_ObjectGetter, function(self, output){
1446 output.add_mapping(self.start, self.key.name);
1448 DEFMAP(AST_ObjectProperty, function(self, output){
1449 output.add_mapping(self.start, self.key);