From 4aafe1d2bc2c645b6986752ac6a7e2804dc283e5 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Mon, 27 Oct 2014 14:55:52 -0700 Subject: [PATCH] Addressed CR feedback. --- src/compiler/checker.ts | 12 +++++++++--- src/compiler/emitter.ts | 19 ++++++++++++++++--- src/compiler/scanner.ts | 24 +++++++++++++----------- 3 files changed, 38 insertions(+), 17 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7275a5e95951f..7011df7e10d19 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5945,10 +5945,17 @@ module ts { return getUnionType([type1, type2]); } - function checkTemplateExpression(node: TemplateExpression): void { + function checkTemplateExpression(node: TemplateExpression): Type { + // We just want to check each expressions, but we are unconcerned with + // the type of each expression, as any value may be coerced into a string. + // It is worth asking whether this is what we really want though. + // A place where we actually *are* concerned with the expressions' types are + // in tagged templates. forEach((node).templateSpans, templateSpan => { checkExpression(templateSpan.expression); }); + + return stringType; } function checkExpressionWithContextualType(node: Expression, contextualType: Type, contextualMapper?: TypeMapper): Type { @@ -6005,8 +6012,7 @@ module ts { case SyntaxKind.NumericLiteral: return numberType; case SyntaxKind.TemplateExpression: - checkTemplateExpression(node); - // fall through + return checkTemplateExpression(node); case SyntaxKind.StringLiteral: case SyntaxKind.NoSubstitutionTemplateLiteral: return stringType; diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index e8550fbce3196..f2e984d18c1a1 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -806,12 +806,14 @@ module ts { } function getTemplateLiteralAsStringLiteral(node: LiteralExpression): string { - return "\"" + escapeString(node.text) + "\""; + return '"' + escapeString(node.text) + '"'; } function emitTemplateExpression(node: TemplateExpression): void { + // In ES6 mode and above, we can simply emit each portion of a template in order, but in + // ES3 & ES5 we must convert the template expression into a series of string concatenations. if (compilerOptions.target >= ScriptTarget.ES6) { - forEachChild(node, emitNode); + forEachChild(node, emit); return; } @@ -825,6 +827,15 @@ module ts { emitLiteral(node.head); forEach(node.templateSpans, templateSpan => { + // Check if the expression has operands and binds its operands less closely than binary '+'. + // If it does, we need to wrap the expression in parentheses. Otherwise, something like + // `abc${ 1 << 2}` + // becomes + // "abc" + 1 << 2 + "" + // which is really + // ("abc" + 1) << (2 + "") + // rather than + // "abc" + (1 << 2) + "" var needsParens = comparePrecedenceToBinaryPlus(templateSpan.expression) !== Comparison.GreaterThan; write(" + "); @@ -858,6 +869,7 @@ module ts { // // TODO (drosen): Note that we need to account for the upcoming 'yield' and // spread ('...') unary operators that are anticipated for ES6. + Debug.assert(compilerOptions.target <= ScriptTarget.ES5); switch (expression.kind) { case SyntaxKind.BinaryExpression: switch ((expression).operator) { @@ -880,7 +892,8 @@ module ts { } function emitTemplateSpan(span: TemplateSpan) { - forEachChild(span, emitNode); + emit(span.expression); + emit(span.literal); } // This function specifically handles numeric/string literals for enum and accessor 'identifiers'. diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index 0d207b1ae92f0..fddfa8a7f338b 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -519,10 +519,10 @@ module ts { return +(text.substring(start, pos)); } - function scanHexDigits(count: number, useExactCount?: boolean): number { + function scanHexDigits(count: number, mustMatchCount?: boolean): number { var digits = 0; var value = 0; - while (digits < count || !useExactCount) { + while (digits < count || !mustMatchCount) { var ch = text.charCodeAt(pos); if (ch >= CharacterCodes._0 && ch <= CharacterCodes._9) { value = value * 16 + ch - CharacterCodes._0; @@ -582,18 +582,18 @@ module ts { * a literal component of a TemplateExpression. */ function scanTemplateAndSetTokenValue(): SyntaxKind { - var isStartOfTemplate = text.charCodeAt(pos) === CharacterCodes.backtick; + var startedWithBacktick = text.charCodeAt(pos) === CharacterCodes.backtick; pos++; var start = pos; var contents = "" - var resultingToken = SyntaxKind.Unknown; + var resultingToken: SyntaxKind; while (true) { if (pos >= len) { contents += text.substring(start, pos); error(Diagnostics.Unexpected_end_of_text); - resultingToken = isStartOfTemplate ? SyntaxKind.NoSubstitutionTemplateLiteral : SyntaxKind.TemplateTail; + resultingToken = startedWithBacktick ? SyntaxKind.NoSubstitutionTemplateLiteral : SyntaxKind.TemplateTail; break; } @@ -603,7 +603,7 @@ module ts { if (currChar === CharacterCodes.backtick) { contents += text.substring(start, pos); pos++; - resultingToken = isStartOfTemplate ? SyntaxKind.NoSubstitutionTemplateLiteral : SyntaxKind.TemplateTail; + resultingToken = startedWithBacktick ? SyntaxKind.NoSubstitutionTemplateLiteral : SyntaxKind.TemplateTail; break; } @@ -611,7 +611,7 @@ module ts { if (currChar === CharacterCodes.$ && pos + 1 < len && text.charCodeAt(pos + 1) === CharacterCodes.openBrace) { contents += text.substring(start, pos); pos += 2; - resultingToken = isStartOfTemplate ? SyntaxKind.TemplateHead : SyntaxKind.TemplateMiddle; + resultingToken = startedWithBacktick ? SyntaxKind.TemplateHead : SyntaxKind.TemplateMiddle; break; } @@ -641,6 +641,8 @@ module ts { pos++; } + Debug.assert(resultingToken !== undefined); + tokenValue = contents; return resultingToken; } @@ -673,7 +675,7 @@ module ts { return "\""; case CharacterCodes.x: case CharacterCodes.u: - var ch = scanHexDigits(ch === CharacterCodes.x ? 2 : 4, /*useExactCount*/ true); + var ch = scanHexDigits(ch === CharacterCodes.x ? 2 : 4, /*mustMatchCount*/ true); if (ch >= 0) { return String.fromCharCode(ch); } @@ -704,7 +706,7 @@ module ts { if (pos + 5 < len && text.charCodeAt(pos + 1) === CharacterCodes.u) { var start = pos; pos += 2; - var value = scanHexDigits(4, /*useExactCount*/ true); + var value = scanHexDigits(4, /*mustMatchCount*/ true); pos = start; return value; } @@ -922,7 +924,7 @@ module ts { case CharacterCodes._0: if (pos + 2 < len && (text.charCodeAt(pos + 1) === CharacterCodes.X || text.charCodeAt(pos + 1) === CharacterCodes.x)) { pos += 2; - var value = scanHexDigits(1, /*useExactCount*/ false); + var value = scanHexDigits(1, /*mustMatchCount*/ false); if (value < 0) { error(Diagnostics.Hexadecimal_digit_expected); value = 0; @@ -1112,7 +1114,7 @@ module ts { * Unconditionally back up and scan a template expression portion. */ function reScanTemplateToken(): SyntaxKind { - Debug.assert("'reScanTemplateToken' should only be called on a '}'"); + Debug.assert(token === SyntaxKind.CloseBraceToken, "'reScanTemplateToken' should only be called on a '}'"); pos = tokenPos; return token = scanTemplateAndSetTokenValue(); }