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