Skip to content

Commit 92e40b7

Browse files
Add support for parsing template expressions.
1 parent bcf319f commit 92e40b7

17 files changed

+789
-499
lines changed

src/services/resources/diagnosticCode.generated.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ module TypeScript {
1313
Automatic_semicolon_insertion_not_allowed: "Automatic semicolon insertion not allowed.",
1414
Unexpected_token_0_expected: "Unexpected token; '{0}' expected.",
1515
Trailing_comma_not_allowed: "Trailing comma not allowed.",
16-
AsteriskSlash_expected: "'*/' expected.",
1716
public_or_private_modifier_must_precede_static: "'public' or 'private' modifier must precede 'static'.",
1817
Unexpected_token: "Unexpected token.",
1918
Catch_clause_parameter_cannot_have_a_type_annotation: "Catch clause parameter cannot have a type annotation.",
@@ -95,6 +94,7 @@ module TypeScript {
9594
return_statement_must_be_contained_within_a_function_body: "'return' statement must be contained within a function body.",
9695
Expression_expected: "Expression expected.",
9796
Type_expected: "Type expected.",
97+
Template_literal_cannot_be_used_as_an_element_name: "Template literal cannot be used as an element name.",
9898
Duplicate_identifier_0: "Duplicate identifier '{0}'.",
9999
The_name_0_does_not_exist_in_the_current_scope: "The name '{0}' does not exist in the current scope.",
100100
The_name_0_does_not_refer_to_a_value: "The name '{0}' does not refer to a value.",

src/services/resources/diagnosticInformationMap.generated.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ module TypeScript {
9696
"'return' statement must be contained within a function body.": { "code": 1108, "category": DiagnosticCategory.Error },
9797
"Expression expected.": { "code": 1109, "category": DiagnosticCategory.Error },
9898
"Type expected.": { "code": 1110, "category": DiagnosticCategory.Error },
99+
"Template literal cannot be used as an element name.": { "code": 1111, "category": DiagnosticCategory.Error },
99100
"Duplicate identifier '{0}'.": { "code": 2000, "category": DiagnosticCategory.Error },
100101
"The name '{0}' does not exist in the current scope.": { "code": 2001, "category": DiagnosticCategory.Error },
101102
"The name '{0}' does not refer to a value.": { "code": 2002, "category": DiagnosticCategory.Error },

src/services/resources/diagnosticMessages.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,6 @@
4747
"category": "Error",
4848
"code": 1009
4949
},
50-
"'*/' expected.": {
51-
"category": "Error",
52-
"code": 1010
53-
},
5450
"'public' or 'private' modifier must precede 'static'.": {
5551
"category": "Error",
5652
"code": 1011
@@ -375,6 +371,10 @@
375371
"category": "Error",
376372
"code": 1110
377373
},
374+
"Template literal cannot be used as an element name.": {
375+
"category": "Error",
376+
"code": 1111
377+
},
378378
"Duplicate identifier '{0}'.": {
379379
"category": "Error",
380380
"code": 2000

src/services/syntax/SyntaxGenerator.js

Lines changed: 402 additions & 395 deletions
Large diffs are not rendered by default.

src/services/syntax/defaultSyntaxVisitor.generated.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,14 @@ module TypeScript {
274274
return this.defaultVisit(node);
275275
}
276276

277+
public visitTemplateExpression(node: TemplateExpressionSyntax): any {
278+
return this.defaultVisit(node);
279+
}
280+
281+
public visitTemplateAccessExpression(node: TemplateAccessExpressionSyntax): any {
282+
return this.defaultVisit(node);
283+
}
284+
277285
public visitVariableDeclaration(node: VariableDeclarationSyntax): any {
278286
return this.defaultVisit(node);
279287
}
@@ -326,6 +334,10 @@ module TypeScript {
326334
return this.defaultVisit(node);
327335
}
328336

337+
public visitTemplateClause(node: TemplateClauseSyntax): any {
338+
return this.defaultVisit(node);
339+
}
340+
329341
public visitTypeParameter(node: TypeParameterSyntax): any {
330342
return this.defaultVisit(node);
331343
}

src/services/syntax/parser.ts

Lines changed: 67 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,7 @@ module TypeScript.Parser {
465465
return Syntax.emptyToken(expectedKind);
466466
}
467467

468-
function getExpectedTokenDiagnostic(expectedKind: SyntaxKind, actual: ISyntaxToken, diagnosticCode: string): Diagnostic {
468+
function getExpectedTokenDiagnostic(expectedKind: SyntaxKind, actual?: ISyntaxToken, diagnosticCode?: string): Diagnostic {
469469
var token = currentToken();
470470

471471
var args: any[] = undefined;
@@ -2133,6 +2133,10 @@ module TypeScript.Parser {
21332133
case SyntaxKind.StringLiteral:
21342134
case SyntaxKind.RegularExpressionLiteral:
21352135

2136+
// Templates
2137+
case SyntaxKind.NoSubstitutionTemplateToken:
2138+
case SyntaxKind.TemplateStartToken:
2139+
21362140
// For array literals.
21372141
case SyntaxKind.OpenBracketToken:
21382142

@@ -2638,6 +2642,11 @@ module TypeScript.Parser {
26382642
case SyntaxKind.DotToken:
26392643
expression = new MemberAccessExpressionSyntax(parseNodeData, expression, consumeToken(_currentToken), eatIdentifierNameToken());
26402644
continue;
2645+
2646+
case SyntaxKind.NoSubstitutionTemplateToken:
2647+
case SyntaxKind.TemplateStartToken:
2648+
expression = new TemplateAccessExpressionSyntax(parseNodeData, expression, parseTemplateExpression(_currentToken));
2649+
continue;
26412650
}
26422651

26432652
return expression;
@@ -2657,6 +2666,11 @@ module TypeScript.Parser {
26572666
case SyntaxKind.DotToken:
26582667
expression = new MemberAccessExpressionSyntax(parseNodeData, expression, consumeToken(_currentToken), eatIdentifierNameToken());
26592668
continue;
2669+
2670+
case SyntaxKind.NoSubstitutionTemplateToken:
2671+
case SyntaxKind.TemplateStartToken:
2672+
expression = new TemplateAccessExpressionSyntax(parseNodeData, expression, parseTemplateExpression(_currentToken));
2673+
continue;
26602674
}
26612675

26622676
return expression;
@@ -2870,11 +2884,15 @@ module TypeScript.Parser {
28702884
case SyntaxKind.StringLiteral:
28712885
return consumeToken(_currentToken);
28722886

2873-
case SyntaxKind.FunctionKeyword: return parseFunctionExpression(_currentToken);
2874-
case SyntaxKind.OpenBracketToken: return parseArrayLiteralExpression(_currentToken);
2875-
case SyntaxKind.OpenBraceToken: return parseObjectLiteralExpression(_currentToken);
2876-
case SyntaxKind.OpenParenToken: return parseParenthesizedExpression(_currentToken);
2877-
case SyntaxKind.NewKeyword: return parseObjectCreationExpression(_currentToken);
2887+
case SyntaxKind.FunctionKeyword: return parseFunctionExpression(_currentToken);
2888+
case SyntaxKind.OpenBracketToken: return parseArrayLiteralExpression(_currentToken);
2889+
case SyntaxKind.OpenBraceToken: return parseObjectLiteralExpression(_currentToken);
2890+
case SyntaxKind.OpenParenToken: return parseParenthesizedExpression(_currentToken);
2891+
case SyntaxKind.NewKeyword: return parseObjectCreationExpression(_currentToken);
2892+
2893+
case SyntaxKind.NoSubstitutionTemplateToken:
2894+
case SyntaxKind.TemplateStartToken:
2895+
return parseTemplateExpression(_currentToken);
28782896

28792897
case SyntaxKind.SlashToken:
28802898
case SyntaxKind.SlashEqualsToken:
@@ -2961,6 +2979,46 @@ module TypeScript.Parser {
29612979
consumeToken(newKeyword), tryParseMemberExpressionOrHigher(currentToken(), /*force:*/ true, /*inObjectCreation:*/ true), tryParseArgumentList());
29622980
}
29632981

2982+
function parseTemplateExpression(startToken: ISyntaxToken): IPrimaryExpressionSyntax {
2983+
consumeToken(startToken);
2984+
2985+
if (startToken.kind() === SyntaxKind.NoSubstitutionTemplateToken) {
2986+
return startToken;
2987+
}
2988+
2989+
var templateClausesArray: TemplateClauseSyntax[] = getArray();
2990+
2991+
do {
2992+
// Keep consuming template spans as long as the last one we keep getting template
2993+
// middle pieces.
2994+
templateClausesArray.push(parseTemplateClause());
2995+
}
2996+
while (templateClausesArray[templateClausesArray.length - 1].templateMiddleOrEndToken.kind() === SyntaxKind.TemplateMiddleToken);
2997+
2998+
var templateClauses = Syntax.list(templateClausesArray);
2999+
returnZeroLengthArray(templateClausesArray);
3000+
3001+
return new TemplateExpressionSyntax(parseNodeData, startToken, templateClauses);
3002+
}
3003+
3004+
function parseTemplateClause(): TemplateClauseSyntax {
3005+
var expression = parseExpression(/*allowIn:*/ true);
3006+
var token = currentToken();
3007+
3008+
if (token.kind() === SyntaxKind.CloseBraceToken) {
3009+
token = currentContextualToken();
3010+
Debug.assert(token.kind() === SyntaxKind.TemplateMiddleToken || token.kind() === SyntaxKind.TemplateEndToken);
3011+
consumeToken(token);
3012+
}
3013+
else {
3014+
var diagnostic = getExpectedTokenDiagnostic(SyntaxKind.CloseBraceToken);
3015+
addDiagnostic(diagnostic);
3016+
token = Syntax.emptyToken(SyntaxKind.TemplateEndToken);
3017+
}
3018+
3019+
return new TemplateClauseSyntax(parseNodeData, expression, token);
3020+
}
3021+
29643022
function parseCastExpression(lessThanToken: ISyntaxToken): CastExpressionSyntax {
29653023
return new CastExpressionSyntax(parseNodeData,
29663024
consumeToken(lessThanToken), parseType(), eatToken(SyntaxKind.GreaterThanToken), tryParseUnaryExpressionOrHigher(currentToken(), /*force:*/ true));
@@ -3332,8 +3390,10 @@ module TypeScript.Parser {
33323390
}
33333391
}
33343392

3393+
// We allow a template literal while parser for error tolerance. We'll report errors
3394+
// on this later in the grammar checker walker.
33353395
var kind = token.kind();
3336-
return kind === SyntaxKind.StringLiteral || kind === SyntaxKind.NumericLiteral;
3396+
return kind === SyntaxKind.StringLiteral || kind === SyntaxKind.NumericLiteral || kind === SyntaxKind.NoSubstitutionTemplateToken;
33373397
}
33383398

33393399
function parseArrayLiteralExpression(openBracketToken: ISyntaxToken): ArrayLiteralExpressionSyntax {

src/services/syntax/prettyPrinter.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1023,5 +1023,23 @@ module TypeScript.PrettyPrinter {
10231023
this.appendToken(node.debuggerKeyword);
10241024
this.appendToken(node.semicolonToken);
10251025
}
1026+
1027+
public visitTemplateExpression(node: TemplateExpressionSyntax): void {
1028+
this.appendToken(node.templateStartToken);
1029+
this.ensureSpace();
1030+
this.appendSpaceList(node.templateClauses);
1031+
}
1032+
1033+
public visitTemplateClause(node: TemplateClauseSyntax): void {
1034+
node.expression.accept(this);
1035+
this.ensureSpace();
1036+
this.appendToken(node.templateMiddleOrEndToken);
1037+
}
1038+
1039+
public visitTemplateAccessExpression(node: TemplateAccessExpressionSyntax): void {
1040+
node.expression.accept(this);
1041+
this.ensureSpace();
1042+
node.templateExpression.accept(this);
1043+
}
10261044
}
10271045
}

src/services/syntax/scanner.ts

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,11 @@ module TypeScript.Scanner {
178178
case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken:
179179
return true;
180180

181+
// Created by the parser when it sees } while parsing a template expression.
182+
case SyntaxKind.TemplateMiddleToken:
183+
case SyntaxKind.TemplateEndToken:
184+
return true;
185+
181186
default:
182187
return token.isKeywordConvertedToIdentifier();
183188
}
@@ -708,7 +713,7 @@ module TypeScript.Scanner {
708713

709714
while (true) {
710715
if (index === end) {
711-
reportDiagnostic(end, 0, DiagnosticCode.AsteriskSlash_expected, undefined);
716+
reportDiagnostic(end, 0, DiagnosticCode._0_expected, ["*/"]);
712717
return;
713718
}
714719

@@ -750,10 +755,10 @@ module TypeScript.Scanner {
750755
index++;
751756

752757
switch (character) {
753-
case CharacterCodes.exclamation /*33*/: return scanExclamationToken();
758+
case CharacterCodes.exclamation/*33*/: return scanExclamationToken();
754759
case CharacterCodes.doubleQuote/*34*/: return scanStringLiteral(character);
755-
case CharacterCodes.percent /*37*/: return scanPercentToken();
756-
case CharacterCodes.ampersand /*38*/: return scanAmpersandToken();
760+
case CharacterCodes.percent/*37*/: return scanPercentToken();
761+
case CharacterCodes.ampersand/*38*/: return scanAmpersandToken();
757762
case CharacterCodes.singleQuote/*39*/: return scanStringLiteral(character);
758763
case CharacterCodes.openParen/*40*/: return SyntaxKind.OpenParenToken;
759764
case CharacterCodes.closeParen/*41*/: return SyntaxKind.CloseParenToken;
@@ -779,10 +784,11 @@ module TypeScript.Scanner {
779784
case CharacterCodes.openBracket/*91*/: return SyntaxKind.OpenBracketToken;
780785
case CharacterCodes.closeBracket/*93*/: return SyntaxKind.CloseBracketToken;
781786
case CharacterCodes.caret/*94*/: return scanCaretToken();
787+
case CharacterCodes.backtick/*96*/: return scanTemplateToken(character);
782788

783789
case CharacterCodes.openBrace/*123*/: return SyntaxKind.OpenBraceToken;
784790
case CharacterCodes.bar/*124*/: return scanBarToken();
785-
case CharacterCodes.closeBrace/*125*/: return SyntaxKind.CloseBraceToken;
791+
case CharacterCodes.closeBrace/*125*/: return scanCloseBraceToken(allowContextualToken, character);
786792
case CharacterCodes.tilde/*126*/: return SyntaxKind.TildeToken;
787793
}
788794

@@ -1081,6 +1087,39 @@ module TypeScript.Scanner {
10811087
}
10821088
}
10831089

1090+
function scanCloseBraceToken(allowContextualToken: boolean, startChar: number): SyntaxKind {
1091+
return allowContextualToken ? scanTemplateToken(startChar) : SyntaxKind.CloseBraceToken;
1092+
}
1093+
1094+
function scanTemplateToken(startChar: number): SyntaxKind {
1095+
var startedWithBacktick = startChar === CharacterCodes.backtick;
1096+
1097+
while (true) {
1098+
if (index === end) {
1099+
// Hit the end of the file.
1100+
reportDiagnostic(end, 0, DiagnosticCode._0_expected, ["`"]);
1101+
break;
1102+
}
1103+
1104+
var ch = str.charCodeAt(index);
1105+
index++;
1106+
1107+
if (ch === CharacterCodes.backtick) {
1108+
break;
1109+
}
1110+
1111+
if (ch === CharacterCodes.$ &&
1112+
index < end &&
1113+
str.charCodeAt(index) === CharacterCodes.openBrace) {
1114+
1115+
index++;
1116+
return startedWithBacktick ? SyntaxKind.TemplateStartToken : SyntaxKind.TemplateMiddleToken;
1117+
}
1118+
}
1119+
1120+
return startedWithBacktick ? SyntaxKind.NoSubstitutionTemplateToken : SyntaxKind.TemplateEndToken;
1121+
}
1122+
10841123
function scanAmpersandToken(): SyntaxKind {
10851124
var character = str.charCodeAt(index);
10861125
if (character === CharacterCodes.equals) {

0 commit comments

Comments
 (0)