Skip to content

Commit 9afd5ac

Browse files
lauraharkercopybara-github
authored andcommitted
Inject JSCompiler runtime libraries for use in @closureUnaware transpilation
PiperOrigin-RevId: 851365024
1 parent c338941 commit 9afd5ac

File tree

7 files changed

+479
-20
lines changed

7 files changed

+479
-20
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,8 @@ protected PassListBuilder getTranspileOnlyPasses() {
170170
passes.maybeAdd(gatherGettersAndSetters);
171171

172172
TranspilationPasses.addTranspilationPasses(passes, options);
173+
174+
passes.maybeAdd(markUnnormalized);
173175
// The transpilation passes may rely on normalize making all variables unique,
174176
// but we're doing only transpilation, so we want to put back the original variable names
175177
// wherever we can to meet user expectations.
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/*
2+
* Copyright 2025 The Closure Compiler Authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.javascript.jscomp;
18+
19+
import static com.google.common.base.Preconditions.checkArgument;
20+
import static com.google.common.collect.ImmutableMap.toImmutableMap;
21+
import static java.util.function.Function.identity;
22+
23+
import com.google.common.collect.ImmutableMap;
24+
import com.google.javascript.jscomp.js.RuntimeJsLibManager;
25+
import com.google.javascript.jscomp.js.RuntimeJsLibManager.ExternedField;
26+
import com.google.javascript.rhino.IR;
27+
import com.google.javascript.rhino.Node;
28+
import java.util.LinkedHashSet;
29+
import java.util.List;
30+
import java.util.Set;
31+
32+
/**
33+
* Takes the set of runtime libraries and fields used in a @closureUnaware nested compilation, and
34+
* then 1) ensures the library definitions are injected in the main AST and 2) passes all specific
35+
* $jscomp.* fields used into the @closureUnaware shadow AST as an argument.
36+
*
37+
* <p>Before:
38+
*
39+
* <pre>
40+
* /** @closureUnaware * /
41+
* (function() {
42+
* use($jscomp_someField$$);
43+
* }).call(undefined);
44+
* </pre>
45+
*
46+
* <p>After:
47+
*
48+
* <pre>
49+
* /** @closureUnaware * /
50+
* (function($jscomp_someField$$) {
51+
* use($jscomp_someField$$);
52+
* }).call(undefined, $jscomp.someField);
53+
* </pre>
54+
*/
55+
final class InjectClosureUnawareRuntimeLibraries implements CompilerPass {
56+
private final AbstractCompiler mainCompiler;
57+
private final AbstractCompiler shadowCompiler;
58+
private final List<ClosureUnawareCallSite> closureUnawareCalls;
59+
60+
record ClosureUnawareCallSite(Node callNode, Node closureUnawareFunction) {
61+
ClosureUnawareCallSite {
62+
checkArgument(callNode.isCall(), callNode);
63+
checkArgument(closureUnawareFunction.isFunction(), closureUnawareFunction);
64+
}
65+
}
66+
67+
InjectClosureUnawareRuntimeLibraries(
68+
AbstractCompiler mainCompiler,
69+
AbstractCompiler shadowCompiler,
70+
List<ClosureUnawareCallSite> closureUnawareCalls) {
71+
this.mainCompiler = mainCompiler;
72+
this.shadowCompiler = shadowCompiler;
73+
this.closureUnawareCalls = closureUnawareCalls;
74+
}
75+
76+
@Override
77+
public void process(Node externs, Node root) {
78+
injectLibrariesIntoMainAst();
79+
injectFieldsIntoCallSites();
80+
}
81+
82+
private void injectLibrariesIntoMainAst() {
83+
RuntimeJsLibManager mainManager = mainCompiler.getRuntimeJsLibManager();
84+
RuntimeJsLibManager shadowManager = shadowCompiler.getRuntimeJsLibManager();
85+
for (String runtimeLib : shadowManager.getInjectedLibraries()) {
86+
mainManager.ensureLibraryInjected(runtimeLib, /* force= */ false);
87+
}
88+
}
89+
90+
private void injectFieldsIntoCallSites() {
91+
var fieldsByName =
92+
shadowCompiler.getRuntimeJsLibManager().getExternedFields().stream()
93+
.collect(toImmutableMap(ExternedField::qualifiedName, identity()));
94+
95+
for (ClosureUnawareCallSite callSite : closureUnawareCalls) {
96+
Set<ExternedField> toInject =
97+
gatherRuntimeFields(callSite.closureUnawareFunction(), fieldsByName);
98+
injectAll(callSite, toInject);
99+
}
100+
}
101+
102+
private void injectAll(ClosureUnawareCallSite callSite, Set<ExternedField> toInject) {
103+
if (toInject.isEmpty()) {
104+
return;
105+
}
106+
Node parameterParent = NodeUtil.getFunctionParameters(callSite.closureUnawareFunction());
107+
Node argumentParent = callSite.callNode();
108+
109+
if (parameterParent.hasChildren() && parameterParent.getLastChild().isRest()) {
110+
throw new IllegalStateException(
111+
"rest parameters not allowed in closureUnaware function parameters, found "
112+
+ parameterParent.getLastChild());
113+
}
114+
115+
for (var field : toInject) {
116+
Node parameter = IR.name(field.qualifiedName()).srcref(parameterParent);
117+
Node argument =
118+
NodeUtil.newQName(mainCompiler, field.uncompiledName()).srcrefTree(argumentParent);
119+
120+
parameterParent.addChildToBack(parameter);
121+
shadowCompiler.reportChangeToEnclosingScope(parameterParent);
122+
123+
argumentParent.addChildToBack(argument);
124+
mainCompiler.reportChangeToEnclosingScope(argumentParent);
125+
}
126+
}
127+
128+
/**
129+
* Returns the subset of {@code allInjectedFields} that are actually referenced in the given
130+
* {@code shadowAst}.
131+
*/
132+
private static Set<ExternedField> gatherRuntimeFields(
133+
Node root, ImmutableMap<String, ExternedField> allKnownFields) {
134+
Set<ExternedField> seen = new LinkedHashSet<>();
135+
NodeUtil.Visitor visitor =
136+
(Node n) -> {
137+
if (!n.isName()) {
138+
return;
139+
}
140+
var field = allKnownFields.get(n.getString());
141+
if (field != null) {
142+
seen.add(field);
143+
}
144+
};
145+
NodeUtil.visitPreOrder(root, visitor);
146+
return seen;
147+
}
148+
}

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

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,22 +50,30 @@ final class NestedCompilerRunner {
5050
private final AbstractCompiler original;
5151
private final CompilerOptions shadowOptions;
5252
private final Mode mode;
53+
private final PassConfig shadowPassConfig;
5354

5455
enum Mode {
5556
TRANSPILE_AND_OPTIMIZE,
5657
TRANSPILE_ONLY
5758
}
5859

5960
private NestedCompilerRunner(
60-
AbstractCompiler original, CompilerOptions shadowOptions, Mode mode) {
61+
AbstractCompiler original,
62+
CompilerOptions shadowOptions,
63+
Mode mode,
64+
PassConfig shadowPassConfig) {
6165
this.original = original;
6266
this.shadowOptions = shadowOptions;
6367
this.mode = mode;
68+
this.shadowPassConfig = shadowPassConfig;
6469
}
6570

6671
static NestedCompilerRunner create(
67-
AbstractCompiler original, CompilerOptions shadowOptions, Mode mode) {
68-
return new NestedCompilerRunner(original, shadowOptions, mode);
72+
AbstractCompiler original,
73+
CompilerOptions shadowOptions,
74+
Mode mode,
75+
PassConfig shadowPassConfig) {
76+
return new NestedCompilerRunner(original, shadowOptions, mode, shadowPassConfig);
6977
}
7078

7179
@CanIgnoreReturnValue
@@ -105,6 +113,7 @@ private void initializeCompiler() {
105113

106114
ImmutableList<JSChunk> chunks = createChunks();
107115
shadowCompiler.initChunks(ImmutableList.of(), chunks, shadowOptions);
116+
shadowCompiler.setPassConfig(shadowPassConfig);
108117
shadowCompiler.parseForCompilation();
109118
}
110119

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

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import static com.google.common.base.Preconditions.checkState;
2020

21+
import com.google.common.collect.ImmutableList;
2122
import com.google.common.collect.LinkedHashMultimap;
2223
import com.google.javascript.jscomp.parsing.FeatureCollector;
2324
import com.google.javascript.rhino.Node;
@@ -46,10 +47,13 @@ public void process(Node externs, Node root) {
4647
return;
4748
}
4849

49-
var shadowOptions = ClosureUnawareOptions.convert(original.getOptions());
5050
setScriptFeaturesets(collector.shadowAsts.values());
51+
var shadowOptions = ClosureUnawareOptions.convert(original.getOptions());
52+
var shadowPassConfig =
53+
new RuntimeLibInjectionPassConfig(
54+
shadowOptions, createInjectRuntimeLibrariesPass(collector.shadowAsts.values()));
5155
NestedCompilerRunner shadowCompiler =
52-
NestedCompilerRunner.create(original, shadowOptions, mode);
56+
NestedCompilerRunner.create(original, shadowOptions, mode, shadowPassConfig);
5357

5458
initShadowInputs(shadowCompiler, collector.shadowAsts);
5559
shadowCompiler.compile();
@@ -131,4 +135,45 @@ private void setScriptFeaturesets(Collection<ShadowAst> inputs) {
131135
shadowScript.putProp(Node.FEATURE_SET, collector.allFeatures());
132136
}
133137
}
138+
139+
private static final class RuntimeLibInjectionPassConfig extends PassConfig.PassConfigDelegate {
140+
private final PassFactory injectRuntimeLibs;
141+
142+
private RuntimeLibInjectionPassConfig(CompilerOptions options, PassFactory injectRuntimeLibs) {
143+
super(new DefaultPassConfig(options));
144+
this.injectRuntimeLibs = injectRuntimeLibs;
145+
}
146+
147+
@Override
148+
protected PassListBuilder getTranspileOnlyPasses() {
149+
var passes = super.getTranspileOnlyPasses();
150+
passes.addAfter(injectRuntimeLibs, PassNames.MARK_UNNORMALIZED);
151+
return passes;
152+
}
153+
154+
@Override
155+
protected PassListBuilder getFinalizations() {
156+
var passes = super.getFinalizations();
157+
passes.addAfter(injectRuntimeLibs, PassNames.MARK_UNNORMALIZED);
158+
return passes;
159+
}
160+
}
161+
162+
private PassFactory createInjectRuntimeLibrariesPass(Collection<ShadowAst> shadowAsts) {
163+
ImmutableList.Builder<InjectClosureUnawareRuntimeLibraries.ClosureUnawareCallSite> callSites =
164+
ImmutableList.builder();
165+
for (ShadowAst shadowAst : shadowAsts) {
166+
Node closureUnawareFunction =
167+
NodeUtil.findPreorder(shadowAst.script(), Node::isFunction, (childToVisit) -> true);
168+
callSites.add(
169+
new InjectClosureUnawareRuntimeLibraries.ClosureUnawareCallSite(
170+
shadowAst.callNode(), closureUnawareFunction));
171+
}
172+
return PassFactory.builder()
173+
.setName("injectClosureUnawareRuntimeLibs")
174+
.setInternalFactory(
175+
(compiler) ->
176+
new InjectClosureUnawareRuntimeLibraries(original, compiler, callSites.build()))
177+
.build();
178+
}
134179
}

0 commit comments

Comments
 (0)