-
Notifications
You must be signed in to change notification settings - Fork 12.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add support for Lift Template Literal Restriction #23801
Changes from 2 commits
344d505
6ef797f
3932d20
dbbcdae
c642abe
e92b6bc
9bfb15b
2c973b1
c2580ed
21c91d6
cd46d9f
472223b
789cbed
4fa33a1
2b97fca
78355db
3da97e2
dde0b37
8bf6c64
c4ccddf
3e87998
826802f
7b67e5d
8671a24
6237adf
12a1252
8b65fdc
c4cb21d
36c3bb3
b7f5c77
fd47863
569b35e
45406d4
f8b9bbb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,7 +27,8 @@ namespace ts { | |
getTokenFlags(): TokenFlags; | ||
reScanGreaterToken(): SyntaxKind; | ||
reScanSlashToken(): SyntaxKind; | ||
reScanTemplateToken(): SyntaxKind; | ||
reScanTemplateToken(isTaggedTemplate?: boolean): SyntaxKind; | ||
reScanTemplateHead(): SyntaxKind; | ||
scanJsxIdentifier(): SyntaxKind; | ||
scanJsxAttributeValue(): SyntaxKind; | ||
reScanJsxToken(): JsxTokenSyntaxKind; | ||
|
@@ -428,6 +429,16 @@ namespace ts { | |
return ch >= CharacterCodes._0 && ch <= CharacterCodes._7; | ||
} | ||
|
||
/* @internal */ | ||
export function isHexDigit(ch: number): boolean { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Neither of these are used outside of |
||
return isDigit(ch) || ch >= CharacterCodes.A && ch <= CharacterCodes.F || ch >= CharacterCodes.a && ch <= CharacterCodes.f; | ||
} | ||
|
||
/* @internal */ | ||
export function isCodePoint(code: number): boolean { | ||
return code <= 0x10FFFF; | ||
} | ||
|
||
export function couldStartTrivia(text: string, pos: number): boolean { | ||
// Keep in sync with skipTrivia | ||
const ch = text.charCodeAt(pos); | ||
|
@@ -838,6 +849,7 @@ namespace ts { | |
reScanGreaterToken, | ||
reScanSlashToken, | ||
reScanTemplateToken, | ||
reScanTemplateHead, | ||
scanJsxIdentifier, | ||
scanJsxAttributeValue, | ||
reScanJsxToken, | ||
|
@@ -1054,7 +1066,7 @@ namespace ts { | |
* Sets the current 'tokenValue' and returns a NoSubstitutionTemplateLiteral or | ||
* a literal component of a TemplateExpression. | ||
*/ | ||
function scanTemplateAndSetTokenValue(): SyntaxKind { | ||
function scanTemplateAndSetTokenValue(isTaggedTemplate?: boolean): SyntaxKind { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd just make this parameter required |
||
const startedWithBacktick = text.charCodeAt(pos) === CharacterCodes.backtick; | ||
|
||
pos++; | ||
|
@@ -1092,7 +1104,7 @@ namespace ts { | |
// Escape character | ||
if (currChar === CharacterCodes.backslash) { | ||
contents += text.substring(start, pos); | ||
contents += scanEscapeSequence(); | ||
contents += scanEscapeSequence(isTaggedTemplate); | ||
start = pos; | ||
continue; | ||
} | ||
|
@@ -1121,7 +1133,8 @@ namespace ts { | |
return resultingToken; | ||
} | ||
|
||
function scanEscapeSequence(): string { | ||
function scanEscapeSequence(isTaggedTemplate?: boolean): string { | ||
const start = pos; | ||
pos++; | ||
if (pos >= end) { | ||
error(Diagnostics.Unexpected_end_of_text); | ||
|
@@ -1131,6 +1144,12 @@ namespace ts { | |
pos++; | ||
switch (ch) { | ||
case CharacterCodes._0: | ||
// '\01' | ||
if (isTaggedTemplate && pos < end && isDigit(text.charCodeAt(pos))) { | ||
pos++; | ||
tokenFlags |= TokenFlags.NotEscape; | ||
return text.substring(start, pos); | ||
} | ||
return "\0"; | ||
case CharacterCodes.b: | ||
return "\b"; | ||
|
@@ -1149,17 +1168,59 @@ namespace ts { | |
case CharacterCodes.doubleQuote: | ||
return "\""; | ||
case CharacterCodes.u: | ||
if (isTaggedTemplate) { | ||
// '\u' | ||
if (pos < end && !isHexDigit(text.charCodeAt(pos)) && text.charCodeAt(pos) !== CharacterCodes.openBrace) { | ||
tokenFlags |= TokenFlags.NotEscape; | ||
return text.substring(start, pos); | ||
} | ||
|
||
// '\u0' or '\u00' or '\u000' | ||
for (let i = 0; i < 3; i++) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe call it |
||
if (pos + i + 1 < end && isHexDigit(text.charCodeAt(pos + i)) && !isHexDigit(text.charCodeAt(pos + i + 1)) && text.charCodeAt(pos + i + 1) !== CharacterCodes.openBrace) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove double space here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you fold the previous |
||
pos += i; | ||
tokenFlags |= TokenFlags.NotEscape; | ||
return text.substring(start, pos); | ||
} | ||
} | ||
} | ||
// '\u{DDDDDDDD}' | ||
if (pos < end && text.charCodeAt(pos) === CharacterCodes.openBrace) { | ||
tokenFlags |= TokenFlags.ExtendedUnicodeEscape; | ||
pos++; | ||
return scanExtendedUnicodeEscape(); | ||
|
||
// '\u{' | ||
if (isTaggedTemplate && !isHexDigit(text.charCodeAt(pos))) { | ||
tokenFlags |= TokenFlags.NotEscape; | ||
return text.substring(start, pos); | ||
} | ||
|
||
const escapedValue = scanMinimumNumberOfHexDigits(1, /*canHaveSeparators*/ false); | ||
if (isTaggedTemplate) { | ||
// '\u{Not Code Point' or '\u{CodePoint' | ||
if (!isCodePoint(escapedValue) || text.charCodeAt(pos) !== CharacterCodes.closeBrace) { | ||
tokenFlags |= TokenFlags.NotEscape; | ||
return text.substring(start, pos); | ||
} | ||
} | ||
tokenFlags |= TokenFlags.ExtendedUnicodeEscape; | ||
return scanExtendedUnicodeEscape(escapedValue); | ||
} | ||
|
||
// '\uDDDD' | ||
return scanHexadecimalEscape(/*numDigits*/ 4); | ||
|
||
case CharacterCodes.x: | ||
if (isTaggedTemplate) { | ||
if (!isHexDigit(text.charCodeAt(pos))) { | ||
tokenFlags |= TokenFlags.NotEscape; | ||
return text.substring(start, pos); | ||
} | ||
else if (!isHexDigit(text.charCodeAt(pos + 1))) { | ||
pos++; | ||
tokenFlags |= TokenFlags.NotEscape; | ||
return text.substring(start, pos); | ||
} | ||
} | ||
// '\xDD' | ||
return scanHexadecimalEscape(/*numDigits*/ 2); | ||
|
||
|
@@ -1191,8 +1252,7 @@ namespace ts { | |
} | ||
} | ||
|
||
function scanExtendedUnicodeEscape(): string { | ||
const escapedValue = scanMinimumNumberOfHexDigits(1, /*canHaveSeparators*/ false); | ||
function scanExtendedUnicodeEscape(escapedValue: number): string { | ||
let isInvalidExtendedEscape = false; | ||
|
||
// Validate the value of the digit | ||
|
@@ -1825,10 +1885,15 @@ namespace ts { | |
/** | ||
* Unconditionally back up and scan a template expression portion. | ||
*/ | ||
function reScanTemplateToken(): SyntaxKind { | ||
function reScanTemplateToken(isTaggedTemplate?: boolean): SyntaxKind { | ||
Debug.assert(token === SyntaxKind.CloseBraceToken, "'reScanTemplateToken' should only be called on a '}'"); | ||
pos = tokenPos; | ||
return token = scanTemplateAndSetTokenValue(); | ||
return token = scanTemplateAndSetTokenValue(isTaggedTemplate); | ||
} | ||
|
||
function reScanTemplateHead(): SyntaxKind { | ||
pos = tokenPos; | ||
return token = scanTemplateAndSetTokenValue(/* isTaggedTemplate */ true); | ||
} | ||
|
||
function reScanJsxToken(): JsxTokenSyntaxKind { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1593,6 +1593,7 @@ namespace ts { | |
BinarySpecifier = 1 << 7, // e.g. `0b0110010000000000` | ||
OctalSpecifier = 1 << 8, // e.g. `0o777` | ||
ContainsSeparator = 1 << 9, // e.g. `0b1100_0101` | ||
NotEscape = 1 << 10, // e.g. `\uhello` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
BinaryOrOctalSpecifier = BinarySpecifier | OctalSpecifier, | ||
NumericLiteralFlags = Scientific | Octal | HexSpecifier | BinarySpecifier | OctalSpecifier | ContainsSeparator | ||
} | ||
|
@@ -1606,16 +1607,22 @@ namespace ts { | |
export interface TemplateHead extends LiteralLikeNode { | ||
kind: SyntaxKind.TemplateHead; | ||
parent?: TemplateExpression; | ||
/* @internal */ | ||
notEscapeFlags?: TokenFlags; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider just calling these |
||
} | ||
|
||
export interface TemplateMiddle extends LiteralLikeNode { | ||
kind: SyntaxKind.TemplateMiddle; | ||
parent?: TemplateSpan; | ||
/* @internal */ | ||
notEscapeFlags?: TokenFlags; | ||
} | ||
|
||
export interface TemplateTail extends LiteralLikeNode { | ||
kind: SyntaxKind.TemplateTail; | ||
parent?: TemplateSpan; | ||
/* @internal */ | ||
notEscapeFlags?: TokenFlags; | ||
} | ||
|
||
export type TemplateLiteral = TemplateExpression | NoSubstitutionTemplateLiteral; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
tests/cases/conformance/es2018/invalidTaggedTemplateEscapeSequences.ts(5,18): error TS1125: Hexadecimal digit expected. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We still need to figure out exactly how to prevent the errors from the initial scan... |
||
tests/cases/conformance/es2018/invalidTaggedTemplateEscapeSequences.ts(6,15): error TS1125: Hexadecimal digit expected. | ||
tests/cases/conformance/es2018/invalidTaggedTemplateEscapeSequences.ts(6,33): error TS1125: Hexadecimal digit expected. | ||
tests/cases/conformance/es2018/invalidTaggedTemplateEscapeSequences.ts(6,75): error TS1125: Hexadecimal digit expected. | ||
|
||
|
||
==== tests/cases/conformance/es2018/invalidTaggedTemplateEscapeSequences.ts (4 errors) ==== | ||
function tag (str: any, ...args: any[]): string { | ||
return str | ||
} | ||
|
||
const x = tag`\u{hello} ${ 100 } \xtraordinary ${ 200 } wonderful ${ 300 } \uworld`; | ||
|
||
!!! error TS1125: Hexadecimal digit expected. | ||
const y = `\u{hello} ${ 100 } \xtraordinary ${ 200 } wonderful ${ 300 } \uworld`; | ||
|
||
!!! error TS1125: Hexadecimal digit expected. | ||
|
||
!!! error TS1125: Hexadecimal digit expected. | ||
|
||
!!! error TS1125: Hexadecimal digit expected. | ||
|
||
const a1 = tag`${ 100 }\0` // \0 | ||
const a2 = tag`${ 100 }\00` // \\00 | ||
const a3 = tag`${ 100 }\u` // \\u | ||
const a4 = tag`${ 100 }\u0` // \\u0 | ||
const a5 = tag`${ 100 }\u00` // \\u00 | ||
const a6 = tag`${ 100 }\u000` // \\u000 | ||
const a7 = tag`${ 100 }\u0000` // \u0000 | ||
const a8 = tag`${ 100 }\u{` // \\u{ | ||
const a9 = tag`${ 100 }\u{10FFFF}` // \\u{10FFFF | ||
const a10 = tag`${ 100 }\u{1f622` // \\u{1f622 | ||
const a11 = tag`${ 100 }\u{1f622}` // \u{1f622} | ||
const a12 = tag`${ 100 }\x` // \\x | ||
const a13 = tag`${ 100 }\x0` // \\x0 | ||
const a14 = tag`${ 100 }\x00` // \x00 | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mhegazy, thoughts on just having
reScanTemplateToken
operate on template heads as well? The implementation could have aDebug.assert
like so: