Skip to content

Commit e92b6bc

Browse files
committed
extract tagged template and add more test
1 parent c642abe commit e92b6bc

20 files changed

+834
-96
lines changed

src/compiler/binder.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3523,8 +3523,18 @@ namespace ts {
35233523
case SyntaxKind.TemplateHead:
35243524
case SyntaxKind.TemplateMiddle:
35253525
case SyntaxKind.TemplateTail:
3526-
case SyntaxKind.TemplateExpression:
3526+
if ((<NoSubstitutionTemplateLiteral | TemplateHead | TemplateMiddle | TemplateTail>node).templateFlags) {
3527+
transformFlags |= TransformFlags.AssertESNext;
3528+
break;
3529+
}
3530+
// falls through
35273531
case SyntaxKind.TaggedTemplateExpression:
3532+
if (hasInvalidEscape((<TaggedTemplateExpression>node).template)) {
3533+
transformFlags |= TransformFlags.AssertESNext;
3534+
break;
3535+
}
3536+
// falls through
3537+
case SyntaxKind.TemplateExpression:
35283538
case SyntaxKind.ShorthandPropertyAssignment:
35293539
case SyntaxKind.StaticKeyword:
35303540
case SyntaxKind.MetaProperty:

src/compiler/transformers/es2015.ts

Lines changed: 8 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -3643,75 +3643,14 @@ namespace ts {
36433643
* @param node A TaggedTemplateExpression node.
36443644
*/
36453645
function visitTaggedTemplateExpression(node: TaggedTemplateExpression) {
3646-
// Visit the tag expression
3647-
const tag = visitNode(node.tag, visitor, isExpression);
3648-
3649-
// Build up the template arguments and the raw and cooked strings for the template.
3650-
// We start out with 'undefined' for the first argument and revisit later
3651-
// to avoid walking over the template string twice and shifting all our arguments over after the fact.
3652-
const templateArguments: Expression[] = [undefined!];
3653-
const cookedStrings: Expression[] = [];
3654-
const rawStrings: Expression[] = [];
3655-
const template = node.template;
3656-
if (isNoSubstitutionTemplateLiteral(template)) {
3657-
cookedStrings.push(template.templateFlags ? createIdentifier("undefined") : createLiteral(template.text));
3658-
rawStrings.push(getRawLiteral(template));
3659-
}
3660-
else {
3661-
cookedStrings.push(template.head.templateFlags ? createIdentifier("undefined") : createLiteral(template.head.text));
3662-
rawStrings.push(getRawLiteral(template.head));
3663-
for (const templateSpan of template.templateSpans) {
3664-
cookedStrings.push(templateSpan.literal.templateFlags ? createIdentifier("undefined") : createLiteral(templateSpan.literal.text));
3665-
rawStrings.push(getRawLiteral(templateSpan.literal));
3666-
templateArguments.push(visitNode(templateSpan.expression, visitor, isExpression));
3667-
}
3668-
}
3669-
3670-
const helperCall = createTemplateObjectHelper(context, createArrayLiteral(cookedStrings), createArrayLiteral(rawStrings));
3671-
3672-
// Create a variable to cache the template object if we're in a module.
3673-
// Do not do this in the global scope, as any variable we currently generate could conflict with
3674-
// variables from outside of the current compilation. In the future, we can revisit this behavior.
3675-
if (isExternalModule(currentSourceFile)) {
3676-
const tempVar = createUniqueName("templateObject");
3677-
recordTaggedTemplateString(tempVar);
3678-
templateArguments[0] = createLogicalOr(
3679-
tempVar,
3680-
createAssignment(
3681-
tempVar,
3682-
helperCall)
3683-
);
3684-
}
3685-
else {
3686-
templateArguments[0] = helperCall;
3687-
}
3688-
3689-
return createCall(tag, /*typeArguments*/ undefined, templateArguments);
3690-
}
3691-
3692-
/**
3693-
* Creates an ES5 compatible literal from an ES6 template literal.
3694-
*
3695-
* @param node The ES6 template literal.
3696-
*/
3697-
function getRawLiteral(node: LiteralLikeNode) {
3698-
// Find original source text, since we need to emit the raw strings of the tagged template.
3699-
// The raw strings contain the (escaped) strings of what the user wrote.
3700-
// Examples: `\n` is converted to "\\n", a template string with a newline to "\n".
3701-
let text = getSourceTextOfNodeFromSourceFile(currentSourceFile, node);
3702-
3703-
// text contains the original source, it will also contain quotes ("`"), dolar signs and braces ("${" and "}"),
3704-
// thus we need to remove those characters.
3705-
// First template piece starts with "`", others with "}"
3706-
// Last template piece ends with "`", others with "${"
3707-
const isLast = node.kind === SyntaxKind.NoSubstitutionTemplateLiteral || node.kind === SyntaxKind.TemplateTail;
3708-
text = text.substring(1, text.length - (isLast ? 1 : 2));
3709-
3710-
// Newline normalization:
3711-
// ES6 Spec 11.8.6.1 - Static Semantics of TV's and TRV's
3712-
// <CR><LF> and <CR> LineTerminatorSequences are normalized to <LF> for both TV and TRV.
3713-
text = text.replace(/\r\n?/g, "\n");
3714-
return setTextRange(createLiteral(text), node);
3646+
return processTaggedTemplateExpression(
3647+
context,
3648+
node,
3649+
visitor,
3650+
currentSourceFile,
3651+
recordTaggedTemplateString,
3652+
ProcessLevel.All
3653+
);
37153654
}
37163655

37173656
/**
@@ -4054,18 +3993,6 @@ namespace ts {
40543993
);
40553994
}
40563995

4057-
function createTemplateObjectHelper(context: TransformationContext, cooked: ArrayLiteralExpression, raw: ArrayLiteralExpression) {
4058-
context.requestEmitHelper(templateObjectHelper);
4059-
return createCall(
4060-
getHelperName("__makeTemplateObject"),
4061-
/*typeArguments*/ undefined,
4062-
[
4063-
cooked,
4064-
raw
4065-
]
4066-
);
4067-
}
4068-
40693996
const extendsHelper: EmitHelper = {
40703997
name: "typescript:extends",
40713998
scoped: false,
@@ -4082,16 +4009,4 @@ namespace ts {
40824009
};
40834010
})();`
40844011
};
4085-
4086-
const templateObjectHelper: EmitHelper = {
4087-
name: "typescript:makeTemplateObject",
4088-
scoped: false,
4089-
priority: 0,
4090-
text: `
4091-
var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cooked, raw) {
4092-
if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; }
4093-
return cooked;
4094-
};`
4095-
};
4096-
40974012
}

src/compiler/transformers/esnext.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,27 @@ namespace ts {
2525
let enabledSubstitutions: ESNextSubstitutionFlags;
2626
let enclosingFunctionFlags: FunctionFlags;
2727
let enclosingSuperContainerFlags: NodeCheckFlags = 0;
28+
let currentSourceFile: SourceFile;
29+
let taggedTemplateStringDeclarations: VariableDeclaration[];
2830

2931
return chainBundle(transformSourceFile);
3032

33+
function recordTaggedTemplateString(temp: Identifier) {
34+
taggedTemplateStringDeclarations = append(
35+
taggedTemplateStringDeclarations,
36+
createVariableDeclaration(temp));
37+
}
38+
3139
function transformSourceFile(node: SourceFile) {
3240
if (node.isDeclarationFile) {
3341
return node;
3442
}
35-
36-
const visited = visitEachChild(node, visitor, context);
43+
currentSourceFile = node;
44+
const visited = visitSourceFile(node);
3745
addEmitHelpers(visited, context.readEmitHelpers());
46+
47+
currentSourceFile = undefined!;
48+
taggedTemplateStringDeclarations = undefined!;
3849
return visited;
3950
}
4051

@@ -99,6 +110,8 @@ namespace ts {
99110
return visitParenthesizedExpression(node as ParenthesizedExpression, noDestructuringValue);
100111
case SyntaxKind.CatchClause:
101112
return visitCatchClause(node as CatchClause);
113+
case SyntaxKind.TaggedTemplateExpression:
114+
return visitTaggedTemplateExpression(node as TaggedTemplateExpression);
102115
default:
103116
return visitEachChild(node, visitor, context);
104117
}
@@ -637,6 +650,26 @@ namespace ts {
637650
return updated;
638651
}
639652

653+
function visitSourceFile(node: SourceFile): SourceFile {
654+
const visited = visitEachChild(node, visitor, context);
655+
const statement = concatenate(visited.statements, taggedTemplateStringDeclarations && [
656+
createVariableStatement(/*modifiers*/ undefined,
657+
createVariableDeclarationList(taggedTemplateStringDeclarations))
658+
]);
659+
return updateSourceFileNode(visited, setTextRange(createNodeArray(statement), node.statements));
660+
}
661+
662+
function visitTaggedTemplateExpression (node: TaggedTemplateExpression) {
663+
return processTaggedTemplateExpression(
664+
context,
665+
node,
666+
visitor,
667+
currentSourceFile,
668+
recordTaggedTemplateString,
669+
ProcessLevel.LiftRestriction
670+
);
671+
}
672+
640673
function transformAsyncGeneratorFunctionBody(node: MethodDeclaration | AccessorDeclaration | FunctionDeclaration | FunctionExpression): FunctionBody {
641674
resumeLexicalEnvironment();
642675
const statements: Statement[] = [];
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/*@internal*/
2+
namespace ts {
3+
export enum ProcessLevel {
4+
LiftRestriction,
5+
All
6+
}
7+
8+
export function processTaggedTemplateExpression(
9+
context: TransformationContext,
10+
node: TaggedTemplateExpression,
11+
visitor: ((node: Node) => VisitResult<Node>) | undefined,
12+
currentSourceFile: SourceFile,
13+
recordTaggedTemplateString: (temp: Identifier) => void,
14+
level: ProcessLevel) {
15+
16+
// Visit the tag expression
17+
const tag = visitNode(node.tag, visitor, isExpression);
18+
19+
// Build up the template arguments and the raw and cooked strings for the template.
20+
// We start out with 'undefined' for the first argument and revisit later
21+
// to avoid walking over the template string twice and shifting all our arguments over after the fact.
22+
const templateArguments: Expression[] = [undefined!];
23+
const cookedStrings: Expression[] = [];
24+
const rawStrings: Expression[] = [];
25+
const template = node.template;
26+
27+
if (level === ProcessLevel.LiftRestriction && !hasInvalidEscape(template)) return tag;
28+
29+
if (isNoSubstitutionTemplateLiteral(template)) {
30+
cookedStrings.push(createTemplateCooked(template));
31+
rawStrings.push(getRawLiteral(template, currentSourceFile));
32+
}
33+
else {
34+
cookedStrings.push(createTemplateCooked(template.head));
35+
rawStrings.push(getRawLiteral(template.head, currentSourceFile));
36+
for (const templateSpan of template.templateSpans) {
37+
cookedStrings.push(createTemplateCooked(templateSpan.literal));
38+
rawStrings.push(getRawLiteral(templateSpan.literal, currentSourceFile));
39+
templateArguments.push(visitNode(templateSpan.expression, visitor, isExpression));
40+
}
41+
}
42+
43+
const helperCall = createTemplateObjectHelper(context, createArrayLiteral(cookedStrings), createArrayLiteral(rawStrings));
44+
45+
// Create a variable to cache the template object if we're in a module.
46+
// Do not do this in the global scope, as any variable we currently generate could conflict with
47+
// variables from outside of the current compilation. In the future, we can revisit this behavior.
48+
if (isExternalModule(currentSourceFile)) {
49+
const tempVar = createUniqueName("templateObject");
50+
recordTaggedTemplateString(tempVar);
51+
templateArguments[0] = createLogicalOr(
52+
tempVar,
53+
createAssignment(
54+
tempVar,
55+
helperCall)
56+
);
57+
}
58+
else {
59+
templateArguments[0] = helperCall;
60+
}
61+
62+
return createCall(tag, /*typeArguments*/ undefined, templateArguments);
63+
}
64+
65+
function createTemplateCooked (template: TemplateHead | TemplateMiddle | TemplateTail | NoSubstitutionTemplateLiteral) {
66+
return template.templateFlags ? createIdentifier("undefined") : createLiteral(template.text);
67+
}
68+
69+
/**
70+
* Creates an ES5 compatible literal from an ES6 template literal.
71+
*
72+
* @param node The ES6 template literal.
73+
*/
74+
function getRawLiteral(node: LiteralLikeNode, currentSourceFile: SourceFile) {
75+
// Find original source text, since we need to emit the raw strings of the tagged template.
76+
// The raw strings contain the (escaped) strings of what the user wrote.
77+
// Examples: `\n` is converted to "\\n", a template string with a newline to "\n".
78+
let text = getSourceTextOfNodeFromSourceFile(currentSourceFile, node);
79+
80+
// text contains the original source, it will also contain quotes ("`"), dolar signs and braces ("${" and "}"),
81+
// thus we need to remove those characters.
82+
// First template piece starts with "`", others with "}"
83+
// Last template piece ends with "`", others with "${"
84+
const isLast = node.kind === SyntaxKind.NoSubstitutionTemplateLiteral || node.kind === SyntaxKind.TemplateTail;
85+
text = text.substring(1, text.length - (isLast ? 1 : 2));
86+
87+
// Newline normalization:
88+
// ES6 Spec 11.8.6.1 - Static Semantics of TV's and TRV's
89+
// <CR><LF> and <CR> LineTerminatorSequences are normalized to <LF> for both TV and TRV.
90+
text = text.replace(/\r\n?/g, "\n");
91+
return setTextRange(createLiteral(text), node);
92+
}
93+
94+
function createTemplateObjectHelper(context: TransformationContext, cooked: ArrayLiteralExpression, raw: ArrayLiteralExpression) {
95+
context.requestEmitHelper(templateObjectHelper);
96+
return createCall(
97+
getHelperName("__makeTemplateObject"),
98+
/*typeArguments*/ undefined,
99+
[
100+
cooked,
101+
raw
102+
]
103+
);
104+
}
105+
106+
const templateObjectHelper: EmitHelper = {
107+
name: "typescript:makeTemplateObject",
108+
scoped: false,
109+
priority: 0,
110+
text: `
111+
var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cooked, raw) {
112+
if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; }
113+
return cooked;
114+
};`
115+
};
116+
}

src/compiler/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"visitor.ts",
2323
"transformers/utilities.ts",
2424
"transformers/destructuring.ts",
25+
"transformers/taggedTemplate.ts",
2526
"transformers/ts.ts",
2627
"transformers/es2017.ts",
2728
"transformers/esnext.ts",

src/compiler/utilities.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6433,4 +6433,11 @@ namespace ts {
64336433
export function isNamedImportsOrExports(node: Node): node is NamedImportsOrExports {
64346434
return node.kind === SyntaxKind.NamedImports || node.kind === SyntaxKind.NamedExports;
64356435
}
6436+
6437+
/** @internal */
6438+
export function hasInvalidEscape(template: TemplateLiteral): boolean {
6439+
return template && !!(isNoSubstitutionTemplateLiteral(template)
6440+
? template.templateFlags
6441+
: (template.head.templateFlags || some(template.templateSpans, span => !!span.literal.templateFlags)));
6442+
}
64366443
}

src/harness/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"../compiler/visitor.ts",
3030
"../compiler/transformers/utilities.ts",
3131
"../compiler/transformers/destructuring.ts",
32+
"../compiler/transformers/taggedTemplate.ts",
3233
"../compiler/transformers/ts.ts",
3334
"../compiler/transformers/es2017.ts",
3435
"../compiler/transformers/esnext.ts",

src/server/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"../compiler/visitor.ts",
2626
"../compiler/transformers/utilities.ts",
2727
"../compiler/transformers/destructuring.ts",
28+
"../compiler/transformers/taggedTemplate.ts",
2829
"../compiler/transformers/ts.ts",
2930
"../compiler/transformers/es2017.ts",
3031
"../compiler/transformers/esnext.ts",

src/server/tsconfig.library.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"../compiler/visitor.ts",
3232
"../compiler/transformers/utilities.ts",
3333
"../compiler/transformers/destructuring.ts",
34+
"../compiler/transformers/taggedTemplate.ts",
3435
"../compiler/transformers/ts.ts",
3536
"../compiler/transformers/es2017.ts",
3637
"../compiler/transformers/esnext.ts",

src/services/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"../compiler/visitor.ts",
2323
"../compiler/transformers/utilities.ts",
2424
"../compiler/transformers/destructuring.ts",
25+
"../compiler/transformers/taggedTemplate.ts",
2526
"../compiler/transformers/ts.ts",
2627
"../compiler/transformers/es2017.ts",
2728
"../compiler/transformers/esnext.ts",

0 commit comments

Comments
 (0)