Skip to content

Commit c2af98e

Browse files
Closure Teamcopybara-github
Closure Team
authored andcommitted
Add the ability to limit selecting candidates for the RenameProperties based on a predicate function, supplied via the constructor.
PiperOrigin-RevId: 728793352
1 parent fdda0c3 commit c2af98e

File tree

4 files changed

+234
-24
lines changed

4 files changed

+234
-24
lines changed

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

+1
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,7 @@ protected PassListBuilder getFinalizations() {
640640
passes.maybeAdd(ambiguateProperties);
641641
}
642642

643+
passes.maybeAdd(createEmptyPass(PassNames.BEFORE_RENAME_PROPERTIES));
643644
if (options.propertyRenaming == PropertyRenamingPolicy.ALL_UNQUOTED) {
644645
passes.maybeAdd(renameProperties);
645646
} else {

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

+1
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ public final class PassNames {
8888
public static final String RESOLVE_TYPES = "resolveTypes";
8989
public static final String REWRITE_FUNCTION_EXPRESSIONS = "rewriteFunctionExpressions";
9090
public static final String RENAME_PROPERTIES = "renameProperties";
91+
public static final String BEFORE_RENAME_PROPERTIES = "beforeRenameProperties";
9192
public static final String STRIP_SIDE_EFFECT_PROTECTION = "stripSideEffectProtection";
9293
public static final String WIZ_PASS = "wizPass";
9394

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

+86-23
Original file line numberDiff line numberDiff line change
@@ -36,32 +36,32 @@
3636
import java.util.Map;
3737
import java.util.Set;
3838
import java.util.TreeSet;
39+
import java.util.function.Predicate;
3940
import org.jspecify.annotations.Nullable;
4041

4142
/**
42-
* RenameProperties renames properties (including methods) of all JavaScript
43-
* objects. This includes prototypes, functions, object literals, etc.
43+
* RenameProperties renames properties (including methods) of all JavaScript objects. This includes
44+
* prototypes, functions, object literals, etc.
4445
*
45-
* <p> If provided a VariableMap of previously used names, it tries to reuse
46-
* those names.
46+
* <p>If provided a VariableMap of previously used names, it tries to reuse those names.
4747
*
48-
* <p> To prevent a property from getting renamed you may extern it (add it to
49-
* your externs file) or put it in quotes.
48+
* <p>To prevent a property from getting renamed you may extern it (add it to your externs file) or
49+
* put it in quotes.
5050
*
51-
* <p> To avoid run-time JavaScript errors, use quotes when accessing properties
52-
* that are defined using quotes.
51+
* <p>To avoid run-time JavaScript errors, use quotes when accessing properties that are defined
52+
* using quotes.
5353
*
5454
* <pre>
5555
* var a = {'myprop': 0}, b = a['myprop']; // correct
5656
* var x = {'myprop': 0}, y = x.myprop; // incorrect
5757
* </pre>
5858
*
59-
* This pass also recognizes and replaces special renaming functions. They supply
60-
* a property name as the string literal for the first argument.
61-
* This pass will replace them as though they were JS property
62-
* references. Here are two examples:
63-
* JSCompiler_renameProperty('propertyName') -> 'jYq'
64-
* JSCompiler_renameProperty('myProp.nestedProp.innerProp') -> 'e4.sW.C$'
59+
* This pass also recognizes and replaces special renaming functions. They supply a property name as
60+
* the string literal for the first argument. This pass will replace them as though they were JS
61+
* property references. Here are two examples: JSCompiler_renameProperty('propertyName') -> 'jYq'
62+
* JSCompiler_renameProperty('myProp.nestedProp.innerProp') -> 'e4.sW.C$'
63+
*
64+
* <p>This class is not thread-safe.
6565
*/
6666
class RenameProperties implements CompilerPass {
6767
private static final Splitter DOT_SPLITTER = Splitter.on('.');
@@ -92,6 +92,13 @@ class RenameProperties implements CompilerPass {
9292
// Shared name generator
9393
private final NameGenerator nameGenerator;
9494

95+
// Filter function for property names. The function takes a node as input and returns true if the
96+
// node should be renamed.
97+
private final Predicate<Node> propertyRenameEligibilityFilter;
98+
// Property names that should not be renamed, based on the propertyNameFilter. Unlike
99+
// externedNames, this set is dynamically constructed during the pass.
100+
private final Set<String> filteredOutNames = new LinkedHashSet<>();
101+
95102
private static final Comparator<Property> FREQUENCY_COMPARATOR =
96103
(Property p1, Property p2) -> {
97104

@@ -133,12 +140,50 @@ class RenameProperties implements CompilerPass {
133140
char @Nullable [] reservedFirstCharacters,
134141
char @Nullable [] reservedNonFirstCharacters,
135142
NameGenerator nameGenerator) {
143+
this(
144+
compiler,
145+
generatePseudoNames,
146+
prevUsedPropertyMap,
147+
reservedFirstCharacters,
148+
reservedNonFirstCharacters,
149+
nameGenerator,
150+
(Node n) -> true);
151+
}
152+
153+
/**
154+
* Creates an instance.
155+
*
156+
* @param compiler the JSCompiler
157+
* @param generatePseudoNames generate pseudo names. e.g. foo -> $foo$ instead of compact
158+
* obfuscated names. This is used for debugging.
159+
* @param prevUsedPropertyMap the property renaming map used in a previous compilation
160+
* @param reservedFirstCharacters if specified these characters won't be used in generated names
161+
* for the first character
162+
* @param reservedNonFirstCharacters if specified these characters won't be used in generated
163+
* names for characters after the first
164+
* @param nameGenerator a shared NameGenerator that this instance can use; the instance may reset
165+
* or reconfigure it, so the caller should not expect any state to be preserved
166+
* @param propertyRenameEligibilityFilter limit the nodes that are renamed using a predicate
167+
* function. When this returns true, the property name is renamed if and only if all other
168+
* property references for the same name match the predicate. If this returns false for any
169+
* instance of a property, then no property references anywhere in the AST to that property
170+
* are renamed
171+
*/
172+
RenameProperties(
173+
AbstractCompiler compiler,
174+
boolean generatePseudoNames,
175+
VariableMap prevUsedPropertyMap,
176+
char @Nullable [] reservedFirstCharacters,
177+
char @Nullable [] reservedNonFirstCharacters,
178+
NameGenerator nameGenerator,
179+
Predicate<Node> propertyRenameEligibilityFilter) {
136180
this.compiler = compiler;
137181
this.generatePseudoNames = generatePseudoNames;
138182
this.prevUsedPropertyMap = prevUsedPropertyMap;
139183
this.reservedFirstCharacters = reservedFirstCharacters;
140184
this.reservedNonFirstCharacters = reservedNonFirstCharacters;
141185
this.nameGenerator = nameGenerator;
186+
this.propertyRenameEligibilityFilter = propertyRenameEligibilityFilter;
142187
externedNames.addAll(compiler.getExternProperties());
143188
}
144189

@@ -168,6 +213,9 @@ public void process(Node externs, Node root) {
168213
// Update the string nodes.
169214
for (Node n : stringNodesToRename) {
170215
String oldName = n.getString();
216+
if (filteredOutNames.contains(oldName)) {
217+
continue;
218+
}
171219
Property p = propertyMap.get(oldName);
172220
if (p != null && p.newName != null) {
173221
checkState(oldName.equals(p.oldName));
@@ -188,7 +236,11 @@ public void process(Node externs, Node root) {
188236
String replacement;
189237
if (p != null && p.newName != null) {
190238
checkState(oldName.equals(p.oldName));
191-
replacement = p.newName;
239+
if (filteredOutNames.contains(oldName)) {
240+
replacement = oldName;
241+
} else {
242+
replacement = p.newName;
243+
}
192244
} else {
193245
replacement = oldName;
194246
}
@@ -306,16 +358,24 @@ public void visit(NodeTraversal t, Node n, Node parent) {
306358
quotedNames.add(child.getString());
307359
}
308360
break;
309-
case CALL: {
310-
// We replace property renaming function calls with a string
311-
// containing the renamed property.
312-
Node fnName = n.getFirstChild();
361+
case CALL:
362+
{
363+
// We replace property renaming function calls with a string
364+
// containing the renamed property.
365+
Node fnName = n.getFirstChild();
366+
313367
if (compiler.getCodingConvention().isPropertyRenameFunction(fnName)) {
314-
callNodeToParentMap.put(n, parent);
315-
countCallCandidates(t, n);
368+
callNodeToParentMap.put(n, parent);
369+
countCallCandidates(t, n);
370+
Node stringArgument = n.getSecondChild();
371+
if (!propertyRenameEligibilityFilter.test(n)) {
372+
for (String name : DOT_SPLITTER.split(stringArgument.getString())) {
373+
filteredOutNames.add(name);
374+
}
375+
}
376+
}
377+
break;
316378
}
317-
break;
318-
}
319379
case MEMBER_FUNCTION_DEF:
320380
checkState(!n.isQuotedStringKey());
321381
if (NodeUtil.isEs6ConstructorMemberFunctionDef(n)) {
@@ -382,6 +442,9 @@ public void visit(NodeTraversal t, Node n, Node parent) {
382442
private void maybeMarkCandidate(Node n) {
383443
String name = n.getString();
384444
if (!externedNames.contains(name)) {
445+
if (!propertyRenameEligibilityFilter.test(n)) {
446+
filteredOutNames.add(n.getString());
447+
}
385448
stringNodesToRename.add(n);
386449
countPropertyOccurrence(name);
387450
}

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

+146-1
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,12 @@
1616

1717
package com.google.javascript.jscomp;
1818

19+
import static com.google.common.base.Strings.nullToEmpty;
1920
import static com.google.common.truth.Truth.assertThat;
2021

2122
import com.google.common.collect.ImmutableList;
23+
import com.google.javascript.rhino.Node;
24+
import java.util.function.Predicate;
2225
import org.jspecify.annotations.Nullable;
2326
import org.junit.Before;
2427
import org.junit.Test;
@@ -35,6 +38,7 @@ public final class RenamePropertiesTest extends CompilerTestCase {
3538
private RenameProperties renameProperties;
3639
private boolean generatePseudoNames;
3740
private @Nullable VariableMap prevUsedPropertyMap;
41+
private @Nullable Predicate<Node> propertyNameFilter = (Node name) -> true;
3842

3943
public RenamePropertiesTest() {
4044
super(EXTERNS);
@@ -787,6 +791,146 @@ public void testObjectMethodProperty() {
787791
lines("var foo = { ", " a: 1, ", " b() {", " return this.a", " }", "};", "foo.b();"));
788792
}
789793

794+
@Test
795+
public void testPrototypePropertiesUsingFilterFunction() {
796+
propertyNameFilter =
797+
(node) -> {
798+
String name = node.getString();
799+
name = nullToEmpty(name);
800+
return name.startsWith("get");
801+
};
802+
803+
test(
804+
"Bar.prototype.getA = function(){}; bar.getA();"
805+
+ "Bar.prototype.getB = function(){};"
806+
+ "Bar.prototype.setC = function(){};",
807+
"Bar.prototype.a = function(){}; bar.a();"
808+
+ "Bar.prototype.b = function(){};"
809+
+ "Bar.prototype.setC = function(){}");
810+
}
811+
812+
@Test
813+
public void testPrototypePropertiesUsingFilterOnFilename() {
814+
propertyNameFilter =
815+
(node) -> {
816+
String name = node.getSourceFileName();
817+
name = nullToEmpty(name);
818+
return name.equals("foo.js");
819+
};
820+
821+
test(
822+
srcs(
823+
SourceFile.fromCode(
824+
"foo.js",
825+
"""
826+
Foo.prototype.getA = function(){};
827+
Foo.prototype.getB = function(){};
828+
foo.getA();
829+
foo.getB();
830+
"""),
831+
SourceFile.fromCode(
832+
"bar.js",
833+
"""
834+
Bar.prototype.getA = function(){};
835+
bar.getA();
836+
""")),
837+
expected(
838+
SourceFile.fromCode(
839+
"foo.js",
840+
"""
841+
Foo.prototype.getA = function(){};
842+
Foo.prototype.b = function(){};
843+
foo.getA();
844+
foo.b();
845+
"""),
846+
SourceFile.fromCode(
847+
"bar.js",
848+
"""
849+
Bar.prototype.getA = function(){};
850+
bar.getA();
851+
""")));
852+
}
853+
854+
@Test
855+
public void testPrototypeAndRenameFunctionsFilterOnFilename() {
856+
propertyNameFilter =
857+
(node) -> {
858+
String name = node.getSourceFileName();
859+
name = nullToEmpty(name);
860+
return name.equals("foo.js");
861+
};
862+
863+
test(
864+
srcs(
865+
SourceFile.fromCode(
866+
"foo.js",
867+
"""
868+
var foo = {myProp: 0, myProp2: 1};
869+
f(foo[JSCompiler_renameProperty('myProp')]);
870+
f(foo[JSCompiler_renameProperty('myProp2')]);
871+
"""),
872+
SourceFile.fromCode(
873+
"bar.js",
874+
"""
875+
var bar = {myProp: 0};
876+
f(bar[JSCompiler_renameProperty('myProp')]);
877+
""")),
878+
expected(
879+
SourceFile.fromCode(
880+
"foo.js",
881+
"""
882+
var foo = {myProp: 0, b: 1};
883+
f(foo['myProp']);
884+
f(foo['b']);
885+
"""),
886+
SourceFile.fromCode(
887+
"bar.js",
888+
"""
889+
var bar = {myProp: 0};
890+
f(bar['myProp']);
891+
""")));
892+
}
893+
894+
@Test
895+
public void testPrototypeAndRenameFunctionsFilterOnFilename2() {
896+
propertyNameFilter =
897+
(node) -> {
898+
String name = node.getSourceFileName();
899+
name = nullToEmpty(name);
900+
return name.equals("foo.js");
901+
};
902+
903+
test(
904+
srcs(
905+
SourceFile.fromCode(
906+
"foo.js",
907+
"""
908+
var foo = {myProp: 0, myProp2: 1};
909+
f(JSCompiler_renameProperty('otherProp.myProp.someProp'));
910+
f(JSCompiler_renameProperty('otherProp.myProp2.someProp'));
911+
"""),
912+
SourceFile.fromCode(
913+
"bar.js",
914+
"""
915+
var bar = {myProp: 0};
916+
f(JSCompiler_renameProperty('otherProp.myProp.someProp'));
917+
""")),
918+
expected(
919+
SourceFile.fromCode(
920+
"foo.js",
921+
"""
922+
var foo = {myProp: 0, d: 1};
923+
f('otherProp.myProp.someProp');
924+
f('otherProp.d.someProp');
925+
"""),
926+
SourceFile.fromCode(
927+
"bar.js",
928+
"""
929+
var bar = {myProp: 0};
930+
f('otherProp.myProp.someProp');
931+
""")));
932+
}
933+
790934
private Compiler compileChunks(String externs, JSChunk[] chunks) {
791935
SourceFile externsInput = SourceFile.fromCode("externs", externs);
792936

@@ -810,6 +954,7 @@ protected CompilerPass getProcessor(Compiler compiler) {
810954
prevUsedPropertyMap,
811955
null,
812956
null,
813-
new DefaultNameGenerator());
957+
new DefaultNameGenerator(),
958+
propertyNameFilter);
814959
}
815960
}

0 commit comments

Comments
 (0)