enhance `mangle.properties` (#4064)
authorAlex Lam S.L <alexlamsl@gmail.com>
Sun, 23 Aug 2020 00:45:39 +0000 (01:45 +0100)
committerGitHub <noreply@github.com>
Sun, 23 Aug 2020 00:45:39 +0000 (08:45 +0800)
lib/minify.js
lib/propmangle.js
test/compress.js
test/compress/properties.js

index 3817b63..c264f69 100644 (file)
@@ -197,9 +197,7 @@ function minify(files, options) {
             toplevel.mangle_names(options.mangle);
         }
         if (timings) timings.properties = Date.now();
-        if (options.mangle && options.mangle.properties) {
-            toplevel = mangle_properties(toplevel, options.mangle.properties);
-        }
+        if (options.mangle && options.mangle.properties) mangle_properties(toplevel, options.mangle.properties);
         if (timings) timings.output = Date.now();
         var result = {};
         if (options.output.ast) {
index ebd1f4e..0419439 100644 (file)
@@ -43,7 +43,8 @@
 
 "use strict";
 
-function find_builtins(reserved) {
+var builtins = function() {
+    var names = [];
     // NaN will be included due to Number.NaN
     [
         "null",
@@ -67,19 +68,21 @@ function find_builtins(reserved) {
     ].forEach(function(ctor) {
         Object.getOwnPropertyNames(ctor).map(add);
         if (ctor.prototype) {
+            Object.getOwnPropertyNames(new ctor()).map(add);
             Object.getOwnPropertyNames(ctor.prototype).map(add);
         }
     });
+    return makePredicate(names);
 
     function add(name) {
-        push_uniq(reserved, name);
+        names.push(name);
     }
-}
+}();
 
 function reserve_quoted_keys(ast, reserved) {
     ast.walk(new TreeWalker(function(node) {
-        if (node instanceof AST_ObjectKeyVal && node.quote) {
-            add(node.key);
+        if (node instanceof AST_ObjectKeyVal) {
+            if (node.quote) add(node.key);
         } else if (node instanceof AST_Sub) {
             addStrings(node.property, add);
         }
@@ -91,17 +94,14 @@ function reserve_quoted_keys(ast, reserved) {
 }
 
 function addStrings(node, add) {
-    node.walk(new TreeWalker(function(node) {
-        if (node instanceof AST_Sequence) {
-            addStrings(node.tail_node(), add);
-        } else if (node instanceof AST_String) {
-            add(node.value);
-        } else if (node instanceof AST_Conditional) {
-            addStrings(node.consequent, add);
-            addStrings(node.alternative, add);
-        }
-        return true;
-    }));
+    if (node instanceof AST_Conditional) {
+        addStrings(node.consequent, add);
+        addStrings(node.alternative, add);
+    } else if (node instanceof AST_Sequence) {
+        addStrings(node.tail_node(), add);
+    } else if (node instanceof AST_String) {
+        add(node.value);
+    }
 }
 
 function mangle_properties(ast, options) {
@@ -115,16 +115,17 @@ function mangle_properties(ast, options) {
         reserved: null,
     }, true);
 
-    var reserved = options.reserved;
-    if (!Array.isArray(reserved)) reserved = [];
-    if (!options.builtins) find_builtins(reserved);
+    var reserved = Object.create(options.builtins ? null : builtins);
+    if (Array.isArray(options.reserved)) options.reserved.forEach(function(name) {
+        reserved[name] = true;
+    });
 
     var cname = -1;
     var cache;
     if (options.cache) {
         cache = options.cache.props;
-        cache.each(function(mangled_name) {
-            push_uniq(reserved, mangled_name);
+        cache.each(function(name) {
+            reserved[name] = true;
         });
     } else {
         cache = new Dictionary();
@@ -139,62 +140,93 @@ function mangle_properties(ast, options) {
     var debug_suffix;
     if (debug) debug_suffix = options.debug === true ? "" : options.debug;
 
-    var names_to_mangle = [];
-    var unmangleable = [];
+    var names_to_mangle = Object.create(null);
+    var unmangleable = Object.create(reserved);
 
     // step 1: find candidates to mangle
     ast.walk(new TreeWalker(function(node) {
-        if (node instanceof AST_ObjectKeyVal) {
+        if (node instanceof AST_Binary) {
+            if (node.operator == "in") addStrings(node.left, add);
+        } else if (node.TYPE == "Call") {
+            var exp = node.expression;
+            if (exp instanceof AST_Dot) switch (exp.property) {
+              case "defineProperty":
+              case "getOwnPropertyDescriptor":
+                if (node.args.length < 2) break;
+                exp = exp.expression;
+                if (!(exp instanceof AST_SymbolRef)) break;
+                if (exp.name != "Object") break;
+                if (!exp.definition().undeclared) break;
+                addStrings(node.args[1], add);
+                break;
+              case "hasOwnProperty":
+                if (node.args.length < 1) break;
+                addStrings(node.args[0], add);
+                break;
+            }
+        } else if (node instanceof AST_Dot) {
+            add(node.property);
+        } else if (node instanceof AST_ObjectKeyVal) {
             add(node.key);
         } else if (node instanceof AST_ObjectProperty) {
             // setter or getter, since KeyVal is handled above
             add(node.key.name);
-        } else if (node instanceof AST_Dot) {
-            add(node.property);
         } else if (node instanceof AST_Sub) {
             addStrings(node.property, add);
-        } else if (node instanceof AST_Call
-            && node.expression.print_to_string() == "Object.defineProperty") {
-            addStrings(node.args[1], add);
         }
     }));
 
-    // step 2: transform the tree, renaming properties
-    return ast.transform(new TreeTransformer(function(node) {
-        if (node instanceof AST_ObjectKeyVal) {
+    // step 2: renaming properties
+    ast.walk(new TreeWalker(function(node) {
+        if (node instanceof AST_Binary) {
+            if (node.operator == "in") mangleStrings(node.left);
+        } else if (node.TYPE == "Call") {
+            var exp = node.expression;
+            if (exp instanceof AST_Dot) switch (exp.property) {
+              case "defineProperty":
+              case "getOwnPropertyDescriptor":
+                if (node.args.length < 2) break;
+                exp = exp.expression;
+                if (!(exp instanceof AST_SymbolRef)) break;
+                if (exp.name != "Object") break;
+                if (!exp.definition().undeclared) break;
+                mangleStrings(node.args[1]);
+                break;
+              case "hasOwnProperty":
+                if (node.args.length < 1) break;
+                mangleStrings(node.args[0]);
+                break;
+            }
+        } else if (node instanceof AST_Dot) {
+            node.property = mangle(node.property);
+        } else if (node instanceof AST_ObjectKeyVal) {
             node.key = mangle(node.key);
         } else if (node instanceof AST_ObjectProperty) {
             // setter or getter
             node.key.name = mangle(node.key.name);
-        } else if (node instanceof AST_Dot) {
-            node.property = mangle(node.property);
-        } else if (!options.keep_quoted && node instanceof AST_Sub) {
-            node.property = mangleStrings(node.property);
-        } else if (node instanceof AST_Call
-            && node.expression.print_to_string() == "Object.defineProperty") {
-            node.args[1] = mangleStrings(node.args[1]);
+        } else if (node instanceof AST_Sub) {
+            if (!options.keep_quoted) mangleStrings(node.property);
         }
     }));
 
     // only function declarations after this line
 
     function can_mangle(name) {
-        if (unmangleable.indexOf(name) >= 0) return false;
-        if (reserved.indexOf(name) >= 0) return false;
+        if (unmangleable[name]) return false;
         if (options.only_cache) return cache.has(name);
         if (/^-?[0-9]+(\.[0-9]+)?(e[+-][0-9]+)?$/.test(name)) return false;
         return true;
     }
 
     function should_mangle(name) {
+        if (reserved[name]) return false;
         if (regex && !regex.test(name)) return false;
-        if (reserved.indexOf(name) >= 0) return false;
-        return cache.has(name) || names_to_mangle.indexOf(name) >= 0;
+        return cache.has(name) || names_to_mangle[name];
     }
 
     function add(name) {
-        if (can_mangle(name)) push_uniq(names_to_mangle, name);
-        if (!should_mangle(name)) push_uniq(unmangleable, name);
+        if (can_mangle(name)) names_to_mangle[name] = true;
+        if (!should_mangle(name)) unmangleable[name] = true;
     }
 
     function mangle(name) {
@@ -218,17 +250,13 @@ function mangle_properties(ast, options) {
     }
 
     function mangleStrings(node) {
-        return node.transform(new TreeTransformer(function(node) {
-            if (node instanceof AST_Sequence) {
-                var last = node.expressions.length - 1;
-                node.expressions[last] = mangleStrings(node.expressions[last]);
-            } else if (node instanceof AST_String) {
-                node.value = mangle(node.value);
-            } else if (node instanceof AST_Conditional) {
-                node.consequent = mangleStrings(node.consequent);
-                node.alternative = mangleStrings(node.alternative);
-            }
-            return node;
-        }));
+        if (node instanceof AST_Sequence) {
+            mangleStrings(node.expressions.tail_node());
+        } else if (node instanceof AST_String) {
+            node.value = mangle(node.value);
+        } else if (node instanceof AST_Conditional) {
+            mangleStrings(node.consequent);
+            mangleStrings(node.alternative);
+        }
     }
 }
index 0afd98a..77b09f0 100644 (file)
@@ -312,9 +312,7 @@ function test_case(test) {
     if (test.mangle) {
         output.compute_char_frequency(test.mangle);
         output.mangle_names(test.mangle);
-        if (test.mangle.properties) {
-            output = U.mangle_properties(output, test.mangle.properties);
-        }
+        if (test.mangle.properties) U.mangle_properties(output, test.mangle.properties);
     }
     var output_code = make_code(output, output_options);
     if (expect != output_code) {
index af57c6b..169dcd1 100644 (file)
@@ -130,7 +130,7 @@ evaluate_string_length: {
     }
 }
 
-mangle_properties: {
+mangle_properties_1: {
     mangle = {
         properties: {
             keep_quoted: false,
@@ -152,6 +152,53 @@ mangle_properties: {
     }
 }
 
+mangle_properties_2: {
+    mangle = {
+        properties: {
+            reserved: [
+                "value",
+            ]
+        },
+    }
+    input: {
+        var o = {
+            prop1: 1,
+        };
+        Object.defineProperty(o, "prop2", {
+            value: 2,
+        });
+        Object.defineProperties(o, {
+            prop3: {
+                value: 3,
+            },
+        });
+        console.log("prop1", o.prop1, "prop1" in o);
+        console.log("prop2", o.prop2, o.hasOwnProperty("prop2"));
+        console.log("prop3", o.prop3, Object.getOwnPropertyDescriptor(o, "prop3").value);
+    }
+    expect: {
+        var o = {
+            o: 1,
+        };
+        Object.defineProperty(o, "p", {
+            value: 2,
+        });
+        Object.defineProperties(o, {
+            r: {
+                value: 3,
+            },
+        });
+        console.log("prop1", o.o, "o" in o);
+        console.log("prop2", o.p, o.hasOwnProperty("p"));
+        console.log("prop3", o.r, Object.getOwnPropertyDescriptor(o, "r").value);
+    }
+    expect_stdout: [
+        "prop1 1 true",
+        "prop2 2 true",
+        "prop3 3 3",
+    ]
+}
+
 mangle_unquoted_properties: {
     options = {
         evaluate: true,