diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9174aa3c023..e8c47a468d8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16587,6 +16587,9 @@ namespace ts { } function checkTaggedTemplateExpression(node: TaggedTemplateExpression): Type { + if (languageVersion < ScriptTarget.ES2015) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.GetTemplateObject); + } return getReturnTypeOfSignature(getResolvedSignature(node)); } @@ -23919,6 +23922,7 @@ namespace ts { case ExternalEmitHelpers.AsyncDelegator: return "__asyncDelegator"; case ExternalEmitHelpers.AsyncValues: return "__asyncValues"; case ExternalEmitHelpers.ExportStar: return "__exportStar"; + case ExternalEmitHelpers.GetTemplateObject: return "__getTemplateObject"; default: Debug.fail("Unrecognized helper"); } } diff --git a/src/compiler/transformers/es2015.ts b/src/compiler/transformers/es2015.ts index e9c84be5176..3a21e1fc94f 100644 --- a/src/compiler/transformers/es2015.ts +++ b/src/compiler/transformers/es2015.ts @@ -279,6 +279,16 @@ namespace ts { let currentSourceFile: SourceFile; let currentText: string; let hierarchyFacts: HierarchyFacts; + let taggedTemplateStringDeclarations: VariableDeclaration[]; + function recordTaggedTemplateString(temp: Identifier) { + const decl = createVariableDeclaration(temp); + if (!taggedTemplateStringDeclarations) { + taggedTemplateStringDeclarations = [decl]; + } + else { + taggedTemplateStringDeclarations.push(decl); + } + } /** * Used to track if we are emitting body of the converted loop @@ -307,6 +317,7 @@ namespace ts { currentSourceFile = undefined; currentText = undefined; + taggedTemplateStringDeclarations = undefined; hierarchyFacts = HierarchyFacts.None; return visited; } @@ -520,6 +531,11 @@ namespace ts { addCaptureThisForNodeIfNeeded(statements, node); statementOffset = addCustomPrologue(statements, node.statements, statementOffset, visitor); addRange(statements, visitNodes(node.statements, visitor, isStatement, statementOffset)); + if (taggedTemplateStringDeclarations) { + statements.push( + createVariableStatement(/*modifiers*/ undefined, + createVariableDeclarationList(taggedTemplateStringDeclarations))); + } addRange(statements, endLexicalEnvironment()); exitSubtree(ancestorFacts, HierarchyFacts.None, HierarchyFacts.None); return updateSourceFileNode( @@ -3637,10 +3653,12 @@ namespace ts { const tag = visitNode(node.tag, visitor, isExpression); // Allocate storage for the template site object - const temp = createTempVariable(hoistVariableDeclaration); + const temp = createTempVariable(recordTaggedTemplateString); // Build up the template arguments and the raw and cooked strings for the template. - const templateArguments: Expression[] = [temp]; + // We start out with 'undefined' for the first argument and revisit later + // to avoid walking over the template string twice and shifting all our arguments over after the fact. + const templateArguments: Expression[] = [undefined]; const cookedStrings: Expression[] = []; const rawStrings: Expression[] = []; const template = node.template; @@ -3658,16 +3676,14 @@ namespace ts { } } - // NOTE: The parentheses here is entirely optional as we are now able to auto- - // parenthesize when rebuilding the tree. This should be removed in a - // future version. It is here for now to match our existing emit. - return createParen( - inlineExpressions([ - createAssignment(temp, createArrayLiteral(cookedStrings)), - createAssignment(createPropertyAccess(temp, "raw"), createArrayLiteral(rawStrings)), - createCall(tag, /*typeArguments*/ undefined, templateArguments) - ]) - ); + // Initialize the template object if necessary + templateArguments[0] = createLogicalOr( + temp, + createAssignment( + temp, + createTemplateObjectHelper(context, createArrayLiteral(cookedStrings), createArrayLiteral(rawStrings)))); + + return createCall(tag, /*typeArguments*/ undefined, templateArguments); } /** @@ -4036,6 +4052,18 @@ namespace ts { ); } + function createTemplateObjectHelper(context: TransformationContext, cooked: ArrayLiteralExpression, raw: ArrayLiteralExpression) { + context.requestEmitHelper(templateObjectHelper); + return createCall( + getHelperName("__getTemplateObject"), + /*typeArguments*/ undefined, + [ + cooked, + raw + ] + ); + } + const extendsHelper: EmitHelper = { name: "typescript:extends", scoped: false, @@ -4052,4 +4080,19 @@ namespace ts { }; })();` }; + + const templateObjectHelper: EmitHelper = { + name: "typescript:getTemplateObject", + scoped: false, + priority: 0, + text: ` + var __getTemplateObject = (this && this.__getTemplateObject) || function (cooked, raw) { + if (Object.freeze && Object.defineProperty) { + return Object.freeze(Object.defineProperty(cooked, "raw", { value: Object.freeze(raw) })); + } + cooked.raw = raw; + return cooked; + };` + }; + } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 0d9bb8d50e8..b22638789c1 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4217,22 +4217,23 @@ namespace ts { */ /* @internal */ export const enum ExternalEmitHelpers { - Extends = 1 << 0, // __extends (used by the ES2015 class transformation) - Assign = 1 << 1, // __assign (used by Jsx and ESNext object spread transformations) - Rest = 1 << 2, // __rest (used by ESNext object rest transformation) - Decorate = 1 << 3, // __decorate (used by TypeScript decorators transformation) - Metadata = 1 << 4, // __metadata (used by TypeScript decorators transformation) - Param = 1 << 5, // __param (used by TypeScript decorators transformation) - Awaiter = 1 << 6, // __awaiter (used by ES2017 async functions transformation) - Generator = 1 << 7, // __generator (used by ES2015 generator transformation) - Values = 1 << 8, // __values (used by ES2015 for..of and yield* transformations) - Read = 1 << 9, // __read (used by ES2015 iterator destructuring transformation) - Spread = 1 << 10, // __spread (used by ES2015 array spread and argument list spread transformations) - Await = 1 << 11, // __await (used by ES2017 async generator transformation) - AsyncGenerator = 1 << 12, // __asyncGenerator (used by ES2017 async generator transformation) - AsyncDelegator = 1 << 13, // __asyncDelegator (used by ES2017 async generator yield* transformation) - AsyncValues = 1 << 14, // __asyncValues (used by ES2017 for..await..of transformation) - ExportStar = 1 << 15, // __exportStar (used by CommonJS/AMD/UMD module transformation) + Extends = 1 << 0, // __extends (used by the ES2015 class transformation) + Assign = 1 << 1, // __assign (used by Jsx and ESNext object spread transformations) + Rest = 1 << 2, // __rest (used by ESNext object rest transformation) + Decorate = 1 << 3, // __decorate (used by TypeScript decorators transformation) + Metadata = 1 << 4, // __metadata (used by TypeScript decorators transformation) + Param = 1 << 5, // __param (used by TypeScript decorators transformation) + Awaiter = 1 << 6, // __awaiter (used by ES2017 async functions transformation) + Generator = 1 << 7, // __generator (used by ES2015 generator transformation) + Values = 1 << 8, // __values (used by ES2015 for..of and yield* transformations) + Read = 1 << 9, // __read (used by ES2015 iterator destructuring transformation) + Spread = 1 << 10, // __spread (used by ES2015 array spread and argument list spread transformations) + Await = 1 << 11, // __await (used by ES2017 async generator transformation) + AsyncGenerator = 1 << 12, // __asyncGenerator (used by ES2017 async generator transformation) + AsyncDelegator = 1 << 13, // __asyncDelegator (used by ES2017 async generator yield* transformation) + AsyncValues = 1 << 14, // __asyncValues (used by ES2017 for..await..of transformation) + ExportStar = 1 << 15, // __exportStar (used by CommonJS/AMD/UMD module transformation) + GetTemplateObject = 1 << 16, // __getTemplateObject (used for constructing template string array objects) // Helpers included by ES2015 for..of ForOfIncludes = Values,