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+ }
0 commit comments