Skip to content
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

Merged
merged 34 commits into from
Feb 5, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
344d505
add support for Lift Template Literal Restriction
Kingwl May 1, 2018
6ef797f
rename file and improve comment and tests
Kingwl May 1, 2018
3932d20
fix NoSubstitutionTemplateLiteral support
Kingwl May 2, 2018
dbbcdae
Merge branch 'master' into Lift-Template-Literal-Restriction
Kingwl May 24, 2018
c642abe
Merge branch 'Lift-Template-Literal-Restriction' of https://github.co…
Kingwl May 25, 2018
e92b6bc
extract tagged template and add more test
Kingwl May 26, 2018
9bfb15b
Merge branch 'master' into Lift-Template-Literal-Restriction
Kingwl Jun 8, 2018
2c973b1
avoid useless parameter
Kingwl Jun 8, 2018
c2580ed
fix incorrect return node if cannot transform
Kingwl Jun 8, 2018
21c91d6
accept baseline
Kingwl Jun 8, 2018
cd46d9f
Merge branch 'master' into Lift-Template-Literal-Restriction
Kingwl Jun 30, 2018
472223b
correctly baseline
Kingwl Jun 30, 2018
789cbed
Merge branch 'master' of https://github.com/kingwl/typescript into Li…
Kingwl Aug 28, 2018
4fa33a1
Merge branch 'master' into Lift-Template-Literal-Restriction
Kingwl Aug 28, 2018
2b97fca
Merge branch 'master' into Lift-Template-Literal-Restriction
Kingwl Aug 29, 2018
78355db
accept baseline
Kingwl Aug 29, 2018
3da97e2
Merge branch 'master' into Lift-Template-Literal-Restriction
Kingwl Jan 19, 2019
dde0b37
fix merge break
Kingwl Jan 20, 2019
8bf6c64
Merge branch 'master' into Lift-Template-Literal-Restriction
Kingwl Apr 10, 2019
c4ccddf
fix merge break
Kingwl Apr 10, 2019
3e87998
inline rescan template head or no subsititution template
Kingwl Apr 10, 2019
826802f
update scan error
Kingwl Apr 10, 2019
7b67e5d
add comment and fix lint
Kingwl Apr 10, 2019
8671a24
refactor and fix lint
Kingwl Apr 12, 2019
6237adf
avoid blank
Kingwl Apr 12, 2019
12a1252
Merge branch 'master' of https://github.com/microsoft/TypeScript into…
Kingwl Aug 19, 2019
8b65fdc
fix merge conflict
Kingwl Aug 19, 2019
c4cb21d
Merge branch 'master' into Lift-Template-Literal-Restriction
Kingwl Aug 27, 2019
36c3bb3
fix again
Kingwl Aug 27, 2019
b7f5c77
fix again
Kingwl Aug 27, 2019
fd47863
Merge branch 'master' into Lift-Template-Literal-Restriction
Kingwl Jan 9, 2020
569b35e
use multiple target
Kingwl Jan 9, 2020
45406d4
Merge branch 'master' into Lift-Template-Literal-Restriction
sandersn Feb 5, 2020
f8b9bbb
fix space lint
sandersn Feb 5, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 21 additions & 10 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1034,8 +1034,12 @@ namespace ts {
return currentToken = scanner.reScanSlashToken();
}

function reScanTemplateToken(): SyntaxKind {
return currentToken = scanner.reScanTemplateToken();
function reScanTemplateToken(isTaggedTemplate?: boolean): SyntaxKind {
return currentToken = scanner.reScanTemplateToken(isTaggedTemplate);
}

function reScanTemplateHead(): SyntaxKind {
return currentToken = scanner.reScanTemplateHead();
}

function scanJsxIdentifier(): SyntaxKind {
Expand Down Expand Up @@ -2104,17 +2108,17 @@ namespace ts {
return allowIdentifierNames ? parseIdentifierName() : parseIdentifier();
}

function parseTemplateExpression(): TemplateExpression {
function parseTemplateExpression(isTaggedTemplate?: boolean): TemplateExpression {
const template = <TemplateExpression>createNode(SyntaxKind.TemplateExpression);

template.head = parseTemplateHead();
template.head = parseTemplateHead(isTaggedTemplate);
Debug.assert(template.head.kind === SyntaxKind.TemplateHead, "Template head has wrong token kind");

const list = [];
const listPos = getNodePos();

do {
list.push(parseTemplateSpan());
list.push(parseTemplateSpan(isTaggedTemplate));
}
while (lastOrUndefined(list).literal.kind === SyntaxKind.TemplateMiddle);

Expand All @@ -2123,13 +2127,13 @@ namespace ts {
return finishNode(template);
}

function parseTemplateSpan(): TemplateSpan {
function parseTemplateSpan(isTaggedTemplate?: boolean): TemplateSpan {
const span = <TemplateSpan>createNode(SyntaxKind.TemplateSpan);
span.expression = allowInAnd(parseExpression);

let literal: TemplateMiddle | TemplateTail;
if (token() === SyntaxKind.CloseBraceToken) {
reScanTemplateToken();
reScanTemplateToken(isTaggedTemplate);
literal = parseTemplateMiddleOrTemplateTail();
}
else {
Expand All @@ -2144,7 +2148,10 @@ namespace ts {
return <LiteralExpression>parseLiteralLikeNode(token());
}

function parseTemplateHead(): TemplateHead {
function parseTemplateHead(isTaggedTemplate?: boolean): TemplateHead {
if (isTaggedTemplate) {
reScanTemplateHead();
}
const fragment = parseLiteralLikeNode(token());
Debug.assert(fragment.kind === SyntaxKind.TemplateHead, "Template head has wrong token kind");
return <TemplateHead>fragment;
Expand All @@ -2157,7 +2164,7 @@ namespace ts {
}

function parseLiteralLikeNode(kind: SyntaxKind): LiteralExpression | LiteralLikeNode {
const node = <LiteralExpression>createNode(kind);
const node = <LiteralExpression | LiteralLikeNode>createNode(kind);
const text = scanner.getTokenValue();
node.text = text;

Expand All @@ -2179,6 +2186,10 @@ namespace ts {
(<NumericLiteral>node).numericLiteralFlags = scanner.getTokenFlags() & TokenFlags.NumericLiteralFlags;
}

if (node.kind === SyntaxKind.TemplateHead || node.kind === SyntaxKind.TemplateMiddle || node.kind === SyntaxKind.TemplateTail) {
(<TemplateHead | TemplateMiddle | TemplateTail>node).notEscapeFlags = scanner.getTokenFlags() & TokenFlags.NotEscape;
}

nextToken();
finishNode(node);

Expand Down Expand Up @@ -4414,7 +4425,7 @@ namespace ts {
tagExpression.typeArguments = typeArguments;
tagExpression.template = token() === SyntaxKind.NoSubstitutionTemplateLiteral
? <NoSubstitutionTemplateLiteral>parseLiteralNode()
: parseTemplateExpression();
: parseTemplateExpression(/*isTaggedTemplate*/ true);
return finishNode(tagExpression);
}

Expand Down
85 changes: 75 additions & 10 deletions src/compiler/scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ namespace ts {
getTokenFlags(): TokenFlags;
reScanGreaterToken(): SyntaxKind;
reScanSlashToken(): SyntaxKind;
reScanTemplateToken(): SyntaxKind;
reScanTemplateToken(isTaggedTemplate?: boolean): SyntaxKind;
reScanTemplateHead(): SyntaxKind;
Copy link
Member

@DanielRosenwasser DanielRosenwasser May 2, 2018

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 a Debug.assert like so:

if (token !== SyntaxKind.TemplateHead) {
    Debug.assert(isTaggedTemplate, "'reScanTemplateToken' should only be called with a template head when in a tagged template.");
}
else {
    Debug.assert(token === SyntaxKind.CloseBraceToken, "'reScanTemplateToken' should only be called on a '}' or TemplateHead.");
}

scanJsxIdentifier(): SyntaxKind;
scanJsxAttributeValue(): SyntaxKind;
reScanJsxToken(): JsxTokenSyntaxKind;
Expand Down Expand Up @@ -428,6 +429,16 @@ namespace ts {
return ch >= CharacterCodes._0 && ch <= CharacterCodes._7;
}

/* @internal */
export function isHexDigit(ch: number): boolean {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neither of these are used outside of scanner.ts, so just don't export them.

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);
Expand Down Expand Up @@ -838,6 +849,7 @@ namespace ts {
reScanGreaterToken,
reScanSlashToken,
reScanTemplateToken,
reScanTemplateHead,
scanJsxIdentifier,
scanJsxAttributeValue,
reScanJsxToken,
Expand Down Expand Up @@ -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 {
Copy link
Member

Choose a reason for hiding this comment

The 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++;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
Expand All @@ -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";
Expand All @@ -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++) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe call it escapePos and initialize it to pos (bounded against pos + 3) so you don't have to write pos + i over and over.

if (pos + i + 1 < end && isHexDigit(text.charCodeAt(pos + i)) && !isHexDigit(text.charCodeAt(pos + i + 1)) && text.charCodeAt(pos + i + 1) !== CharacterCodes.openBrace) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove double space here

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you fold the previous if into part of the loop and iterate over 4 times? If it gets too gross, don't worry about it

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);

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions src/compiler/transformers/es2015.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3656,10 +3656,10 @@ namespace ts {
rawStrings.push(getRawLiteral(template));
}
else {
cookedStrings.push(createLiteral(template.head.text));
cookedStrings.push(template.head.notEscapeFlags ? createIdentifier("undefined") : createLiteral(template.head.text));
rawStrings.push(getRawLiteral(template.head));
for (const templateSpan of template.templateSpans) {
cookedStrings.push(createLiteral(templateSpan.literal.text));
cookedStrings.push(templateSpan.literal.notEscapeFlags ? createIdentifier("undefined") : createLiteral(templateSpan.literal.text));
rawStrings.push(getRawLiteral(templateSpan.literal));
templateArguments.push(visitNode(templateSpan.expression, visitor, isExpression));
}
Expand Down
7 changes: 7 additions & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ContainsInvalidEscape

BinaryOrOctalSpecifier = BinarySpecifier | OctalSpecifier,
NumericLiteralFlags = Scientific | Octal | HexSpecifier | BinarySpecifier | OctalSpecifier | ContainsSeparator
}
Expand All @@ -1606,16 +1607,22 @@ namespace ts {
export interface TemplateHead extends LiteralLikeNode {
kind: SyntaxKind.TemplateHead;
parent?: TemplateExpression;
/* @internal */
notEscapeFlags?: TokenFlags;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider just calling these templateFlags.

}

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;
Expand Down
3 changes: 2 additions & 1 deletion tests/baselines/reference/api/tsserverlibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2953,7 +2953,8 @@ declare namespace ts {
isUnterminated(): boolean;
reScanGreaterToken(): SyntaxKind;
reScanSlashToken(): SyntaxKind;
reScanTemplateToken(): SyntaxKind;
reScanTemplateToken(isTaggedTemplate?: boolean): SyntaxKind;
reScanTemplateHead(): SyntaxKind;
scanJsxIdentifier(): SyntaxKind;
scanJsxAttributeValue(): SyntaxKind;
reScanJsxToken(): JsxTokenSyntaxKind;
Expand Down
3 changes: 2 additions & 1 deletion tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2953,7 +2953,8 @@ declare namespace ts {
isUnterminated(): boolean;
reScanGreaterToken(): SyntaxKind;
reScanSlashToken(): SyntaxKind;
reScanTemplateToken(): SyntaxKind;
reScanTemplateToken(isTaggedTemplate?: boolean): SyntaxKind;
reScanTemplateHead(): SyntaxKind;
scanJsxIdentifier(): SyntaxKind;
scanJsxAttributeValue(): SyntaxKind;
reScanJsxToken(): JsxTokenSyntaxKind;
Expand Down
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.
Copy link
Member

Choose a reason for hiding this comment

The 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

Loading