36
36
import java .util .Map ;
37
37
import java .util .Set ;
38
38
import java .util .TreeSet ;
39
+ import java .util .function .Predicate ;
39
40
import org .jspecify .annotations .Nullable ;
40
41
41
42
/**
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.
44
45
*
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.
47
47
*
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.
50
50
*
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.
53
53
*
54
54
* <pre>
55
55
* var a = {'myprop': 0}, b = a['myprop']; // correct
56
56
* var x = {'myprop': 0}, y = x.myprop; // incorrect
57
57
* </pre>
58
58
*
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.
65
65
*/
66
66
class RenameProperties implements CompilerPass {
67
67
private static final Splitter DOT_SPLITTER = Splitter .on ('.' );
@@ -92,6 +92,13 @@ class RenameProperties implements CompilerPass {
92
92
// Shared name generator
93
93
private final NameGenerator nameGenerator ;
94
94
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
+
95
102
private static final Comparator <Property > FREQUENCY_COMPARATOR =
96
103
(Property p1 , Property p2 ) -> {
97
104
@@ -133,12 +140,50 @@ class RenameProperties implements CompilerPass {
133
140
char @ Nullable [] reservedFirstCharacters ,
134
141
char @ Nullable [] reservedNonFirstCharacters ,
135
142
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 ) {
136
180
this .compiler = compiler ;
137
181
this .generatePseudoNames = generatePseudoNames ;
138
182
this .prevUsedPropertyMap = prevUsedPropertyMap ;
139
183
this .reservedFirstCharacters = reservedFirstCharacters ;
140
184
this .reservedNonFirstCharacters = reservedNonFirstCharacters ;
141
185
this .nameGenerator = nameGenerator ;
186
+ this .propertyRenameEligibilityFilter = propertyRenameEligibilityFilter ;
142
187
externedNames .addAll (compiler .getExternProperties ());
143
188
}
144
189
@@ -168,6 +213,9 @@ public void process(Node externs, Node root) {
168
213
// Update the string nodes.
169
214
for (Node n : stringNodesToRename ) {
170
215
String oldName = n .getString ();
216
+ if (filteredOutNames .contains (oldName )) {
217
+ continue ;
218
+ }
171
219
Property p = propertyMap .get (oldName );
172
220
if (p != null && p .newName != null ) {
173
221
checkState (oldName .equals (p .oldName ));
@@ -188,7 +236,11 @@ public void process(Node externs, Node root) {
188
236
String replacement ;
189
237
if (p != null && p .newName != null ) {
190
238
checkState (oldName .equals (p .oldName ));
191
- replacement = p .newName ;
239
+ if (filteredOutNames .contains (oldName )) {
240
+ replacement = oldName ;
241
+ } else {
242
+ replacement = p .newName ;
243
+ }
192
244
} else {
193
245
replacement = oldName ;
194
246
}
@@ -306,16 +358,24 @@ public void visit(NodeTraversal t, Node n, Node parent) {
306
358
quotedNames .add (child .getString ());
307
359
}
308
360
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
+
313
367
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 ;
316
378
}
317
- break ;
318
- }
319
379
case MEMBER_FUNCTION_DEF :
320
380
checkState (!n .isQuotedStringKey ());
321
381
if (NodeUtil .isEs6ConstructorMemberFunctionDef (n )) {
@@ -382,6 +442,9 @@ public void visit(NodeTraversal t, Node n, Node parent) {
382
442
private void maybeMarkCandidate (Node n ) {
383
443
String name = n .getString ();
384
444
if (!externedNames .contains (name )) {
445
+ if (!propertyRenameEligibilityFilter .test (n )) {
446
+ filteredOutNames .add (n .getString ());
447
+ }
385
448
stringNodesToRename .add (n );
386
449
countPropertyOccurrence (name );
387
450
}
0 commit comments