michael@0: /* michael@0: * Copyright 2013 Mozilla Foundation and contributors michael@0: * Licensed under the New BSD license. See LICENSE.md or: michael@0: * http://opensource.org/licenses/BSD-2-Clause michael@0: */ michael@0: (function (root, factory) { michael@0: if (typeof define === 'function' && define.amd) { michael@0: define(factory); michael@0: } else if (typeof exports === 'object') { michael@0: module.exports = factory(); michael@0: } else { michael@0: root.prettyFast = factory(); michael@0: } michael@0: }(this, function () { michael@0: "use strict"; michael@0: michael@0: var acorn = this.acorn || require("acorn/acorn"); michael@0: var sourceMap = this.sourceMap || require("source-map"); michael@0: var SourceNode = sourceMap.SourceNode; michael@0: michael@0: // If any of these tokens are seen before a "[" token, we know that "[" token michael@0: // is the start of an array literal, rather than a property access. michael@0: // michael@0: // The only exception is "}", which would need to be disambiguated by michael@0: // parsing. The majority of the time, an open bracket following a closing michael@0: // curly is going to be an array literal, so we brush the complication under michael@0: // the rug, and handle the ambiguity by always assuming that it will be an michael@0: // array literal. michael@0: var PRE_ARRAY_LITERAL_TOKENS = { michael@0: "typeof": true, michael@0: "void": true, michael@0: "delete": true, michael@0: "case": true, michael@0: "do": true, michael@0: "=": true, michael@0: "in": true, michael@0: "{": true, michael@0: "*": true, michael@0: "/": true, michael@0: "%": true, michael@0: "else": true, michael@0: ";": true, michael@0: "++": true, michael@0: "--": true, michael@0: "+": true, michael@0: "-": true, michael@0: "~": true, michael@0: "!": true, michael@0: ":": true, michael@0: "?": true, michael@0: ">>": true, michael@0: ">>>": true, michael@0: "<<": true, michael@0: "||": true, michael@0: "&&": true, michael@0: "<": true, michael@0: ">": true, michael@0: "<=": true, michael@0: ">=": true, michael@0: "instanceof": true, michael@0: "&": true, michael@0: "^": true, michael@0: "|": true, michael@0: "==": true, michael@0: "!=": true, michael@0: "===": true, michael@0: "!==": true, michael@0: ",": true, michael@0: michael@0: "}": true michael@0: }; michael@0: michael@0: /** michael@0: * Determines if we think that the given token starts an array literal. michael@0: * michael@0: * @param Object token michael@0: * The token we want to determine if it is an array literal. michael@0: * @param Object lastToken michael@0: * The last token we added to the pretty printed results. michael@0: * michael@0: * @returns Boolean michael@0: * True if we believe it is an array literal, false otherwise. michael@0: */ michael@0: function isArrayLiteral(token, lastToken) { michael@0: if (token.type.type != "[") { michael@0: return false; michael@0: } michael@0: if (!lastToken) { michael@0: return true; michael@0: } michael@0: if (lastToken.type.isAssign) { michael@0: return true; michael@0: } michael@0: return !!PRE_ARRAY_LITERAL_TOKENS[lastToken.type.keyword || lastToken.type.type]; michael@0: } michael@0: michael@0: // If any of these tokens are followed by a token on a new line, we know that michael@0: // ASI cannot happen. michael@0: var PREVENT_ASI_AFTER_TOKENS = { michael@0: // Binary operators michael@0: "*": true, michael@0: "/": true, michael@0: "%": true, michael@0: "+": true, michael@0: "-": true, michael@0: "<<": true, michael@0: ">>": true, michael@0: ">>>": true, michael@0: "<": true, michael@0: ">": true, michael@0: "<=": true, michael@0: ">=": true, michael@0: "instanceof": true, michael@0: "in": true, michael@0: "==": true, michael@0: "!=": true, michael@0: "===": true, michael@0: "!==": true, michael@0: "&": true, michael@0: "^": true, michael@0: "|": true, michael@0: "&&": true, michael@0: "||": true, michael@0: ",": true, michael@0: ".": true, michael@0: "=": true, michael@0: "*=": true, michael@0: "/=": true, michael@0: "%=": true, michael@0: "+=": true, michael@0: "-=": true, michael@0: "<<=": true, michael@0: ">>=": true, michael@0: ">>>=": true, michael@0: "&=": true, michael@0: "^=": true, michael@0: "|=": true, michael@0: // Unary operators michael@0: "delete": true, michael@0: "void": true, michael@0: "typeof": true, michael@0: "~": true, michael@0: "!": true, michael@0: "new": true, michael@0: // Function calls and grouped expressions michael@0: "(": true michael@0: }; michael@0: michael@0: // If any of these tokens are on a line after the token before it, we know michael@0: // that ASI cannot happen. michael@0: var PREVENT_ASI_BEFORE_TOKENS = { michael@0: // Binary operators michael@0: "*": true, michael@0: "/": true, michael@0: "%": true, michael@0: "<<": true, michael@0: ">>": true, michael@0: ">>>": true, michael@0: "<": true, michael@0: ">": true, michael@0: "<=": true, michael@0: ">=": true, michael@0: "instanceof": true, michael@0: "in": true, michael@0: "==": true, michael@0: "!=": true, michael@0: "===": true, michael@0: "!==": true, michael@0: "&": true, michael@0: "^": true, michael@0: "|": true, michael@0: "&&": true, michael@0: "||": true, michael@0: ",": true, michael@0: ".": true, michael@0: "=": true, michael@0: "*=": true, michael@0: "/=": true, michael@0: "%=": true, michael@0: "+=": true, michael@0: "-=": true, michael@0: "<<=": true, michael@0: ">>=": true, michael@0: ">>>=": true, michael@0: "&=": true, michael@0: "^=": true, michael@0: "|=": true, michael@0: // Function calls michael@0: "(": true michael@0: }; michael@0: michael@0: /** michael@0: * Determines if Automatic Semicolon Insertion (ASI) occurs between these michael@0: * tokens. michael@0: * michael@0: * @param Object token michael@0: * The current token. michael@0: * @param Object lastToken michael@0: * The last token we added to the pretty printed results. michael@0: * michael@0: * @returns Boolean michael@0: * True if we believe ASI occurs. michael@0: */ michael@0: function isASI(token, lastToken) { michael@0: if (!lastToken) { michael@0: return false; michael@0: } michael@0: if (token.startLoc.line === lastToken.startLoc.line) { michael@0: return false; michael@0: } michael@0: if (PREVENT_ASI_AFTER_TOKENS[lastToken.type.type || lastToken.type.keyword]) { michael@0: return false; michael@0: } michael@0: if (PREVENT_ASI_BEFORE_TOKENS[token.type.type || token.type.keyword]) { michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: /** michael@0: * Determine if we have encountered a getter or setter. michael@0: * michael@0: * @param Object token michael@0: * The current token. If this is a getter or setter, it would be the michael@0: * property name. michael@0: * @param Object lastToken michael@0: * The last token we added to the pretty printed results. If this is a michael@0: * getter or setter, it would be the `get` or `set` keyword michael@0: * respectively. michael@0: * @param Array stack michael@0: * The stack of open parens/curlies/brackets/etc. michael@0: * michael@0: * @returns Boolean michael@0: * True if this is a getter or setter. michael@0: */ michael@0: function isGetterOrSetter(token, lastToken, stack) { michael@0: return stack[stack.length - 1] == "{" michael@0: && lastToken michael@0: && lastToken.type.type == "name" michael@0: && (lastToken.value == "get" || lastToken.value == "set") michael@0: && token.type.type == "name"; michael@0: } michael@0: michael@0: /** michael@0: * Determine if we should add a newline after the given token. michael@0: * michael@0: * @param Object token michael@0: * The token we are looking at. michael@0: * @param Array stack michael@0: * The stack of open parens/curlies/brackets/etc. michael@0: * michael@0: * @returns Boolean michael@0: * True if we should add a newline. michael@0: */ michael@0: function isLineDelimiter(token, stack) { michael@0: if (token.isArrayLiteral) { michael@0: return true; michael@0: } michael@0: var ttt = token.type.type; michael@0: var top = stack[stack.length - 1]; michael@0: return ttt == ";" && top != "(" michael@0: || ttt == "{" michael@0: || ttt == "," && top != "(" michael@0: || ttt == ":" && (top == "case" || top == "default"); michael@0: } michael@0: michael@0: /** michael@0: * Append the necessary whitespace to the result after we have added the given michael@0: * token. michael@0: * michael@0: * @param Object token michael@0: * The token that was just added to the result. michael@0: * @param Function write michael@0: * The function to write to the pretty printed results. michael@0: * @param Array stack michael@0: * The stack of open parens/curlies/brackets/etc. michael@0: * michael@0: * @returns Boolean michael@0: * Returns true if we added a newline to result, false in all other michael@0: * cases. michael@0: */ michael@0: function appendNewline(token, write, stack) { michael@0: if (isLineDelimiter(token, stack)) { michael@0: write("\n", token.startLoc.line, token.startLoc.column); michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: /** michael@0: * Determines if we need to add a space between the last token we added and michael@0: * the token we are about to add. michael@0: * michael@0: * @param Object token michael@0: * The token we are about to add to the pretty printed code. michael@0: * @param Object lastToken michael@0: * The last token added to the pretty printed code. michael@0: */ michael@0: function needsSpaceAfter(token, lastToken) { michael@0: if (lastToken) { michael@0: if (lastToken.type.isLoop) { michael@0: return true; michael@0: } michael@0: if (lastToken.type.isAssign) { michael@0: return true; michael@0: } michael@0: if (lastToken.type.binop != null) { michael@0: return true; michael@0: } michael@0: michael@0: var ltt = lastToken.type.type; michael@0: if (ltt == "?") { michael@0: return true; michael@0: } michael@0: if (ltt == ":") { michael@0: return true; michael@0: } michael@0: if (ltt == ",") { michael@0: return true; michael@0: } michael@0: if (ltt == ";") { michael@0: return true; michael@0: } michael@0: michael@0: var ltk = lastToken.type.keyword; michael@0: if (ltk != null) { michael@0: if (ltk == "break" || ltk == "continue") { michael@0: return token.type.type != ";"; michael@0: } michael@0: if (ltk != "debugger" michael@0: && ltk != "null" michael@0: && ltk != "true" michael@0: && ltk != "false" michael@0: && ltk != "this" michael@0: && ltk != "default") { michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: if (ltt == ")" && (token.type.type != ")" michael@0: && token.type.type != "]" michael@0: && token.type.type != ";" michael@0: && token.type.type != ",")) { michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: if (token.type.isAssign) { michael@0: return true; michael@0: } michael@0: if (token.type.binop != null) { michael@0: return true; michael@0: } michael@0: if (token.type.type == "?") { michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: /** michael@0: * Add the required whitespace before this token, whether that is a single michael@0: * space, newline, and/or the indent on fresh lines. michael@0: * michael@0: * @param Object token michael@0: * The token we are about to add to the pretty printed code. michael@0: * @param Object lastToken michael@0: * The last token we added to the pretty printed code. michael@0: * @param Boolean addedNewline michael@0: * Whether we added a newline after adding the last token to the pretty michael@0: * printed code. michael@0: * @param Function write michael@0: * The function to write pretty printed code to the result SourceNode. michael@0: * @param Object options michael@0: * The options object. michael@0: * @param Number indentLevel michael@0: * The number of indents deep we are. michael@0: * @param Array stack michael@0: * The stack of open curlies, brackets, etc. michael@0: */ michael@0: function prependWhiteSpace(token, lastToken, addedNewline, write, options, michael@0: indentLevel, stack) { michael@0: var ttk = token.type.keyword; michael@0: var ttt = token.type.type; michael@0: var newlineAdded = addedNewline; michael@0: var ltt = lastToken ? lastToken.type.type : null; michael@0: michael@0: // Handle whitespace and newlines after "}" here instead of in michael@0: // `isLineDelimiter` because it is only a line delimiter some of the michael@0: // time. For example, we don't want to put "else if" on a new line after michael@0: // the first if's block. michael@0: if (lastToken && ltt == "}") { michael@0: if (ttk == "while" && stack[stack.length - 1] == "do") { michael@0: write(" ", michael@0: lastToken.startLoc.line, michael@0: lastToken.startLoc.column); michael@0: } else if (ttk == "else" || michael@0: ttk == "catch" || michael@0: ttk == "finally") { michael@0: write(" ", michael@0: lastToken.startLoc.line, michael@0: lastToken.startLoc.column); michael@0: } else if (ttt != "(" && michael@0: ttt != ";" && michael@0: ttt != "," && michael@0: ttt != ")" && michael@0: ttt != ".") { michael@0: write("\n", michael@0: lastToken.startLoc.line, michael@0: lastToken.startLoc.column); michael@0: newlineAdded = true; michael@0: } michael@0: } michael@0: michael@0: if (isGetterOrSetter(token, lastToken, stack)) { michael@0: write(" ", michael@0: lastToken.startLoc.line, michael@0: lastToken.startLoc.column); michael@0: } michael@0: michael@0: if (ttt == ":" && stack[stack.length - 1] == "?") { michael@0: write(" ", michael@0: lastToken.startLoc.line, michael@0: lastToken.startLoc.column); michael@0: } michael@0: michael@0: if (lastToken && ltt != "}" && ttk == "else") { michael@0: write(" ", michael@0: lastToken.startLoc.line, michael@0: lastToken.startLoc.column); michael@0: } michael@0: michael@0: function ensureNewline() { michael@0: if (!newlineAdded) { michael@0: write("\n", michael@0: lastToken.startLoc.line, michael@0: lastToken.startLoc.column); michael@0: newlineAdded = true; michael@0: } michael@0: } michael@0: michael@0: if (isASI(token, lastToken)) { michael@0: ensureNewline(); michael@0: } michael@0: michael@0: if (decrementsIndent(ttt, stack)) { michael@0: ensureNewline(); michael@0: } michael@0: michael@0: if (newlineAdded) { michael@0: if (ttk == "case" || ttk == "default") { michael@0: write(repeat(options.indent, indentLevel - 1), michael@0: token.startLoc.line, michael@0: token.startLoc.column); michael@0: } else { michael@0: write(repeat(options.indent, indentLevel), michael@0: token.startLoc.line, michael@0: token.startLoc.column); michael@0: } michael@0: } else if (needsSpaceAfter(token, lastToken)) { michael@0: write(" ", michael@0: lastToken.startLoc.line, michael@0: lastToken.startLoc.column); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Repeat the `str` string `n` times. michael@0: * michael@0: * @param String str michael@0: * The string to be repeated. michael@0: * @param Number n michael@0: * The number of times to repeat the string. michael@0: * michael@0: * @returns String michael@0: * The repeated string. michael@0: */ michael@0: function repeat(str, n) { michael@0: var result = ""; michael@0: while (n > 0) { michael@0: if (n & 1) { michael@0: result += str; michael@0: } michael@0: n >>= 1; michael@0: str += str; michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: /** michael@0: * Make sure that we output the escaped character combination inside string literals michael@0: * instead of various problematic characters. michael@0: */ michael@0: var sanitize = (function () { michael@0: var escapeCharacters = { michael@0: // Backslash michael@0: "\\": "\\\\", michael@0: // Newlines michael@0: "\n": "\\n", michael@0: // Carriage return michael@0: "\r": "\\r", michael@0: // Tab michael@0: "\t": "\\t", michael@0: // Vertical tab michael@0: "\v": "\\v", michael@0: // Form feed michael@0: "\f": "\\f", michael@0: // Null character michael@0: "\0": "\\0", michael@0: // Single quotes michael@0: "'": "\\'" michael@0: }; michael@0: michael@0: var regExpString = "(" michael@0: + Object.keys(escapeCharacters) michael@0: .map(function (c) { return escapeCharacters[c]; }) michael@0: .join("|") michael@0: + ")"; michael@0: var escapeCharactersRegExp = new RegExp(regExpString, "g"); michael@0: michael@0: return function(str) { michael@0: return str.replace(escapeCharactersRegExp, function (_, c) { michael@0: return escapeCharacters[c]; michael@0: }); michael@0: } michael@0: }()); michael@0: /** michael@0: * Add the given token to the pretty printed results. michael@0: * michael@0: * @param Object token michael@0: * The token to add. michael@0: * @param Function write michael@0: * The function to write pretty printed code to the result SourceNode. michael@0: * @param Object options michael@0: * The options object. michael@0: */ michael@0: function addToken(token, write, options) { michael@0: if (token.type.type == "string") { michael@0: write("'" + sanitize(token.value) + "'", michael@0: token.startLoc.line, michael@0: token.startLoc.column); michael@0: } else { michael@0: write(String(token.value != null ? token.value : token.type.type), michael@0: token.startLoc.line, michael@0: token.startLoc.column); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Returns true if the given token type belongs on the stack. michael@0: */ michael@0: function belongsOnStack(token) { michael@0: var ttt = token.type.type; michael@0: var ttk = token.type.keyword; michael@0: return ttt == "{" michael@0: || ttt == "(" michael@0: || ttt == "[" michael@0: || ttt == "?" michael@0: || ttk == "do" michael@0: || ttk == "case" michael@0: || ttk == "default"; michael@0: } michael@0: michael@0: /** michael@0: * Returns true if the given token should cause us to pop the stack. michael@0: */ michael@0: function shouldStackPop(token, stack) { michael@0: var ttt = token.type.type; michael@0: var ttk = token.type.keyword; michael@0: var top = stack[stack.length - 1]; michael@0: return ttt == "]" michael@0: || ttt == ")" michael@0: || ttt == "}" michael@0: || (ttt == ":" && (top == "case" || top == "default" || top == "?")) michael@0: || (ttk == "while" && top == "do"); michael@0: } michael@0: michael@0: /** michael@0: * Returns true if the given token type should cause us to decrement the michael@0: * indent level. michael@0: */ michael@0: function decrementsIndent(tokenType, stack) { michael@0: return tokenType == "}" michael@0: || (tokenType == "]" && stack[stack.length - 1] == "[\n") michael@0: } michael@0: michael@0: /** michael@0: * Returns true if the given token should cause us to increment the indent michael@0: * level. michael@0: */ michael@0: function incrementsIndent(token) { michael@0: return token.type.type == "{" || token.isArrayLiteral; michael@0: } michael@0: michael@0: /** michael@0: * Add a comment to the pretty printed code. michael@0: * michael@0: * @param Function write michael@0: * The function to write pretty printed code to the result SourceNode. michael@0: * @param Number indentLevel michael@0: * The number of indents deep we are. michael@0: * @param Object options michael@0: * The options object. michael@0: * @param Boolean block michael@0: * True if the comment is a multiline block style comment. michael@0: * @param String text michael@0: * The text of the comment. michael@0: * @param Number line michael@0: * The line number to comment appeared on. michael@0: * @param Number column michael@0: * The column number the comment appeared on. michael@0: */ michael@0: function addComment(write, indentLevel, options, block, text, line, column) { michael@0: var indentString = repeat(options.indent, indentLevel); michael@0: michael@0: write(indentString, line, column); michael@0: if (block) { michael@0: write("/*"); michael@0: write(text michael@0: .split(new RegExp("/\n" + indentString + "/", "g")) michael@0: .join("\n" + indentString)); michael@0: write("*/"); michael@0: } else { michael@0: write("//"); michael@0: write(text); michael@0: } michael@0: write("\n"); michael@0: } michael@0: michael@0: /** michael@0: * The main function. michael@0: * michael@0: * @param String input michael@0: * The ugly JS code we want to pretty print. michael@0: * @param Object options michael@0: * The options object. Provides configurability of the pretty michael@0: * printing. Properties: michael@0: * - url: The URL string of the ugly JS code. michael@0: * - indent: The string to indent code by. michael@0: * michael@0: * @returns Object michael@0: * An object with the following properties: michael@0: * - code: The pretty printed code string. michael@0: * - map: A SourceMapGenerator instance. michael@0: */ michael@0: return function prettyFast(input, options) { michael@0: // The level of indents deep we are. michael@0: var indentLevel = 0; michael@0: michael@0: // We will accumulate the pretty printed code in this SourceNode. michael@0: var result = new SourceNode(); michael@0: michael@0: /** michael@0: * Write a pretty printed string to the result SourceNode. michael@0: * michael@0: * We buffer our writes so that we only create one mapping for each line in michael@0: * the source map. This enhances performance by avoiding extraneous mapping michael@0: * serialization, and flattening the tree that michael@0: * `SourceNode#toStringWithSourceMap` will have to recursively walk. When michael@0: * timing how long it takes to pretty print jQuery, this optimization michael@0: * brought the time down from ~390 ms to ~190ms! michael@0: * michael@0: * @param String str michael@0: * The string to be added to the result. michael@0: * @param Number line michael@0: * The line number the string came from in the ugly source. michael@0: * @param Number column michael@0: * The column number the string came from in the ugly source. michael@0: */ michael@0: var write = (function () { michael@0: var buffer = []; michael@0: var bufferLine = -1; michael@0: var bufferColumn = -1; michael@0: return function write(str, line, column) { michael@0: if (line != null && bufferLine === -1) { michael@0: bufferLine = line; michael@0: } michael@0: if (column != null && bufferColumn === -1) { michael@0: bufferColumn = column; michael@0: } michael@0: buffer.push(str); michael@0: michael@0: if (str == "\n") { michael@0: var lineStr = ""; michael@0: for (var i = 0, len = buffer.length; i < len; i++) { michael@0: lineStr += buffer[i]; michael@0: } michael@0: result.add(new SourceNode(bufferLine, bufferColumn, options.url, lineStr)); michael@0: buffer.splice(0, buffer.length); michael@0: bufferLine = -1; michael@0: bufferColumn = -1; michael@0: } michael@0: } michael@0: }()); michael@0: michael@0: // Whether or not we added a newline on after we added the last token. michael@0: var addedNewline = false; michael@0: michael@0: // The current token we will be adding to the pretty printed code. michael@0: var token; michael@0: michael@0: // Shorthand for token.type.type, so we don't have to repeatedly access michael@0: // properties. michael@0: var ttt; michael@0: michael@0: // Shorthand for token.type.keyword, so we don't have to repeatedly access michael@0: // properties. michael@0: var ttk; michael@0: michael@0: // The last token we added to the pretty printed code. michael@0: var lastToken; michael@0: michael@0: // Stack of token types/keywords that can affect whether we want to add a michael@0: // newline or a space. We can make that decision based on what token type is michael@0: // on the top of the stack. For example, a comma in a parameter list should michael@0: // be followed by a space, while a comma in an object literal should be michael@0: // followed by a newline. michael@0: // michael@0: // Strings that go on the stack: michael@0: // michael@0: // - "{" michael@0: // - "(" michael@0: // - "[" michael@0: // - "[\n" michael@0: // - "do" michael@0: // - "?" michael@0: // - "case" michael@0: // - "default" michael@0: // michael@0: // The difference between "[" and "[\n" is that "[\n" is used when we are michael@0: // treating "[" and "]" tokens as line delimiters and should increment and michael@0: // decrement the indent level when we find them. michael@0: var stack = []; michael@0: michael@0: // Acorn's tokenizer will always yield comments *before* the token they michael@0: // follow (unless the very first thing in the source is a comment), so we michael@0: // have to queue the comments in order to pretty print them in the correct michael@0: // location. For example, the source file: michael@0: // michael@0: // foo michael@0: // // a michael@0: // // b michael@0: // bar michael@0: // michael@0: // When tokenized by acorn, gives us the following token stream: michael@0: // michael@0: // [ '// a', '// b', foo, bar ] michael@0: var commentQueue = []; michael@0: michael@0: var getToken = acorn.tokenize(input, { michael@0: locations: true, michael@0: sourceFile: options.url, michael@0: onComment: function (block, text, start, end, startLoc, endLoc) { michael@0: if (lastToken) { michael@0: commentQueue.push({ michael@0: block: block, michael@0: text: text, michael@0: line: startLoc.line, michael@0: column: startLoc.column michael@0: }); michael@0: } else { michael@0: addComment(write, indentLevel, options, block, text, startLoc.line, michael@0: startLoc.column); michael@0: addedNewline = true; michael@0: } michael@0: } michael@0: }); michael@0: michael@0: while (true) { michael@0: token = getToken(); michael@0: michael@0: ttk = token.type.keyword; michael@0: ttt = token.type.type; michael@0: michael@0: if (ttt == "eof") { michael@0: if (!addedNewline) { michael@0: write("\n"); michael@0: } michael@0: break; michael@0: } michael@0: michael@0: token.isArrayLiteral = isArrayLiteral(token, lastToken); michael@0: michael@0: if (belongsOnStack(token)) { michael@0: if (token.isArrayLiteral) { michael@0: stack.push("[\n"); michael@0: } else { michael@0: stack.push(ttt || ttk); michael@0: } michael@0: } michael@0: michael@0: if (decrementsIndent(ttt, stack)) { michael@0: indentLevel--; michael@0: } michael@0: michael@0: prependWhiteSpace(token, lastToken, addedNewline, write, options, michael@0: indentLevel, stack); michael@0: addToken(token, write, options); michael@0: addedNewline = appendNewline(token, write, stack); michael@0: michael@0: if (shouldStackPop(token, stack)) { michael@0: stack.pop(); michael@0: } michael@0: michael@0: if (incrementsIndent(token)) { michael@0: indentLevel++; michael@0: } michael@0: michael@0: // Acorn's tokenizer re-uses tokens, so we have to copy the last token on michael@0: // every iteration. We follow acorn's lead here, and reuse the lastToken michael@0: // object the same way that acorn reuses the token object. This allows us michael@0: // to avoid allocations and minimize GC pauses. michael@0: if (!lastToken) { michael@0: lastToken = { startLoc: {}, endLoc: {} }; michael@0: } michael@0: lastToken.start = token.start; michael@0: lastToken.end = token.end; michael@0: lastToken.startLoc.line = token.startLoc.line; michael@0: lastToken.startLoc.column = token.startLoc.column; michael@0: lastToken.endLoc.line = token.endLoc.line; michael@0: lastToken.endLoc.column = token.endLoc.column; michael@0: lastToken.type = token.type; michael@0: lastToken.value = token.value; michael@0: lastToken.isArrayLiteral = token.isArrayLiteral; michael@0: michael@0: // Apply all the comments that have been queued up. michael@0: if (commentQueue.length) { michael@0: if (!addedNewline) { michael@0: write("\n"); michael@0: } michael@0: for (var i = 0, n = commentQueue.length; i < n; i++) { michael@0: var comment = commentQueue[i]; michael@0: addComment(write, indentLevel, options, comment.block, comment.text, michael@0: comment.line, comment.column); michael@0: } michael@0: addedNewline = true; michael@0: commentQueue.splice(0, commentQueue.length); michael@0: } michael@0: } michael@0: michael@0: return result.toStringWithSourceMap({ file: options.url }); michael@0: }; michael@0: michael@0: }.bind(this)));