• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*@internal*/
2namespace ts {
3    export enum ProcessLevel {
4        LiftRestriction,
5        All
6    }
7
8    export function processTaggedTemplateExpression(
9        context: TransformationContext,
10        node: TaggedTemplateExpression,
11        visitor: Visitor,
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)) {
28            return visitEachChild(node, visitor, context);
29        }
30
31        if (isNoSubstitutionTemplateLiteral(template)) {
32            cookedStrings.push(createTemplateCooked(template));
33            rawStrings.push(getRawLiteral(template, currentSourceFile));
34        }
35        else {
36            cookedStrings.push(createTemplateCooked(template.head));
37            rawStrings.push(getRawLiteral(template.head, currentSourceFile));
38            for (const templateSpan of template.templateSpans) {
39                cookedStrings.push(createTemplateCooked(templateSpan.literal));
40                rawStrings.push(getRawLiteral(templateSpan.literal, currentSourceFile));
41                templateArguments.push(visitNode(templateSpan.expression, visitor, isExpression));
42            }
43        }
44
45        const helperCall = context.getEmitHelperFactory().createTemplateObjectHelper(
46            factory.createArrayLiteralExpression(cookedStrings),
47            factory.createArrayLiteralExpression(rawStrings));
48
49        // Create a variable to cache the template object if we're in a module.
50        // Do not do this in the global scope, as any variable we currently generate could conflict with
51        // variables from outside of the current compilation. In the future, we can revisit this behavior.
52        if (isExternalModule(currentSourceFile)) {
53            const tempVar = factory.createUniqueName("templateObject");
54            recordTaggedTemplateString(tempVar);
55            templateArguments[0] = factory.createLogicalOr(
56                tempVar,
57                factory.createAssignment(
58                    tempVar,
59                    helperCall)
60            );
61        }
62        else {
63            templateArguments[0] = helperCall;
64        }
65
66        return factory.createCallExpression(tag, /*typeArguments*/ undefined, templateArguments);
67    }
68
69    function createTemplateCooked(template: TemplateHead | TemplateMiddle | TemplateTail | NoSubstitutionTemplateLiteral) {
70        return template.templateFlags ? factory.createVoidZero() : factory.createStringLiteral(template.text);
71    }
72
73    /**
74     * Creates an ES5 compatible literal from an ES6 template literal.
75     *
76     * @param node The ES6 template literal.
77     */
78    function getRawLiteral(node: TemplateLiteralLikeNode, currentSourceFile: SourceFile) {
79        // Find original source text, since we need to emit the raw strings of the tagged template.
80        // The raw strings contain the (escaped) strings of what the user wrote.
81        // Examples: `\n` is converted to "\\n", a template string with a newline to "\n".
82        let text = node.rawText;
83        if (text === undefined) {
84            text = getSourceTextOfNodeFromSourceFile(currentSourceFile, node);
85
86            // text contains the original source, it will also contain quotes ("`"), dolar signs and braces ("${" and "}"),
87            // thus we need to remove those characters.
88            // First template piece starts with "`", others with "}"
89            // Last template piece ends with "`", others with "${"
90            const isLast = node.kind === SyntaxKind.NoSubstitutionTemplateLiteral || node.kind === SyntaxKind.TemplateTail;
91            text = text.substring(1, text.length - (isLast ? 1 : 2));
92        }
93
94        // Newline normalization:
95        // ES6 Spec 11.8.6.1 - Static Semantics of TV's and TRV's
96        // <CR><LF> and <CR> LineTerminatorSequences are normalized to <LF> for both TV and TRV.
97        text = text.replace(/\r\n?/g, "\n");
98        return setTextRange(factory.createStringLiteral(text), node);
99    }
100}
101