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

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial