Skip to content

Commit 0a97f5f

Browse files
Merge pull request #960 from Microsoft/templates
Support for ES6 Templates
2 parents 33cee0c + 3e8978f commit 0a97f5f

File tree

304 files changed

+3226
-206
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

304 files changed

+3226
-206
lines changed

src/compiler/checker.ts

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,7 @@ module ts {
427427
if (result.flags & SymbolFlags.BlockScopedVariable) {
428428
// Block-scoped variables cannot be used before their definition
429429
var declaration = forEach(result.declarations, d => d.flags & NodeFlags.BlockScoped ? d : undefined);
430-
Debug.assert(declaration, "Block-scoped variable declaration is undefined");
430+
Debug.assert(declaration !== undefined, "Block-scoped variable declaration is undefined");
431431
var declarationSourceFile = getSourceFileOfNode(declaration);
432432
var referenceSourceFile = getSourceFileOfNode(errorLocation);
433433
if (declarationSourceFile === referenceSourceFile) {
@@ -472,7 +472,7 @@ module ts {
472472
function getSymbolOfPartOfRightHandSideOfImport(entityName: EntityName, importDeclaration?: ImportDeclaration): Symbol {
473473
if (!importDeclaration) {
474474
importDeclaration = getAncestor(entityName, SyntaxKind.ImportDeclaration);
475-
Debug.assert(importDeclaration);
475+
Debug.assert(importDeclaration !== undefined);
476476
}
477477
// There are three things we might try to look for. In the following examples,
478478
// the search term is enclosed in |...|:
@@ -3334,7 +3334,6 @@ module ts {
33343334
}
33353335
if (reportErrors) {
33363336
headMessage = headMessage || Diagnostics.Type_0_is_not_assignable_to_type_1;
3337-
Debug.assert(headMessage);
33383337
reportError(headMessage, typeToString(source), typeToString(target));
33393338
}
33403339
return Ternary.False;
@@ -4912,7 +4911,7 @@ module ts {
49124911
}
49134912
return createArrayType(getUnionType(elementTypes));
49144913
}
4915-
4914+
49164915
function isNumericName(name: string) {
49174916
// The intent of numeric names is that
49184917
// - they are names with text in a numeric form, and that
@@ -4937,7 +4936,7 @@ module ts {
49374936
// with the strings '"Infinity"', '"-Infinity"', and '"NaN"' respectively.
49384937
return (+name).toString() === name;
49394938
}
4940-
4939+
49414940
function checkObjectLiteral(node: ObjectLiteral, contextualMapper?: TypeMapper): Type {
49424941
var members = node.symbol.members;
49434942
var properties: SymbolTable = {};
@@ -5660,6 +5659,13 @@ module ts {
56605659
return getReturnTypeOfSignature(signature);
56615660
}
56625661

5662+
function checkTaggedTemplateExpression(node: TaggedTemplateExpression): Type {
5663+
// TODO (drosen): Make sure substitutions are assignable to the tag's arguments.
5664+
checkExpression(node.tag);
5665+
checkExpression(node.template);
5666+
return anyType;
5667+
}
5668+
56635669
function checkTypeAssertion(node: TypeAssertion): Type {
56645670
var exprType = checkExpression(node.operand);
56655671
var targetType = getTypeFromTypeNode(node.type);
@@ -6170,6 +6176,19 @@ module ts {
61706176
return getUnionType([type1, type2]);
61716177
}
61726178

6179+
function checkTemplateExpression(node: TemplateExpression): Type {
6180+
// We just want to check each expressions, but we are unconcerned with
6181+
// the type of each expression, as any value may be coerced into a string.
6182+
// It is worth asking whether this is what we really want though.
6183+
// A place where we actually *are* concerned with the expressions' types are
6184+
// in tagged templates.
6185+
forEach((<TemplateExpression>node).templateSpans, templateSpan => {
6186+
checkExpression(templateSpan.expression);
6187+
});
6188+
6189+
return stringType;
6190+
}
6191+
61736192
function checkExpressionWithContextualType(node: Expression, contextualType: Type, contextualMapper?: TypeMapper): Type {
61746193
var saveContextualType = node.contextualType;
61756194
node.contextualType = contextualType;
@@ -6223,7 +6242,10 @@ module ts {
62236242
return booleanType;
62246243
case SyntaxKind.NumericLiteral:
62256244
return numberType;
6245+
case SyntaxKind.TemplateExpression:
6246+
return checkTemplateExpression(<TemplateExpression>node);
62266247
case SyntaxKind.StringLiteral:
6248+
case SyntaxKind.NoSubstitutionTemplateLiteral:
62276249
return stringType;
62286250
case SyntaxKind.RegularExpressionLiteral:
62296251
return globalRegExpType;
@@ -6240,6 +6262,8 @@ module ts {
62406262
case SyntaxKind.CallExpression:
62416263
case SyntaxKind.NewExpression:
62426264
return checkCallExpression(<CallExpression>node);
6265+
case SyntaxKind.TaggedTemplateExpression:
6266+
return checkTaggedTemplateExpression(<TaggedTemplateExpression>node);
62436267
case SyntaxKind.TypeAssertion:
62446268
return checkTypeAssertion(<TypeAssertion>node);
62456269
case SyntaxKind.ParenExpression:
@@ -7549,17 +7573,17 @@ module ts {
75497573
errorMessage = Diagnostics.Class_0_defines_instance_member_function_1_but_extended_class_2_defines_it_as_instance_member_accessor;
75507574
}
75517575
else {
7552-
Debug.assert(derived.flags & SymbolFlags.Property);
7576+
Debug.assert((derived.flags & SymbolFlags.Property) !== 0);
75537577
errorMessage = Diagnostics.Class_0_defines_instance_member_function_1_but_extended_class_2_defines_it_as_instance_member_property;
75547578
}
75557579
}
75567580
else if (base.flags & SymbolFlags.Property) {
7557-
Debug.assert(derived.flags & SymbolFlags.Method);
7581+
Debug.assert((derived.flags & SymbolFlags.Method) !== 0);
75587582
errorMessage = Diagnostics.Class_0_defines_instance_member_property_1_but_extended_class_2_defines_it_as_instance_member_function;
75597583
}
75607584
else {
7561-
Debug.assert(base.flags & SymbolFlags.Accessor);
7562-
Debug.assert(derived.flags & SymbolFlags.Method);
7585+
Debug.assert((base.flags & SymbolFlags.Accessor) !== 0);
7586+
Debug.assert((derived.flags & SymbolFlags.Method) !== 0);
75637587
errorMessage = Diagnostics.Class_0_defines_instance_member_accessor_1_but_extended_class_2_defines_it_as_instance_member_function;
75647588
}
75657589

@@ -8016,6 +8040,7 @@ module ts {
80168040
case SyntaxKind.IndexedAccess:
80178041
case SyntaxKind.CallExpression:
80188042
case SyntaxKind.NewExpression:
8043+
case SyntaxKind.TaggedTemplateExpression:
80198044
case SyntaxKind.TypeAssertion:
80208045
case SyntaxKind.ParenExpression:
80218046
case SyntaxKind.PrefixOperator:
@@ -8293,6 +8318,9 @@ module ts {
82938318
case SyntaxKind.CallExpression:
82948319
case SyntaxKind.NewExpression:
82958320
return (<CallExpression>parent).typeArguments && (<CallExpression>parent).typeArguments.indexOf(node) >= 0;
8321+
case SyntaxKind.TaggedTemplateExpression:
8322+
// TODO (drosen): TaggedTemplateExpressions may eventually support type arguments.
8323+
return false;
82968324
}
82978325
}
82988326

src/compiler/core.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ module ts {
1919
[index: string]: T;
2020
}
2121

22+
export enum Comparison {
23+
LessThan = -1,
24+
EqualTo = 0,
25+
GreaterThan = 1
26+
}
27+
2228
export interface StringSet extends Map<any> { }
2329

2430
export function forEach<T, U>(array: T[], callback: (element: T) => U): U {
@@ -93,6 +99,7 @@ module ts {
9399
export function concatenate<T>(array1: T[], array2: T[]): T[] {
94100
if (!array2 || !array2.length) return array1;
95101
if (!array1 || !array1.length) return array2;
102+
96103
return array1.concat(array2);
97104
}
98105

@@ -326,11 +333,11 @@ module ts {
326333
};
327334
}
328335

329-
export function compareValues<T>(a: T, b: T): number {
330-
if (a === b) return 0;
331-
if (a === undefined) return -1;
332-
if (b === undefined) return 1;
333-
return a < b ? -1 : 1;
336+
export function compareValues<T>(a: T, b: T): Comparison {
337+
if (a === b) return Comparison.EqualTo;
338+
if (a === undefined) return Comparison.LessThan;
339+
if (b === undefined) return Comparison.GreaterThan;
340+
return a < b ? Comparison.LessThan : Comparison.GreaterThan;
334341
}
335342

336343
function getDiagnosticFilename(diagnostic: Diagnostic): string {
@@ -355,7 +362,7 @@ module ts {
355362
var previousDiagnostic = diagnostics[0];
356363
for (var i = 1; i < diagnostics.length; i++) {
357364
var currentDiagnostic = diagnostics[i];
358-
var isDupe = compareDiagnostics(currentDiagnostic, previousDiagnostic) === 0;
365+
var isDupe = compareDiagnostics(currentDiagnostic, previousDiagnostic) === Comparison.EqualTo;
359366
if (!isDupe) {
360367
newDiagnostics.push(currentDiagnostic);
361368
previousDiagnostic = currentDiagnostic;
@@ -644,7 +651,7 @@ module ts {
644651
return currentAssertionLevel >= level;
645652
}
646653

647-
export function assert(expression: any, message?: string, verboseDebugInfo?: () => string): void {
654+
export function assert(expression: boolean, message?: string, verboseDebugInfo?: () => string): void {
648655
if (!expression) {
649656
var verboseDebugString = "";
650657
if (verboseDebugInfo) {

src/compiler/diagnosticInformationMap.generated.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,8 @@ module ts {
121121
const_declarations_can_only_be_declared_inside_a_block: { code: 1156, category: DiagnosticCategory.Error, key: "'const' declarations can only be declared inside a block." },
122122
let_declarations_can_only_be_declared_inside_a_block: { code: 1157, category: DiagnosticCategory.Error, key: "'let' declarations can only be declared inside a block." },
123123
Aliased_type_cannot_be_an_object_type_literal_Use_an_interface_declaration_instead: { code: 1158, category: DiagnosticCategory.Error, key: "Aliased type cannot be an object type literal. Use an interface declaration instead." },
124+
Invalid_template_literal_expected: { code: 1159, category: DiagnosticCategory.Error, key: "Invalid template literal; expected '}'" },
125+
Tagged_templates_are_only_available_when_targeting_ECMAScript_6_and_higher: { code: 1160, category: DiagnosticCategory.Error, key: "Tagged templates are only available when targeting ECMAScript 6 and higher." },
124126
Duplicate_identifier_0: { code: 2300, category: DiagnosticCategory.Error, key: "Duplicate identifier '{0}'." },
125127
Initializer_of_instance_member_variable_0_cannot_reference_identifier_1_declared_in_the_constructor: { code: 2301, category: DiagnosticCategory.Error, key: "Initializer of instance member variable '{0}' cannot reference identifier '{1}' declared in the constructor." },
126128
Static_members_cannot_reference_class_type_parameters: { code: 2302, category: DiagnosticCategory.Error, key: "Static members cannot reference class type parameters." },

src/compiler/diagnosticMessages.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,14 @@
475475
"category": "Error",
476476
"code": 1158
477477
},
478+
"Invalid template literal; expected '}'": {
479+
"category": "Error",
480+
"code": 1159
481+
},
482+
"Tagged templates are only available when targeting ECMAScript 6 and higher.": {
483+
"category": "Error",
484+
"code": 1160
485+
},
478486

479487
"Duplicate identifier '{0}'.": {
480488
"category": "Error",

src/compiler/emitter.ts

Lines changed: 129 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -786,14 +786,123 @@ module ts {
786786
}
787787
}
788788

789-
function emitLiteral(node: LiteralExpression) {
790-
var text = getSourceTextOfLocalNode(node);
791-
if (node.kind === SyntaxKind.StringLiteral && compilerOptions.sourceMap) {
789+
function emitLiteral(node: LiteralExpression): void {
790+
var text = getLiteralText();
791+
792+
if (compilerOptions.sourceMap && (node.kind === SyntaxKind.StringLiteral || isTemplateLiteralKind(node.kind))) {
792793
writer.writeLiteral(text);
793794
}
794795
else {
795796
write(text);
796797
}
798+
799+
function getLiteralText() {
800+
if (compilerOptions.target < ScriptTarget.ES6 && isTemplateLiteralKind(node.kind)) {
801+
return getTemplateLiteralAsStringLiteral(node)
802+
}
803+
804+
return getSourceTextOfLocalNode(node);
805+
}
806+
}
807+
808+
function getTemplateLiteralAsStringLiteral(node: LiteralExpression): string {
809+
return '"' + escapeString(node.text) + '"';
810+
}
811+
812+
function emitTemplateExpression(node: TemplateExpression): void {
813+
// In ES6 mode and above, we can simply emit each portion of a template in order, but in
814+
// ES3 & ES5 we must convert the template expression into a series of string concatenations.
815+
if (compilerOptions.target >= ScriptTarget.ES6) {
816+
forEachChild(node, emit);
817+
return;
818+
}
819+
820+
Debug.assert(node.parent.kind !== SyntaxKind.TaggedTemplateExpression);
821+
822+
var templateNeedsParens = isExpression(node.parent)
823+
&& node.parent.kind !== SyntaxKind.ParenExpression
824+
&& comparePrecedenceToBinaryPlus(node.parent) !== Comparison.LessThan;
825+
826+
if (templateNeedsParens) {
827+
write("(");
828+
}
829+
830+
emitLiteral(node.head);
831+
832+
forEach(node.templateSpans, templateSpan => {
833+
// Check if the expression has operands and binds its operands less closely than binary '+'.
834+
// If it does, we need to wrap the expression in parentheses. Otherwise, something like
835+
// `abc${ 1 << 2}`
836+
// becomes
837+
// "abc" + 1 << 2 + ""
838+
// which is really
839+
// ("abc" + 1) << (2 + "")
840+
// rather than
841+
// "abc" + (1 << 2) + ""
842+
var needsParens = templateSpan.expression.kind !== SyntaxKind.ParenExpression
843+
&& comparePrecedenceToBinaryPlus(templateSpan.expression) !== Comparison.GreaterThan;
844+
845+
write(" + ");
846+
847+
if (needsParens) {
848+
write("(");
849+
}
850+
emit(templateSpan.expression);
851+
if (needsParens) {
852+
write(")");
853+
}
854+
855+
// Only emit if the literal is non-empty.
856+
// The binary '+' operator is left-associative, so the first string concatenation will force
857+
// the result up to this point to be a string. Emitting a '+ ""' has no semantic effect.
858+
if (templateSpan.literal.text.length !== 0) {
859+
write(" + ")
860+
emitLiteral(templateSpan.literal);
861+
}
862+
});
863+
864+
if (templateNeedsParens) {
865+
write(")");
866+
}
867+
868+
/**
869+
* Returns whether the expression has lesser, greater,
870+
* or equal precedence to the binary '+' operator
871+
*/
872+
function comparePrecedenceToBinaryPlus(expression: Expression): Comparison {
873+
// All binary expressions have lower precedence than '+' apart from '*', '/', and '%'.
874+
// All unary operators have a higher precedence apart from yield.
875+
// Arrow functions and conditionals have a lower precedence,
876+
// although we convert the former into regular function expressions in ES5 mode,
877+
// and in ES6 mode this function won't get called anyway.
878+
//
879+
// TODO (drosen): Note that we need to account for the upcoming 'yield' and
880+
// spread ('...') unary operators that are anticipated for ES6.
881+
Debug.assert(compilerOptions.target <= ScriptTarget.ES5);
882+
switch (expression.kind) {
883+
case SyntaxKind.BinaryExpression:
884+
switch ((<BinaryExpression>expression).operator) {
885+
case SyntaxKind.AsteriskToken:
886+
case SyntaxKind.SlashToken:
887+
case SyntaxKind.PercentToken:
888+
return Comparison.GreaterThan;
889+
case SyntaxKind.PlusToken:
890+
return Comparison.EqualTo;
891+
default:
892+
return Comparison.LessThan;
893+
}
894+
case SyntaxKind.ConditionalExpression:
895+
return Comparison.LessThan;
896+
default:
897+
return Comparison.GreaterThan;
898+
}
899+
}
900+
901+
}
902+
903+
function emitTemplateSpan(span: TemplateSpan) {
904+
emit(span.expression);
905+
emit(span.literal);
797906
}
798907

799908
// This function specifically handles numeric/string literals for enum and accessor 'identifiers'.
@@ -977,6 +1086,13 @@ module ts {
9771086
}
9781087
}
9791088

1089+
function emitTaggedTemplateExpression(node: TaggedTemplateExpression): void {
1090+
Debug.assert(compilerOptions.target >= ScriptTarget.ES6, "Trying to emit a tagged template in pre-ES6 mode.");
1091+
emit(node.tag);
1092+
write(" ");
1093+
emit(node.template);
1094+
}
1095+
9801096
function emitParenExpression(node: ParenExpression) {
9811097
if (node.expression.kind === SyntaxKind.TypeAssertion) {
9821098
var operand = (<TypeAssertion>node.expression).operand;
@@ -2085,7 +2201,15 @@ module ts {
20852201
case SyntaxKind.NumericLiteral:
20862202
case SyntaxKind.StringLiteral:
20872203
case SyntaxKind.RegularExpressionLiteral:
2204+
case SyntaxKind.NoSubstitutionTemplateLiteral:
2205+
case SyntaxKind.TemplateHead:
2206+
case SyntaxKind.TemplateMiddle:
2207+
case SyntaxKind.TemplateTail:
20882208
return emitLiteral(<LiteralExpression>node);
2209+
case SyntaxKind.TemplateExpression:
2210+
return emitTemplateExpression(<TemplateExpression>node);
2211+
case SyntaxKind.TemplateSpan:
2212+
return emitTemplateSpan(<TemplateSpan>node);
20892213
case SyntaxKind.QualifiedName:
20902214
return emitPropertyAccess(<QualifiedName>node);
20912215
case SyntaxKind.ArrayLiteral:
@@ -2102,6 +2226,8 @@ module ts {
21022226
return emitCallExpression(<CallExpression>node);
21032227
case SyntaxKind.NewExpression:
21042228
return emitNewExpression(<NewExpression>node);
2229+
case SyntaxKind.TaggedTemplateExpression:
2230+
return emitTaggedTemplateExpression(<TaggedTemplateExpression>node);
21052231
case SyntaxKind.TypeAssertion:
21062232
return emit((<TypeAssertion>node).operand);
21072233
case SyntaxKind.ParenExpression:

0 commit comments

Comments
 (0)