enhance `typeofs` (#3556)
authorAlex Lam S.L <alexlamsl@gmail.com>
Thu, 31 Oct 2019 00:00:04 +0000 (08:00 +0800)
committerGitHub <noreply@github.com>
Thu, 31 Oct 2019 00:00:04 +0000 (08:00 +0800)
lib/compress.js
test/compress/typeof.js

index a28c7a6..9e2b689 100644 (file)
@@ -1042,7 +1042,8 @@ merge(Compressor.prototype, {
 
     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");
     AST_SymbolRef.DEFMETHOD("is_declared", function(compressor) {
-        return !this.definition().undeclared
+        return this.defined
+            || !this.definition().undeclared
             || compressor.option("unsafe") && global_names[this.name];
     });
 
@@ -4570,6 +4571,49 @@ merge(Compressor.prototype, {
         return if_break_in_loop(self, compressor);
     });
 
+    function mark_locally_defined(condition, consequent, alternative, operator) {
+        if (!(condition instanceof AST_Binary)) return;
+        if (!(condition.left instanceof AST_String)) {
+            if (!operator) operator = condition.operator;
+            if (condition.operator != operator) return;
+            switch (operator) {
+              case "&&":
+              case "||":
+                mark_locally_defined(condition.left, consequent, alternative, operator);
+                mark_locally_defined(condition.right, consequent, alternative, operator);
+                break;
+            }
+            return;
+        }
+        if (!(condition.right instanceof AST_UnaryPrefix)) return;
+        if (condition.right.operator != "typeof") return;
+        var sym = condition.right.expression;
+        if (!is_undeclared_ref(sym)) return;
+        var body;
+        var undef = condition.left.getValue() == "undefined";
+        switch (condition.operator) {
+          case "==":
+            body = undef ? alternative : consequent;
+            break;
+          case "!=":
+            body = undef ? consequent : alternative;
+            break;
+          default:
+            return;
+        }
+        if (!body) return;
+        var def = sym.definition();
+        var tw = new TreeWalker(function(node) {
+            if (node instanceof AST_Scope) {
+                var parent = tw.parent();
+                if (parent instanceof AST_Call && parent.expression === node) return;
+                return true;
+            }
+            if (node instanceof AST_SymbolRef && node.definition() === def) node.defined = true;
+        });
+        body.walk(tw);
+    }
+
     OPT(AST_If, function(self, compressor) {
         if (is_empty(self.alternative)) self.alternative = null;
 
@@ -4711,6 +4755,7 @@ merge(Compressor.prototype, {
                 body: [ self, body ]
             }).optimize(compressor);
         }
+        if (compressor.option("typeofs")) mark_locally_defined(self.condition, self.body, self.alternative);
         return self;
     });
 
@@ -5698,7 +5743,7 @@ merge(Compressor.prototype, {
             // "undefined" == typeof x => undefined === x
             else if (compressor.option("typeofs")
                 && self.left instanceof AST_String
-                && self.left.value == "undefined"
+                && self.left.getValue() == "undefined"
                 && self.right instanceof AST_UnaryPrefix
                 && self.right.operator == "typeof") {
                 var expr = self.right.expression;
@@ -6089,6 +6134,14 @@ merge(Compressor.prototype, {
                 break;
             }
         }
+        if (compressor.option("typeofs")) switch (self.operator) {
+          case "&&":
+            mark_locally_defined(self.left, self.right, null, "&&");
+            break;
+          case "||":
+            mark_locally_defined(self.left, null, self.right, "||");
+            break;
+        }
         if (compressor.option("unsafe")) {
             var indexRight = is_indexFn(self.right);
             if (in_bool
@@ -6651,8 +6704,8 @@ merge(Compressor.prototype, {
             }).optimize(compressor);
         }
         var in_bool = compressor.option("booleans") && compressor.in_boolean_context();
-        if (is_true(self.consequent)) {
-            if (is_false(self.alternative)) {
+        if (is_true(consequent)) {
+            if (is_false(alternative)) {
                 // c ? true : false ---> !!c
                 return booleanize(condition);
             }
@@ -6660,11 +6713,11 @@ merge(Compressor.prototype, {
             return make_node(AST_Binary, self, {
                 operator: "||",
                 left: booleanize(condition),
-                right: self.alternative
+                right: alternative
             });
         }
-        if (is_false(self.consequent)) {
-            if (is_true(self.alternative)) {
+        if (is_false(consequent)) {
+            if (is_true(alternative)) {
                 // c ? false : true ---> !c
                 return booleanize(condition.negate(compressor));
             }
@@ -6672,26 +6725,26 @@ merge(Compressor.prototype, {
             return make_node(AST_Binary, self, {
                 operator: "&&",
                 left: booleanize(condition.negate(compressor)),
-                right: self.alternative
+                right: alternative
             });
         }
-        if (is_true(self.alternative)) {
+        if (is_true(alternative)) {
             // c ? x : true ---> !c || x
             return make_node(AST_Binary, self, {
                 operator: "||",
                 left: booleanize(condition.negate(compressor)),
-                right: self.consequent
+                right: consequent
             });
         }
-        if (is_false(self.alternative)) {
+        if (is_false(alternative)) {
             // c ? x : false ---> !!c && x
             return make_node(AST_Binary, self, {
                 operator: "&&",
                 left: booleanize(condition),
-                right: self.consequent
+                right: consequent
             });
         }
-
+        if (compressor.option("typeofs")) mark_locally_defined(condition, consequent, alternative);
         return self;
 
         function booleanize(node) {
index 8012383..c3c2e9e 100644 (file)
@@ -295,3 +295,117 @@ issue_2728_6: {
     }
     expect_stdout: "function undefined"
 }
+
+typeof_defined_1: {
+    options = {
+        side_effects: true,
+        typeofs: true,
+    }
+    input: {
+        "undefined" == typeof A && A;
+        "undefined" != typeof A && A;
+        "undefined" == typeof A || A;
+        "undefined" != typeof A || A;
+    }
+    expect: {
+        "undefined" == typeof A && A;
+        "undefined" != typeof A || A;
+    }
+}
+
+typeof_defined_2: {
+    options = {
+        side_effects: true,
+        typeofs: true,
+    }
+    input: {
+        "function" == typeof A && A;
+        "function" != typeof A && A;
+        "function" == typeof A || A;
+        "function" != typeof A || A;
+    }
+    expect: {
+        "function" != typeof A && A;
+        "function" == typeof A || A;
+    }
+}
+
+typeof_defined_3: {
+    options = {
+        side_effects: true,
+        typeofs: true,
+    }
+    input: {
+        "undefined" == typeof A && "undefined" == typeof B && (A, B);
+        "undefined" == typeof A && "undefined" != typeof B && (A, B);
+        "undefined" != typeof A && "undefined" == typeof B && (A, B);
+        "undefined" != typeof A && "undefined" != typeof B && (A, B);
+        "undefined" == typeof A && "undefined" == typeof B || (A, B);
+        "undefined" == typeof A && "undefined" != typeof B || (A, B);
+        "undefined" != typeof A && "undefined" == typeof B || (A, B);
+        "undefined" != typeof A && "undefined" != typeof B || (A, B);
+        "undefined" == typeof A || "undefined" == typeof B && (A, B);
+        "undefined" == typeof A || "undefined" != typeof B && (A, B);
+        "undefined" != typeof A || "undefined" == typeof B && (A, B);
+        "undefined" != typeof A || "undefined" != typeof B && (A, B);
+        "undefined" == typeof A || "undefined" == typeof B || (A, B);
+        "undefined" == typeof A || "undefined" != typeof B || (A, B);
+        "undefined" != typeof A || "undefined" == typeof B || (A, B);
+        "undefined" != typeof A || "undefined" != typeof B || (A, B);
+    }
+    expect: {
+        "undefined" == typeof A && "undefined" == typeof B && (A, B);
+        "undefined" == typeof A && "undefined" != typeof B && A;
+        "undefined" != typeof A && "undefined" == typeof B && B;
+        "undefined" == typeof A && "undefined" == typeof B || (A, B);
+        "undefined" == typeof A && "undefined" != typeof B || (A, B);
+        "undefined" != typeof A && "undefined" == typeof B || (A, B);
+        "undefined" != typeof A && "undefined" != typeof B || (A, B);
+        "undefined" == typeof A || "undefined" == typeof B && B;
+        "undefined" != typeof A || "undefined" == typeof B && (A, B);
+        "undefined" != typeof A || "undefined" != typeof B && A;
+        "undefined" == typeof A || "undefined" != typeof B || B;
+        "undefined" != typeof A || "undefined" == typeof B || A;
+        "undefined" != typeof A || "undefined" != typeof B || (A, B);
+    }
+}
+
+typeof_defined_4: {
+    options = {
+        side_effects: true,
+        typeofs: true,
+    }
+    input: {
+        "object" == typeof A && "object" == typeof B && (A, B);
+        "object" == typeof A && "object" != typeof B && (A, B);
+        "object" != typeof A && "object" == typeof B && (A, B);
+        "object" != typeof A && "object" != typeof B && (A, B);
+        "object" == typeof A && "object" == typeof B || (A, B);
+        "object" == typeof A && "object" != typeof B || (A, B);
+        "object" != typeof A && "object" == typeof B || (A, B);
+        "object" != typeof A && "object" != typeof B || (A, B);
+        "object" == typeof A || "object" == typeof B && (A, B);
+        "object" == typeof A || "object" != typeof B && (A, B);
+        "object" != typeof A || "object" == typeof B && (A, B);
+        "object" != typeof A || "object" != typeof B && (A, B);
+        "object" == typeof A || "object" == typeof B || (A, B);
+        "object" == typeof A || "object" != typeof B || (A, B);
+        "object" != typeof A || "object" == typeof B || (A, B);
+        "object" != typeof A || "object" != typeof B || (A, B);
+    }
+    expect: {
+        "object" == typeof A && "object" != typeof B && B;
+        "object" != typeof A && "object" == typeof B && A;
+        "object" != typeof A && "object" != typeof B && (A, B);
+        "object" == typeof A && "object" == typeof B || (A, B);
+        "object" == typeof A && "object" != typeof B || (A, B);
+        "object" != typeof A && "object" == typeof B || (A, B);
+        "object" != typeof A && "object" != typeof B || (A, B);
+        "object" == typeof A || "object" == typeof B && A;
+        "object" == typeof A || "object" != typeof B && (A, B);
+        "object" != typeof A || "object" != typeof B && B;
+        "object" == typeof A || "object" == typeof B || (A, B);
+        "object" == typeof A || "object" != typeof B || A;
+        "object" != typeof A || "object" == typeof B || B;
+    }
+}