Skip to content

Commit da85d10

Browse files
authored
enhance mangle.properties (#4064)
1 parent 35fe109 commit da85d10

File tree

4 files changed

+135
-64
lines changed

4 files changed

+135
-64
lines changed

lib/minify.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -197,9 +197,7 @@ function minify(files, options) {
197197
toplevel.mangle_names(options.mangle);
198198
}
199199
if (timings) timings.properties = Date.now();
200-
if (options.mangle && options.mangle.properties) {
201-
toplevel = mangle_properties(toplevel, options.mangle.properties);
202-
}
200+
if (options.mangle && options.mangle.properties) mangle_properties(toplevel, options.mangle.properties);
203201
if (timings) timings.output = Date.now();
204202
var result = {};
205203
if (options.output.ast) {

lib/propmangle.js

Lines changed: 85 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@
4343

4444
"use strict";
4545

46-
function find_builtins(reserved) {
46+
var builtins = function() {
47+
var names = [];
4748
// NaN will be included due to Number.NaN
4849
[
4950
"null",
@@ -67,19 +68,21 @@ function find_builtins(reserved) {
6768
].forEach(function(ctor) {
6869
Object.getOwnPropertyNames(ctor).map(add);
6970
if (ctor.prototype) {
71+
Object.getOwnPropertyNames(new ctor()).map(add);
7072
Object.getOwnPropertyNames(ctor.prototype).map(add);
7173
}
7274
});
75+
return makePredicate(names);
7376

7477
function add(name) {
75-
push_uniq(reserved, name);
78+
names.push(name);
7679
}
77-
}
80+
}();
7881

7982
function reserve_quoted_keys(ast, reserved) {
8083
ast.walk(new TreeWalker(function(node) {
81-
if (node instanceof AST_ObjectKeyVal && node.quote) {
82-
add(node.key);
84+
if (node instanceof AST_ObjectKeyVal) {
85+
if (node.quote) add(node.key);
8386
} else if (node instanceof AST_Sub) {
8487
addStrings(node.property, add);
8588
}
@@ -91,17 +94,14 @@ function reserve_quoted_keys(ast, reserved) {
9194
}
9295

9396
function addStrings(node, add) {
94-
node.walk(new TreeWalker(function(node) {
95-
if (node instanceof AST_Sequence) {
96-
addStrings(node.tail_node(), add);
97-
} else if (node instanceof AST_String) {
98-
add(node.value);
99-
} else if (node instanceof AST_Conditional) {
100-
addStrings(node.consequent, add);
101-
addStrings(node.alternative, add);
102-
}
103-
return true;
104-
}));
97+
if (node instanceof AST_Conditional) {
98+
addStrings(node.consequent, add);
99+
addStrings(node.alternative, add);
100+
} else if (node instanceof AST_Sequence) {
101+
addStrings(node.tail_node(), add);
102+
} else if (node instanceof AST_String) {
103+
add(node.value);
104+
}
105105
}
106106

107107
function mangle_properties(ast, options) {
@@ -115,16 +115,17 @@ function mangle_properties(ast, options) {
115115
reserved: null,
116116
}, true);
117117

118-
var reserved = options.reserved;
119-
if (!Array.isArray(reserved)) reserved = [];
120-
if (!options.builtins) find_builtins(reserved);
118+
var reserved = Object.create(options.builtins ? null : builtins);
119+
if (Array.isArray(options.reserved)) options.reserved.forEach(function(name) {
120+
reserved[name] = true;
121+
});
121122

122123
var cname = -1;
123124
var cache;
124125
if (options.cache) {
125126
cache = options.cache.props;
126-
cache.each(function(mangled_name) {
127-
push_uniq(reserved, mangled_name);
127+
cache.each(function(name) {
128+
reserved[name] = true;
128129
});
129130
} else {
130131
cache = new Dictionary();
@@ -139,62 +140,93 @@ function mangle_properties(ast, options) {
139140
var debug_suffix;
140141
if (debug) debug_suffix = options.debug === true ? "" : options.debug;
141142

142-
var names_to_mangle = [];
143-
var unmangleable = [];
143+
var names_to_mangle = Object.create(null);
144+
var unmangleable = Object.create(reserved);
144145

145146
// step 1: find candidates to mangle
146147
ast.walk(new TreeWalker(function(node) {
147-
if (node instanceof AST_ObjectKeyVal) {
148+
if (node instanceof AST_Binary) {
149+
if (node.operator == "in") addStrings(node.left, add);
150+
} else if (node.TYPE == "Call") {
151+
var exp = node.expression;
152+
if (exp instanceof AST_Dot) switch (exp.property) {
153+
case "defineProperty":
154+
case "getOwnPropertyDescriptor":
155+
if (node.args.length < 2) break;
156+
exp = exp.expression;
157+
if (!(exp instanceof AST_SymbolRef)) break;
158+
if (exp.name != "Object") break;
159+
if (!exp.definition().undeclared) break;
160+
addStrings(node.args[1], add);
161+
break;
162+
case "hasOwnProperty":
163+
if (node.args.length < 1) break;
164+
addStrings(node.args[0], add);
165+
break;
166+
}
167+
} else if (node instanceof AST_Dot) {
168+
add(node.property);
169+
} else if (node instanceof AST_ObjectKeyVal) {
148170
add(node.key);
149171
} else if (node instanceof AST_ObjectProperty) {
150172
// setter or getter, since KeyVal is handled above
151173
add(node.key.name);
152-
} else if (node instanceof AST_Dot) {
153-
add(node.property);
154174
} else if (node instanceof AST_Sub) {
155175
addStrings(node.property, add);
156-
} else if (node instanceof AST_Call
157-
&& node.expression.print_to_string() == "Object.defineProperty") {
158-
addStrings(node.args[1], add);
159176
}
160177
}));
161178

162-
// step 2: transform the tree, renaming properties
163-
return ast.transform(new TreeTransformer(function(node) {
164-
if (node instanceof AST_ObjectKeyVal) {
179+
// step 2: renaming properties
180+
ast.walk(new TreeWalker(function(node) {
181+
if (node instanceof AST_Binary) {
182+
if (node.operator == "in") mangleStrings(node.left);
183+
} else if (node.TYPE == "Call") {
184+
var exp = node.expression;
185+
if (exp instanceof AST_Dot) switch (exp.property) {
186+
case "defineProperty":
187+
case "getOwnPropertyDescriptor":
188+
if (node.args.length < 2) break;
189+
exp = exp.expression;
190+
if (!(exp instanceof AST_SymbolRef)) break;
191+
if (exp.name != "Object") break;
192+
if (!exp.definition().undeclared) break;
193+
mangleStrings(node.args[1]);
194+
break;
195+
case "hasOwnProperty":
196+
if (node.args.length < 1) break;
197+
mangleStrings(node.args[0]);
198+
break;
199+
}
200+
} else if (node instanceof AST_Dot) {
201+
node.property = mangle(node.property);
202+
} else if (node instanceof AST_ObjectKeyVal) {
165203
node.key = mangle(node.key);
166204
} else if (node instanceof AST_ObjectProperty) {
167205
// setter or getter
168206
node.key.name = mangle(node.key.name);
169-
} else if (node instanceof AST_Dot) {
170-
node.property = mangle(node.property);
171-
} else if (!options.keep_quoted && node instanceof AST_Sub) {
172-
node.property = mangleStrings(node.property);
173-
} else if (node instanceof AST_Call
174-
&& node.expression.print_to_string() == "Object.defineProperty") {
175-
node.args[1] = mangleStrings(node.args[1]);
207+
} else if (node instanceof AST_Sub) {
208+
if (!options.keep_quoted) mangleStrings(node.property);
176209
}
177210
}));
178211

179212
// only function declarations after this line
180213

181214
function can_mangle(name) {
182-
if (unmangleable.indexOf(name) >= 0) return false;
183-
if (reserved.indexOf(name) >= 0) return false;
215+
if (unmangleable[name]) return false;
184216
if (options.only_cache) return cache.has(name);
185217
if (/^-?[0-9]+(\.[0-9]+)?(e[+-][0-9]+)?$/.test(name)) return false;
186218
return true;
187219
}
188220

189221
function should_mangle(name) {
222+
if (reserved[name]) return false;
190223
if (regex && !regex.test(name)) return false;
191-
if (reserved.indexOf(name) >= 0) return false;
192-
return cache.has(name) || names_to_mangle.indexOf(name) >= 0;
224+
return cache.has(name) || names_to_mangle[name];
193225
}
194226

195227
function add(name) {
196-
if (can_mangle(name)) push_uniq(names_to_mangle, name);
197-
if (!should_mangle(name)) push_uniq(unmangleable, name);
228+
if (can_mangle(name)) names_to_mangle[name] = true;
229+
if (!should_mangle(name)) unmangleable[name] = true;
198230
}
199231

200232
function mangle(name) {
@@ -218,17 +250,13 @@ function mangle_properties(ast, options) {
218250
}
219251

220252
function mangleStrings(node) {
221-
return node.transform(new TreeTransformer(function(node) {
222-
if (node instanceof AST_Sequence) {
223-
var last = node.expressions.length - 1;
224-
node.expressions[last] = mangleStrings(node.expressions[last]);
225-
} else if (node instanceof AST_String) {
226-
node.value = mangle(node.value);
227-
} else if (node instanceof AST_Conditional) {
228-
node.consequent = mangleStrings(node.consequent);
229-
node.alternative = mangleStrings(node.alternative);
230-
}
231-
return node;
232-
}));
253+
if (node instanceof AST_Sequence) {
254+
mangleStrings(node.expressions.tail_node());
255+
} else if (node instanceof AST_String) {
256+
node.value = mangle(node.value);
257+
} else if (node instanceof AST_Conditional) {
258+
mangleStrings(node.consequent);
259+
mangleStrings(node.alternative);
260+
}
233261
}
234262
}

test/compress.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -312,9 +312,7 @@ function test_case(test) {
312312
if (test.mangle) {
313313
output.compute_char_frequency(test.mangle);
314314
output.mangle_names(test.mangle);
315-
if (test.mangle.properties) {
316-
output = U.mangle_properties(output, test.mangle.properties);
317-
}
315+
if (test.mangle.properties) U.mangle_properties(output, test.mangle.properties);
318316
}
319317
var output_code = make_code(output, output_options);
320318
if (expect != output_code) {

test/compress/properties.js

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ evaluate_string_length: {
130130
}
131131
}
132132

133-
mangle_properties: {
133+
mangle_properties_1: {
134134
mangle = {
135135
properties: {
136136
keep_quoted: false,
@@ -152,6 +152,53 @@ mangle_properties: {
152152
}
153153
}
154154

155+
mangle_properties_2: {
156+
mangle = {
157+
properties: {
158+
reserved: [
159+
"value",
160+
]
161+
},
162+
}
163+
input: {
164+
var o = {
165+
prop1: 1,
166+
};
167+
Object.defineProperty(o, "prop2", {
168+
value: 2,
169+
});
170+
Object.defineProperties(o, {
171+
prop3: {
172+
value: 3,
173+
},
174+
});
175+
console.log("prop1", o.prop1, "prop1" in o);
176+
console.log("prop2", o.prop2, o.hasOwnProperty("prop2"));
177+
console.log("prop3", o.prop3, Object.getOwnPropertyDescriptor(o, "prop3").value);
178+
}
179+
expect: {
180+
var o = {
181+
o: 1,
182+
};
183+
Object.defineProperty(o, "p", {
184+
value: 2,
185+
});
186+
Object.defineProperties(o, {
187+
r: {
188+
value: 3,
189+
},
190+
});
191+
console.log("prop1", o.o, "o" in o);
192+
console.log("prop2", o.p, o.hasOwnProperty("p"));
193+
console.log("prop3", o.r, Object.getOwnPropertyDescriptor(o, "r").value);
194+
}
195+
expect_stdout: [
196+
"prop1 1 true",
197+
"prop2 2 true",
198+
"prop3 3 3",
199+
]
200+
}
201+
155202
mangle_unquoted_properties: {
156203
options = {
157204
evaluate: true,

0 commit comments

Comments
 (0)