UNPKG

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