toolkit/devtools/pretty-fast/pretty-fast.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 /*
     2  * Copyright 2013 Mozilla Foundation and contributors
     3  * Licensed under the New BSD license. See LICENSE.md or:
     4  * http://opensource.org/licenses/BSD-2-Clause
     5  */
     6 (function (root, factory) {
     7   if (typeof define === 'function' && define.amd) {
     8     define(factory);
     9   } else if (typeof exports === 'object') {
    10     module.exports = factory();
    11   } else {
    12     root.prettyFast = factory();
    13   }
    14 }(this, function () {
    15   "use strict";
    17   var acorn = this.acorn || require("acorn/acorn");
    18   var sourceMap = this.sourceMap || require("source-map");
    19   var SourceNode = sourceMap.SourceNode;
    21   // If any of these tokens are seen before a "[" token, we know that "[" token
    22   // is the start of an array literal, rather than a property access.
    23   //
    24   // The only exception is "}", which would need to be disambiguated by
    25   // parsing. The majority of the time, an open bracket following a closing
    26   // curly is going to be an array literal, so we brush the complication under
    27   // the rug, and handle the ambiguity by always assuming that it will be an
    28   // array literal.
    29   var PRE_ARRAY_LITERAL_TOKENS = {
    30     "typeof": true,
    31     "void": true,
    32     "delete": true,
    33     "case": true,
    34     "do": true,
    35     "=": true,
    36     "in": true,
    37     "{": true,
    38     "*": true,
    39     "/": true,
    40     "%": true,
    41     "else": true,
    42     ";": true,
    43     "++": true,
    44     "--": true,
    45     "+": true,
    46     "-": true,
    47     "~": true,
    48     "!": true,
    49     ":": true,
    50     "?": true,
    51     ">>": true,
    52     ">>>": true,
    53     "<<": true,
    54     "||": true,
    55     "&&": true,
    56     "<": true,
    57     ">": true,
    58     "<=": true,
    59     ">=": true,
    60     "instanceof": true,
    61     "&": true,
    62     "^": true,
    63     "|": true,
    64     "==": true,
    65     "!=": true,
    66     "===": true,
    67     "!==": true,
    68     ",": true,
    70     "}": true
    71   };
    73   /**
    74    * Determines if we think that the given token starts an array literal.
    75    *
    76    * @param Object token
    77    *        The token we want to determine if it is an array literal.
    78    * @param Object lastToken
    79    *        The last token we added to the pretty printed results.
    80    *
    81    * @returns Boolean
    82    *          True if we believe it is an array literal, false otherwise.
    83    */
    84   function isArrayLiteral(token, lastToken) {
    85     if (token.type.type != "[") {
    86       return false;
    87     }
    88     if (!lastToken) {
    89       return true;
    90     }
    91     if (lastToken.type.isAssign) {
    92       return true;
    93     }
    94     return !!PRE_ARRAY_LITERAL_TOKENS[lastToken.type.keyword || lastToken.type.type];
    95   }
    97   // If any of these tokens are followed by a token on a new line, we know that
    98   // ASI cannot happen.
    99   var PREVENT_ASI_AFTER_TOKENS = {
   100     // Binary operators
   101     "*": true,
   102     "/": true,
   103     "%": true,
   104     "+": true,
   105     "-": true,
   106     "<<": true,
   107     ">>": true,
   108     ">>>": true,
   109     "<": true,
   110     ">": true,
   111     "<=": true,
   112     ">=": true,
   113     "instanceof": true,
   114     "in": true,
   115     "==": true,
   116     "!=": true,
   117     "===": true,
   118     "!==": true,
   119     "&": true,
   120     "^": true,
   121     "|": true,
   122     "&&": true,
   123     "||": true,
   124     ",": true,
   125     ".": true,
   126     "=": true,
   127     "*=": true,
   128     "/=": true,
   129     "%=": true,
   130     "+=": true,
   131     "-=": true,
   132     "<<=": true,
   133     ">>=": true,
   134     ">>>=": true,
   135     "&=": true,
   136     "^=": true,
   137     "|=": true,
   138     // Unary operators
   139     "delete": true,
   140     "void": true,
   141     "typeof": true,
   142     "~": true,
   143     "!": true,
   144     "new": true,
   145     // Function calls and grouped expressions
   146     "(": true
   147   };
   149   // If any of these tokens are on a line after the token before it, we know
   150   // that ASI cannot happen.
   151   var PREVENT_ASI_BEFORE_TOKENS = {
   152     // Binary operators
   153     "*": true,
   154     "/": true,
   155     "%": true,
   156     "<<": true,
   157     ">>": true,
   158     ">>>": true,
   159     "<": true,
   160     ">": true,
   161     "<=": true,
   162     ">=": true,
   163     "instanceof": true,
   164     "in": true,
   165     "==": true,
   166     "!=": true,
   167     "===": true,
   168     "!==": true,
   169     "&": true,
   170     "^": true,
   171     "|": true,
   172     "&&": true,
   173     "||": true,
   174     ",": true,
   175     ".": true,
   176     "=": true,
   177     "*=": true,
   178     "/=": true,
   179     "%=": true,
   180     "+=": true,
   181     "-=": true,
   182     "<<=": true,
   183     ">>=": true,
   184     ">>>=": true,
   185     "&=": true,
   186     "^=": true,
   187     "|=": true,
   188     // Function calls
   189     "(": true
   190   };
   192   /**
   193    * Determines if Automatic Semicolon Insertion (ASI) occurs between these
   194    * tokens.
   195    *
   196    * @param Object token
   197    *        The current token.
   198    * @param Object lastToken
   199    *        The last token we added to the pretty printed results.
   200    *
   201    * @returns Boolean
   202    *          True if we believe ASI occurs.
   203    */
   204   function isASI(token, lastToken) {
   205     if (!lastToken) {
   206       return false;
   207     }
   208     if (token.startLoc.line === lastToken.startLoc.line) {
   209       return false;
   210     }
   211     if (PREVENT_ASI_AFTER_TOKENS[lastToken.type.type || lastToken.type.keyword]) {
   212       return false;
   213     }
   214     if (PREVENT_ASI_BEFORE_TOKENS[token.type.type || token.type.keyword]) {
   215       return false;
   216     }
   217     return true;
   218   }
   220   /**
   221    * Determine if we have encountered a getter or setter.
   222    *
   223    * @param Object token
   224    *        The current token. If this is a getter or setter, it would be the
   225    *        property name.
   226    * @param Object lastToken
   227    *        The last token we added to the pretty printed results. If this is a
   228    *        getter or setter, it would be the `get` or `set` keyword
   229    *        respectively.
   230    * @param Array stack
   231    *        The stack of open parens/curlies/brackets/etc.
   232    *
   233    * @returns Boolean
   234    *          True if this is a getter or setter.
   235    */
   236   function isGetterOrSetter(token, lastToken, stack) {
   237     return stack[stack.length - 1] == "{"
   238       && lastToken
   239       && lastToken.type.type == "name"
   240       && (lastToken.value == "get" || lastToken.value == "set")
   241       && token.type.type == "name";
   242   }
   244   /**
   245    * Determine if we should add a newline after the given token.
   246    *
   247    * @param Object token
   248    *        The token we are looking at.
   249    * @param Array stack
   250    *        The stack of open parens/curlies/brackets/etc.
   251    *
   252    * @returns Boolean
   253    *          True if we should add a newline.
   254    */
   255   function isLineDelimiter(token, stack) {
   256     if (token.isArrayLiteral) {
   257       return true;
   258     }
   259     var ttt = token.type.type;
   260     var top = stack[stack.length - 1];
   261     return ttt == ";" && top != "("
   262       || ttt == "{"
   263       || ttt == "," && top != "("
   264       || ttt == ":" && (top == "case" || top == "default");
   265   }
   267   /**
   268    * Append the necessary whitespace to the result after we have added the given
   269    * token.
   270    *
   271    * @param Object token
   272    *        The token that was just added to the result.
   273    * @param Function write
   274    *        The function to write to the pretty printed results.
   275    * @param Array stack
   276    *        The stack of open parens/curlies/brackets/etc.
   277    *
   278    * @returns Boolean
   279    *          Returns true if we added a newline to result, false in all other
   280    *          cases.
   281    */
   282   function appendNewline(token, write, stack) {
   283     if (isLineDelimiter(token, stack)) {
   284       write("\n", token.startLoc.line, token.startLoc.column);
   285       return true;
   286     }
   287     return false;
   288   }
   290   /**
   291    * Determines if we need to add a space between the last token we added and
   292    * the token we are about to add.
   293    *
   294    * @param Object token
   295    *        The token we are about to add to the pretty printed code.
   296    * @param Object lastToken
   297    *        The last token added to the pretty printed code.
   298    */
   299   function needsSpaceAfter(token, lastToken) {
   300     if (lastToken) {
   301       if (lastToken.type.isLoop) {
   302         return true;
   303       }
   304       if (lastToken.type.isAssign) {
   305         return true;
   306       }
   307       if (lastToken.type.binop != null) {
   308         return true;
   309       }
   311       var ltt = lastToken.type.type;
   312       if (ltt == "?") {
   313         return true;
   314       }
   315       if (ltt == ":") {
   316         return true;
   317       }
   318       if (ltt == ",") {
   319         return true;
   320       }
   321       if (ltt == ";") {
   322         return true;
   323       }
   325       var ltk = lastToken.type.keyword;
   326       if (ltk != null) {
   327         if (ltk == "break" || ltk == "continue") {
   328           return token.type.type != ";";
   329         }
   330         if (ltk != "debugger"
   331             && ltk != "null"
   332             && ltk != "true"
   333             && ltk != "false"
   334             && ltk != "this"
   335             && ltk != "default") {
   336           return true;
   337         }
   338       }
   340       if (ltt == ")" && (token.type.type != ")"
   341                          && token.type.type != "]"
   342                          && token.type.type != ";"
   343                          && token.type.type != ",")) {
   344         return true;
   345       }
   346     }
   348     if (token.type.isAssign) {
   349       return true;
   350     }
   351     if (token.type.binop != null) {
   352       return true;
   353     }
   354     if (token.type.type == "?") {
   355       return true;
   356     }
   358     return false;
   359   }
   361   /**
   362    * Add the required whitespace before this token, whether that is a single
   363    * space, newline, and/or the indent on fresh lines.
   364    *
   365    * @param Object token
   366    *        The token we are about to add to the pretty printed code.
   367    * @param Object lastToken
   368    *        The last token we added to the pretty printed code.
   369    * @param Boolean addedNewline
   370    *        Whether we added a newline after adding the last token to the pretty
   371    *        printed code.
   372    * @param Function write
   373    *        The function to write pretty printed code to the result SourceNode.
   374    * @param Object options
   375    *        The options object.
   376    * @param Number indentLevel
   377    *        The number of indents deep we are.
   378    * @param Array stack
   379    *        The stack of open curlies, brackets, etc.
   380    */
   381   function prependWhiteSpace(token, lastToken, addedNewline, write, options,
   382                              indentLevel, stack) {
   383     var ttk = token.type.keyword;
   384     var ttt = token.type.type;
   385     var newlineAdded = addedNewline;
   386     var ltt = lastToken ? lastToken.type.type : null;
   388     // Handle whitespace and newlines after "}" here instead of in
   389     // `isLineDelimiter` because it is only a line delimiter some of the
   390     // time. For example, we don't want to put "else if" on a new line after
   391     // the first if's block.
   392     if (lastToken && ltt == "}") {
   393       if (ttk == "while" && stack[stack.length - 1] == "do") {
   394         write(" ",
   395               lastToken.startLoc.line,
   396               lastToken.startLoc.column);
   397       } else if (ttk == "else" ||
   398                  ttk == "catch" ||
   399                  ttk == "finally") {
   400         write(" ",
   401               lastToken.startLoc.line,
   402               lastToken.startLoc.column);
   403       } else if (ttt != "(" &&
   404                  ttt != ";" &&
   405                  ttt != "," &&
   406                  ttt != ")" &&
   407                  ttt != ".") {
   408         write("\n",
   409               lastToken.startLoc.line,
   410               lastToken.startLoc.column);
   411         newlineAdded = true;
   412       }
   413     }
   415     if (isGetterOrSetter(token, lastToken, stack)) {
   416       write(" ",
   417             lastToken.startLoc.line,
   418             lastToken.startLoc.column);
   419     }
   421     if (ttt == ":" && stack[stack.length - 1] == "?") {
   422       write(" ",
   423             lastToken.startLoc.line,
   424             lastToken.startLoc.column);
   425     }
   427     if (lastToken && ltt != "}" && ttk == "else") {
   428       write(" ",
   429             lastToken.startLoc.line,
   430             lastToken.startLoc.column);
   431     }
   433     function ensureNewline() {
   434       if (!newlineAdded) {
   435         write("\n",
   436               lastToken.startLoc.line,
   437               lastToken.startLoc.column);
   438         newlineAdded = true;
   439       }
   440     }
   442     if (isASI(token, lastToken)) {
   443       ensureNewline();
   444     }
   446     if (decrementsIndent(ttt, stack)) {
   447       ensureNewline();
   448     }
   450     if (newlineAdded) {
   451       if (ttk == "case" || ttk == "default") {
   452         write(repeat(options.indent, indentLevel - 1),
   453               token.startLoc.line,
   454               token.startLoc.column);
   455       } else {
   456         write(repeat(options.indent, indentLevel),
   457               token.startLoc.line,
   458               token.startLoc.column);
   459       }
   460     } else if (needsSpaceAfter(token, lastToken)) {
   461       write(" ",
   462             lastToken.startLoc.line,
   463             lastToken.startLoc.column);
   464     }
   465   }
   467   /**
   468    * Repeat the `str` string `n` times.
   469    *
   470    * @param String str
   471    *        The string to be repeated.
   472    * @param Number n
   473    *        The number of times to repeat the string.
   474    *
   475    * @returns String
   476    *          The repeated string.
   477    */
   478   function repeat(str, n) {
   479     var result = "";
   480     while (n > 0) {
   481       if (n & 1) {
   482         result += str;
   483       }
   484       n >>= 1;
   485       str += str;
   486     }
   487     return result;
   488   }
   490   /**
   491    * Make sure that we output the escaped character combination inside string literals
   492    * instead of various problematic characters.
   493    */
   494   var sanitize = (function () {
   495     var escapeCharacters = {
   496       // Backslash
   497       "\\": "\\\\",
   498       // Newlines
   499       "\n": "\\n",
   500       // Carriage return
   501       "\r": "\\r",
   502       // Tab
   503       "\t": "\\t",
   504       // Vertical tab
   505       "\v": "\\v",
   506       // Form feed
   507       "\f": "\\f",
   508       // Null character
   509       "\0": "\\0",
   510       // Single quotes
   511       "'": "\\'"
   512     };
   514     var regExpString = "("
   515       + Object.keys(escapeCharacters)
   516               .map(function (c) { return escapeCharacters[c]; })
   517               .join("|")
   518       + ")";
   519     var escapeCharactersRegExp = new RegExp(regExpString, "g");
   521     return function(str) {
   522       return str.replace(escapeCharactersRegExp, function (_, c) {
   523         return escapeCharacters[c];
   524       });
   525     }
   526   }());
   527   /**
   528    * Add the given token to the pretty printed results.
   529    *
   530    * @param Object token
   531    *        The token to add.
   532    * @param Function write
   533    *        The function to write pretty printed code to the result SourceNode.
   534    * @param Object options
   535    *        The options object.
   536    */
   537   function addToken(token, write, options) {
   538     if (token.type.type == "string") {
   539       write("'" + sanitize(token.value) + "'",
   540             token.startLoc.line,
   541             token.startLoc.column);
   542     } else {
   543       write(String(token.value != null ? token.value : token.type.type),
   544             token.startLoc.line,
   545             token.startLoc.column);
   546     }
   547   }
   549   /**
   550    * Returns true if the given token type belongs on the stack.
   551    */
   552   function belongsOnStack(token) {
   553     var ttt = token.type.type;
   554     var ttk = token.type.keyword;
   555     return ttt == "{"
   556       || ttt == "("
   557       || ttt == "["
   558       || ttt == "?"
   559       || ttk == "do"
   560       || ttk == "case"
   561       || ttk == "default";
   562   }
   564   /**
   565    * Returns true if the given token should cause us to pop the stack.
   566    */
   567   function shouldStackPop(token, stack) {
   568     var ttt = token.type.type;
   569     var ttk = token.type.keyword;
   570     var top = stack[stack.length - 1];
   571     return ttt == "]"
   572       || ttt == ")"
   573       || ttt == "}"
   574       || (ttt == ":" && (top == "case" || top == "default" || top == "?"))
   575       || (ttk == "while" && top == "do");
   576   }
   578   /**
   579    * Returns true if the given token type should cause us to decrement the
   580    * indent level.
   581    */
   582   function decrementsIndent(tokenType, stack) {
   583     return tokenType == "}"
   584       || (tokenType == "]" && stack[stack.length - 1] == "[\n")
   585   }
   587   /**
   588    * Returns true if the given token should cause us to increment the indent
   589    * level.
   590    */
   591   function incrementsIndent(token) {
   592     return token.type.type == "{" || token.isArrayLiteral;
   593   }
   595   /**
   596    * Add a comment to the pretty printed code.
   597    *
   598    * @param Function write
   599    *        The function to write pretty printed code to the result SourceNode.
   600    * @param Number indentLevel
   601    *        The number of indents deep we are.
   602    * @param Object options
   603    *        The options object.
   604    * @param Boolean block
   605    *        True if the comment is a multiline block style comment.
   606    * @param String text
   607    *        The text of the comment.
   608    * @param Number line
   609    *        The line number to comment appeared on.
   610    * @param Number column
   611    *        The column number the comment appeared on.
   612    */
   613   function addComment(write, indentLevel, options, block, text, line, column) {
   614     var indentString = repeat(options.indent, indentLevel);
   616     write(indentString, line, column);
   617     if (block) {
   618       write("/*");
   619       write(text
   620             .split(new RegExp("/\n" + indentString + "/", "g"))
   621             .join("\n" + indentString));
   622       write("*/");
   623     } else {
   624       write("//");
   625       write(text);
   626     }
   627     write("\n");
   628   }
   630   /**
   631    * The main function.
   632    *
   633    * @param String input
   634    *        The ugly JS code we want to pretty print.
   635    * @param Object options
   636    *        The options object. Provides configurability of the pretty
   637    *        printing. Properties:
   638    *          - url: The URL string of the ugly JS code.
   639    *          - indent: The string to indent code by.
   640    *
   641    * @returns Object
   642    *          An object with the following properties:
   643    *            - code: The pretty printed code string.
   644    *            - map: A SourceMapGenerator instance.
   645    */
   646   return function prettyFast(input, options) {
   647     // The level of indents deep we are.
   648     var indentLevel = 0;
   650     // We will accumulate the pretty printed code in this SourceNode.
   651     var result = new SourceNode();
   653     /**
   654      * Write a pretty printed string to the result SourceNode.
   655      *
   656      * We buffer our writes so that we only create one mapping for each line in
   657      * the source map. This enhances performance by avoiding extraneous mapping
   658      * serialization, and flattening the tree that
   659      * `SourceNode#toStringWithSourceMap` will have to recursively walk. When
   660      * timing how long it takes to pretty print jQuery, this optimization
   661      * brought the time down from ~390 ms to ~190ms!
   662      *
   663      * @param String str
   664      *        The string to be added to the result.
   665      * @param Number line
   666      *        The line number the string came from in the ugly source.
   667      * @param Number column
   668      *        The column number the string came from in the ugly source.
   669      */
   670     var write = (function () {
   671       var buffer = [];
   672       var bufferLine = -1;
   673       var bufferColumn = -1;
   674       return function write(str, line, column) {
   675         if (line != null && bufferLine === -1) {
   676           bufferLine = line;
   677         }
   678         if (column != null && bufferColumn === -1) {
   679           bufferColumn = column;
   680         }
   681         buffer.push(str);
   683         if (str == "\n") {
   684           var lineStr = "";
   685           for (var i = 0, len = buffer.length; i < len; i++) {
   686             lineStr += buffer[i];
   687           }
   688           result.add(new SourceNode(bufferLine, bufferColumn, options.url, lineStr));
   689           buffer.splice(0, buffer.length);
   690           bufferLine = -1;
   691           bufferColumn = -1;
   692         }
   693       }
   694     }());
   696     // Whether or not we added a newline on after we added the last token.
   697     var addedNewline = false;
   699     // The current token we will be adding to the pretty printed code.
   700     var token;
   702     // Shorthand for token.type.type, so we don't have to repeatedly access
   703     // properties.
   704     var ttt;
   706     // Shorthand for token.type.keyword, so we don't have to repeatedly access
   707     // properties.
   708     var ttk;
   710     // The last token we added to the pretty printed code.
   711     var lastToken;
   713     // Stack of token types/keywords that can affect whether we want to add a
   714     // newline or a space. We can make that decision based on what token type is
   715     // on the top of the stack. For example, a comma in a parameter list should
   716     // be followed by a space, while a comma in an object literal should be
   717     // followed by a newline.
   718     //
   719     // Strings that go on the stack:
   720     //
   721     //   - "{"
   722     //   - "("
   723     //   - "["
   724     //   - "[\n"
   725     //   - "do"
   726     //   - "?"
   727     //   - "case"
   728     //   - "default"
   729     //
   730     // The difference between "[" and "[\n" is that "[\n" is used when we are
   731     // treating "[" and "]" tokens as line delimiters and should increment and
   732     // decrement the indent level when we find them.
   733     var stack = [];
   735     // Acorn's tokenizer will always yield comments *before* the token they
   736     // follow (unless the very first thing in the source is a comment), so we
   737     // have to queue the comments in order to pretty print them in the correct
   738     // location. For example, the source file:
   739     //
   740     //     foo
   741     //     // a
   742     //     // b
   743     //     bar
   744     //
   745     // When tokenized by acorn, gives us the following token stream:
   746     //
   747     //     [ '// a', '// b', foo, bar ]
   748     var commentQueue = [];
   750     var getToken = acorn.tokenize(input, {
   751       locations: true,
   752       sourceFile: options.url,
   753       onComment: function (block, text, start, end, startLoc, endLoc) {
   754         if (lastToken) {
   755           commentQueue.push({
   756             block: block,
   757             text: text,
   758             line: startLoc.line,
   759             column: startLoc.column
   760           });
   761         } else {
   762           addComment(write, indentLevel, options, block, text, startLoc.line,
   763                      startLoc.column);
   764           addedNewline = true;
   765         }
   766       }
   767     });
   769     while (true) {
   770       token = getToken();
   772       ttk = token.type.keyword;
   773       ttt = token.type.type;
   775       if (ttt == "eof") {
   776         if (!addedNewline) {
   777           write("\n");
   778         }
   779         break;
   780       }
   782       token.isArrayLiteral = isArrayLiteral(token, lastToken);
   784       if (belongsOnStack(token)) {
   785         if (token.isArrayLiteral) {
   786           stack.push("[\n");
   787         } else {
   788           stack.push(ttt || ttk);
   789         }
   790       }
   792       if (decrementsIndent(ttt, stack)) {
   793         indentLevel--;
   794       }
   796       prependWhiteSpace(token, lastToken, addedNewline, write, options,
   797                         indentLevel, stack);
   798       addToken(token, write, options);
   799       addedNewline = appendNewline(token, write, stack);
   801       if (shouldStackPop(token, stack)) {
   802         stack.pop();
   803       }
   805       if (incrementsIndent(token)) {
   806         indentLevel++;
   807       }
   809       // Acorn's tokenizer re-uses tokens, so we have to copy the last token on
   810       // every iteration. We follow acorn's lead here, and reuse the lastToken
   811       // object the same way that acorn reuses the token object. This allows us
   812       // to avoid allocations and minimize GC pauses.
   813       if (!lastToken) {
   814         lastToken = { startLoc: {}, endLoc: {} };
   815       }
   816       lastToken.start = token.start;
   817       lastToken.end = token.end;
   818       lastToken.startLoc.line = token.startLoc.line;
   819       lastToken.startLoc.column = token.startLoc.column;
   820       lastToken.endLoc.line = token.endLoc.line;
   821       lastToken.endLoc.column = token.endLoc.column;
   822       lastToken.type = token.type;
   823       lastToken.value = token.value;
   824       lastToken.isArrayLiteral = token.isArrayLiteral;
   826       // Apply all the comments that have been queued up.
   827       if (commentQueue.length) {
   828         if (!addedNewline) {
   829           write("\n");
   830         }
   831         for (var i = 0, n = commentQueue.length; i < n; i++) {
   832           var comment = commentQueue[i];
   833           addComment(write, indentLevel, options, comment.block, comment.text,
   834                      comment.line, comment.column);
   835         }
   836         addedNewline = true;
   837         commentQueue.splice(0, commentQueue.length);
   838       }
   839     }
   841     return result.toStringWithSourceMap({ file: options.url });
   842   };
   844 }.bind(this)));

mercurial