UNPKG

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