Repository: mishoo/UglifyJS (JavaScript)

Issue:
feature request: add WeakMap, Map, Set, WeakSet to known `unsafe` side effect free classes
WeakMap, Map, Set, and WeakSet are known globals present in ES6+.

```
$ echo 'WeakMap, Map, Set, WeakSet; String; Number;' | node
```
```
$ echo 'WeakMap, Map, Set, WeakSet; String; Number;' | uglify-js -c toplevel,unsafe
WeakMap,Map,Set,WeakSet;
```

Instantiating them are side effect free:
```
$ echo 'new WeakMap; new Map; new Set; new WeakSet; new String; new Number;' | node
```
```
$ echo 'new WeakMap; new Map; new Set; new WeakSet; new String; new Number;' | uglify-js -c unsafe
new WeakMap,new Map,new Set,new WeakSet;
```

A top level `--ecma` flag could be useful to distinguish the target, as these container classes are not present in Node 0.10, for example:

```
$ echo 'new WeakMap; new Map; new Set; new WeakSet; new String; new Number;' | node-v0.10.41 

[stdin]:1
new WeakMap; new Map; new Set; new WeakSet; new String; new Number;
    ^
ReferenceError: WeakMap is not defined
```

Or these container classes could all be included in `unsafe` regardless of the target and just assume users would be using polyfills anyway.


Discussion:
Yeah they aren't very commonly re-/mis-used names anyway, so just adding them to `unsafe` should be fine.
Babel and other transpilers generate `new WeakMap`s and `new WeakSet`s to implement private/static fields when targeting ES2015. If such generated classes are unused then top level instances of these weak containers are left over after minification.
One thing we can't drop is `Set(...)` etc., since those will throw.



Patch:
diff --git a/lib/compress.js b/lib/compress.js
index 8ec9ab887c7..fea4459bd1c 100644
--- a/lib/compress.js
+++ b/lib/compress.js
@@ -1638,7 +1638,7 @@ merge(Compressor.prototype, {
         return node instanceof AST_SymbolRef && node.definition().undeclared;
     }
 
-    var global_names = makePredicate("Array Boolean clearInterval clearTimeout console Date decodeURI decodeURIComponent encodeURI encodeURIComponent Error escape eval EvalError Function isFinite isNaN JSON Math Number parseFloat parseInt RangeError ReferenceError RegExp Object setInterval setTimeout String SyntaxError TypeError unescape URIError");
+    var global_names = makePredicate("Array Boolean clearInterval clearTimeout console Date decodeURI decodeURIComponent encodeURI encodeURIComponent Error escape eval EvalError Function isFinite isNaN JSON Map Math Number parseFloat parseInt RangeError ReferenceError RegExp Object Set setInterval setTimeout String SyntaxError TypeError unescape URIError WeakMap WeakSet");
     AST_SymbolRef.DEFMETHOD("is_declared", function(compressor) {
         return this.defined
             || !this.definition().undeclared
@@ -4800,10 +4800,14 @@ merge(Compressor.prototype, {
     });
 
     var global_pure_fns = makePredicate("Boolean decodeURI decodeURIComponent Date encodeURI encodeURIComponent Error escape EvalError isFinite isNaN Number Object parseFloat parseInt RangeError ReferenceError String SyntaxError TypeError unescape URIError");
+    var global_pure_constructors = makePredicate("Map Set WeakMap WeakSet");
     AST_Call.DEFMETHOD("is_expr_pure", function(compressor) {
         if (compressor.option("unsafe")) {
             var expr = this.expression;
-            if (is_undeclared_ref(expr) && global_pure_fns[expr.name]) return true;
+            if (is_undeclared_ref(expr)) {
+                if (global_pure_fns[expr.name]) return true;
+                if (this instanceof AST_New && global_pure_constructors[expr.name]) return true;
+            }
             if (expr instanceof AST_Dot && is_undeclared_ref(expr.expression)) {
                 var static_fn = static_fns[expr.expression.name];
                 return static_fn && (static_fn[expr.property]


Test patch:
diff --git a/test/compress/side_effects.js b/test/compress/side_effects.js
index 0e6a7442b9c..63a6270367d 100644
--- a/test/compress/side_effects.js
+++ b/test/compress/side_effects.js
@@ -198,6 +198,36 @@ global_fns: {
     ]
 }
 
+global_constructors: {
+    options = {
+        side_effects: true,
+        unsafe: true,
+    }
+    input: {
+        Map;
+        new Map(console.log("foo"));
+        Set;
+        new Set(console.log("bar"));
+        WeakMap;
+        new WeakMap(console.log("baz"));
+        WeakSet;
+        new WeakSet(console.log("moo"));
+    }
+    expect: {
+        console.log("foo");
+        console.log("bar");
+        console.log("baz");
+        console.log("moo");
+    }
+    expect_stdout: [
+        "foo",
+        "bar",
+        "baz",
+        "moo",
+    ]
+    node_version: ">=0.12"
+}
+
 unsafe_builtin_1: {
     options = {
         side_effects: true,