Skip to content

Commit

Permalink
Only record inline toggles that are actually used.
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 561118170
  • Loading branch information
shicks authored and copybara-github committed Aug 29, 2023
1 parent 3b72456 commit 92557f3
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 21 deletions.
82 changes: 65 additions & 17 deletions src/com/google/javascript/jscomp/GatherModuleMetadata.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.QualifiedName;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import org.jspecify.nullness.Nullable;

Expand Down Expand Up @@ -81,10 +83,8 @@ public final class GatherModuleMetadata implements CompilerPass {
static final DiagnosticType INVALID_NESTED_LOAD_MODULE =
DiagnosticType.error("JSC_INVALID_NESTED_LOAD_MODULE", "goog.loadModule cannot be nested.");

static final DiagnosticType INVALID_READ_TOGGLE =
DiagnosticType.error(
"JSC_INVALID_READ_TOGGLE",
"Argument to goog.readToggleInternalDoNotCallDirectly must be a string.");
static final DiagnosticType INVALID_TOGGLE_USAGE =
DiagnosticType.error("JSC_INVALID_TOGGLE_USAGE", "Invalid toggle usage: {0}");

private static final Node GOOG_PROVIDE = IR.getprop(IR.name("goog"), "provide");
private static final Node GOOG_MODULE = IR.getprop(IR.name("goog"), "module");
Expand All @@ -95,13 +95,13 @@ public final class GatherModuleMetadata implements CompilerPass {
private static final Node GOOG_MODULE_DECLARELEGACYNAMESPACE =
IR.getprop(GOOG_MODULE.cloneTree(), "declareLegacyNamespace");
private static final Node GOOG_DECLARE_MODULE_ID = IR.getprop(IR.name("goog"), "declareModuleId");
private static final Node GOOG_READ_TOGGLE =
IR.getprop(IR.name("goog"), "readToggleInternalDoNotCallDirectly");

// TODO(johnplaisted): Remove once clients have migrated to declareModuleId
private static final Node GOOG_MODULE_DECLARNAMESPACE =
IR.getprop(GOOG_MODULE.cloneTree(), "declareNamespace");

private static final String TOGGLE_NAME_PREFIX = "TOGGLE_";

/**
* Map from module path to module. These modules represent files and thus will contain all goog
* namespaces that are in the file. These are not the same modules in modulesByGoogNamespace.
Expand Down Expand Up @@ -216,6 +216,14 @@ ModuleMetadata build() {

/** Traverses the AST and build a sets of {@link ModuleMetadata}s. */
private final class Finder implements NodeTraversal.Callback {

// Store both names and vars. Strings alone is insufficient to determine whether a name is
// actually a toggle module (since it could have been shadowed, or may have been defined in a
// different file), but looking up by only vars is much slower. This way we can do a fast name
// lookup, followed by a slower var lookup only if the name is known to be a toggle module name.
final Set<String> toggleModuleNames = new HashSet<>();
final Set<Var> toggleModules = new HashSet<>();

@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
switch (n.getToken()) {
Expand Down Expand Up @@ -337,7 +345,22 @@ private boolean isFromGoogImport(Var goog) {
}

private void visitName(NodeTraversal t, Node n) {
if (!"goog".equals(n.getString())) {
String name = n.getString();
if (toggleModuleNames.contains(name)) {
Var nameVar = t.getScope().getVar(name);
if (toggleModules.contains(nameVar)) {
Node parent = n.getParent();
if (parent.isGetProp()) {
addToggle(t, n, parent.getString());
} else if (!NodeUtil.isNameDeclaration(parent)) {
t.report(
n,
INVALID_TOGGLE_USAGE,
"toggle modules may not be used other than looking up properties");
}
}
}
if (!"goog".equals(name)) {
return;
}

Expand Down Expand Up @@ -421,10 +444,31 @@ private void visitGoogCall(NodeTraversal t, Node n) {
}
} else if (getprop.matchesQualifiedName(GOOG_REQUIRE)) {
if (n.hasTwoChildren() && n.getLastChild().isStringLit()) {
currentModule
.metadataBuilder
.stronglyRequiredGoogNamespacesBuilder()
.add(n.getLastChild().getString());
String namespace = n.getLastChild().getString();
currentModule.metadataBuilder.stronglyRequiredGoogNamespacesBuilder().add(namespace);
if (namespace.endsWith("$2etoggles")) {
// Track imports of *.toggles.ts, which are rewritten to $2etoggles.
Node callParent = n.getParent();
Node lhs = callParent.getFirstChild();
if (callParent.isDestructuringLhs()) {
// const {TOGGLE_foo} = goog.require('foo$2etoggles');
for (Node key : lhs.children()) {
if (key.isStringKey()) {
addToggle(t, n, key.getString());
} else {
t.report(n, INVALID_TOGGLE_USAGE, "must be destructured with string keys");
}
}
} else if (callParent.isName()) {
// const fooToggles = goog.require('foo$2etoggles');
String name = callParent.getString();
Var nameVar = t.getScope().getVar(name);
toggleModules.add(nameVar);
toggleModuleNames.add(name);
} else {
t.report(n, INVALID_TOGGLE_USAGE, "import must be assigned");
}
}
} else {
t.report(n, INVALID_REQUIRE_NAMESPACE);
}
Expand Down Expand Up @@ -452,12 +496,16 @@ private void visitGoogCall(NodeTraversal t, Node n) {
} else {
t.report(n, INVALID_REQUIRE_DYNAMIC);
}
} else if (getprop.matchesQualifiedName(GOOG_READ_TOGGLE)) {
if (n.hasTwoChildren() && n.getLastChild().isStringLit()) {
currentModule.metadataBuilder.readTogglesBuilder().add(n.getLastChild().getString());
} else {
t.report(n, INVALID_READ_TOGGLE);
}
}
}

/** Record a toggle usage (either a destructured import or a property lookup on a module). */
private void addToggle(NodeTraversal t, Node n, String name) {
if (name.startsWith(TOGGLE_NAME_PREFIX)) {
String toggleName = name.substring(TOGGLE_NAME_PREFIX.length());
currentModule.metadataBuilder.readTogglesBuilder().add(toggleName);
} else {
t.report(n, INVALID_TOGGLE_USAGE, "all toggle names must start with `TOGGLE_`");
}
}

Expand Down
86 changes: 83 additions & 3 deletions test/com/google/javascript/jscomp/GatherModuleMetadataTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -737,11 +737,91 @@ public void testDynamicImport() {

@Test
public void testReadToggle() {
test(
srcs(
SourceFile.fromCode(
"foo$2etoggles.js",
lines(
"goog.module('foo$2etoggles');",
"exports.TOGGLE_foo = goog.readToggleInternalDoNotCallDirectly('foo');",
"exports.TOGGLE_bar = goog.readToggleInternalDoNotCallDirectly('bar');",
"exports.TOGGLE_b_a_z = goog.readToggleInternalDoNotCallDirectly('b_a_z');"))));
ModuleMetadata m = metadataMap().getModulesByPath().get("foo$2etoggles.js");
assertThat(m.readToggles()).isEmpty();
}

@Test
public void testToggleModuleImportDestructured() {
testSame("const {TOGGLE_foo, TOGGLE_b_a_z} = goog.require('foo$2etoggles');");
ModuleMetadata m = metadataMap().getModulesByPath().get("testcode");
assertThat(m.readToggles()).containsExactly("foo", "b_a_z");
}

@Test
public void testToggleModuleImportDestructuredAliased() {
testSame("const {TOGGLE_bar: baz} = goog.require('foo$2etoggles');");
ModuleMetadata m = metadataMap().getModulesByPath().get("testcode");
assertThat(m.readToggles()).containsExactly("bar");
}

@Test
public void testToggleModuleImportAsModule() {
testSame(
lines(
"const toggles = goog.require('foo$2etoggles');",
"function foo() {",
" console.log(toggles.TOGGLE_foo);",
" console.log(toggles.TOGGLE_b_a_z);",
"}"));
ModuleMetadata m = metadataMap().getModulesByPath().get("testcode");
assertThat(m.readToggles()).containsExactly("foo", "b_a_z");
}

@Test
public void testToggleModuleImportAsSideEffect() {
test(
srcs("goog.require('foo$2etoggles');"), //
error(GatherModuleMetadata.INVALID_TOGGLE_USAGE));
}

@Test
public void testToggleModuleImportDestructuredWithInvalidName() {
test(
srcs("const {foo} = goog.require('foo$2etoggles');"),
error(GatherModuleMetadata.INVALID_TOGGLE_USAGE));
}

@Test
public void testToggleModuleImportAsModuleWithInvalidPropertyName() {
test(
srcs(
lines(
"const foo = goog.require('foo$2etoggles');", //
"console.log(foo.bar);")),
error(GatherModuleMetadata.INVALID_TOGGLE_USAGE));
}

@Test
public void testToggleModuleInvalidModuleUsage() {
test(
srcs(
lines(
"const foo = goog.require('foo$2etoggles');", //
"console.log(foo);")),
error(GatherModuleMetadata.INVALID_TOGGLE_USAGE));
}

@Test
public void testToggleModuleIgnoreShadowedUsage() {
testSame(
lines(
"goog.readToggleInternalDoNotCallDirectly('foo_bar');",
"goog.readToggleInternalDoNotCallDirectly('baz');"));
"const toggles = goog.require('foo$2etoggles');",
"function foo() {",
" const toggles = {};",
" console.log(toggles.foo)",
" console.log(toggles.TOGGLE_bar)",
"}"));
ModuleMetadata m = metadataMap().getModulesByPath().get("testcode");
assertThat(m.readToggles()).containsExactly("foo_bar", "baz");
assertThat(m.readToggles()).isEmpty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ public void testSoyDelcall_legacy() {

@Test
public void testReadToggle() {
FileInfo info = parse("goog.readToggleInternalDoNotCallDirectly('foo_bar');");
FileInfo info = parse("const {TOGGLE_foo_bar} = goog.require('foo$2etoggles');");
assertThat(info.readToggles).containsExactly("foo_bar");
}

Expand Down

0 comments on commit 92557f3

Please sign in to comment.