Skip to content

Commit 635f5bd

Browse files
committed
Some tweaks for backtick strings
1. `getRawLiteral()`: barf if `currentSourceFile` is missing, since if it is, then the following `getSourceTextOfNodeFromSourceFile` will return a bogus `""`. 2. One `||` -> `??` change. 3. `backtickQuoteEscapedCharsRegExp`: escape the usual control characters except for a simple LF. This code does get used to generate backtick strings when `rawText` is not given, and not escaping things like TAB characters can get mangled by editor settings. Worse, not escaping a CRLF and putting it verbatim in sthe string source will interpret it as LF, so add a special case for escaping these as `\r\n`. Added test. Related to #44313 and #40625.
1 parent 3266ffb commit 635f5bd

File tree

7 files changed

+70
-4
lines changed

7 files changed

+70
-4
lines changed

src/compiler/transformers/taggedTemplate.ts

+2
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ namespace ts {
8181
// Examples: `\n` is converted to "\\n", a template string with a newline to "\n".
8282
let text = node.rawText;
8383
if (text === undefined) {
84+
Debug.assertIsDefined(currentSourceFile,
85+
"Template literal node is missing 'rawText' and does not have a source file. Possibly bad transform.");
8486
text = getSourceTextOfNodeFromSourceFile(currentSourceFile, node);
8587

8688
// text contains the original source, it will also contain quotes ("`"), dolar signs and braces ("${" and "}"),

src/compiler/utilities.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -670,7 +670,7 @@ namespace ts {
670670
const escapeText = flags & GetLiteralTextFlags.NeverAsciiEscape || (getEmitFlags(node) & EmitFlags.NoAsciiEscaping) ? escapeString :
671671
escapeNonAsciiString;
672672

673-
const rawText = (node as TemplateLiteralLikeNode).rawText || escapeTemplateSubstitution(escapeText(node.text, CharacterCodes.backtick));
673+
const rawText = (node as TemplateLiteralLikeNode).rawText ?? escapeTemplateSubstitution(escapeText(node.text, CharacterCodes.backtick));
674674
switch (node.kind) {
675675
case SyntaxKind.NoSubstitutionTemplateLiteral:
676676
return "`" + rawText + "`";
@@ -3851,8 +3851,8 @@ namespace ts {
38513851
// There is no reason for this other than that JSON.stringify does not handle it either.
38523852
const doubleQuoteEscapedCharsRegExp = /[\\\"\u0000-\u001f\t\v\f\b\r\n\u2028\u2029\u0085]/g;
38533853
const singleQuoteEscapedCharsRegExp = /[\\\'\u0000-\u001f\t\v\f\b\r\n\u2028\u2029\u0085]/g;
3854-
// Template strings should be preserved as much as possible
3855-
const backtickQuoteEscapedCharsRegExp = /[\\`]/g;
3854+
// Template strings preserve simple LF newlines, still encode CRLF (or CR)
3855+
const backtickQuoteEscapedCharsRegExp = /\r\n|[\\\`\u0000-\u001f\t\v\f\b\r\u2028\u2029\u0085]/g;
38563856
const escapedCharsMap = new Map(getEntries({
38573857
"\t": "\\t",
38583858
"\v": "\\v",
@@ -3866,7 +3866,8 @@ namespace ts {
38663866
"\`": "\\\`",
38673867
"\u2028": "\\u2028", // lineSeparator
38683868
"\u2029": "\\u2029", // paragraphSeparator
3869-
"\u0085": "\\u0085" // nextLine
3869+
"\u0085": "\\u0085", // nextLine
3870+
"\r\n": "\\r\\n", // special case for CRLFs in backticks
38703871
}));
38713872

38723873
function encodeUtf16EscapeSequence(charCode: number): string {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
tests/cases/compiler/templateLiteralsInTypes.ts(3,1): error TS2554: Expected 2 arguments, but got 1.
2+
tests/cases/compiler/templateLiteralsInTypes.ts(3,8): error TS2339: Property 'foo' does not exist on type '`${string}:\t${number}\r\n`'.
3+
4+
5+
==== tests/cases/compiler/templateLiteralsInTypes.ts (2 errors) ====
6+
const f = (hdr: string, val: number) => `${hdr}:\t${val}\r\n` as `${string}:\t${number}\r\n`;
7+
8+
f("x").foo;
9+
~~~~~~
10+
!!! error TS2554: Expected 2 arguments, but got 1.
11+
!!! related TS6210 tests/cases/compiler/templateLiteralsInTypes.ts:1:25: An argument for 'val' was not provided.
12+
~~~
13+
!!! error TS2339: Property 'foo' does not exist on type '`${string}:\t${number}\r\n`'.
14+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//// [templateLiteralsInTypes.ts]
2+
const f = (hdr: string, val: number) => `${hdr}:\t${val}\r\n` as `${string}:\t${number}\r\n`;
3+
4+
f("x").foo;
5+
6+
7+
//// [templateLiteralsInTypes.js]
8+
"use strict";
9+
var f = function (hdr, val) { return hdr + ":\t" + val + "\r\n"; };
10+
f("x").foo;
11+
12+
13+
//// [templateLiteralsInTypes.d.ts]
14+
declare const f: (hdr: string, val: number) => `${string}:\t${number}\r\n`;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
=== tests/cases/compiler/templateLiteralsInTypes.ts ===
2+
const f = (hdr: string, val: number) => `${hdr}:\t${val}\r\n` as `${string}:\t${number}\r\n`;
3+
>f : Symbol(f, Decl(templateLiteralsInTypes.ts, 0, 5))
4+
>hdr : Symbol(hdr, Decl(templateLiteralsInTypes.ts, 0, 11))
5+
>val : Symbol(val, Decl(templateLiteralsInTypes.ts, 0, 23))
6+
>hdr : Symbol(hdr, Decl(templateLiteralsInTypes.ts, 0, 11))
7+
>val : Symbol(val, Decl(templateLiteralsInTypes.ts, 0, 23))
8+
9+
f("x").foo;
10+
>f : Symbol(f, Decl(templateLiteralsInTypes.ts, 0, 5))
11+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
=== tests/cases/compiler/templateLiteralsInTypes.ts ===
2+
const f = (hdr: string, val: number) => `${hdr}:\t${val}\r\n` as `${string}:\t${number}\r\n`;
3+
>f : (hdr: string, val: number) => `${string}:\t${number}\r\n`
4+
>(hdr: string, val: number) => `${hdr}:\t${val}\r\n` as `${string}:\t${number}\r\n` : (hdr: string, val: number) => `${string}:\t${number}\r\n`
5+
>hdr : string
6+
>val : number
7+
>`${hdr}:\t${val}\r\n` as `${string}:\t${number}\r\n` : `${string}:\t${number}\r\n`
8+
>`${hdr}:\t${val}\r\n` : `${string}:\t${number}\r\n`
9+
>hdr : string
10+
>val : number
11+
12+
f("x").foo;
13+
>f("x").foo : any
14+
>f("x") : `${string}:\t${number}\r\n`
15+
>f : (hdr: string, val: number) => `${string}:\t${number}\r\n`
16+
>"x" : "x"
17+
>foo : any
18+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// @strict: true
2+
// @declaration: true
3+
4+
const f = (hdr: string, val: number) => `${hdr}:\t${val}\r\n` as `${string}:\t${number}\r\n`;
5+
6+
f("x").foo;

0 commit comments

Comments
 (0)