escodegen.js
   1  /*
   2    Copyright (C) 2012-2014 Yusuke Suzuki <utatane.tea@gmail.com>
   3    Copyright (C) 2015 Ingvar Stepanyan <me@rreverser.com>
   4    Copyright (C) 2014 Ivan Nikulin <ifaaan@gmail.com>
   5    Copyright (C) 2012-2013 Michael Ficarra <escodegen.copyright@michael.ficarra.me>
   6    Copyright (C) 2012-2013 Mathias Bynens <mathias@qiwi.be>
   7    Copyright (C) 2013 Irakli Gozalishvili <rfobic@gmail.com>
   8    Copyright (C) 2012 Robert Gust-Bardon <donate@robert.gust-bardon.org>
   9    Copyright (C) 2012 John Freeman <jfreeman08@gmail.com>
  10    Copyright (C) 2011-2012 Ariya Hidayat <ariya.hidayat@gmail.com>
  11    Copyright (C) 2012 Joost-Wim Boekesteijn <joost-wim@boekesteijn.nl>
  12    Copyright (C) 2012 Kris Kowal <kris.kowal@cixar.com>
  13    Copyright (C) 2012 Arpad Borsos <arpad.borsos@googlemail.com>
  14  
  15    Redistribution and use in source and binary forms, with or without
  16    modification, are permitted provided that the following conditions are met:
  17  
  18      * Redistributions of source code must retain the above copyright
  19        notice, this list of conditions and the following disclaimer.
  20      * Redistributions in binary form must reproduce the above copyright
  21        notice, this list of conditions and the following disclaimer in the
  22        documentation and/or other materials provided with the distribution.
  23  
  24    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  25    AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  26    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  27    ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
  28    DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  29    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  30    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  31    ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  32    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  33    THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  34  */
  35  
  36  /*global exports:true, require:true, global:true*/
  37  (function () {
  38      'use strict';
  39  
  40      var Syntax,
  41          Precedence,
  42          BinaryPrecedence,
  43          SourceNode,
  44          estraverse,
  45          esutils,
  46          base,
  47          indent,
  48          json,
  49          renumber,
  50          hexadecimal,
  51          quotes,
  52          escapeless,
  53          newline,
  54          space,
  55          parentheses,
  56          semicolons,
  57          safeConcatenation,
  58          directive,
  59          extra,
  60          parse,
  61          sourceMap,
  62          sourceCode,
  63          preserveBlankLines,
  64          FORMAT_MINIFY,
  65          FORMAT_DEFAULTS;
  66  
  67      estraverse = require('estraverse');
  68      esutils = require('esutils');
  69  
  70      Syntax = estraverse.Syntax;
  71  
  72      // Generation is done by generateExpression.
  73      function isExpression(node) {
  74          return CodeGenerator.Expression.hasOwnProperty(node.type);
  75      }
  76  
  77      // Generation is done by generateStatement.
  78      function isStatement(node) {
  79          return CodeGenerator.Statement.hasOwnProperty(node.type);
  80      }
  81  
  82      Precedence = {
  83          Sequence: 0,
  84          Yield: 1,
  85          Assignment: 1,
  86          Conditional: 2,
  87          ArrowFunction: 2,
  88          LogicalOR: 3,
  89          LogicalAND: 4,
  90          BitwiseOR: 5,
  91          BitwiseXOR: 6,
  92          BitwiseAND: 7,
  93          Equality: 8,
  94          Relational: 9,
  95          BitwiseSHIFT: 10,
  96          Additive: 11,
  97          Multiplicative: 12,
  98          Exponentiation: 13,
  99          Await: 14,
 100          Unary: 14,
 101          Postfix: 15,
 102          Call: 16,
 103          New: 17,
 104          TaggedTemplate: 18,
 105          Member: 19,
 106          Primary: 20
 107      };
 108  
 109      BinaryPrecedence = {
 110          '||': Precedence.LogicalOR,
 111          '&&': Precedence.LogicalAND,
 112          '|': Precedence.BitwiseOR,
 113          '^': Precedence.BitwiseXOR,
 114          '&': Precedence.BitwiseAND,
 115          '==': Precedence.Equality,
 116          '!=': Precedence.Equality,
 117          '===': Precedence.Equality,
 118          '!==': Precedence.Equality,
 119          'is': Precedence.Equality,
 120          'isnt': Precedence.Equality,
 121          '<': Precedence.Relational,
 122          '>': Precedence.Relational,
 123          '<=': Precedence.Relational,
 124          '>=': Precedence.Relational,
 125          'in': Precedence.Relational,
 126          'instanceof': Precedence.Relational,
 127          '<<': Precedence.BitwiseSHIFT,
 128          '>>': Precedence.BitwiseSHIFT,
 129          '>>>': Precedence.BitwiseSHIFT,
 130          '+': Precedence.Additive,
 131          '-': Precedence.Additive,
 132          '*': Precedence.Multiplicative,
 133          '%': Precedence.Multiplicative,
 134          '/': Precedence.Multiplicative,
 135          '**': Precedence.Exponentiation
 136      };
 137  
 138      //Flags
 139      var F_ALLOW_IN = 1,
 140          F_ALLOW_CALL = 1 << 1,
 141          F_ALLOW_UNPARATH_NEW = 1 << 2,
 142          F_FUNC_BODY = 1 << 3,
 143          F_DIRECTIVE_CTX = 1 << 4,
 144          F_SEMICOLON_OPT = 1 << 5;
 145  
 146      //Expression flag sets
 147      //NOTE: Flag order:
 148      // F_ALLOW_IN
 149      // F_ALLOW_CALL
 150      // F_ALLOW_UNPARATH_NEW
 151      var E_FTT = F_ALLOW_CALL | F_ALLOW_UNPARATH_NEW,
 152          E_TTF = F_ALLOW_IN | F_ALLOW_CALL,
 153          E_TTT = F_ALLOW_IN | F_ALLOW_CALL | F_ALLOW_UNPARATH_NEW,
 154          E_TFF = F_ALLOW_IN,
 155          E_FFT = F_ALLOW_UNPARATH_NEW,
 156          E_TFT = F_ALLOW_IN | F_ALLOW_UNPARATH_NEW;
 157  
 158      //Statement flag sets
 159      //NOTE: Flag order:
 160      // F_ALLOW_IN
 161      // F_FUNC_BODY
 162      // F_DIRECTIVE_CTX
 163      // F_SEMICOLON_OPT
 164      var S_TFFF = F_ALLOW_IN,
 165          S_TFFT = F_ALLOW_IN | F_SEMICOLON_OPT,
 166          S_FFFF = 0x00,
 167          S_TFTF = F_ALLOW_IN | F_DIRECTIVE_CTX,
 168          S_TTFF = F_ALLOW_IN | F_FUNC_BODY;
 169  
 170      function getDefaultOptions() {
 171          // default options
 172          return {
 173              indent: null,
 174              base: null,
 175              parse: null,
 176              comment: false,
 177              format: {
 178                  indent: {
 179                      style: '    ',
 180                      base: 0,
 181                      adjustMultilineComment: false
 182                  },
 183                  newline: '\n',
 184                  space: ' ',
 185                  json: false,
 186                  renumber: false,
 187                  hexadecimal: false,
 188                  quotes: 'single',
 189                  escapeless: false,
 190                  compact: false,
 191                  parentheses: true,
 192                  semicolons: true,
 193                  safeConcatenation: false,
 194                  preserveBlankLines: false
 195              },
 196              moz: {
 197                  comprehensionExpressionStartsWithAssignment: false,
 198                  starlessGenerator: false
 199              },
 200              sourceMap: null,
 201              sourceMapRoot: null,
 202              sourceMapWithCode: false,
 203              directive: false,
 204              raw: true,
 205              verbatim: null,
 206              sourceCode: null
 207          };
 208      }
 209  
 210      function stringRepeat(str, num) {
 211          var result = '';
 212  
 213          for (num |= 0; num > 0; num >>>= 1, str += str) {
 214              if (num & 1) {
 215                  result += str;
 216              }
 217          }
 218  
 219          return result;
 220      }
 221  
 222      function hasLineTerminator(str) {
 223          return (/[\r\n]/g).test(str);
 224      }
 225  
 226      function endsWithLineTerminator(str) {
 227          var len = str.length;
 228          return len && esutils.code.isLineTerminator(str.charCodeAt(len - 1));
 229      }
 230  
 231      function merge(target, override) {
 232          var key;
 233          for (key in override) {
 234              if (override.hasOwnProperty(key)) {
 235                  target[key] = override[key];
 236              }
 237          }
 238          return target;
 239      }
 240  
 241      function updateDeeply(target, override) {
 242          var key, val;
 243  
 244          function isHashObject(target) {
 245              return typeof target === 'object' && target instanceof Object && !(target instanceof RegExp);
 246          }
 247  
 248          for (key in override) {
 249              if (override.hasOwnProperty(key)) {
 250                  val = override[key];
 251                  if (isHashObject(val)) {
 252                      if (isHashObject(target[key])) {
 253                          updateDeeply(target[key], val);
 254                      } else {
 255                          target[key] = updateDeeply({}, val);
 256                      }
 257                  } else {
 258                      target[key] = val;
 259                  }
 260              }
 261          }
 262          return target;
 263      }
 264  
 265      function generateNumber(value) {
 266          var result, point, temp, exponent, pos;
 267  
 268          if (value !== value) {
 269              throw new Error('Numeric literal whose value is NaN');
 270          }
 271          if (value < 0 || (value === 0 && 1 / value < 0)) {
 272              throw new Error('Numeric literal whose value is negative');
 273          }
 274  
 275          if (value === 1 / 0) {
 276              return json ? 'null' : renumber ? '1e400' : '1e+400';
 277          }
 278  
 279          result = '' + value;
 280          if (!renumber || result.length < 3) {
 281              return result;
 282          }
 283  
 284          point = result.indexOf('.');
 285          if (!json && result.charCodeAt(0) === 0x30  /* 0 */ && point === 1) {
 286              point = 0;
 287              result = result.slice(1);
 288          }
 289          temp = result;
 290          result = result.replace('e+', 'e');
 291          exponent = 0;
 292          if ((pos = temp.indexOf('e')) > 0) {
 293              exponent = +temp.slice(pos + 1);
 294              temp = temp.slice(0, pos);
 295          }
 296          if (point >= 0) {
 297              exponent -= temp.length - point - 1;
 298              temp = +(temp.slice(0, point) + temp.slice(point + 1)) + '';
 299          }
 300          pos = 0;
 301          while (temp.charCodeAt(temp.length + pos - 1) === 0x30  /* 0 */) {
 302              --pos;
 303          }
 304          if (pos !== 0) {
 305              exponent -= pos;
 306              temp = temp.slice(0, pos);
 307          }
 308          if (exponent !== 0) {
 309              temp += 'e' + exponent;
 310          }
 311          if ((temp.length < result.length ||
 312                      (hexadecimal && value > 1e12 && Math.floor(value) === value && (temp = '0x' + value.toString(16)).length < result.length)) &&
 313                  +temp === value) {
 314              result = temp;
 315          }
 316  
 317          return result;
 318      }
 319  
 320      // Generate valid RegExp expression.
 321      // This function is based on https://github.com/Constellation/iv Engine
 322  
 323      function escapeRegExpCharacter(ch, previousIsBackslash) {
 324          // not handling '\' and handling \u2028 or \u2029 to unicode escape sequence
 325          if ((ch & ~1) === 0x2028) {
 326              return (previousIsBackslash ? 'u' : '\\u') + ((ch === 0x2028) ? '2028' : '2029');
 327          } else if (ch === 10 || ch === 13) {  // \n, \r
 328              return (previousIsBackslash ? '' : '\\') + ((ch === 10) ? 'n' : 'r');
 329          }
 330          return String.fromCharCode(ch);
 331      }
 332  
 333      function generateRegExp(reg) {
 334          var match, result, flags, i, iz, ch, characterInBrack, previousIsBackslash;
 335  
 336          result = reg.toString();
 337  
 338          if (reg.source) {
 339              // extract flag from toString result
 340              match = result.match(/\/([^/]*)$/);
 341              if (!match) {
 342                  return result;
 343              }
 344  
 345              flags = match[1];
 346              result = '';
 347  
 348              characterInBrack = false;
 349              previousIsBackslash = false;
 350              for (i = 0, iz = reg.source.length; i < iz; ++i) {
 351                  ch = reg.source.charCodeAt(i);
 352  
 353                  if (!previousIsBackslash) {
 354                      if (characterInBrack) {
 355                          if (ch === 93) {  // ]
 356                              characterInBrack = false;
 357                          }
 358                      } else {
 359                          if (ch === 47) {  // /
 360                              result += '\\';
 361                          } else if (ch === 91) {  // [
 362                              characterInBrack = true;
 363                          }
 364                      }
 365                      result += escapeRegExpCharacter(ch, previousIsBackslash);
 366                      previousIsBackslash = ch === 92;  // \
 367                  } else {
 368                      // if new RegExp("\\\n') is provided, create /\n/
 369                      result += escapeRegExpCharacter(ch, previousIsBackslash);
 370                      // prevent like /\\[/]/
 371                      previousIsBackslash = false;
 372                  }
 373              }
 374  
 375              return '/' + result + '/' + flags;
 376          }
 377  
 378          return result;
 379      }
 380  
 381      function escapeAllowedCharacter(code, next) {
 382          var hex;
 383  
 384          if (code === 0x08  /* \b */) {
 385              return '\\b';
 386          }
 387  
 388          if (code === 0x0C  /* \f */) {
 389              return '\\f';
 390          }
 391  
 392          if (code === 0x09  /* \t */) {
 393              return '\\t';
 394          }
 395  
 396          hex = code.toString(16).toUpperCase();
 397          if (json || code > 0xFF) {
 398              return '\\u' + '0000'.slice(hex.length) + hex;
 399          } else if (code === 0x0000 && !esutils.code.isDecimalDigit(next)) {
 400              return '\\0';
 401          } else if (code === 0x000B  /* \v */) { // '\v'
 402              return '\\x0B';
 403          } else {
 404              return '\\x' + '00'.slice(hex.length) + hex;
 405          }
 406      }
 407  
 408      function escapeDisallowedCharacter(code) {
 409          if (code === 0x5C  /* \ */) {
 410              return '\\\\';
 411          }
 412  
 413          if (code === 0x0A  /* \n */) {
 414              return '\\n';
 415          }
 416  
 417          if (code === 0x0D  /* \r */) {
 418              return '\\r';
 419          }
 420  
 421          if (code === 0x2028) {
 422              return '\\u2028';
 423          }
 424  
 425          if (code === 0x2029) {
 426              return '\\u2029';
 427          }
 428  
 429          throw new Error('Incorrectly classified character');
 430      }
 431  
 432      function escapeDirective(str) {
 433          var i, iz, code, quote;
 434  
 435          quote = quotes === 'double' ? '"' : '\'';
 436          for (i = 0, iz = str.length; i < iz; ++i) {
 437              code = str.charCodeAt(i);
 438              if (code === 0x27  /* ' */) {
 439                  quote = '"';
 440                  break;
 441              } else if (code === 0x22  /* " */) {
 442                  quote = '\'';
 443                  break;
 444              } else if (code === 0x5C  /* \ */) {
 445                  ++i;
 446              }
 447          }
 448  
 449          return quote + str + quote;
 450      }
 451  
 452      function escapeString(str) {
 453          var result = '', i, len, code, singleQuotes = 0, doubleQuotes = 0, single, quote;
 454  
 455          for (i = 0, len = str.length; i < len; ++i) {
 456              code = str.charCodeAt(i);
 457              if (code === 0x27  /* ' */) {
 458                  ++singleQuotes;
 459              } else if (code === 0x22  /* " */) {
 460                  ++doubleQuotes;
 461              } else if (code === 0x2F  /* / */ && json) {
 462                  result += '\\';
 463              } else if (esutils.code.isLineTerminator(code) || code === 0x5C  /* \ */) {
 464                  result += escapeDisallowedCharacter(code);
 465                  continue;
 466              } else if (!esutils.code.isIdentifierPartES5(code) && (json && code < 0x20  /* SP */ || !json && !escapeless && (code < 0x20  /* SP */ || code > 0x7E  /* ~ */))) {
 467                  result += escapeAllowedCharacter(code, str.charCodeAt(i + 1));
 468                  continue;
 469              }
 470              result += String.fromCharCode(code);
 471          }
 472  
 473          single = !(quotes === 'double' || (quotes === 'auto' && doubleQuotes < singleQuotes));
 474          quote = single ? '\'' : '"';
 475  
 476          if (!(single ? singleQuotes : doubleQuotes)) {
 477              return quote + result + quote;
 478          }
 479  
 480          str = result;
 481          result = quote;
 482  
 483          for (i = 0, len = str.length; i < len; ++i) {
 484              code = str.charCodeAt(i);
 485              if ((code === 0x27  /* ' */ && single) || (code === 0x22  /* " */ && !single)) {
 486                  result += '\\';
 487              }
 488              result += String.fromCharCode(code);
 489          }
 490  
 491          return result + quote;
 492      }
 493  
 494      /**
 495       * flatten an array to a string, where the array can contain
 496       * either strings or nested arrays
 497       */
 498      function flattenToString(arr) {
 499          var i, iz, elem, result = '';
 500          for (i = 0, iz = arr.length; i < iz; ++i) {
 501              elem = arr[i];
 502              result += Array.isArray(elem) ? flattenToString(elem) : elem;
 503          }
 504          return result;
 505      }
 506  
 507      /**
 508       * convert generated to a SourceNode when source maps are enabled.
 509       */
 510      function toSourceNodeWhenNeeded(generated, node) {
 511          if (!sourceMap) {
 512              // with no source maps, generated is either an
 513              // array or a string.  if an array, flatten it.
 514              // if a string, just return it
 515              if (Array.isArray(generated)) {
 516                  return flattenToString(generated);
 517              } else {
 518                  return generated;
 519              }
 520          }
 521          if (node == null) {
 522              if (generated instanceof SourceNode) {
 523                  return generated;
 524              } else {
 525                  node = {};
 526              }
 527          }
 528          if (node.loc == null) {
 529              return new SourceNode(null, null, sourceMap, generated, node.name || null);
 530          }
 531          return new SourceNode(node.loc.start.line, node.loc.start.column, (sourceMap === true ? node.loc.source || null : sourceMap), generated, node.name || null);
 532      }
 533  
 534      function noEmptySpace() {
 535          return (space) ? space : ' ';
 536      }
 537  
 538      function join(left, right) {
 539          var leftSource,
 540              rightSource,
 541              leftCharCode,
 542              rightCharCode;
 543  
 544          leftSource = toSourceNodeWhenNeeded(left).toString();
 545          if (leftSource.length === 0) {
 546              return [right];
 547          }
 548  
 549          rightSource = toSourceNodeWhenNeeded(right).toString();
 550          if (rightSource.length === 0) {
 551              return [left];
 552          }
 553  
 554          leftCharCode = leftSource.charCodeAt(leftSource.length - 1);
 555          rightCharCode = rightSource.charCodeAt(0);
 556  
 557          if ((leftCharCode === 0x2B  /* + */ || leftCharCode === 0x2D  /* - */) && leftCharCode === rightCharCode ||
 558              esutils.code.isIdentifierPartES5(leftCharCode) && esutils.code.isIdentifierPartES5(rightCharCode) ||
 559              leftCharCode === 0x2F  /* / */ && rightCharCode === 0x69  /* i */) { // infix word operators all start with `i`
 560              return [left, noEmptySpace(), right];
 561          } else if (esutils.code.isWhiteSpace(leftCharCode) || esutils.code.isLineTerminator(leftCharCode) ||
 562                  esutils.code.isWhiteSpace(rightCharCode) || esutils.code.isLineTerminator(rightCharCode)) {
 563              return [left, right];
 564          }
 565          return [left, space, right];
 566      }
 567  
 568      function addIndent(stmt) {
 569          return [base, stmt];
 570      }
 571  
 572      function withIndent(fn) {
 573          var previousBase;
 574          previousBase = base;
 575          base += indent;
 576          fn(base);
 577          base = previousBase;
 578      }
 579  
 580      function calculateSpaces(str) {
 581          var i;
 582          for (i = str.length - 1; i >= 0; --i) {
 583              if (esutils.code.isLineTerminator(str.charCodeAt(i))) {
 584                  break;
 585              }
 586          }
 587          return (str.length - 1) - i;
 588      }
 589  
 590      function adjustMultilineComment(value, specialBase) {
 591          var array, i, len, line, j, spaces, previousBase, sn;
 592  
 593          array = value.split(/\r\n|[\r\n]/);
 594          spaces = Number.MAX_VALUE;
 595  
 596          // first line doesn't have indentation
 597          for (i = 1, len = array.length; i < len; ++i) {
 598              line = array[i];
 599              j = 0;
 600              while (j < line.length && esutils.code.isWhiteSpace(line.charCodeAt(j))) {
 601                  ++j;
 602              }
 603              if (spaces > j) {
 604                  spaces = j;
 605              }
 606          }
 607  
 608          if (typeof specialBase !== 'undefined') {
 609              // pattern like
 610              // {
 611              //   var t = 20;  /*
 612              //                 * this is comment
 613              //                 */
 614              // }
 615              previousBase = base;
 616              if (array[1][spaces] === '*') {
 617                  specialBase += ' ';
 618              }
 619              base = specialBase;
 620          } else {
 621              if (spaces & 1) {
 622                  // /*
 623                  //  *
 624                  //  */
 625                  // If spaces are odd number, above pattern is considered.
 626                  // We waste 1 space.
 627                  --spaces;
 628              }
 629              previousBase = base;
 630          }
 631  
 632          for (i = 1, len = array.length; i < len; ++i) {
 633              sn = toSourceNodeWhenNeeded(addIndent(array[i].slice(spaces)));
 634              array[i] = sourceMap ? sn.join('') : sn;
 635          }
 636  
 637          base = previousBase;
 638  
 639          return array.join('\n');
 640      }
 641  
 642      function generateComment(comment, specialBase) {
 643          if (comment.type === 'Line') {
 644              if (endsWithLineTerminator(comment.value)) {
 645                  return '//' + comment.value;
 646              } else {
 647                  // Always use LineTerminator
 648                  var result = '//' + comment.value;
 649                  if (!preserveBlankLines) {
 650                      result += '\n';
 651                  }
 652                  return result;
 653              }
 654          }
 655          if (extra.format.indent.adjustMultilineComment && /[\n\r]/.test(comment.value)) {
 656              return adjustMultilineComment('/*' + comment.value + '*/', specialBase);
 657          }
 658          return '/*' + comment.value + '*/';
 659      }
 660  
 661      function addComments(stmt, result) {
 662          var i, len, comment, save, tailingToStatement, specialBase, fragment,
 663              extRange, range, prevRange, prefix, infix, suffix, count;
 664  
 665          if (stmt.leadingComments && stmt.leadingComments.length > 0) {
 666              save = result;
 667  
 668              if (preserveBlankLines) {
 669                  comment = stmt.leadingComments[0];
 670                  result = [];
 671  
 672                  extRange = comment.extendedRange;
 673                  range = comment.range;
 674  
 675                  prefix = sourceCode.substring(extRange[0], range[0]);
 676                  count = (prefix.match(/\n/g) || []).length;
 677                  if (count > 0) {
 678                      result.push(stringRepeat('\n', count));
 679                      result.push(addIndent(generateComment(comment)));
 680                  } else {
 681                      result.push(prefix);
 682                      result.push(generateComment(comment));
 683                  }
 684  
 685                  prevRange = range;
 686  
 687                  for (i = 1, len = stmt.leadingComments.length; i < len; i++) {
 688                      comment = stmt.leadingComments[i];
 689                      range = comment.range;
 690  
 691                      infix = sourceCode.substring(prevRange[1], range[0]);
 692                      count = (infix.match(/\n/g) || []).length;
 693                      result.push(stringRepeat('\n', count));
 694                      result.push(addIndent(generateComment(comment)));
 695  
 696                      prevRange = range;
 697                  }
 698  
 699                  suffix = sourceCode.substring(range[1], extRange[1]);
 700                  count = (suffix.match(/\n/g) || []).length;
 701                  result.push(stringRepeat('\n', count));
 702              } else {
 703                  comment = stmt.leadingComments[0];
 704                  result = [];
 705                  if (safeConcatenation && stmt.type === Syntax.Program && stmt.body.length === 0) {
 706                      result.push('\n');
 707                  }
 708                  result.push(generateComment(comment));
 709                  if (!endsWithLineTerminator(toSourceNodeWhenNeeded(result).toString())) {
 710                      result.push('\n');
 711                  }
 712  
 713                  for (i = 1, len = stmt.leadingComments.length; i < len; ++i) {
 714                      comment = stmt.leadingComments[i];
 715                      fragment = [generateComment(comment)];
 716                      if (!endsWithLineTerminator(toSourceNodeWhenNeeded(fragment).toString())) {
 717                          fragment.push('\n');
 718                      }
 719                      result.push(addIndent(fragment));
 720                  }
 721              }
 722  
 723              result.push(addIndent(save));
 724          }
 725  
 726          if (stmt.trailingComments) {
 727  
 728              if (preserveBlankLines) {
 729                  comment = stmt.trailingComments[0];
 730                  extRange = comment.extendedRange;
 731                  range = comment.range;
 732  
 733                  prefix = sourceCode.substring(extRange[0], range[0]);
 734                  count = (prefix.match(/\n/g) || []).length;
 735  
 736                  if (count > 0) {
 737                      result.push(stringRepeat('\n', count));
 738                      result.push(addIndent(generateComment(comment)));
 739                  } else {
 740                      result.push(prefix);
 741                      result.push(generateComment(comment));
 742                  }
 743              } else {
 744                  tailingToStatement = !endsWithLineTerminator(toSourceNodeWhenNeeded(result).toString());
 745                  specialBase = stringRepeat(' ', calculateSpaces(toSourceNodeWhenNeeded([base, result, indent]).toString()));
 746                  for (i = 0, len = stmt.trailingComments.length; i < len; ++i) {
 747                      comment = stmt.trailingComments[i];
 748                      if (tailingToStatement) {
 749                          // We assume target like following script
 750                          //
 751                          // var t = 20;  /**
 752                          //               * This is comment of t
 753                          //               */
 754                          if (i === 0) {
 755                              // first case
 756                              result = [result, indent];
 757                          } else {
 758                              result = [result, specialBase];
 759                          }
 760                          result.push(generateComment(comment, specialBase));
 761                      } else {
 762                          result = [result, addIndent(generateComment(comment))];
 763                      }
 764                      if (i !== len - 1 && !endsWithLineTerminator(toSourceNodeWhenNeeded(result).toString())) {
 765                          result = [result, '\n'];
 766                      }
 767                  }
 768              }
 769          }
 770  
 771          return result;
 772      }
 773  
 774      function generateBlankLines(start, end, result) {
 775          var j, newlineCount = 0;
 776  
 777          for (j = start; j < end; j++) {
 778              if (sourceCode[j] === '\n') {
 779                  newlineCount++;
 780              }
 781          }
 782  
 783          for (j = 1; j < newlineCount; j++) {
 784              result.push(newline);
 785          }
 786      }
 787  
 788      function parenthesize(text, current, should) {
 789          if (current < should) {
 790              return ['(', text, ')'];
 791          }
 792          return text;
 793      }
 794  
 795      function generateVerbatimString(string) {
 796          var i, iz, result;
 797          result = string.split(/\r\n|\n/);
 798          for (i = 1, iz = result.length; i < iz; i++) {
 799              result[i] = newline + base + result[i];
 800          }
 801          return result;
 802      }
 803  
 804      function generateVerbatim(expr, precedence) {
 805          var verbatim, result, prec;
 806          verbatim = expr[extra.verbatim];
 807  
 808          if (typeof verbatim === 'string') {
 809              result = parenthesize(generateVerbatimString(verbatim), Precedence.Sequence, precedence);
 810          } else {
 811              // verbatim is object
 812              result = generateVerbatimString(verbatim.content);
 813              prec = (verbatim.precedence != null) ? verbatim.precedence : Precedence.Sequence;
 814              result = parenthesize(result, prec, precedence);
 815          }
 816  
 817          return toSourceNodeWhenNeeded(result, expr);
 818      }
 819  
 820      function CodeGenerator() {
 821      }
 822  
 823      // Helpers.
 824  
 825      CodeGenerator.prototype.maybeBlock = function(stmt, flags) {
 826          var result, noLeadingComment, that = this;
 827  
 828          noLeadingComment = !extra.comment || !stmt.leadingComments;
 829  
 830          if (stmt.type === Syntax.BlockStatement && noLeadingComment) {
 831              return [space, this.generateStatement(stmt, flags)];
 832          }
 833  
 834          if (stmt.type === Syntax.EmptyStatement && noLeadingComment) {
 835              return ';';
 836          }
 837  
 838          withIndent(function () {
 839              result = [
 840                  newline,
 841                  addIndent(that.generateStatement(stmt, flags))
 842              ];
 843          });
 844  
 845          return result;
 846      };
 847  
 848      CodeGenerator.prototype.maybeBlockSuffix = function (stmt, result) {
 849          var ends = endsWithLineTerminator(toSourceNodeWhenNeeded(result).toString());
 850          if (stmt.type === Syntax.BlockStatement && (!extra.comment || !stmt.leadingComments) && !ends) {
 851              return [result, space];
 852          }
 853          if (ends) {
 854              return [result, base];
 855          }
 856          return [result, newline, base];
 857      };
 858  
 859      function generateIdentifier(node) {
 860          return toSourceNodeWhenNeeded(node.name, node);
 861      }
 862  
 863      function generateAsyncPrefix(node, spaceRequired) {
 864          return node.async ? 'async' + (spaceRequired ? noEmptySpace() : space) : '';
 865      }
 866  
 867      function generateStarSuffix(node) {
 868          var isGenerator = node.generator && !extra.moz.starlessGenerator;
 869          return isGenerator ? '*' + space : '';
 870      }
 871  
 872      function generateMethodPrefix(prop) {
 873          var func = prop.value, prefix = '';
 874          if (func.async) {
 875              prefix += generateAsyncPrefix(func, !prop.computed);
 876          }
 877          if (func.generator) {
 878              // avoid space before method name
 879              prefix += generateStarSuffix(func) ? '*' : '';
 880          }
 881          return prefix;
 882      }
 883  
 884      CodeGenerator.prototype.generatePattern = function (node, precedence, flags) {
 885          if (node.type === Syntax.Identifier) {
 886              return generateIdentifier(node);
 887          }
 888          return this.generateExpression(node, precedence, flags);
 889      };
 890  
 891      CodeGenerator.prototype.generateFunctionParams = function (node) {
 892          var i, iz, result, hasDefault;
 893  
 894          hasDefault = false;
 895  
 896          if (node.type === Syntax.ArrowFunctionExpression &&
 897                  !node.rest && (!node.defaults || node.defaults.length === 0) &&
 898                  node.params.length === 1 && node.params[0].type === Syntax.Identifier) {
 899              // arg => { } case
 900              result = [generateAsyncPrefix(node, true), generateIdentifier(node.params[0])];
 901          } else {
 902              result = node.type === Syntax.ArrowFunctionExpression ? [generateAsyncPrefix(node, false)] : [];
 903              result.push('(');
 904              if (node.defaults) {
 905                  hasDefault = true;
 906              }
 907              for (i = 0, iz = node.params.length; i < iz; ++i) {
 908                  if (hasDefault && node.defaults[i]) {
 909                      // Handle default values.
 910                      result.push(this.generateAssignment(node.params[i], node.defaults[i], '=', Precedence.Assignment, E_TTT));
 911                  } else {
 912                      result.push(this.generatePattern(node.params[i], Precedence.Assignment, E_TTT));
 913                  }
 914                  if (i + 1 < iz) {
 915                      result.push(',' + space);
 916                  }
 917              }
 918  
 919              if (node.rest) {
 920                  if (node.params.length) {
 921                      result.push(',' + space);
 922                  }
 923                  result.push('...');
 924                  result.push(generateIdentifier(node.rest));
 925              }
 926  
 927              result.push(')');
 928          }
 929  
 930          return result;
 931      };
 932  
 933      CodeGenerator.prototype.generateFunctionBody = function (node) {
 934          var result, expr;
 935  
 936          result = this.generateFunctionParams(node);
 937  
 938          if (node.type === Syntax.ArrowFunctionExpression) {
 939              result.push(space);
 940              result.push('=>');
 941          }
 942  
 943          if (node.expression) {
 944              result.push(space);
 945              expr = this.generateExpression(node.body, Precedence.Assignment, E_TTT);
 946              if (expr.toString().charAt(0) === '{') {
 947                  expr = ['(', expr, ')'];
 948              }
 949              result.push(expr);
 950          } else {
 951              result.push(this.maybeBlock(node.body, S_TTFF));
 952          }
 953  
 954          return result;
 955      };
 956  
 957      CodeGenerator.prototype.generateIterationForStatement = function (operator, stmt, flags) {
 958          var result = ['for' + (stmt.await ? noEmptySpace() + 'await' : '') + space + '('], that = this;
 959          withIndent(function () {
 960              if (stmt.left.type === Syntax.VariableDeclaration) {
 961                  withIndent(function () {
 962                      result.push(stmt.left.kind + noEmptySpace());
 963                      result.push(that.generateStatement(stmt.left.declarations[0], S_FFFF));
 964                  });
 965              } else {
 966                  result.push(that.generateExpression(stmt.left, Precedence.Call, E_TTT));
 967              }
 968  
 969              result = join(result, operator);
 970              result = [join(
 971                  result,
 972                  that.generateExpression(stmt.right, Precedence.Assignment, E_TTT)
 973              ), ')'];
 974          });
 975          result.push(this.maybeBlock(stmt.body, flags));
 976          return result;
 977      };
 978  
 979      CodeGenerator.prototype.generatePropertyKey = function (expr, computed) {
 980          var result = [];
 981  
 982          if (computed) {
 983              result.push('[');
 984          }
 985  
 986          result.push(this.generateExpression(expr, Precedence.Assignment, E_TTT));
 987  
 988          if (computed) {
 989              result.push(']');
 990          }
 991  
 992          return result;
 993      };
 994  
 995      CodeGenerator.prototype.generateAssignment = function (left, right, operator, precedence, flags) {
 996          if (Precedence.Assignment < precedence) {
 997              flags |= F_ALLOW_IN;
 998          }
 999  
1000          return parenthesize(
1001              [
1002                  this.generateExpression(left, Precedence.Call, flags),
1003                  space + operator + space,
1004                  this.generateExpression(right, Precedence.Assignment, flags)
1005              ],
1006              Precedence.Assignment,
1007              precedence
1008          );
1009      };
1010  
1011      CodeGenerator.prototype.semicolon = function (flags) {
1012          if (!semicolons && flags & F_SEMICOLON_OPT) {
1013              return '';
1014          }
1015          return ';';
1016      };
1017  
1018      // Statements.
1019  
1020      CodeGenerator.Statement = {
1021  
1022          BlockStatement: function (stmt, flags) {
1023              var range, content, result = ['{', newline], that = this;
1024  
1025              withIndent(function () {
1026                  // handle functions without any code
1027                  if (stmt.body.length === 0 && preserveBlankLines) {
1028                      range = stmt.range;
1029                      if (range[1] - range[0] > 2) {
1030                          content = sourceCode.substring(range[0] + 1, range[1] - 1);
1031                          if (content[0] === '\n') {
1032                              result = ['{'];
1033                          }
1034                          result.push(content);
1035                      }
1036                  }
1037  
1038                  var i, iz, fragment, bodyFlags;
1039                  bodyFlags = S_TFFF;
1040                  if (flags & F_FUNC_BODY) {
1041                      bodyFlags |= F_DIRECTIVE_CTX;
1042                  }
1043  
1044                  for (i = 0, iz = stmt.body.length; i < iz; ++i) {
1045                      if (preserveBlankLines) {
1046                          // handle spaces before the first line
1047                          if (i === 0) {
1048                              if (stmt.body[0].leadingComments) {
1049                                  range = stmt.body[0].leadingComments[0].extendedRange;
1050                                  content = sourceCode.substring(range[0], range[1]);
1051                                  if (content[0] === '\n') {
1052                                      result = ['{'];
1053                                  }
1054                              }
1055                              if (!stmt.body[0].leadingComments) {
1056                                  generateBlankLines(stmt.range[0], stmt.body[0].range[0], result);
1057                              }
1058                          }
1059  
1060                          // handle spaces between lines
1061                          if (i > 0) {
1062                              if (!stmt.body[i - 1].trailingComments  && !stmt.body[i].leadingComments) {
1063                                  generateBlankLines(stmt.body[i - 1].range[1], stmt.body[i].range[0], result);
1064                              }
1065                          }
1066                      }
1067  
1068                      if (i === iz - 1) {
1069                          bodyFlags |= F_SEMICOLON_OPT;
1070                      }
1071  
1072                      if (stmt.body[i].leadingComments && preserveBlankLines) {
1073                          fragment = that.generateStatement(stmt.body[i], bodyFlags);
1074                      } else {
1075                          fragment = addIndent(that.generateStatement(stmt.body[i], bodyFlags));
1076                      }
1077  
1078                      result.push(fragment);
1079                      if (!endsWithLineTerminator(toSourceNodeWhenNeeded(fragment).toString())) {
1080                          if (preserveBlankLines && i < iz - 1) {
1081                              // don't add a new line if there are leading coments
1082                              // in the next statement
1083                              if (!stmt.body[i + 1].leadingComments) {
1084                                  result.push(newline);
1085                              }
1086                          } else {
1087                              result.push(newline);
1088                          }
1089                      }
1090  
1091                      if (preserveBlankLines) {
1092                          // handle spaces after the last line
1093                          if (i === iz - 1) {
1094                              if (!stmt.body[i].trailingComments) {
1095                                  generateBlankLines(stmt.body[i].range[1], stmt.range[1], result);
1096                              }
1097                          }
1098                      }
1099                  }
1100              });
1101  
1102              result.push(addIndent('}'));
1103              return result;
1104          },
1105  
1106          BreakStatement: function (stmt, flags) {
1107              if (stmt.label) {
1108                  return 'break ' + stmt.label.name + this.semicolon(flags);
1109              }
1110              return 'break' + this.semicolon(flags);
1111          },
1112  
1113          ContinueStatement: function (stmt, flags) {
1114              if (stmt.label) {
1115                  return 'continue ' + stmt.label.name + this.semicolon(flags);
1116              }
1117              return 'continue' + this.semicolon(flags);
1118          },
1119  
1120          ClassBody: function (stmt, flags) {
1121              var result = [ '{', newline], that = this;
1122  
1123              withIndent(function (indent) {
1124                  var i, iz;
1125  
1126                  for (i = 0, iz = stmt.body.length; i < iz; ++i) {
1127                      result.push(indent);
1128                      result.push(that.generateExpression(stmt.body[i], Precedence.Sequence, E_TTT));
1129                      if (i + 1 < iz) {
1130                          result.push(newline);
1131                      }
1132                  }
1133              });
1134  
1135              if (!endsWithLineTerminator(toSourceNodeWhenNeeded(result).toString())) {
1136                  result.push(newline);
1137              }
1138              result.push(base);
1139              result.push('}');
1140              return result;
1141          },
1142  
1143          ClassDeclaration: function (stmt, flags) {
1144              var result, fragment;
1145              result  = ['class'];
1146              if (stmt.id) {
1147                  result = join(result, this.generateExpression(stmt.id, Precedence.Sequence, E_TTT));
1148              }
1149              if (stmt.superClass) {
1150                  fragment = join('extends', this.generateExpression(stmt.superClass, Precedence.Unary, E_TTT));
1151                  result = join(result, fragment);
1152              }
1153              result.push(space);
1154              result.push(this.generateStatement(stmt.body, S_TFFT));
1155              return result;
1156          },
1157  
1158          DirectiveStatement: function (stmt, flags) {
1159              if (extra.raw && stmt.raw) {
1160                  return stmt.raw + this.semicolon(flags);
1161              }
1162              return escapeDirective(stmt.directive) + this.semicolon(flags);
1163          },
1164  
1165          DoWhileStatement: function (stmt, flags) {
1166              // Because `do 42 while (cond)` is Syntax Error. We need semicolon.
1167              var result = join('do', this.maybeBlock(stmt.body, S_TFFF));
1168              result = this.maybeBlockSuffix(stmt.body, result);
1169              return join(result, [
1170                  'while' + space + '(',
1171                  this.generateExpression(stmt.test, Precedence.Sequence, E_TTT),
1172                  ')' + this.semicolon(flags)
1173              ]);
1174          },
1175  
1176          CatchClause: function (stmt, flags) {
1177              var result, that = this;
1178              withIndent(function () {
1179                  var guard;
1180  
1181                  if (stmt.param) {
1182                      result = [
1183                          'catch' + space + '(',
1184                          that.generateExpression(stmt.param, Precedence.Sequence, E_TTT),
1185                          ')'
1186                      ];
1187  
1188                      if (stmt.guard) {
1189                          guard = that.generateExpression(stmt.guard, Precedence.Sequence, E_TTT);
1190                          result.splice(2, 0, ' if ', guard);
1191                      }
1192                  } else {
1193                      result = ['catch'];
1194                  }
1195              });
1196              result.push(this.maybeBlock(stmt.body, S_TFFF));
1197              return result;
1198          },
1199  
1200          DebuggerStatement: function (stmt, flags) {
1201              return 'debugger' + this.semicolon(flags);
1202          },
1203  
1204          EmptyStatement: function (stmt, flags) {
1205              return ';';
1206          },
1207  
1208          ExportDefaultDeclaration: function (stmt, flags) {
1209              var result = [ 'export' ], bodyFlags;
1210  
1211              bodyFlags = (flags & F_SEMICOLON_OPT) ? S_TFFT : S_TFFF;
1212  
1213              // export default HoistableDeclaration[Default]
1214              // export default AssignmentExpression[In] ;
1215              result = join(result, 'default');
1216              if (isStatement(stmt.declaration)) {
1217                  result = join(result, this.generateStatement(stmt.declaration, bodyFlags));
1218              } else {
1219                  result = join(result, this.generateExpression(stmt.declaration, Precedence.Assignment, E_TTT) + this.semicolon(flags));
1220              }
1221              return result;
1222          },
1223  
1224          ExportNamedDeclaration: function (stmt, flags) {
1225              var result = [ 'export' ], bodyFlags, that = this;
1226  
1227              bodyFlags = (flags & F_SEMICOLON_OPT) ? S_TFFT : S_TFFF;
1228  
1229              // export VariableStatement
1230              // export Declaration[Default]
1231              if (stmt.declaration) {
1232                  return join(result, this.generateStatement(stmt.declaration, bodyFlags));
1233              }
1234  
1235              // export ExportClause[NoReference] FromClause ;
1236              // export ExportClause ;
1237              if (stmt.specifiers) {
1238                  if (stmt.specifiers.length === 0) {
1239                      result = join(result, '{' + space + '}');
1240                  } else if (stmt.specifiers[0].type === Syntax.ExportBatchSpecifier) {
1241                      result = join(result, this.generateExpression(stmt.specifiers[0], Precedence.Sequence, E_TTT));
1242                  } else {
1243                      result = join(result, '{');
1244                      withIndent(function (indent) {
1245                          var i, iz;
1246                          result.push(newline);
1247                          for (i = 0, iz = stmt.specifiers.length; i < iz; ++i) {
1248                              result.push(indent);
1249                              result.push(that.generateExpression(stmt.specifiers[i], Precedence.Sequence, E_TTT));
1250                              if (i + 1 < iz) {
1251                                  result.push(',' + newline);
1252                              }
1253                          }
1254                      });
1255                      if (!endsWithLineTerminator(toSourceNodeWhenNeeded(result).toString())) {
1256                          result.push(newline);
1257                      }
1258                      result.push(base + '}');
1259                  }
1260  
1261                  if (stmt.source) {
1262                      result = join(result, [
1263                          'from' + space,
1264                          // ModuleSpecifier
1265                          this.generateExpression(stmt.source, Precedence.Sequence, E_TTT),
1266                          this.semicolon(flags)
1267                      ]);
1268                  } else {
1269                      result.push(this.semicolon(flags));
1270                  }
1271              }
1272              return result;
1273          },
1274  
1275          ExportAllDeclaration: function (stmt, flags) {
1276              // export * FromClause ;
1277              return [
1278                  'export' + space,
1279                  '*' + space,
1280                  'from' + space,
1281                  // ModuleSpecifier
1282                  this.generateExpression(stmt.source, Precedence.Sequence, E_TTT),
1283                  this.semicolon(flags)
1284              ];
1285          },
1286  
1287          ExpressionStatement: function (stmt, flags) {
1288              var result, fragment;
1289  
1290              function isClassPrefixed(fragment) {
1291                  var code;
1292                  if (fragment.slice(0, 5) !== 'class') {
1293                      return false;
1294                  }
1295                  code = fragment.charCodeAt(5);
1296                  return code === 0x7B  /* '{' */ || esutils.code.isWhiteSpace(code) || esutils.code.isLineTerminator(code);
1297              }
1298  
1299              function isFunctionPrefixed(fragment) {
1300                  var code;
1301                  if (fragment.slice(0, 8) !== 'function') {
1302                      return false;
1303                  }
1304                  code = fragment.charCodeAt(8);
1305                  return code === 0x28 /* '(' */ || esutils.code.isWhiteSpace(code) || code === 0x2A  /* '*' */ || esutils.code.isLineTerminator(code);
1306              }
1307  
1308              function isAsyncPrefixed(fragment) {
1309                  var code, i, iz;
1310                  if (fragment.slice(0, 5) !== 'async') {
1311                      return false;
1312                  }
1313                  if (!esutils.code.isWhiteSpace(fragment.charCodeAt(5))) {
1314                      return false;
1315                  }
1316                  for (i = 6, iz = fragment.length; i < iz; ++i) {
1317                      if (!esutils.code.isWhiteSpace(fragment.charCodeAt(i))) {
1318                          break;
1319                      }
1320                  }
1321                  if (i === iz) {
1322                      return false;
1323                  }
1324                  if (fragment.slice(i, i + 8) !== 'function') {
1325                      return false;
1326                  }
1327                  code = fragment.charCodeAt(i + 8);
1328                  return code === 0x28 /* '(' */ || esutils.code.isWhiteSpace(code) || code === 0x2A  /* '*' */ || esutils.code.isLineTerminator(code);
1329              }
1330  
1331              result = [this.generateExpression(stmt.expression, Precedence.Sequence, E_TTT)];
1332              // 12.4 '{', 'function', 'class' is not allowed in this position.
1333              // wrap expression with parentheses
1334              fragment = toSourceNodeWhenNeeded(result).toString();
1335              if (fragment.charCodeAt(0) === 0x7B  /* '{' */ ||  // ObjectExpression
1336                      isClassPrefixed(fragment) ||
1337                      isFunctionPrefixed(fragment) ||
1338                      isAsyncPrefixed(fragment) ||
1339                      (directive && (flags & F_DIRECTIVE_CTX) && stmt.expression.type === Syntax.Literal && typeof stmt.expression.value === 'string')) {
1340                  result = ['(', result, ')' + this.semicolon(flags)];
1341              } else {
1342                  result.push(this.semicolon(flags));
1343              }
1344              return result;
1345          },
1346  
1347          ImportDeclaration: function (stmt, flags) {
1348              // ES6: 15.2.1 valid import declarations:
1349              //     - import ImportClause FromClause ;
1350              //     - import ModuleSpecifier ;
1351              var result, cursor, that = this;
1352  
1353              // If no ImportClause is present,
1354              // this should be `import ModuleSpecifier` so skip `from`
1355              // ModuleSpecifier is StringLiteral.
1356              if (stmt.specifiers.length === 0) {
1357                  // import ModuleSpecifier ;
1358                  return [
1359                      'import',
1360                      space,
1361                      // ModuleSpecifier
1362                      this.generateExpression(stmt.source, Precedence.Sequence, E_TTT),
1363                      this.semicolon(flags)
1364                  ];
1365              }
1366  
1367              // import ImportClause FromClause ;
1368              result = [
1369                  'import'
1370              ];
1371              cursor = 0;
1372  
1373              // ImportedBinding
1374              if (stmt.specifiers[cursor].type === Syntax.ImportDefaultSpecifier) {
1375                  result = join(result, [
1376                          this.generateExpression(stmt.specifiers[cursor], Precedence.Sequence, E_TTT)
1377                  ]);
1378                  ++cursor;
1379              }
1380  
1381              if (stmt.specifiers[cursor]) {
1382                  if (cursor !== 0) {
1383                      result.push(',');
1384                  }
1385  
1386                  if (stmt.specifiers[cursor].type === Syntax.ImportNamespaceSpecifier) {
1387                      // NameSpaceImport
1388                      result = join(result, [
1389                              space,
1390                              this.generateExpression(stmt.specifiers[cursor], Precedence.Sequence, E_TTT)
1391                      ]);
1392                  } else {
1393                      // NamedImports
1394                      result.push(space + '{');
1395  
1396                      if ((stmt.specifiers.length - cursor) === 1) {
1397                          // import { ... } from "...";
1398                          result.push(space);
1399                          result.push(this.generateExpression(stmt.specifiers[cursor], Precedence.Sequence, E_TTT));
1400                          result.push(space + '}' + space);
1401                      } else {
1402                          // import {
1403                          //    ...,
1404                          //    ...,
1405                          // } from "...";
1406                          withIndent(function (indent) {
1407                              var i, iz;
1408                              result.push(newline);
1409                              for (i = cursor, iz = stmt.specifiers.length; i < iz; ++i) {
1410                                  result.push(indent);
1411                                  result.push(that.generateExpression(stmt.specifiers[i], Precedence.Sequence, E_TTT));
1412                                  if (i + 1 < iz) {
1413                                      result.push(',' + newline);
1414                                  }
1415                              }
1416                          });
1417                          if (!endsWithLineTerminator(toSourceNodeWhenNeeded(result).toString())) {
1418                              result.push(newline);
1419                          }
1420                          result.push(base + '}' + space);
1421                      }
1422                  }
1423              }
1424  
1425              result = join(result, [
1426                  'from' + space,
1427                  // ModuleSpecifier
1428                  this.generateExpression(stmt.source, Precedence.Sequence, E_TTT),
1429                  this.semicolon(flags)
1430              ]);
1431              return result;
1432          },
1433  
1434          VariableDeclarator: function (stmt, flags) {
1435              var itemFlags = (flags & F_ALLOW_IN) ? E_TTT : E_FTT;
1436              if (stmt.init) {
1437                  return [
1438                      this.generateExpression(stmt.id, Precedence.Assignment, itemFlags),
1439                      space,
1440                      '=',
1441                      space,
1442                      this.generateExpression(stmt.init, Precedence.Assignment, itemFlags)
1443                  ];
1444              }
1445              return this.generatePattern(stmt.id, Precedence.Assignment, itemFlags);
1446          },
1447  
1448          VariableDeclaration: function (stmt, flags) {
1449              // VariableDeclarator is typed as Statement,
1450              // but joined with comma (not LineTerminator).
1451              // So if comment is attached to target node, we should specialize.
1452              var result, i, iz, node, bodyFlags, that = this;
1453  
1454              result = [ stmt.kind ];
1455  
1456              bodyFlags = (flags & F_ALLOW_IN) ? S_TFFF : S_FFFF;
1457  
1458              function block() {
1459                  node = stmt.declarations[0];
1460                  if (extra.comment && node.leadingComments) {
1461                      result.push('\n');
1462                      result.push(addIndent(that.generateStatement(node, bodyFlags)));
1463                  } else {
1464                      result.push(noEmptySpace());
1465                      result.push(that.generateStatement(node, bodyFlags));
1466                  }
1467  
1468                  for (i = 1, iz = stmt.declarations.length; i < iz; ++i) {
1469                      node = stmt.declarations[i];
1470                      if (extra.comment && node.leadingComments) {
1471                          result.push(',' + newline);
1472                          result.push(addIndent(that.generateStatement(node, bodyFlags)));
1473                      } else {
1474                          result.push(',' + space);
1475                          result.push(that.generateStatement(node, bodyFlags));
1476                      }
1477                  }
1478              }
1479  
1480              if (stmt.declarations.length > 1) {
1481                  withIndent(block);
1482              } else {
1483                  block();
1484              }
1485  
1486              result.push(this.semicolon(flags));
1487  
1488              return result;
1489          },
1490  
1491          ThrowStatement: function (stmt, flags) {
1492              return [join(
1493                  'throw',
1494                  this.generateExpression(stmt.argument, Precedence.Sequence, E_TTT)
1495              ), this.semicolon(flags)];
1496          },
1497  
1498          TryStatement: function (stmt, flags) {
1499              var result, i, iz, guardedHandlers;
1500  
1501              result = ['try', this.maybeBlock(stmt.block, S_TFFF)];
1502              result = this.maybeBlockSuffix(stmt.block, result);
1503  
1504              if (stmt.handlers) {
1505                  // old interface
1506                  for (i = 0, iz = stmt.handlers.length; i < iz; ++i) {
1507                      result = join(result, this.generateStatement(stmt.handlers[i], S_TFFF));
1508                      if (stmt.finalizer || i + 1 !== iz) {
1509                          result = this.maybeBlockSuffix(stmt.handlers[i].body, result);
1510                      }
1511                  }
1512              } else {
1513                  guardedHandlers = stmt.guardedHandlers || [];
1514  
1515                  for (i = 0, iz = guardedHandlers.length; i < iz; ++i) {
1516                      result = join(result, this.generateStatement(guardedHandlers[i], S_TFFF));
1517                      if (stmt.finalizer || i + 1 !== iz) {
1518                          result = this.maybeBlockSuffix(guardedHandlers[i].body, result);
1519                      }
1520                  }
1521  
1522                  // new interface
1523                  if (stmt.handler) {
1524                      if (Array.isArray(stmt.handler)) {
1525                          for (i = 0, iz = stmt.handler.length; i < iz; ++i) {
1526                              result = join(result, this.generateStatement(stmt.handler[i], S_TFFF));
1527                              if (stmt.finalizer || i + 1 !== iz) {
1528                                  result = this.maybeBlockSuffix(stmt.handler[i].body, result);
1529                              }
1530                          }
1531                      } else {
1532                          result = join(result, this.generateStatement(stmt.handler, S_TFFF));
1533                          if (stmt.finalizer) {
1534                              result = this.maybeBlockSuffix(stmt.handler.body, result);
1535                          }
1536                      }
1537                  }
1538              }
1539              if (stmt.finalizer) {
1540                  result = join(result, ['finally', this.maybeBlock(stmt.finalizer, S_TFFF)]);
1541              }
1542              return result;
1543          },
1544  
1545          SwitchStatement: function (stmt, flags) {
1546              var result, fragment, i, iz, bodyFlags, that = this;
1547              withIndent(function () {
1548                  result = [
1549                      'switch' + space + '(',
1550                      that.generateExpression(stmt.discriminant, Precedence.Sequence, E_TTT),
1551                      ')' + space + '{' + newline
1552                  ];
1553              });
1554              if (stmt.cases) {
1555                  bodyFlags = S_TFFF;
1556                  for (i = 0, iz = stmt.cases.length; i < iz; ++i) {
1557                      if (i === iz - 1) {
1558                          bodyFlags |= F_SEMICOLON_OPT;
1559                      }
1560                      fragment = addIndent(this.generateStatement(stmt.cases[i], bodyFlags));
1561                      result.push(fragment);
1562                      if (!endsWithLineTerminator(toSourceNodeWhenNeeded(fragment).toString())) {
1563                          result.push(newline);
1564                      }
1565                  }
1566              }
1567              result.push(addIndent('}'));
1568              return result;
1569          },
1570  
1571          SwitchCase: function (stmt, flags) {
1572              var result, fragment, i, iz, bodyFlags, that = this;
1573              withIndent(function () {
1574                  if (stmt.test) {
1575                      result = [
1576                          join('case', that.generateExpression(stmt.test, Precedence.Sequence, E_TTT)),
1577                          ':'
1578                      ];
1579                  } else {
1580                      result = ['default:'];
1581                  }
1582  
1583                  i = 0;
1584                  iz = stmt.consequent.length;
1585                  if (iz && stmt.consequent[0].type === Syntax.BlockStatement) {
1586                      fragment = that.maybeBlock(stmt.consequent[0], S_TFFF);
1587                      result.push(fragment);
1588                      i = 1;
1589                  }
1590  
1591                  if (i !== iz && !endsWithLineTerminator(toSourceNodeWhenNeeded(result).toString())) {
1592                      result.push(newline);
1593                  }
1594  
1595                  bodyFlags = S_TFFF;
1596                  for (; i < iz; ++i) {
1597                      if (i === iz - 1 && flags & F_SEMICOLON_OPT) {
1598                          bodyFlags |= F_SEMICOLON_OPT;
1599                      }
1600                      fragment = addIndent(that.generateStatement(stmt.consequent[i], bodyFlags));
1601                      result.push(fragment);
1602                      if (i + 1 !== iz && !endsWithLineTerminator(toSourceNodeWhenNeeded(fragment).toString())) {
1603                          result.push(newline);
1604                      }
1605                  }
1606              });
1607              return result;
1608          },
1609  
1610          IfStatement: function (stmt, flags) {
1611              var result, bodyFlags, semicolonOptional, that = this;
1612              withIndent(function () {
1613                  result = [
1614                      'if' + space + '(',
1615                      that.generateExpression(stmt.test, Precedence.Sequence, E_TTT),
1616                      ')'
1617                  ];
1618              });
1619              semicolonOptional = flags & F_SEMICOLON_OPT;
1620              bodyFlags = S_TFFF;
1621              if (semicolonOptional) {
1622                  bodyFlags |= F_SEMICOLON_OPT;
1623              }
1624              if (stmt.alternate) {
1625                  result.push(this.maybeBlock(stmt.consequent, S_TFFF));
1626                  result = this.maybeBlockSuffix(stmt.consequent, result);
1627                  if (stmt.alternate.type === Syntax.IfStatement) {
1628                      result = join(result, ['else ', this.generateStatement(stmt.alternate, bodyFlags)]);
1629                  } else {
1630                      result = join(result, join('else', this.maybeBlock(stmt.alternate, bodyFlags)));
1631                  }
1632              } else {
1633                  result.push(this.maybeBlock(stmt.consequent, bodyFlags));
1634              }
1635              return result;
1636          },
1637  
1638          ForStatement: function (stmt, flags) {
1639              var result, that = this;
1640              withIndent(function () {
1641                  result = ['for' + space + '('];
1642                  if (stmt.init) {
1643                      if (stmt.init.type === Syntax.VariableDeclaration) {
1644                          result.push(that.generateStatement(stmt.init, S_FFFF));
1645                      } else {
1646                          // F_ALLOW_IN becomes false.
1647                          result.push(that.generateExpression(stmt.init, Precedence.Sequence, E_FTT));
1648                          result.push(';');
1649                      }
1650                  } else {
1651                      result.push(';');
1652                  }
1653  
1654                  if (stmt.test) {
1655                      result.push(space);
1656                      result.push(that.generateExpression(stmt.test, Precedence.Sequence, E_TTT));
1657                      result.push(';');
1658                  } else {
1659                      result.push(';');
1660                  }
1661  
1662                  if (stmt.update) {
1663                      result.push(space);
1664                      result.push(that.generateExpression(stmt.update, Precedence.Sequence, E_TTT));
1665                      result.push(')');
1666                  } else {
1667                      result.push(')');
1668                  }
1669              });
1670  
1671              result.push(this.maybeBlock(stmt.body, flags & F_SEMICOLON_OPT ? S_TFFT : S_TFFF));
1672              return result;
1673          },
1674  
1675          ForInStatement: function (stmt, flags) {
1676              return this.generateIterationForStatement('in', stmt, flags & F_SEMICOLON_OPT ? S_TFFT : S_TFFF);
1677          },
1678  
1679          ForOfStatement: function (stmt, flags) {
1680              return this.generateIterationForStatement('of', stmt, flags & F_SEMICOLON_OPT ? S_TFFT : S_TFFF);
1681          },
1682  
1683          LabeledStatement: function (stmt, flags) {
1684              return [stmt.label.name + ':', this.maybeBlock(stmt.body, flags & F_SEMICOLON_OPT ? S_TFFT : S_TFFF)];
1685          },
1686  
1687          Program: function (stmt, flags) {
1688              var result, fragment, i, iz, bodyFlags;
1689              iz = stmt.body.length;
1690              result = [safeConcatenation && iz > 0 ? '\n' : ''];
1691              bodyFlags = S_TFTF;
1692              for (i = 0; i < iz; ++i) {
1693                  if (!safeConcatenation && i === iz - 1) {
1694                      bodyFlags |= F_SEMICOLON_OPT;
1695                  }
1696  
1697                  if (preserveBlankLines) {
1698                      // handle spaces before the first line
1699                      if (i === 0) {
1700                          if (!stmt.body[0].leadingComments) {
1701                              generateBlankLines(stmt.range[0], stmt.body[i].range[0], result);
1702                          }
1703                      }
1704  
1705                      // handle spaces between lines
1706                      if (i > 0) {
1707                          if (!stmt.body[i - 1].trailingComments && !stmt.body[i].leadingComments) {
1708                              generateBlankLines(stmt.body[i - 1].range[1], stmt.body[i].range[0], result);
1709                          }
1710                      }
1711                  }
1712  
1713                  fragment = addIndent(this.generateStatement(stmt.body[i], bodyFlags));
1714                  result.push(fragment);
1715                  if (i + 1 < iz && !endsWithLineTerminator(toSourceNodeWhenNeeded(fragment).toString())) {
1716                      if (preserveBlankLines) {
1717                          if (!stmt.body[i + 1].leadingComments) {
1718                              result.push(newline);
1719                          }
1720                      } else {
1721                          result.push(newline);
1722                      }
1723                  }
1724  
1725                  if (preserveBlankLines) {
1726                      // handle spaces after the last line
1727                      if (i === iz - 1) {
1728                          if (!stmt.body[i].trailingComments) {
1729                              generateBlankLines(stmt.body[i].range[1], stmt.range[1], result);
1730                          }
1731                      }
1732                  }
1733              }
1734              return result;
1735          },
1736  
1737          FunctionDeclaration: function (stmt, flags) {
1738              return [
1739                  generateAsyncPrefix(stmt, true),
1740                  'function',
1741                  generateStarSuffix(stmt) || noEmptySpace(),
1742                  stmt.id ? generateIdentifier(stmt.id) : '',
1743                  this.generateFunctionBody(stmt)
1744              ];
1745          },
1746  
1747          ReturnStatement: function (stmt, flags) {
1748              if (stmt.argument) {
1749                  return [join(
1750                      'return',
1751                      this.generateExpression(stmt.argument, Precedence.Sequence, E_TTT)
1752                  ), this.semicolon(flags)];
1753              }
1754              return ['return' + this.semicolon(flags)];
1755          },
1756  
1757          WhileStatement: function (stmt, flags) {
1758              var result, that = this;
1759              withIndent(function () {
1760                  result = [
1761                      'while' + space + '(',
1762                      that.generateExpression(stmt.test, Precedence.Sequence, E_TTT),
1763                      ')'
1764                  ];
1765              });
1766              result.push(this.maybeBlock(stmt.body, flags & F_SEMICOLON_OPT ? S_TFFT : S_TFFF));
1767              return result;
1768          },
1769  
1770          WithStatement: function (stmt, flags) {
1771              var result, that = this;
1772              withIndent(function () {
1773                  result = [
1774                      'with' + space + '(',
1775                      that.generateExpression(stmt.object, Precedence.Sequence, E_TTT),
1776                      ')'
1777                  ];
1778              });
1779              result.push(this.maybeBlock(stmt.body, flags & F_SEMICOLON_OPT ? S_TFFT : S_TFFF));
1780              return result;
1781          }
1782  
1783      };
1784  
1785      merge(CodeGenerator.prototype, CodeGenerator.Statement);
1786  
1787      // Expressions.
1788  
1789      CodeGenerator.Expression = {
1790  
1791          SequenceExpression: function (expr, precedence, flags) {
1792              var result, i, iz;
1793              if (Precedence.Sequence < precedence) {
1794                  flags |= F_ALLOW_IN;
1795              }
1796              result = [];
1797              for (i = 0, iz = expr.expressions.length; i < iz; ++i) {
1798                  result.push(this.generateExpression(expr.expressions[i], Precedence.Assignment, flags));
1799                  if (i + 1 < iz) {
1800                      result.push(',' + space);
1801                  }
1802              }
1803              return parenthesize(result, Precedence.Sequence, precedence);
1804          },
1805  
1806          AssignmentExpression: function (expr, precedence, flags) {
1807              return this.generateAssignment(expr.left, expr.right, expr.operator, precedence, flags);
1808          },
1809  
1810          ArrowFunctionExpression: function (expr, precedence, flags) {
1811              return parenthesize(this.generateFunctionBody(expr), Precedence.ArrowFunction, precedence);
1812          },
1813  
1814          ConditionalExpression: function (expr, precedence, flags) {
1815              if (Precedence.Conditional < precedence) {
1816                  flags |= F_ALLOW_IN;
1817              }
1818              return parenthesize(
1819                  [
1820                      this.generateExpression(expr.test, Precedence.LogicalOR, flags),
1821                      space + '?' + space,
1822                      this.generateExpression(expr.consequent, Precedence.Assignment, flags),
1823                      space + ':' + space,
1824                      this.generateExpression(expr.alternate, Precedence.Assignment, flags)
1825                  ],
1826                  Precedence.Conditional,
1827                  precedence
1828              );
1829          },
1830  
1831          LogicalExpression: function (expr, precedence, flags) {
1832              return this.BinaryExpression(expr, precedence, flags);
1833          },
1834  
1835          BinaryExpression: function (expr, precedence, flags) {
1836              var result, leftPrecedence, rightPrecedence, currentPrecedence, fragment, leftSource;
1837              currentPrecedence = BinaryPrecedence[expr.operator];
1838              leftPrecedence = expr.operator === '**' ? Precedence.Postfix : currentPrecedence;
1839              rightPrecedence = expr.operator === '**' ? currentPrecedence : currentPrecedence + 1;
1840  
1841              if (currentPrecedence < precedence) {
1842                  flags |= F_ALLOW_IN;
1843              }
1844  
1845              fragment = this.generateExpression(expr.left, leftPrecedence, flags);
1846  
1847              leftSource = fragment.toString();
1848  
1849              if (leftSource.charCodeAt(leftSource.length - 1) === 0x2F /* / */ && esutils.code.isIdentifierPartES5(expr.operator.charCodeAt(0))) {
1850                  result = [fragment, noEmptySpace(), expr.operator];
1851              } else {
1852                  result = join(fragment, expr.operator);
1853              }
1854  
1855              fragment = this.generateExpression(expr.right, rightPrecedence, flags);
1856  
1857              if (expr.operator === '/' && fragment.toString().charAt(0) === '/' ||
1858              expr.operator.slice(-1) === '<' && fragment.toString().slice(0, 3) === '!--') {
1859                  // If '/' concats with '/' or `<` concats with `!--`, it is interpreted as comment start
1860                  result.push(noEmptySpace());
1861                  result.push(fragment);
1862              } else {
1863                  result = join(result, fragment);
1864              }
1865  
1866              if (expr.operator === 'in' && !(flags & F_ALLOW_IN)) {
1867                  return ['(', result, ')'];
1868              }
1869              return parenthesize(result, currentPrecedence, precedence);
1870          },
1871  
1872          CallExpression: function (expr, precedence, flags) {
1873              var result, i, iz;
1874              // F_ALLOW_UNPARATH_NEW becomes false.
1875              result = [this.generateExpression(expr.callee, Precedence.Call, E_TTF)];
1876              result.push('(');
1877              for (i = 0, iz = expr['arguments'].length; i < iz; ++i) {
1878                  result.push(this.generateExpression(expr['arguments'][i], Precedence.Assignment, E_TTT));
1879                  if (i + 1 < iz) {
1880                      result.push(',' + space);
1881                  }
1882              }
1883              result.push(')');
1884  
1885              if (!(flags & F_ALLOW_CALL)) {
1886                  return ['(', result, ')'];
1887              }
1888              return parenthesize(result, Precedence.Call, precedence);
1889          },
1890  
1891          NewExpression: function (expr, precedence, flags) {
1892              var result, length, i, iz, itemFlags;
1893              length = expr['arguments'].length;
1894  
1895              // F_ALLOW_CALL becomes false.
1896              // F_ALLOW_UNPARATH_NEW may become false.
1897              itemFlags = (flags & F_ALLOW_UNPARATH_NEW && !parentheses && length === 0) ? E_TFT : E_TFF;
1898  
1899              result = join(
1900                  'new',
1901                  this.generateExpression(expr.callee, Precedence.New, itemFlags)
1902              );
1903  
1904              if (!(flags & F_ALLOW_UNPARATH_NEW) || parentheses || length > 0) {
1905                  result.push('(');
1906                  for (i = 0, iz = length; i < iz; ++i) {
1907                      result.push(this.generateExpression(expr['arguments'][i], Precedence.Assignment, E_TTT));
1908                      if (i + 1 < iz) {
1909                          result.push(',' + space);
1910                      }
1911                  }
1912                  result.push(')');
1913              }
1914  
1915              return parenthesize(result, Precedence.New, precedence);
1916          },
1917  
1918          MemberExpression: function (expr, precedence, flags) {
1919              var result, fragment;
1920  
1921              // F_ALLOW_UNPARATH_NEW becomes false.
1922              result = [this.generateExpression(expr.object, Precedence.Call, (flags & F_ALLOW_CALL) ? E_TTF : E_TFF)];
1923  
1924              if (expr.computed) {
1925                  result.push('[');
1926                  result.push(this.generateExpression(expr.property, Precedence.Sequence, flags & F_ALLOW_CALL ? E_TTT : E_TFT));
1927                  result.push(']');
1928              } else {
1929                  if (expr.object.type === Syntax.Literal && typeof expr.object.value === 'number') {
1930                      fragment = toSourceNodeWhenNeeded(result).toString();
1931                      // When the following conditions are all true,
1932                      //   1. No floating point
1933                      //   2. Don't have exponents
1934                      //   3. The last character is a decimal digit
1935                      //   4. Not hexadecimal OR octal number literal
1936                      // we should add a floating point.
1937                      if (
1938                              fragment.indexOf('.') < 0 &&
1939                              !/[eExX]/.test(fragment) &&
1940                              esutils.code.isDecimalDigit(fragment.charCodeAt(fragment.length - 1)) &&
1941                              !(fragment.length >= 2 && fragment.charCodeAt(0) === 48)  // '0'
1942                              ) {
1943                          result.push(' ');
1944                      }
1945                  }
1946                  result.push('.');
1947                  result.push(generateIdentifier(expr.property));
1948              }
1949  
1950              return parenthesize(result, Precedence.Member, precedence);
1951          },
1952  
1953          MetaProperty: function (expr, precedence, flags) {
1954              var result;
1955              result = [];
1956              result.push(typeof expr.meta === "string" ? expr.meta : generateIdentifier(expr.meta));
1957              result.push('.');
1958              result.push(typeof expr.property === "string" ? expr.property : generateIdentifier(expr.property));
1959              return parenthesize(result, Precedence.Member, precedence);
1960          },
1961  
1962          UnaryExpression: function (expr, precedence, flags) {
1963              var result, fragment, rightCharCode, leftSource, leftCharCode;
1964              fragment = this.generateExpression(expr.argument, Precedence.Unary, E_TTT);
1965  
1966              if (space === '') {
1967                  result = join(expr.operator, fragment);
1968              } else {
1969                  result = [expr.operator];
1970                  if (expr.operator.length > 2) {
1971                      // delete, void, typeof
1972                      // get `typeof []`, not `typeof[]`
1973                      result = join(result, fragment);
1974                  } else {
1975                      // Prevent inserting spaces between operator and argument if it is unnecessary
1976                      // like, `!cond`
1977                      leftSource = toSourceNodeWhenNeeded(result).toString();
1978                      leftCharCode = leftSource.charCodeAt(leftSource.length - 1);
1979                      rightCharCode = fragment.toString().charCodeAt(0);
1980  
1981                      if (((leftCharCode === 0x2B  /* + */ || leftCharCode === 0x2D  /* - */) && leftCharCode === rightCharCode) ||
1982                              (esutils.code.isIdentifierPartES5(leftCharCode) && esutils.code.isIdentifierPartES5(rightCharCode))) {
1983                          result.push(noEmptySpace());
1984                          result.push(fragment);
1985                      } else {
1986                          result.push(fragment);
1987                      }
1988                  }
1989              }
1990              return parenthesize(result, Precedence.Unary, precedence);
1991          },
1992  
1993          YieldExpression: function (expr, precedence, flags) {
1994              var result;
1995              if (expr.delegate) {
1996                  result = 'yield*';
1997              } else {
1998                  result = 'yield';
1999              }
2000              if (expr.argument) {
2001                  result = join(
2002                      result,
2003                      this.generateExpression(expr.argument, Precedence.Yield, E_TTT)
2004                  );
2005              }
2006              return parenthesize(result, Precedence.Yield, precedence);
2007          },
2008  
2009          AwaitExpression: function (expr, precedence, flags) {
2010              var result = join(
2011                  expr.all ? 'await*' : 'await',
2012                  this.generateExpression(expr.argument, Precedence.Await, E_TTT)
2013              );
2014              return parenthesize(result, Precedence.Await, precedence);
2015          },
2016  
2017          UpdateExpression: function (expr, precedence, flags) {
2018              if (expr.prefix) {
2019                  return parenthesize(
2020                      [
2021                          expr.operator,
2022                          this.generateExpression(expr.argument, Precedence.Unary, E_TTT)
2023                      ],
2024                      Precedence.Unary,
2025                      precedence
2026                  );
2027              }
2028              return parenthesize(
2029                  [
2030                      this.generateExpression(expr.argument, Precedence.Postfix, E_TTT),
2031                      expr.operator
2032                  ],
2033                  Precedence.Postfix,
2034                  precedence
2035              );
2036          },
2037  
2038          FunctionExpression: function (expr, precedence, flags) {
2039              var result = [
2040                  generateAsyncPrefix(expr, true),
2041                  'function'
2042              ];
2043              if (expr.id) {
2044                  result.push(generateStarSuffix(expr) || noEmptySpace());
2045                  result.push(generateIdentifier(expr.id));
2046              } else {
2047                  result.push(generateStarSuffix(expr) || space);
2048              }
2049              result.push(this.generateFunctionBody(expr));
2050              return result;
2051          },
2052  
2053          ArrayPattern: function (expr, precedence, flags) {
2054              return this.ArrayExpression(expr, precedence, flags, true);
2055          },
2056  
2057          ArrayExpression: function (expr, precedence, flags, isPattern) {
2058              var result, multiline, that = this;
2059              if (!expr.elements.length) {
2060                  return '[]';
2061              }
2062              multiline = isPattern ? false : expr.elements.length > 1;
2063              result = ['[', multiline ? newline : ''];
2064              withIndent(function (indent) {
2065                  var i, iz;
2066                  for (i = 0, iz = expr.elements.length; i < iz; ++i) {
2067                      if (!expr.elements[i]) {
2068                          if (multiline) {
2069                              result.push(indent);
2070                          }
2071                          if (i + 1 === iz) {
2072                              result.push(',');
2073                          }
2074                      } else {
2075                          result.push(multiline ? indent : '');
2076                          result.push(that.generateExpression(expr.elements[i], Precedence.Assignment, E_TTT));
2077                      }
2078                      if (i + 1 < iz) {
2079                          result.push(',' + (multiline ? newline : space));
2080                      }
2081                  }
2082              });
2083              if (multiline && !endsWithLineTerminator(toSourceNodeWhenNeeded(result).toString())) {
2084                  result.push(newline);
2085              }
2086              result.push(multiline ? base : '');
2087              result.push(']');
2088              return result;
2089          },
2090  
2091          RestElement: function(expr, precedence, flags) {
2092              return '...' + this.generatePattern(expr.argument);
2093          },
2094  
2095          ClassExpression: function (expr, precedence, flags) {
2096              var result, fragment;
2097              result = ['class'];
2098              if (expr.id) {
2099                  result = join(result, this.generateExpression(expr.id, Precedence.Sequence, E_TTT));
2100              }
2101              if (expr.superClass) {
2102                  fragment = join('extends', this.generateExpression(expr.superClass, Precedence.Unary, E_TTT));
2103                  result = join(result, fragment);
2104              }
2105              result.push(space);
2106              result.push(this.generateStatement(expr.body, S_TFFT));
2107              return result;
2108          },
2109  
2110          MethodDefinition: function (expr, precedence, flags) {
2111              var result, fragment;
2112              if (expr['static']) {
2113                  result = ['static' + space];
2114              } else {
2115                  result = [];
2116              }
2117              if (expr.kind === 'get' || expr.kind === 'set') {
2118                  fragment = [
2119                      join(expr.kind, this.generatePropertyKey(expr.key, expr.computed)),
2120                      this.generateFunctionBody(expr.value)
2121                  ];
2122              } else {
2123                  fragment = [
2124                      generateMethodPrefix(expr),
2125                      this.generatePropertyKey(expr.key, expr.computed),
2126                      this.generateFunctionBody(expr.value)
2127                  ];
2128              }
2129              return join(result, fragment);
2130          },
2131  
2132          Property: function (expr, precedence, flags) {
2133              if (expr.kind === 'get' || expr.kind === 'set') {
2134                  return [
2135                      expr.kind, noEmptySpace(),
2136                      this.generatePropertyKey(expr.key, expr.computed),
2137                      this.generateFunctionBody(expr.value)
2138                  ];
2139              }
2140  
2141              if (expr.shorthand) {
2142                  if (expr.value.type === "AssignmentPattern") {
2143                      return this.AssignmentPattern(expr.value, Precedence.Sequence, E_TTT);
2144                  }
2145                  return this.generatePropertyKey(expr.key, expr.computed);
2146              }
2147  
2148              if (expr.method) {
2149                  return [
2150                      generateMethodPrefix(expr),
2151                      this.generatePropertyKey(expr.key, expr.computed),
2152                      this.generateFunctionBody(expr.value)
2153                  ];
2154              }
2155  
2156              return [
2157                  this.generatePropertyKey(expr.key, expr.computed),
2158                  ':' + space,
2159                  this.generateExpression(expr.value, Precedence.Assignment, E_TTT)
2160              ];
2161          },
2162  
2163          ObjectExpression: function (expr, precedence, flags) {
2164              var multiline, result, fragment, that = this;
2165  
2166              if (!expr.properties.length) {
2167                  return '{}';
2168              }
2169              multiline = expr.properties.length > 1;
2170  
2171              withIndent(function () {
2172                  fragment = that.generateExpression(expr.properties[0], Precedence.Sequence, E_TTT);
2173              });
2174  
2175              if (!multiline) {
2176                  // issues 4
2177                  // Do not transform from
2178                  //   dejavu.Class.declare({
2179                  //       method2: function () {}
2180                  //   });
2181                  // to
2182                  //   dejavu.Class.declare({method2: function () {
2183                  //       }});
2184                  if (!hasLineTerminator(toSourceNodeWhenNeeded(fragment).toString())) {
2185                      return [ '{', space, fragment, space, '}' ];
2186                  }
2187              }
2188  
2189              withIndent(function (indent) {
2190                  var i, iz;
2191                  result = [ '{', newline, indent, fragment ];
2192  
2193                  if (multiline) {
2194                      result.push(',' + newline);
2195                      for (i = 1, iz = expr.properties.length; i < iz; ++i) {
2196                          result.push(indent);
2197                          result.push(that.generateExpression(expr.properties[i], Precedence.Sequence, E_TTT));
2198                          if (i + 1 < iz) {
2199                              result.push(',' + newline);
2200                          }
2201                      }
2202                  }
2203              });
2204  
2205              if (!endsWithLineTerminator(toSourceNodeWhenNeeded(result).toString())) {
2206                  result.push(newline);
2207              }
2208              result.push(base);
2209              result.push('}');
2210              return result;
2211          },
2212  
2213          AssignmentPattern: function(expr, precedence, flags) {
2214              return this.generateAssignment(expr.left, expr.right, '=', precedence, flags);
2215          },
2216  
2217          ObjectPattern: function (expr, precedence, flags) {
2218              var result, i, iz, multiline, property, that = this;
2219              if (!expr.properties.length) {
2220                  return '{}';
2221              }
2222  
2223              multiline = false;
2224              if (expr.properties.length === 1) {
2225                  property = expr.properties[0];
2226                  if (
2227                      property.type === Syntax.Property
2228                      && property.value.type !== Syntax.Identifier
2229                  ) {
2230                      multiline = true;
2231                  }
2232              } else {
2233                  for (i = 0, iz = expr.properties.length; i < iz; ++i) {
2234                      property = expr.properties[i];
2235                      if (
2236                          property.type === Syntax.Property
2237                          && !property.shorthand
2238                      ) {
2239                          multiline = true;
2240                          break;
2241                      }
2242                  }
2243              }
2244              result = ['{', multiline ? newline : '' ];
2245  
2246              withIndent(function (indent) {
2247                  var i, iz;
2248                  for (i = 0, iz = expr.properties.length; i < iz; ++i) {
2249                      result.push(multiline ? indent : '');
2250                      result.push(that.generateExpression(expr.properties[i], Precedence.Sequence, E_TTT));
2251                      if (i + 1 < iz) {
2252                          result.push(',' + (multiline ? newline : space));
2253                      }
2254                  }
2255              });
2256  
2257              if (multiline && !endsWithLineTerminator(toSourceNodeWhenNeeded(result).toString())) {
2258                  result.push(newline);
2259              }
2260              result.push(multiline ? base : '');
2261              result.push('}');
2262              return result;
2263          },
2264  
2265          ThisExpression: function (expr, precedence, flags) {
2266              return 'this';
2267          },
2268  
2269          Super: function (expr, precedence, flags) {
2270              return 'super';
2271          },
2272  
2273          Identifier: function (expr, precedence, flags) {
2274              return generateIdentifier(expr);
2275          },
2276  
2277          ImportDefaultSpecifier: function (expr, precedence, flags) {
2278              return generateIdentifier(expr.id || expr.local);
2279          },
2280  
2281          ImportNamespaceSpecifier: function (expr, precedence, flags) {
2282              var result = ['*'];
2283              var id = expr.id || expr.local;
2284              if (id) {
2285                  result.push(space + 'as' + noEmptySpace() + generateIdentifier(id));
2286              }
2287              return result;
2288          },
2289  
2290          ImportSpecifier: function (expr, precedence, flags) {
2291              var imported = expr.imported;
2292              var result = [ imported.name ];
2293              var local = expr.local;
2294              if (local && local.name !== imported.name) {
2295                  result.push(noEmptySpace() + 'as' + noEmptySpace() + generateIdentifier(local));
2296              }
2297              return result;
2298          },
2299  
2300          ExportSpecifier: function (expr, precedence, flags) {
2301              var local = expr.local;
2302              var result = [ local.name ];
2303              var exported = expr.exported;
2304              if (exported && exported.name !== local.name) {
2305                  result.push(noEmptySpace() + 'as' + noEmptySpace() + generateIdentifier(exported));
2306              }
2307              return result;
2308          },
2309  
2310          Literal: function (expr, precedence, flags) {
2311              var raw;
2312              if (expr.hasOwnProperty('raw') && parse && extra.raw) {
2313                  try {
2314                      raw = parse(expr.raw).body[0].expression;
2315                      if (raw.type === Syntax.Literal) {
2316                          if (raw.value === expr.value) {
2317                              return expr.raw;
2318                          }
2319                      }
2320                  } catch (e) {
2321                      // not use raw property
2322                  }
2323              }
2324  
2325              if (expr.regex) {
2326                return '/' + expr.regex.pattern + '/' + expr.regex.flags;
2327              }
2328  
2329              if (expr.value === null) {
2330                  return 'null';
2331              }
2332  
2333              if (typeof expr.value === 'string') {
2334                  return escapeString(expr.value);
2335              }
2336  
2337              if (typeof expr.value === 'number') {
2338                  return generateNumber(expr.value);
2339              }
2340  
2341              if (typeof expr.value === 'boolean') {
2342                  return expr.value ? 'true' : 'false';
2343              }
2344  
2345              return generateRegExp(expr.value);
2346          },
2347  
2348          GeneratorExpression: function (expr, precedence, flags) {
2349              return this.ComprehensionExpression(expr, precedence, flags);
2350          },
2351  
2352          ComprehensionExpression: function (expr, precedence, flags) {
2353              // GeneratorExpression should be parenthesized with (...), ComprehensionExpression with [...]
2354              // Due to https://bugzilla.mozilla.org/show_bug.cgi?id=883468 position of expr.body can differ in Spidermonkey and ES6
2355  
2356              var result, i, iz, fragment, that = this;
2357              result = (expr.type === Syntax.GeneratorExpression) ? ['('] : ['['];
2358  
2359              if (extra.moz.comprehensionExpressionStartsWithAssignment) {
2360                  fragment = this.generateExpression(expr.body, Precedence.Assignment, E_TTT);
2361                  result.push(fragment);
2362              }
2363  
2364              if (expr.blocks) {
2365                  withIndent(function () {
2366                      for (i = 0, iz = expr.blocks.length; i < iz; ++i) {
2367                          fragment = that.generateExpression(expr.blocks[i], Precedence.Sequence, E_TTT);
2368                          if (i > 0 || extra.moz.comprehensionExpressionStartsWithAssignment) {
2369                              result = join(result, fragment);
2370                          } else {
2371                              result.push(fragment);
2372                          }
2373                      }
2374                  });
2375              }
2376  
2377              if (expr.filter) {
2378                  result = join(result, 'if' + space);
2379                  fragment = this.generateExpression(expr.filter, Precedence.Sequence, E_TTT);
2380                  result = join(result, [ '(', fragment, ')' ]);
2381              }
2382  
2383              if (!extra.moz.comprehensionExpressionStartsWithAssignment) {
2384                  fragment = this.generateExpression(expr.body, Precedence.Assignment, E_TTT);
2385  
2386                  result = join(result, fragment);
2387              }
2388  
2389              result.push((expr.type === Syntax.GeneratorExpression) ? ')' : ']');
2390              return result;
2391          },
2392  
2393          ComprehensionBlock: function (expr, precedence, flags) {
2394              var fragment;
2395              if (expr.left.type === Syntax.VariableDeclaration) {
2396                  fragment = [
2397                      expr.left.kind, noEmptySpace(),
2398                      this.generateStatement(expr.left.declarations[0], S_FFFF)
2399                  ];
2400              } else {
2401                  fragment = this.generateExpression(expr.left, Precedence.Call, E_TTT);
2402              }
2403  
2404              fragment = join(fragment, expr.of ? 'of' : 'in');
2405              fragment = join(fragment, this.generateExpression(expr.right, Precedence.Sequence, E_TTT));
2406  
2407              return [ 'for' + space + '(', fragment, ')' ];
2408          },
2409  
2410          SpreadElement: function (expr, precedence, flags) {
2411              return [
2412                  '...',
2413                  this.generateExpression(expr.argument, Precedence.Assignment, E_TTT)
2414              ];
2415          },
2416  
2417          TaggedTemplateExpression: function (expr, precedence, flags) {
2418              var itemFlags = E_TTF;
2419              if (!(flags & F_ALLOW_CALL)) {
2420                  itemFlags = E_TFF;
2421              }
2422              var result = [
2423                  this.generateExpression(expr.tag, Precedence.Call, itemFlags),
2424                  this.generateExpression(expr.quasi, Precedence.Primary, E_FFT)
2425              ];
2426              return parenthesize(result, Precedence.TaggedTemplate, precedence);
2427          },
2428  
2429          TemplateElement: function (expr, precedence, flags) {
2430              // Don't use "cooked". Since tagged template can use raw template
2431              // representation. So if we do so, it breaks the script semantics.
2432              return expr.value.raw;
2433          },
2434  
2435          TemplateLiteral: function (expr, precedence, flags) {
2436              var result, i, iz;
2437              result = [ '`' ];
2438              for (i = 0, iz = expr.quasis.length; i < iz; ++i) {
2439                  result.push(this.generateExpression(expr.quasis[i], Precedence.Primary, E_TTT));
2440                  if (i + 1 < iz) {
2441                      result.push('${' + space);
2442                      result.push(this.generateExpression(expr.expressions[i], Precedence.Sequence, E_TTT));
2443                      result.push(space + '}');
2444                  }
2445              }
2446              result.push('`');
2447              return result;
2448          },
2449  
2450          ModuleSpecifier: function (expr, precedence, flags) {
2451              return this.Literal(expr, precedence, flags);
2452          },
2453  
2454          ImportExpression: function(expr, precedence, flag) {
2455              return parenthesize([
2456                  'import(',
2457                  this.generateExpression(expr.source, Precedence.Assignment, E_TTT),
2458                  ')'
2459              ], Precedence.Call, precedence);
2460          },
2461  
2462      };
2463  
2464      merge(CodeGenerator.prototype, CodeGenerator.Expression);
2465  
2466      CodeGenerator.prototype.generateExpression = function (expr, precedence, flags) {
2467          var result, type;
2468  
2469          type = expr.type || Syntax.Property;
2470  
2471          if (extra.verbatim && expr.hasOwnProperty(extra.verbatim)) {
2472              return generateVerbatim(expr, precedence);
2473          }
2474  
2475          result = this[type](expr, precedence, flags);
2476  
2477  
2478          if (extra.comment) {
2479              result = addComments(expr, result);
2480          }
2481          return toSourceNodeWhenNeeded(result, expr);
2482      };
2483  
2484      CodeGenerator.prototype.generateStatement = function (stmt, flags) {
2485          var result,
2486              fragment;
2487  
2488          result = this[stmt.type](stmt, flags);
2489  
2490          // Attach comments
2491  
2492          if (extra.comment) {
2493              result = addComments(stmt, result);
2494          }
2495  
2496          fragment = toSourceNodeWhenNeeded(result).toString();
2497          if (stmt.type === Syntax.Program && !safeConcatenation && newline === '' &&  fragment.charAt(fragment.length - 1) === '\n') {
2498              result = sourceMap ? toSourceNodeWhenNeeded(result).replaceRight(/\s+$/, '') : fragment.replace(/\s+$/, '');
2499          }
2500  
2501          return toSourceNodeWhenNeeded(result, stmt);
2502      };
2503  
2504      function generateInternal(node) {
2505          var codegen;
2506  
2507          codegen = new CodeGenerator();
2508          if (isStatement(node)) {
2509              return codegen.generateStatement(node, S_TFFF);
2510          }
2511  
2512          if (isExpression(node)) {
2513              return codegen.generateExpression(node, Precedence.Sequence, E_TTT);
2514          }
2515  
2516          throw new Error('Unknown node type: ' + node.type);
2517      }
2518  
2519      function generate(node, options) {
2520          var defaultOptions = getDefaultOptions(), result, pair;
2521  
2522          if (options != null) {
2523              // Obsolete options
2524              //
2525              //   `options.indent`
2526              //   `options.base`
2527              //
2528              // Instead of them, we can use `option.format.indent`.
2529              if (typeof options.indent === 'string') {
2530                  defaultOptions.format.indent.style = options.indent;
2531              }
2532              if (typeof options.base === 'number') {
2533                  defaultOptions.format.indent.base = options.base;
2534              }
2535              options = updateDeeply(defaultOptions, options);
2536              indent = options.format.indent.style;
2537              if (typeof options.base === 'string') {
2538                  base = options.base;
2539              } else {
2540                  base = stringRepeat(indent, options.format.indent.base);
2541              }
2542          } else {
2543              options = defaultOptions;
2544              indent = options.format.indent.style;
2545              base = stringRepeat(indent, options.format.indent.base);
2546          }
2547          json = options.format.json;
2548          renumber = options.format.renumber;
2549          hexadecimal = json ? false : options.format.hexadecimal;
2550          quotes = json ? 'double' : options.format.quotes;
2551          escapeless = options.format.escapeless;
2552          newline = options.format.newline;
2553          space = options.format.space;
2554          if (options.format.compact) {
2555              newline = space = indent = base = '';
2556          }
2557          parentheses = options.format.parentheses;
2558          semicolons = options.format.semicolons;
2559          safeConcatenation = options.format.safeConcatenation;
2560          directive = options.directive;
2561          parse = json ? null : options.parse;
2562          sourceMap = options.sourceMap;
2563          sourceCode = options.sourceCode;
2564          preserveBlankLines = options.format.preserveBlankLines && sourceCode !== null;
2565          extra = options;
2566  
2567          if (sourceMap) {
2568              if (!exports.browser) {
2569                  // We assume environment is node.js
2570                  // And prevent from including source-map by browserify
2571                  SourceNode = require('source-map').SourceNode;
2572              } else {
2573                  SourceNode = global.sourceMap.SourceNode;
2574              }
2575          }
2576  
2577          result = generateInternal(node);
2578  
2579          if (!sourceMap) {
2580              pair = {code: result.toString(), map: null};
2581              return options.sourceMapWithCode ? pair : pair.code;
2582          }
2583  
2584  
2585          pair = result.toStringWithSourceMap({
2586              file: options.file,
2587              sourceRoot: options.sourceMapRoot
2588          });
2589  
2590          if (options.sourceContent) {
2591              pair.map.setSourceContent(options.sourceMap,
2592                                        options.sourceContent);
2593          }
2594  
2595          if (options.sourceMapWithCode) {
2596              return pair;
2597          }
2598  
2599          return pair.map.toString();
2600      }
2601  
2602      FORMAT_MINIFY = {
2603          indent: {
2604              style: '',
2605              base: 0
2606          },
2607          renumber: true,
2608          hexadecimal: true,
2609          quotes: 'auto',
2610          escapeless: true,
2611          compact: true,
2612          parentheses: false,
2613          semicolons: false
2614      };
2615  
2616      FORMAT_DEFAULTS = getDefaultOptions().format;
2617  
2618      exports.version = require('./package.json').version;
2619      exports.generate = generate;
2620      exports.attachComments = estraverse.attachComments;
2621      exports.Precedence = updateDeeply({}, Precedence);
2622      exports.browser = false;
2623      exports.FORMAT_MINIFY = FORMAT_MINIFY;
2624      exports.FORMAT_DEFAULTS = FORMAT_DEFAULTS;
2625  }());
2626  /* vim: set sw=4 ts=4 et tw=80 : */