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