def test_obfuscate_no_global_recursive(self):
        node = es5(
            dedent("""
        (function named(param1, param2) {
          param1 = param1 * param2 - param2;
          param2--;
          if (param2 < 0) {
            return named(param1, param2);
          }
          return param1;
        })();
        """).strip())

        self.assertEqual(
            dedent("""
        (function named(b, a) {
          b = b * a - a;
          a--;
          if (a < 0) {
            return named(b, a);
          }
          return b;
        })();
        """).lstrip(), ''.join(c.text for c in Unparser(rules=(
                default_rules,
                indent(indent_str='  '),
                obfuscate(obfuscate_globals=False),
            ))(node)))
    def test_obfuscate_try_catch_shadowed(self):
        node = es5(
            dedent("""
        var value = 1;
        try {
          console.log(value);
          throw Error("welp");
        }
        catch (value) {
          console.log(value);
        }
        """).strip())

        self.assertEqual(
            dedent("""
        var a = 1;
        try {
            console.log(a);
            throw Error("welp");
        }
        catch (a) {
            console.log(a);
        }
        """).lstrip(), ''.join(c.text for c in Unparser(rules=(
                default_rules,
                indent(indent_str='    '),
                obfuscate(obfuscate_globals=True),
            ))(node)))
    def test_functions_in_lists(self):
        node = es5(
            dedent("""
        (function main(root) {
          root.exports = [
            (function(module, exports) {
              module.exports = {};
            }),
            (function(module, exports) {
              exports.fun = 1;
            }),
          ];
        })(this);
        """).strip())

        self.assertEqual(
            dedent("""
        (function main(a) {
          a.exports = [(function(a, b) {
            a.exports = {};
          }), (function(b, a) {
            a.fun = 1;
          })];
        })(this);
        """).lstrip(), ''.join(c.text for c in Unparser(rules=(
                default_rules,
                indent(indent_str='  '),
                obfuscate(),
            ))(node)))
    def test_obfuscate_try_catch_scope_inside_catch(self):
        node = es5(
            dedent("""
        var value = 100;
        (function() {
          var dummy = 0;
          console.log(value);
          value = 1;
          try {
            console.log(value);
            throw Error("welp");
          }
          catch (exc) {
            var value = 2;
            var welped = value;
            welped = 'welped';
            (function() {
              console.log(exc);
              console.log(value);
              console.log(welped);
            })();
          }
          value = 3;
        })();
        console.log(value);
        """).strip())

        # note that dummy -> c because exc is a local, not counted in
        # the priority at the parent scope.
        self.assertEqual(
            dedent("""
        var value = 100;
        (function() {
          var c = 0;
          console.log(a);
          a = 1;
          try {
            console.log(a);
            throw Error("welp");
          }
          catch (d) {
            var a = 2;
            var b = a;
            b = 'welped';
            (function() {
              console.log(d);
              console.log(a);
              console.log(b);
            })();
          }
          a = 3;
        })();
        console.log(value);
        """).lstrip(), ''.join(c.text for c in Unparser(rules=(
                default_rules,
                indent(indent_str='  '),
                obfuscate(),
            ))(node)))
    def test_obfuscate_globals(self):
        node = es5(
            dedent("""
        var a_global = 1;
        (function(a_param) {
          var a_local = 1;
          a_local = a_param;
          a_local = a_global;
        })();
        """).strip())

        self.assertEqual(
            dedent("""
        var a_global = 1;
        (function(b) {
            var a = 1;
            a = b;
            a = a_global;
        })();
        """).lstrip(), ''.join(c.text for c in Unparser(rules=(
                default_rules,
                indent(indent_str='    '),
                obfuscate(obfuscate_globals=False),
            ))(node)))

        self.assertEqual(
            dedent("""
        var a = 1;
        (function(c) {
            var b = 1;
            b = c;
            b = a;
        })();
        """).lstrip(), ''.join(c.text for c in Unparser(rules=(
                default_rules,
                indent(indent_str='    '),
                obfuscate(obfuscate_globals=True),
            ))(node)))
    def test_obfuscate_no_shadow_funcname_not_mapped(self):
        node = es5(
            dedent("""
        function a(arg) {
        }
        """).strip())

        self.assertEqual(
            dedent("""
        function a(b) {
        }
        """).lstrip(), ''.join(c.text for c in Unparser(rules=(
                default_rules,
                indent(indent_str='  '),
                obfuscate(obfuscate_globals=False, shadow_funcname=False),
            ))(node)))
    def test_multiple_reuse(self):
        tree = es5(
            dedent("""
        (function() {
          var foo = 1;
          var bar = 2;
          bar = 3;
        })(this);
        """).strip())
        obfuscator_unparser = Unparser(rules=(
            minimum_rules,
            obfuscate(),
        ))

        self.assertEqual(
            ''.join(c.text for c in obfuscator_unparser(tree)),
            ''.join(c.text for c in obfuscator_unparser(tree)),
        )
    def test_obfuscate_try_catch_vardeclare_inside_catch(self):
        node = es5(
            dedent("""
        var value = 100;
        (function() {
          console.log(value);
          value = 1;
          try {
            console.log(value);
            throw Error("welp");
          }
          catch (exc) {
            var value = 2;
            console.log(value);
          }
          value = 3;
        })();
        console.log(value);
        """).strip())

        self.assertEqual(
            dedent("""
        var value = 100;
        (function() {
          console.log(a);
          a = 1;
          try {
            console.log(a);
            throw Error("welp");
          }
          catch (b) {
            var a = 2;
            console.log(a);
          }
          a = 3;
        })();
        console.log(value);
        """).lstrip(), ''.join(c.text for c in Unparser(rules=(
                default_rules,
                indent(indent_str='  '),
                obfuscate(),
            ))(node)))
    def test_no_shadow_children(self):
        node = es5(
            dedent("""
        var some_value = 1;
        (function(param) {
          a.is_not_declared_in_this_file(param);
        })();
        """).strip())

        self.assertEqual(
            dedent("""
        var b = 1;
        (function(b) {
            a.is_not_declared_in_this_file(b);
        })();
        """).lstrip(), ''.join(c.text for c in Unparser(rules=(
                default_rules,
                indent(indent_str='    '),
                obfuscate(obfuscate_globals=True),
            ))(node)))
    def test_obfuscate_skip(self):
        node = es5(
            dedent("""
        (function(a_param) {
          var a_local = a_param;
        })();
        """).strip())

        self.assertEqual(
            dedent("""
        (function(c) {
            var d = c;
        })();
        """).lstrip(), ''.join(c.text for c in Unparser(rules=(
                default_rules,
                indent(indent_str='    '),
                obfuscate(reserved_keywords=(
                    'a',
                    'b',
                )),
            ))(node)))
    def test_obfuscate_try_catch_no_declare(self):
        node = es5(
            dedent("""
        var value = 100;
        (function() {
          value = 1;
          try {
            console.log(value);
            throw Error("welp");
          }
          catch (value) {
            value = 0;
            console.log(value);
          }
          console.log(value);
        })();
        console.log(value);
        """).strip())

        self.assertEqual(
            dedent("""
        var value = 100;
        (function() {
          value = 1;
          try {
            console.log(value);
            throw Error("welp");
          }
          catch (a) {
            a = 0;
            console.log(a);
          }
          console.log(value);
        })();
        console.log(value);
        """).lstrip(), ''.join(c.text for c in Unparser(rules=(
                default_rules,
                indent(indent_str='  '),
                obfuscate(),
            ))(node)))
    def test_no_shadow_parent_remapped(self):
        node = es5(
            dedent("""
        var a = c;
        (function(param) {
          b.is_not_declared_in_this_file(a, param);
        })(c);
        """).strip())

        # param can remap to c because the global 'c' not used in that
        # scope.
        self.assertEqual(
            dedent("""
        var a = c;
        (function(c) {
            b.is_not_declared_in_this_file(a, c);
        })(c);
        """).lstrip(), ''.join(c.text for c in Unparser(rules=(
                default_rules,
                indent(indent_str='    '),
                obfuscate(obfuscate_globals=True),
            ))(node)))
    def test_obfuscate_no_global_recursive_redeclared_no_shadow_funcname(self):
        node = es5(
            dedent("""
        (function $() {
          $();
          (function $() {
            var foo = 1;
          })();
        })();
        """).strip())

        self.assertEqual(
            dedent("""
        (function $() {
          a();
          (function a() {
            var b = 1;
          })();
        })();
        """).lstrip(), ''.join(c.text for c in Unparser(rules=(
                default_rules,
                indent(indent_str='  '),
                obfuscate(obfuscate_globals=False, shadow_funcname=False),
            ))(node)))
    def test_multi_scope(self):
        node = es5(
            dedent("""
        (function(value) {
          var parent = 1;
          (function(value) {
            var child = 2;
            (function(value) {
              var grandchild = 3;
              (function(value) {
                var greatgrandchild = 4;
                (function(value) {
                  var result = 1;
                  // using greatgrandchild a lot to ensure priority
                  // mucking
                  console.log(parent);
                  console.log(greatgrandchild);
                  console.log(greatgrandchild * value);
                  console.log(greatgrandchild * parent);
                  console.log(greatgrandchild * greatgrandchild);
                })(value);
              })(value);
            })(value);
          })(value);
        })(0);
        """).strip())

        # Note that grandchild and greatgrandchild never had any of
        # their local variables referenced by any of their nested
        # scopes, their values got shadowed in the greatgrandchild scope
        # before both that and parent is used in the innermost scope.
        # Also, note that since greatgrandchild stopped propagating the
        # reference usage upwards, even though it has a lot more usage,
        # it never will claim priority over the parent at the parent
        # scope since parent got mapped to 'a' thus forcing
        # greatgrandchild to map to 'b', the next lowest value symbol.
        self.assertEqual(
            dedent("""
        (function(b) {
          var a = 1;
          (function(b) {
            var c = 2;
            (function(b) {
              var c = 3;
              (function(c) {
                var b = 4;
                (function(c) {
                  var d = 1;
                  console.log(a);
                  console.log(b);
                  console.log(b * c);
                  console.log(b * a);
                  console.log(b * b);
                })(c);
              })(b);
            })(b);
          })(b);
        })(0);
        """).lstrip(), ''.join(c.text for c in Unparser(rules=(
                default_rules,
                indent(indent_str='  '),
                obfuscate(),
            ))(node)))