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