pretty-fast.js 23 KB

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