Skip to content

Commit 8323d00

Browse files
committed
fix(39744): make template literals more spec compliant
1 parent 39c653c commit 8323d00

File tree

92 files changed

+455
-516
lines changed

Some content is hidden

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

92 files changed

+455
-516
lines changed

src/compiler/transformers/es2015.ts

Lines changed: 12 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -4102,86 +4102,22 @@ namespace ts {
41024102
* @param node A TemplateExpression node.
41034103
*/
41044104
function visitTemplateExpression(node: TemplateExpression): Expression {
4105-
const expressions: Expression[] = [];
4106-
addTemplateHead(expressions, node);
4107-
addTemplateSpans(expressions, node);
4108-
4109-
// createAdd will check if each expression binds less closely than binary '+'.
4110-
// If it does, it wraps the expression in parentheses. Otherwise, something like
4111-
// `abc${ 1 << 2 }`
4112-
// becomes
4113-
// "abc" + 1 << 2 + ""
4114-
// which is really
4115-
// ("abc" + 1) << (2 + "")
4116-
// rather than
4117-
// "abc" + (1 << 2) + ""
4118-
const expression = reduceLeft(expressions, factory.createAdd)!;
4119-
if (nodeIsSynthesized(expression)) {
4120-
setTextRange(expression, node);
4121-
}
4122-
4123-
return expression;
4124-
}
4125-
4126-
/**
4127-
* Gets a value indicating whether we need to include the head of a TemplateExpression.
4128-
*
4129-
* @param node A TemplateExpression node.
4130-
*/
4131-
function shouldAddTemplateHead(node: TemplateExpression) {
4132-
// If this expression has an empty head literal and the first template span has a non-empty
4133-
// literal, then emitting the empty head literal is not necessary.
4134-
// `${ foo } and ${ bar }`
4135-
// can be emitted as
4136-
// foo + " and " + bar
4137-
// This is because it is only required that one of the first two operands in the emit
4138-
// output must be a string literal, so that the other operand and all following operands
4139-
// are forced into strings.
4140-
//
4141-
// If the first template span has an empty literal, then the head must still be emitted.
4142-
// `${ foo }${ bar }`
4143-
// must still be emitted as
4144-
// "" + foo + bar
4145-
4146-
// There is always atleast one templateSpan in this code path, since
4147-
// NoSubstitutionTemplateLiterals are directly emitted via emitLiteral()
4148-
Debug.assert(node.templateSpans.length !== 0);
4105+
let expression: Expression = factory.createStringLiteral(node.head.text);
4106+
for (const span of node.templateSpans) {
4107+
const args = [visitNode(span.expression, visitor, isExpression)];
41494108

4150-
return node.head.text.length !== 0 || node.templateSpans[0].literal.text.length === 0;
4151-
}
4109+
if (span.literal.text.length > 0) {
4110+
args.push(factory.createStringLiteral(span.literal.text));
4111+
}
41524112

4153-
/**
4154-
* Adds the head of a TemplateExpression to an array of expressions.
4155-
*
4156-
* @param expressions An array of expressions.
4157-
* @param node A TemplateExpression node.
4158-
*/
4159-
function addTemplateHead(expressions: Expression[], node: TemplateExpression): void {
4160-
if (!shouldAddTemplateHead(node)) {
4161-
return;
4113+
expression = factory.createCallExpression(
4114+
factory.createPropertyAccessExpression(expression, "concat"),
4115+
/*typeArguments*/ undefined,
4116+
args,
4117+
);
41624118
}
41634119

4164-
expressions.push(factory.createStringLiteral(node.head.text));
4165-
}
4166-
4167-
/**
4168-
* Visits and adds the template spans of a TemplateExpression to an array of expressions.
4169-
*
4170-
* @param expressions An array of expressions.
4171-
* @param node A TemplateExpression node.
4172-
*/
4173-
function addTemplateSpans(expressions: Expression[], node: TemplateExpression): void {
4174-
for (const span of node.templateSpans) {
4175-
expressions.push(visitNode(span.expression, visitor, isExpression));
4176-
4177-
// Only emit if the literal is non-empty.
4178-
// The binary '+' operator is left-associative, so the first string concatenation
4179-
// with the head will force the result up to this point to be a string.
4180-
// Emitting a '+ ""' has no semantic effect for middles and tails.
4181-
if (span.literal.text.length !== 0) {
4182-
expressions.push(factory.createStringLiteral(span.literal.text));
4183-
}
4184-
}
4120+
return setTextRange(expression, node);
41854121
}
41864122

41874123
/**

tests/baselines/reference/APISample_compile.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,10 @@ function compile(fileNames, options) {
6666
return;
6767
}
6868
var _a = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start), line = _a.line, character = _a.character;
69-
console.log(diagnostic.file.fileName + " (" + (line + 1) + "," + (character + 1) + "): " + message);
69+
console.log("".concat(diagnostic.file.fileName, " (").concat(line + 1, ",").concat(character + 1, "): ").concat(message));
7070
});
7171
var exitCode = emitResult.emitSkipped ? 1 : 0;
72-
console.log("Process exiting with code '" + exitCode + "'.");
72+
console.log("Process exiting with code '".concat(exitCode, "'."));
7373
process.exit(exitCode);
7474
}
7575
exports.compile = compile;

tests/baselines/reference/APISample_linter.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ function delint(sourceFile) {
114114
}
115115
function report(node, message) {
116116
var _a = sourceFile.getLineAndCharacterOfPosition(node.getStart()), line = _a.line, character = _a.character;
117-
console.log(sourceFile.fileName + " (" + (line + 1) + "," + (character + 1) + "): " + message);
117+
console.log("".concat(sourceFile.fileName, " (").concat(line + 1, ",").concat(character + 1, "): ").concat(message));
118118
}
119119
}
120120
exports.delint = delint;

tests/baselines/reference/APISample_parseConfig.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ function printError(error) {
5656
if (!error) {
5757
return;
5858
}
59-
console.log((error.file && error.file.fileName) + ": " + error.messageText);
59+
console.log("".concat(error.file && error.file.fileName, ": ").concat(error.messageText));
6060
}
6161
function createProgram(rootFiles, compilerOptionsJson) {
6262
var _a = ts.parseConfigFileTextToJson("tsconfig.json", compilerOptionsJson), config = _a.config, error = _a.error;

tests/baselines/reference/APISample_watcher.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -166,10 +166,10 @@ function watch(rootFileNames, options) {
166166
function emitFile(fileName) {
167167
var output = services.getEmitOutput(fileName);
168168
if (!output.emitSkipped) {
169-
console.log("Emitting " + fileName);
169+
console.log("Emitting ".concat(fileName));
170170
}
171171
else {
172-
console.log("Emitting " + fileName + " failed");
172+
console.log("Emitting ".concat(fileName, " failed"));
173173
logErrors(fileName);
174174
}
175175
output.outputFiles.forEach(function (o) {
@@ -184,10 +184,10 @@ function watch(rootFileNames, options) {
184184
var message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
185185
if (diagnostic.file) {
186186
var _a = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start), line = _a.line, character = _a.character;
187-
console.log(" Error " + diagnostic.file.fileName + " (" + (line + 1) + "," + (character + 1) + "): " + message);
187+
console.log(" Error ".concat(diagnostic.file.fileName, " (").concat(line + 1, ",").concat(character + 1, "): ").concat(message));
188188
}
189189
else {
190-
console.log(" Error: " + message);
190+
console.log(" Error: ".concat(message));
191191
}
192192
});
193193
}

tests/baselines/reference/TemplateExpression1.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
var v = `foo ${ a
33

44
//// [TemplateExpression1.js]
5-
var v = "foo " + a;
5+
var v = "foo ".concat(a);

tests/baselines/reference/asOperator3.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cook
1515
if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; }
1616
return cooked;
1717
};
18-
var a = "" + (123 + 456);
19-
var b = "leading " + (123 + 456);
20-
var c = 123 + 456 + " trailing";
21-
var d = "Hello " + 123 + " World";
18+
var a = "".concat(123 + 456);
19+
var b = "leading ".concat(123 + 456);
20+
var c = "".concat(123 + 456, " trailing");
21+
var d = "Hello ".concat(123, " World");
2222
var e = "Hello";
23-
var f = 1 + (1 + " end of string");
23+
var f = 1 + "".concat(1, " end of string");
2424
var g = tag(__makeTemplateObject(["Hello ", " World"], ["Hello ", " World"]), 123);
2525
var h = tag(__makeTemplateObject(["Hello"], ["Hello"]));

tests/baselines/reference/checkJsObjectLiteralIndexSignatures.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ stringIndex[s].toFixed();
1616
// @ts-check
1717
var _a, _b;
1818
var n = Math.random();
19-
var s = "" + n;
19+
var s = "".concat(n);
2020
var numericIndex = (_a = {}, _a[n] = 1, _a);
2121
numericIndex[n].toFixed();
2222
var stringIndex = (_b = {}, _b[s] = 1, _b);

tests/baselines/reference/classAttributeInferenceTemplate.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ var MyClass = /** @class */ (function () {
1919
function MyClass() {
2020
var variable = 'something';
2121
this.property = "foo"; // Correctly inferred as `string`
22-
this.property2 = "foo-" + variable; // Causes an error
23-
var localProperty = "foo-" + variable; // Correctly inferred as `string`
22+
this.property2 = "foo-".concat(variable); // Causes an error
23+
var localProperty = "foo-".concat(variable); // Correctly inferred as `string`
2424
}
2525
return MyClass;
2626
}());

tests/baselines/reference/computedPropertyNames10_ES5.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,5 @@ var v = (_a = {},
3232
_a[a] = function () { },
3333
_a[true] = function () { },
3434
_a["hello bye"] = function () { },
35-
_a["hello " + a + " bye"] = function () { },
35+
_a["hello ".concat(a, " bye")] = function () { },
3636
_a);

0 commit comments

Comments
 (0)