Skip to content

Commit b941efa

Browse files
lauraharkercopybara-github
authored andcommitted
Make tagged template literal transpilation work in @closureUnaware mode
PiperOrigin-RevId: 851365723
1 parent 9afd5ac commit b941efa

File tree

3 files changed

+70
-2
lines changed

3 files changed

+70
-2
lines changed

src/com/google/javascript/jscomp/LateEs6ToEs3Converter.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,24 @@ public void process(Node externs, Node root) {
7676
@Override
7777
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
7878
if (n.isScript()) {
79-
// For all scripts, initialize the injection point to be top of the script
80-
templateLitInsertionPoint = n.getFirstChild();
79+
templateLitInsertionPoint = findTemplateLitInsertionPoint(n);
8180
}
8281
return true;
8382
}
8483

84+
private static Node findTemplateLitInsertionPoint(Node script) {
85+
if (script.getIsInClosureUnawareSubtree()) {
86+
// For all closure-unaware scripts, initialize the injection point within the closure
87+
// aware function.
88+
Node closureUnawareBlock = NodeUtil.findClosureUnawareScriptRoot(script);
89+
return NodeUtil.getInsertionPointAfterAllInnerFunctionDeclarations(closureUnawareBlock);
90+
}
91+
// For all other scripts, initialize the injection point to be top of the script. The spec
92+
// requires a single unique array per template literal, so for a template literal in a function
93+
// e.g. it would be incorrect to initialize a new array per every function call.
94+
return script.getFirstChild();
95+
}
96+
8597
@Override
8698
public void visit(NodeTraversal t, Node n, Node parent) {
8799
switch (n.getToken()) {

src/com/google/javascript/jscomp/NodeUtil.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -821,6 +821,22 @@ public static boolean isLiteralValue(Node n, boolean includeFunctions) {
821821
}
822822
}
823823

824+
/**
825+
* Returns the closureUnaware function body within a script modelling a shadow AST
826+
*
827+
* <p>TranspileAndOptimizeClosureUnaware pass models each @closureUnaware block as a function
828+
* body, and will throw a runtime exception if any pass inserts new code outside that function
829+
* body. So we sometimes need to map from an actual script node to the
830+
* corresponding @closureUnaware script-esque block.
831+
*
832+
* <p>TODO: lharker - if we end up needing this special logic often, consider instead making
833+
* TranspileAndOptimizeclosureUnaware support inserting code at the script level.
834+
*/
835+
static Node findClosureUnawareScriptRoot(Node script) {
836+
checkArgument(script.isScript() && script.getIsInClosureUnawareSubtree(), script);
837+
return NodeUtil.findPreorder(script, Node::isBlock, (c) -> true);
838+
}
839+
824840
/**
825841
* Returns the node within the given block at which something can be inserted such that it's after
826842
* all inner function declarations in that block. We want this because normalization expects all

test/com/google/javascript/jscomp/TranspileAndOptimizeClosureUnawareTest.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,46 @@ public void doesNotAutomaticallyInjectPolyfills() {
455455
"""));
456456
}
457457

458+
@Test
459+
public void taggedTemplateLitTranspilation() {
460+
extraOptions = (options) -> options.setGeneratePseudoNames(true);
461+
setLanguageOut(CompilerOptions.LanguageMode.ECMASCRIPT5);
462+
463+
test(
464+
srcs(
465+
closureUnaware(
466+
"""
467+
function tag(strings, ...values) {
468+
return {strings, values};
469+
}
470+
tag`a${42}b`;
471+
tag`a${42}b`; // identical template lit
472+
tag`a${-42}b`; // identical template strings, different substitution content
473+
""")),
474+
expected(
475+
"""
476+
/** @fileoverview @closureUnaware */
477+
goog.module('test0');
478+
/** @closureUnaware */
479+
(function($$jscomp_getRestArguments$$, $$jscomp_createTemplateTagFirstArg$$) {
480+
function $tag$$($strings$$) {
481+
var $values$$ = $$jscomp_getRestArguments$$.apply(1, arguments);
482+
return {strings:$strings$$, values:$values$$};
483+
}
484+
/** @noinline */
485+
var $$jscomp$templatelit$m854121055$0$$ =
486+
$$jscomp_createTemplateTagFirstArg$$(["a", "b"]),
487+
$$jscomp$templatelit$m854121055$1$$ =
488+
$$jscomp_createTemplateTagFirstArg$$(["a", "b"]),
489+
$$jscomp$templatelit$m854121055$2$$ =
490+
$$jscomp_createTemplateTagFirstArg$$(["a", "b"]);
491+
$tag$$($$jscomp$templatelit$m854121055$0$$, 42);
492+
$tag$$($$jscomp$templatelit$m854121055$1$$, 42);
493+
$tag$$($$jscomp$templatelit$m854121055$2$$, -42);
494+
}).call(undefined, $jscomp.getRestArguments, $jscomp.createTemplateTagFirstArg);
495+
"""));
496+
}
497+
458498
private static String loadFile(Path path) {
459499
try (Stream<String> lines = Files.lines(path)) {
460500
return lines.collect(joining("\n"));

0 commit comments

Comments
 (0)