mod.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. const string_utils_js_1 = require("./utils/string_utils.js");
  4. /** @internal */
  5. var CommentChar;
  6. (function (CommentChar) {
  7. CommentChar[CommentChar["Line"] = 0] = "Line";
  8. CommentChar[CommentChar["Star"] = 1] = "Star";
  9. })(CommentChar || (CommentChar = {}));
  10. // Using the char codes is a performance improvement (about 5.5% faster when writing because it eliminates additional string allocations).
  11. const CHARS = {
  12. BACK_SLASH: "\\".charCodeAt(0),
  13. FORWARD_SLASH: "/".charCodeAt(0),
  14. NEW_LINE: "\n".charCodeAt(0),
  15. CARRIAGE_RETURN: "\r".charCodeAt(0),
  16. ASTERISK: "*".charCodeAt(0),
  17. DOUBLE_QUOTE: "\"".charCodeAt(0),
  18. SINGLE_QUOTE: "'".charCodeAt(0),
  19. BACK_TICK: "`".charCodeAt(0),
  20. OPEN_BRACE: "{".charCodeAt(0),
  21. CLOSE_BRACE: "}".charCodeAt(0),
  22. DOLLAR_SIGN: "$".charCodeAt(0),
  23. SPACE: " ".charCodeAt(0),
  24. TAB: "\t".charCodeAt(0),
  25. };
  26. const isCharToHandle = new Set([
  27. CHARS.BACK_SLASH,
  28. CHARS.FORWARD_SLASH,
  29. CHARS.NEW_LINE,
  30. CHARS.CARRIAGE_RETURN,
  31. CHARS.ASTERISK,
  32. CHARS.DOUBLE_QUOTE,
  33. CHARS.SINGLE_QUOTE,
  34. CHARS.BACK_TICK,
  35. CHARS.OPEN_BRACE,
  36. CHARS.CLOSE_BRACE,
  37. ]);
  38. /**
  39. * Code writer that assists with formatting and visualizing blocks of JavaScript or TypeScript code.
  40. */
  41. class CodeBlockWriter {
  42. /**
  43. * Constructor.
  44. * @param opts - Options for the writer.
  45. */
  46. constructor(opts = {}) {
  47. /** @internal */
  48. Object.defineProperty(this, "_indentationText", {
  49. enumerable: true,
  50. configurable: true,
  51. writable: true,
  52. value: void 0
  53. });
  54. /** @internal */
  55. Object.defineProperty(this, "_newLine", {
  56. enumerable: true,
  57. configurable: true,
  58. writable: true,
  59. value: void 0
  60. });
  61. /** @internal */
  62. Object.defineProperty(this, "_useTabs", {
  63. enumerable: true,
  64. configurable: true,
  65. writable: true,
  66. value: void 0
  67. });
  68. /** @internal */
  69. Object.defineProperty(this, "_quoteChar", {
  70. enumerable: true,
  71. configurable: true,
  72. writable: true,
  73. value: void 0
  74. });
  75. /** @internal */
  76. Object.defineProperty(this, "_indentNumberOfSpaces", {
  77. enumerable: true,
  78. configurable: true,
  79. writable: true,
  80. value: void 0
  81. });
  82. /** @internal */
  83. Object.defineProperty(this, "_currentIndentation", {
  84. enumerable: true,
  85. configurable: true,
  86. writable: true,
  87. value: 0
  88. });
  89. /** @internal */
  90. Object.defineProperty(this, "_queuedIndentation", {
  91. enumerable: true,
  92. configurable: true,
  93. writable: true,
  94. value: void 0
  95. });
  96. /** @internal */
  97. Object.defineProperty(this, "_queuedOnlyIfNotBlock", {
  98. enumerable: true,
  99. configurable: true,
  100. writable: true,
  101. value: void 0
  102. });
  103. /** @internal */
  104. Object.defineProperty(this, "_length", {
  105. enumerable: true,
  106. configurable: true,
  107. writable: true,
  108. value: 0
  109. });
  110. /** @internal */
  111. Object.defineProperty(this, "_newLineOnNextWrite", {
  112. enumerable: true,
  113. configurable: true,
  114. writable: true,
  115. value: false
  116. });
  117. /** @internal */
  118. Object.defineProperty(this, "_currentCommentChar", {
  119. enumerable: true,
  120. configurable: true,
  121. writable: true,
  122. value: undefined
  123. });
  124. /** @internal */
  125. Object.defineProperty(this, "_stringCharStack", {
  126. enumerable: true,
  127. configurable: true,
  128. writable: true,
  129. value: []
  130. });
  131. /** @internal */
  132. Object.defineProperty(this, "_isInRegEx", {
  133. enumerable: true,
  134. configurable: true,
  135. writable: true,
  136. value: false
  137. });
  138. /** @internal */
  139. Object.defineProperty(this, "_isOnFirstLineOfBlock", {
  140. enumerable: true,
  141. configurable: true,
  142. writable: true,
  143. value: true
  144. });
  145. // An array of strings is used rather than a single string because it was
  146. // found to be ~11x faster when printing a 10K line file (~11s to ~1s).
  147. /** @internal */
  148. Object.defineProperty(this, "_texts", {
  149. enumerable: true,
  150. configurable: true,
  151. writable: true,
  152. value: []
  153. });
  154. this._newLine = opts.newLine || "\n";
  155. this._useTabs = opts.useTabs || false;
  156. this._indentNumberOfSpaces = opts.indentNumberOfSpaces || 4;
  157. this._indentationText = getIndentationText(this._useTabs, this._indentNumberOfSpaces);
  158. this._quoteChar = opts.useSingleQuote ? "'" : `"`;
  159. }
  160. /**
  161. * Gets the options.
  162. */
  163. getOptions() {
  164. return {
  165. indentNumberOfSpaces: this._indentNumberOfSpaces,
  166. newLine: this._newLine,
  167. useTabs: this._useTabs,
  168. useSingleQuote: this._quoteChar === "'",
  169. };
  170. }
  171. queueIndentationLevel(countOrText) {
  172. this._queuedIndentation = this._getIndentationLevelFromArg(countOrText);
  173. this._queuedOnlyIfNotBlock = undefined;
  174. return this;
  175. }
  176. /**
  177. * Writes the text within the provided action with hanging indentation.
  178. * @param action - Action to perform with hanging indentation.
  179. */
  180. hangingIndent(action) {
  181. return this._withResetIndentation(() => this.queueIndentationLevel(this.getIndentationLevel() + 1), action);
  182. }
  183. /**
  184. * Writes the text within the provided action with hanging indentation unless writing a block.
  185. * @param action - Action to perform with hanging indentation unless a block is written.
  186. */
  187. hangingIndentUnlessBlock(action) {
  188. return this._withResetIndentation(() => {
  189. this.queueIndentationLevel(this.getIndentationLevel() + 1);
  190. this._queuedOnlyIfNotBlock = true;
  191. }, action);
  192. }
  193. setIndentationLevel(countOrText) {
  194. this._currentIndentation = this._getIndentationLevelFromArg(countOrText);
  195. return this;
  196. }
  197. withIndentationLevel(countOrText, action) {
  198. return this._withResetIndentation(() => this.setIndentationLevel(countOrText), action);
  199. }
  200. /** @internal */
  201. _withResetIndentation(setStateAction, writeAction) {
  202. const previousState = this._getIndentationState();
  203. setStateAction();
  204. try {
  205. writeAction();
  206. }
  207. finally {
  208. this._setIndentationState(previousState);
  209. }
  210. return this;
  211. }
  212. /**
  213. * Gets the current indentation level.
  214. */
  215. getIndentationLevel() {
  216. return this._currentIndentation;
  217. }
  218. /**
  219. * Writes a block using braces.
  220. * @param block - Write using the writer within this block.
  221. */
  222. block(block) {
  223. this._newLineIfNewLineOnNextWrite();
  224. if (this.getLength() > 0 && !this.isLastNewLine()) {
  225. this.spaceIfLastNot();
  226. }
  227. this.inlineBlock(block);
  228. this._newLineOnNextWrite = true;
  229. return this;
  230. }
  231. /**
  232. * Writes an inline block with braces.
  233. * @param block - Write using the writer within this block.
  234. */
  235. inlineBlock(block) {
  236. this._newLineIfNewLineOnNextWrite();
  237. this.write("{");
  238. this._indentBlockInternal(block);
  239. this.newLineIfLastNot().write("}");
  240. return this;
  241. }
  242. indent(timesOrBlock = 1) {
  243. if (typeof timesOrBlock === "number") {
  244. this._newLineIfNewLineOnNextWrite();
  245. return this.write(this._indentationText.repeat(timesOrBlock));
  246. }
  247. else {
  248. this._indentBlockInternal(timesOrBlock);
  249. if (!this.isLastNewLine()) {
  250. this._newLineOnNextWrite = true;
  251. }
  252. return this;
  253. }
  254. }
  255. /** @internal */
  256. _indentBlockInternal(block) {
  257. if (this.getLastChar() != null) {
  258. this.newLineIfLastNot();
  259. }
  260. this._currentIndentation++;
  261. this._isOnFirstLineOfBlock = true;
  262. if (block != null) {
  263. block();
  264. }
  265. this._isOnFirstLineOfBlock = false;
  266. this._currentIndentation = Math.max(0, this._currentIndentation - 1);
  267. }
  268. conditionalWriteLine(condition, strOrFunc) {
  269. if (condition) {
  270. this.writeLine((0, string_utils_js_1.getStringFromStrOrFunc)(strOrFunc));
  271. }
  272. return this;
  273. }
  274. /**
  275. * Writes a line of text.
  276. * @param text - String to write.
  277. */
  278. writeLine(text) {
  279. this._newLineIfNewLineOnNextWrite();
  280. if (this.getLastChar() != null) {
  281. this.newLineIfLastNot();
  282. }
  283. this._writeIndentingNewLines(text);
  284. this.newLine();
  285. return this;
  286. }
  287. /**
  288. * Writes a newline if the last line was not a newline.
  289. */
  290. newLineIfLastNot() {
  291. this._newLineIfNewLineOnNextWrite();
  292. if (!this.isLastNewLine()) {
  293. this.newLine();
  294. }
  295. return this;
  296. }
  297. /**
  298. * Writes a blank line if the last written text was not a blank line.
  299. */
  300. blankLineIfLastNot() {
  301. if (!this.isLastBlankLine()) {
  302. this.blankLine();
  303. }
  304. return this;
  305. }
  306. /**
  307. * Writes a blank line if the condition is true.
  308. * @param condition - Condition to evaluate.
  309. */
  310. conditionalBlankLine(condition) {
  311. if (condition) {
  312. this.blankLine();
  313. }
  314. return this;
  315. }
  316. /**
  317. * Writes a blank line.
  318. */
  319. blankLine() {
  320. return this.newLineIfLastNot().newLine();
  321. }
  322. /**
  323. * Writes a newline if the condition is true.
  324. * @param condition - Condition to evaluate.
  325. */
  326. conditionalNewLine(condition) {
  327. if (condition) {
  328. this.newLine();
  329. }
  330. return this;
  331. }
  332. /**
  333. * Writes a newline.
  334. */
  335. newLine() {
  336. this._newLineOnNextWrite = false;
  337. this._baseWriteNewline();
  338. return this;
  339. }
  340. quote(text) {
  341. this._newLineIfNewLineOnNextWrite();
  342. this._writeIndentingNewLines(text == null ? this._quoteChar : this._quoteChar + (0, string_utils_js_1.escapeForWithinString)(text, this._quoteChar) + this._quoteChar);
  343. return this;
  344. }
  345. /**
  346. * Writes a space if the last character was not a space.
  347. */
  348. spaceIfLastNot() {
  349. this._newLineIfNewLineOnNextWrite();
  350. if (!this.isLastSpace()) {
  351. this._writeIndentingNewLines(" ");
  352. }
  353. return this;
  354. }
  355. /**
  356. * Writes a space.
  357. * @param times - Number of times to write a space.
  358. */
  359. space(times = 1) {
  360. this._newLineIfNewLineOnNextWrite();
  361. this._writeIndentingNewLines(" ".repeat(times));
  362. return this;
  363. }
  364. /**
  365. * Writes a tab if the last character was not a tab.
  366. */
  367. tabIfLastNot() {
  368. this._newLineIfNewLineOnNextWrite();
  369. if (!this.isLastTab()) {
  370. this._writeIndentingNewLines("\t");
  371. }
  372. return this;
  373. }
  374. /**
  375. * Writes a tab.
  376. * @param times - Number of times to write a tab.
  377. */
  378. tab(times = 1) {
  379. this._newLineIfNewLineOnNextWrite();
  380. this._writeIndentingNewLines("\t".repeat(times));
  381. return this;
  382. }
  383. conditionalWrite(condition, textOrFunc) {
  384. if (condition) {
  385. this.write((0, string_utils_js_1.getStringFromStrOrFunc)(textOrFunc));
  386. }
  387. return this;
  388. }
  389. /**
  390. * Writes the provided text.
  391. * @param text - Text to write.
  392. */
  393. write(text) {
  394. this._newLineIfNewLineOnNextWrite();
  395. this._writeIndentingNewLines(text);
  396. return this;
  397. }
  398. /**
  399. * Writes text to exit a comment if in a comment.
  400. */
  401. closeComment() {
  402. const commentChar = this._currentCommentChar;
  403. switch (commentChar) {
  404. case CommentChar.Line:
  405. this.newLine();
  406. break;
  407. case CommentChar.Star:
  408. if (!this.isLastNewLine()) {
  409. this.spaceIfLastNot();
  410. }
  411. this.write("*/");
  412. break;
  413. default: {
  414. const _assertUndefined = commentChar;
  415. break;
  416. }
  417. }
  418. return this;
  419. }
  420. /**
  421. * Inserts text at the provided position.
  422. *
  423. * This method is "unsafe" because it won't update the state of the writer unless
  424. * inserting at the end position. It is biased towards being fast at inserting closer
  425. * to the start or end, but slower to insert in the middle. Only use this if
  426. * absolutely necessary.
  427. * @param pos - Position to insert at.
  428. * @param text - Text to insert.
  429. */
  430. unsafeInsert(pos, text) {
  431. const textLength = this._length;
  432. const texts = this._texts;
  433. verifyInput();
  434. if (pos === textLength) {
  435. return this.write(text);
  436. }
  437. updateInternalArray();
  438. this._length += text.length;
  439. return this;
  440. function verifyInput() {
  441. if (pos < 0) {
  442. throw new Error(`Provided position of '${pos}' was less than zero.`);
  443. }
  444. if (pos > textLength) {
  445. throw new Error(`Provided position of '${pos}' was greater than the text length of '${textLength}'.`);
  446. }
  447. }
  448. function updateInternalArray() {
  449. const { index, localIndex } = getArrayIndexAndLocalIndex();
  450. if (localIndex === 0) {
  451. texts.splice(index, 0, text);
  452. }
  453. else if (localIndex === texts[index].length) {
  454. texts.splice(index + 1, 0, text);
  455. }
  456. else {
  457. const textItem = texts[index];
  458. const startText = textItem.substring(0, localIndex);
  459. const endText = textItem.substring(localIndex);
  460. texts.splice(index, 1, startText, text, endText);
  461. }
  462. }
  463. function getArrayIndexAndLocalIndex() {
  464. if (pos < textLength / 2) {
  465. // start searching from the front
  466. let endPos = 0;
  467. for (let i = 0; i < texts.length; i++) {
  468. const textItem = texts[i];
  469. const startPos = endPos;
  470. endPos += textItem.length;
  471. if (endPos >= pos) {
  472. return { index: i, localIndex: pos - startPos };
  473. }
  474. }
  475. }
  476. else {
  477. // start searching from the back
  478. let startPos = textLength;
  479. for (let i = texts.length - 1; i >= 0; i--) {
  480. const textItem = texts[i];
  481. startPos -= textItem.length;
  482. if (startPos <= pos) {
  483. return { index: i, localIndex: pos - startPos };
  484. }
  485. }
  486. }
  487. throw new Error("Unhandled situation inserting. This should never happen.");
  488. }
  489. }
  490. /**
  491. * Gets the length of the string in the writer.
  492. */
  493. getLength() {
  494. return this._length;
  495. }
  496. /**
  497. * Gets if the writer is currently in a comment.
  498. */
  499. isInComment() {
  500. return this._currentCommentChar !== undefined;
  501. }
  502. /**
  503. * Gets if the writer is currently at the start of the first line of the text, block, or indentation block.
  504. */
  505. isAtStartOfFirstLineOfBlock() {
  506. return this.isOnFirstLineOfBlock() && (this.isLastNewLine() || this.getLastChar() == null);
  507. }
  508. /**
  509. * Gets if the writer is currently on the first line of the text, block, or indentation block.
  510. */
  511. isOnFirstLineOfBlock() {
  512. return this._isOnFirstLineOfBlock;
  513. }
  514. /**
  515. * Gets if the writer is currently in a string.
  516. */
  517. isInString() {
  518. return this._stringCharStack.length > 0 && this._stringCharStack[this._stringCharStack.length - 1] !== CHARS.OPEN_BRACE;
  519. }
  520. /**
  521. * Gets if the last chars written were for a newline.
  522. */
  523. isLastNewLine() {
  524. const lastChar = this.getLastChar();
  525. return lastChar === "\n" || lastChar === "\r";
  526. }
  527. /**
  528. * Gets if the last chars written were for a blank line.
  529. */
  530. isLastBlankLine() {
  531. let foundCount = 0;
  532. // todo: consider extracting out iterating over past characters, but don't use
  533. // an iterator because it will be slow.
  534. for (let i = this._texts.length - 1; i >= 0; i--) {
  535. const currentText = this._texts[i];
  536. for (let j = currentText.length - 1; j >= 0; j--) {
  537. const currentChar = currentText.charCodeAt(j);
  538. if (currentChar === CHARS.NEW_LINE) {
  539. foundCount++;
  540. if (foundCount === 2) {
  541. return true;
  542. }
  543. }
  544. else if (currentChar !== CHARS.CARRIAGE_RETURN) {
  545. return false;
  546. }
  547. }
  548. }
  549. return false;
  550. }
  551. /**
  552. * Gets if the last char written was a space.
  553. */
  554. isLastSpace() {
  555. return this.getLastChar() === " ";
  556. }
  557. /**
  558. * Gets if the last char written was a tab.
  559. */
  560. isLastTab() {
  561. return this.getLastChar() === "\t";
  562. }
  563. /**
  564. * Gets the last char written.
  565. */
  566. getLastChar() {
  567. const charCode = this._getLastCharCodeWithOffset(0);
  568. return charCode == null ? undefined : String.fromCharCode(charCode);
  569. }
  570. /**
  571. * Gets if the writer ends with the provided text.
  572. * @param text - Text to check if the writer ends with the provided text.
  573. */
  574. endsWith(text) {
  575. const length = this._length;
  576. return this.iterateLastCharCodes((charCode, index) => {
  577. const offset = length - index;
  578. const textIndex = text.length - offset;
  579. if (text.charCodeAt(textIndex) !== charCode) {
  580. return false;
  581. }
  582. return textIndex === 0 ? true : undefined;
  583. }) || false;
  584. }
  585. /**
  586. * Iterates over the writer characters in reverse order. The iteration stops when a non-null or
  587. * undefined value is returned from the action. The returned value is then returned by the method.
  588. *
  589. * @remarks It is much more efficient to use this method rather than `#toString()` since `#toString()`
  590. * will combine the internal array into a string.
  591. */
  592. iterateLastChars(action) {
  593. return this.iterateLastCharCodes((charCode, index) => action(String.fromCharCode(charCode), index));
  594. }
  595. /**
  596. * Iterates over the writer character char codes in reverse order. The iteration stops when a non-null or
  597. * undefined value is returned from the action. The returned value is then returned by the method.
  598. *
  599. * @remarks It is much more efficient to use this method rather than `#toString()` since `#toString()`
  600. * will combine the internal array into a string. Additionally, this is slightly more efficient that
  601. * `iterateLastChars` as this won't allocate a string per character.
  602. */
  603. iterateLastCharCodes(action) {
  604. let index = this._length;
  605. for (let i = this._texts.length - 1; i >= 0; i--) {
  606. const currentText = this._texts[i];
  607. for (let j = currentText.length - 1; j >= 0; j--) {
  608. index--;
  609. const result = action(currentText.charCodeAt(j), index);
  610. if (result != null) {
  611. return result;
  612. }
  613. }
  614. }
  615. return undefined;
  616. }
  617. /**
  618. * Gets the writer's text.
  619. */
  620. toString() {
  621. if (this._texts.length > 1) {
  622. const text = this._texts.join("");
  623. this._texts.length = 0;
  624. this._texts.push(text);
  625. }
  626. return this._texts[0] || "";
  627. }
  628. /** @internal */
  629. _writeIndentingNewLines(text) {
  630. text = text || "";
  631. if (text.length === 0) {
  632. writeIndividual(this, "");
  633. return;
  634. }
  635. const items = text.split(CodeBlockWriter._newLineRegEx);
  636. items.forEach((s, i) => {
  637. if (i > 0) {
  638. this._baseWriteNewline();
  639. }
  640. if (s.length === 0) {
  641. return;
  642. }
  643. writeIndividual(this, s);
  644. });
  645. function writeIndividual(writer, s) {
  646. if (!writer.isInString()) {
  647. const isAtStartOfLine = writer.isLastNewLine() || writer.getLastChar() == null;
  648. if (isAtStartOfLine) {
  649. writer._writeIndentation();
  650. }
  651. }
  652. writer._updateInternalState(s);
  653. writer._internalWrite(s);
  654. }
  655. }
  656. /** @internal */
  657. _baseWriteNewline() {
  658. if (this._currentCommentChar === CommentChar.Line) {
  659. this._currentCommentChar = undefined;
  660. }
  661. const lastStringCharOnStack = this._stringCharStack[this._stringCharStack.length - 1];
  662. if ((lastStringCharOnStack === CHARS.DOUBLE_QUOTE || lastStringCharOnStack === CHARS.SINGLE_QUOTE) && this._getLastCharCodeWithOffset(0) !== CHARS.BACK_SLASH) {
  663. this._stringCharStack.pop();
  664. }
  665. this._internalWrite(this._newLine);
  666. this._isOnFirstLineOfBlock = false;
  667. this._dequeueQueuedIndentation();
  668. }
  669. /** @internal */
  670. _dequeueQueuedIndentation() {
  671. if (this._queuedIndentation == null) {
  672. return;
  673. }
  674. if (this._queuedOnlyIfNotBlock && wasLastBlock(this)) {
  675. this._queuedIndentation = undefined;
  676. this._queuedOnlyIfNotBlock = undefined;
  677. }
  678. else {
  679. this._currentIndentation = this._queuedIndentation;
  680. this._queuedIndentation = undefined;
  681. }
  682. function wasLastBlock(writer) {
  683. let foundNewLine = false;
  684. return writer.iterateLastCharCodes(charCode => {
  685. switch (charCode) {
  686. case CHARS.NEW_LINE:
  687. if (foundNewLine) {
  688. return false;
  689. }
  690. else {
  691. foundNewLine = true;
  692. }
  693. break;
  694. case CHARS.CARRIAGE_RETURN:
  695. return undefined;
  696. case CHARS.OPEN_BRACE:
  697. return true;
  698. default:
  699. return false;
  700. }
  701. });
  702. }
  703. }
  704. /** @internal */
  705. _updateInternalState(str) {
  706. for (let i = 0; i < str.length; i++) {
  707. const currentChar = str.charCodeAt(i);
  708. // This is a performance optimization to short circuit all the checks below. If the current char
  709. // is not in this set then it won't change any internal state so no need to continue and do
  710. // so many other checks (this made it 3x faster in one scenario I tested).
  711. if (!isCharToHandle.has(currentChar)) {
  712. continue;
  713. }
  714. const pastChar = i === 0 ? this._getLastCharCodeWithOffset(0) : str.charCodeAt(i - 1);
  715. const pastPastChar = i === 0 ? this._getLastCharCodeWithOffset(1) : i === 1 ? this._getLastCharCodeWithOffset(0) : str.charCodeAt(i - 2);
  716. // handle regex
  717. if (this._isInRegEx) {
  718. if (pastChar === CHARS.FORWARD_SLASH && pastPastChar !== CHARS.BACK_SLASH || pastChar === CHARS.NEW_LINE) {
  719. this._isInRegEx = false;
  720. }
  721. else {
  722. continue;
  723. }
  724. }
  725. else if (!this.isInString() && !this.isInComment() && isRegExStart(currentChar, pastChar, pastPastChar)) {
  726. this._isInRegEx = true;
  727. continue;
  728. }
  729. // handle comments
  730. if (!this.isInString()) {
  731. if (this._currentCommentChar == null && pastChar === CHARS.FORWARD_SLASH && currentChar === CHARS.FORWARD_SLASH) {
  732. this._currentCommentChar = CommentChar.Line;
  733. }
  734. else if (this._currentCommentChar == null && pastChar === CHARS.FORWARD_SLASH && currentChar === CHARS.ASTERISK) {
  735. this._currentCommentChar = CommentChar.Star;
  736. }
  737. else if (this._currentCommentChar === CommentChar.Star && pastChar === CHARS.ASTERISK && currentChar === CHARS.FORWARD_SLASH) {
  738. this._currentCommentChar = undefined;
  739. }
  740. }
  741. if (this.isInComment()) {
  742. continue;
  743. }
  744. // handle strings
  745. const lastStringCharOnStack = this._stringCharStack.length === 0 ? undefined : this._stringCharStack[this._stringCharStack.length - 1];
  746. if (pastChar !== CHARS.BACK_SLASH && (currentChar === CHARS.DOUBLE_QUOTE || currentChar === CHARS.SINGLE_QUOTE || currentChar === CHARS.BACK_TICK)) {
  747. if (lastStringCharOnStack === currentChar) {
  748. this._stringCharStack.pop();
  749. }
  750. else if (lastStringCharOnStack === CHARS.OPEN_BRACE || lastStringCharOnStack === undefined) {
  751. this._stringCharStack.push(currentChar);
  752. }
  753. }
  754. else if (pastPastChar !== CHARS.BACK_SLASH && pastChar === CHARS.DOLLAR_SIGN && currentChar === CHARS.OPEN_BRACE && lastStringCharOnStack === CHARS.BACK_TICK) {
  755. this._stringCharStack.push(currentChar);
  756. }
  757. else if (currentChar === CHARS.CLOSE_BRACE && lastStringCharOnStack === CHARS.OPEN_BRACE) {
  758. this._stringCharStack.pop();
  759. }
  760. }
  761. }
  762. /** @internal - This is private, but exposed for testing. */
  763. _getLastCharCodeWithOffset(offset) {
  764. if (offset >= this._length || offset < 0) {
  765. return undefined;
  766. }
  767. for (let i = this._texts.length - 1; i >= 0; i--) {
  768. const currentText = this._texts[i];
  769. if (offset >= currentText.length) {
  770. offset -= currentText.length;
  771. }
  772. else {
  773. return currentText.charCodeAt(currentText.length - 1 - offset);
  774. }
  775. }
  776. return undefined;
  777. }
  778. /** @internal */
  779. _writeIndentation() {
  780. const flooredIndentation = Math.floor(this._currentIndentation);
  781. this._internalWrite(this._indentationText.repeat(flooredIndentation));
  782. const overflow = this._currentIndentation - flooredIndentation;
  783. if (this._useTabs) {
  784. if (overflow > 0.5) {
  785. this._internalWrite(this._indentationText);
  786. }
  787. }
  788. else {
  789. const portion = Math.round(this._indentationText.length * overflow);
  790. // build up the string first, then append it for performance reasons
  791. let text = "";
  792. for (let i = 0; i < portion; i++) {
  793. text += this._indentationText[i];
  794. }
  795. this._internalWrite(text);
  796. }
  797. }
  798. /** @internal */
  799. _newLineIfNewLineOnNextWrite() {
  800. if (!this._newLineOnNextWrite) {
  801. return;
  802. }
  803. this._newLineOnNextWrite = false;
  804. this.newLine();
  805. }
  806. /** @internal */
  807. _internalWrite(text) {
  808. if (text.length === 0) {
  809. return;
  810. }
  811. this._texts.push(text);
  812. this._length += text.length;
  813. }
  814. /** @internal */
  815. _getIndentationLevelFromArg(countOrText) {
  816. if (typeof countOrText === "number") {
  817. if (countOrText < 0) {
  818. throw new Error("Passed in indentation level should be greater than or equal to 0.");
  819. }
  820. return countOrText;
  821. }
  822. else if (typeof countOrText === "string") {
  823. if (!CodeBlockWriter._spacesOrTabsRegEx.test(countOrText)) {
  824. throw new Error("Provided string must be empty or only contain spaces or tabs.");
  825. }
  826. const { spacesCount, tabsCount } = getSpacesAndTabsCount(countOrText);
  827. return tabsCount + spacesCount / this._indentNumberOfSpaces;
  828. }
  829. else {
  830. throw new Error("Argument provided must be a string or number.");
  831. }
  832. }
  833. /** @internal */
  834. _setIndentationState(state) {
  835. this._currentIndentation = state.current;
  836. this._queuedIndentation = state.queued;
  837. this._queuedOnlyIfNotBlock = state.queuedOnlyIfNotBlock;
  838. }
  839. /** @internal */
  840. _getIndentationState() {
  841. return {
  842. current: this._currentIndentation,
  843. queued: this._queuedIndentation,
  844. queuedOnlyIfNotBlock: this._queuedOnlyIfNotBlock,
  845. };
  846. }
  847. }
  848. /** @internal */
  849. Object.defineProperty(CodeBlockWriter, "_newLineRegEx", {
  850. enumerable: true,
  851. configurable: true,
  852. writable: true,
  853. value: /\r?\n/
  854. });
  855. /** @internal */
  856. Object.defineProperty(CodeBlockWriter, "_spacesOrTabsRegEx", {
  857. enumerable: true,
  858. configurable: true,
  859. writable: true,
  860. value: /^[ \t]*$/
  861. });
  862. exports.default = CodeBlockWriter;
  863. function isRegExStart(currentChar, pastChar, pastPastChar) {
  864. return pastChar === CHARS.FORWARD_SLASH
  865. && currentChar !== CHARS.FORWARD_SLASH
  866. && currentChar !== CHARS.ASTERISK
  867. && pastPastChar !== CHARS.ASTERISK
  868. && pastPastChar !== CHARS.FORWARD_SLASH;
  869. }
  870. function getIndentationText(useTabs, numberSpaces) {
  871. if (useTabs) {
  872. return "\t";
  873. }
  874. return Array(numberSpaces + 1).join(" ");
  875. }
  876. function getSpacesAndTabsCount(str) {
  877. let spacesCount = 0;
  878. let tabsCount = 0;
  879. for (let i = 0; i < str.length; i++) {
  880. const charCode = str.charCodeAt(i);
  881. if (charCode === CHARS.SPACE) {
  882. spacesCount++;
  883. }
  884. else if (charCode === CHARS.TAB) {
  885. tabsCount++;
  886. }
  887. }
  888. return { spacesCount, tabsCount };
  889. }