()
| 837 | } |
| 838 | |
| 839 | private scanString() { |
| 840 | const quote = this.#ch; |
| 841 | const quoteLength = 1; |
| 842 | const closing = String.fromCharCode(this.#ch); |
| 843 | let escaped = false; |
| 844 | let crlf = false; |
| 845 | let isEscaping = false; |
| 846 | |
| 847 | const text = this.scanUntil((ch, chNext, _chNextNext) => { |
| 848 | if (isEscaping) { |
| 849 | isEscaping = false; |
| 850 | return false; |
| 851 | } |
| 852 | |
| 853 | if (ch === CharacterCodes.backslash) { |
| 854 | isEscaping = escaped = true; |
| 855 | return false; |
| 856 | } |
| 857 | |
| 858 | if (ch === CharacterCodes.carriageReturn) { |
| 859 | if (chNext === CharacterCodes.lineFeed) { |
| 860 | crlf = true; |
| 861 | } |
| 862 | return false; |
| 863 | } |
| 864 | |
| 865 | return ch === quote; |
| 866 | }, closing, quoteLength); |
| 867 | |
| 868 | // TODO: optimize to single pass over string, easier if we refactor some bookkeeping first. |
| 869 | |
| 870 | // strip quotes |
| 871 | let value = text.substring(quoteLength, text.length - quoteLength); |
| 872 | |
| 873 | // Normalize CRLF to LF when interpreting value of multi-line string |
| 874 | // literals. Matches JavaScript behavior and ensures program behavior does |
| 875 | // not change due to line-ending conversion. |
| 876 | if (crlf) { |
| 877 | value = value.replace(/\r\n/g, '\n'); |
| 878 | } |
| 879 | |
| 880 | if (escaped) { |
| 881 | value = this.unescapeString(value); |
| 882 | } |
| 883 | |
| 884 | this.text = text; |
| 885 | this.stringValue = value; |
| 886 | return this.kind = Kind.StringLiteral; |
| 887 | } |
| 888 | |
| 889 | private scanCodeFence() { |
| 890 | // skip to the end of this line first |
no test coverage detected