use swc_common::chain;
use swc_ecma_parser::Syntax;
use swc_ecma_transforms_compat::es2015::spread;
use swc_ecma_transforms_compat::es2018::object_rest_spread;
use swc_ecma_transforms_testing::test;
use swc_ecma_transforms_testing::test_exec;
use swc_ecma_visit::Fold;

fn syntax() -> Syntax {
    Syntax::default()
}

fn tr() -> impl Fold {
    object_rest_spread()
}

test!(
    syntax(),
    |_| tr(),
    issue_233,
    "const foo = () => ({ x, ...y }) => y",
    "const foo = ()=>(_param)=>{
        var { x } = _param, y = _objectWithoutProperties(_param, ['x']);
        return y;
    };"
);

test!(
    syntax(),
    |_| tr(),
    issue_239,
    "class Foo {
  constructor ({ ...bar }) {}
}",
    "class Foo{
    constructor(_param){
        var bar = _extends({
        }, _param);
    }
}"
);

// object rest spread pass should not touch rest in parameters and spread in
// args.
test!(
    syntax(),
    |_| tr(),
    issue_227,
    "export default function fn1(...args) {
  fn2(...args);
}",
    r#"export default function fn1(...args) {
  fn2(...args);
}"#
);

test!(
    syntax(),
    |_| tr(),
    issue_162,
    r#"
export const good = {
  a(bad1) {
    (...bad2) => { };
  }
};
"#,
    r#"
export const good = {
    a (bad1) {
        (...bad2)=>{
        };
    }
};
"#
);

test!(
    syntax(),
    |_| tr(),
    issue_181,
    r#"
const fn = ({ a, ...otherProps }) => otherProps;
"#,
    r#"
const fn = (_param)=>{
  var { a  } = _param, otherProps = _objectWithoutProperties(_param, ['a']);
  return otherProps;
};
"#
);

test!(
    syntax(),
    |_| tr(),
    rest_function_array,
    r#"
function foo([{...bar}]) {
}
"#,
    r#"
function foo([_param]) {
  var bar = _extends({}, _param);
}

"#
);

test!(
    syntax(),
    |_| tr(),
    rest_var_basic,
    r#"
var { a , ...b } = _ref;
"#,
    r#"
var { a } = _ref, b = _objectWithoutProperties(_ref, ['a']);
"#
);

test_exec!(
    syntax(),
    |_| tr(),
    rest_assignment_exec,
    r#"
let foo = {
  a: 1,
  b: 2,
};

({ a, ...c } = foo);
expect(a).toBe(1);
expect(c).toEqual({b: 2});
"#
);

test_exec!(
    syntax(),
    |_| tr(),
    rest_catch_exec,
    r#"
try {
  throw {
    a2: 1,
    b2: 2,
    c2: {
      c3: 'c3',
      extras: 3,
    }
  };
} catch({a2, b2, c2: { c3, ...c4 }}) {
  expect(a2).toBe(1);
  expect(b2).toBe(2);
  expect(c3).toBe('c3');
  expect(c4).toEqual({extras: 3});
}"#
);

test!(
    syntax(),
    |_| tr(),
    rest_assignment_expression,
    r#"({ a1 } = c1);
({ a2, ...b2 } = c2);

console.log({ a3, ...b3 } = c3);"#,
    r#"
({ a1  } = c1);
var _c2;
_c2 = c2, b2 = _objectWithoutProperties(_c2, ['a2']), ({ a2  } = _c2), _c2;
var _c3;
console.log(( _c3 = c3, b3 = _objectWithoutProperties(_c3, ['a3']), { a3  } = _c3, _c3));
"#
);

test!(
    syntax(),
    |_| tr(),
    rest_catch_clause,
    r#"
try {} catch({ ...a34 }) {}
try {} catch({a1, ...b1}) {}
try {} catch({a2, b2, ...c2}) {}
try {} catch({a2, b2, c2: { c3, ...c4 }}) {}

// Unchanged
try {} catch(a) {}
try {} catch({ b }) {}
"#,
    r#"
try {
} catch (_param) {
    var a34 = _extends({
    }, _param);
}
try {
} catch (_param) {
    var { a1  } = _param, b1 = _objectWithoutProperties(_param, ['a1']);
}
try {
} catch (_param) {
    var { a2 , b2  } = _param, c2 = _objectWithoutProperties(_param, ['a2', 'b2']);
}
try {
} catch (_param) {
    var { a2 , b2 , c2: { c3  }  } = _param, c4 = _objectWithoutProperties(_param.c2, ['c3']);
}
try {
} catch (a) {
}
try {
} catch ({ b  }) {
}"#
);

test!(
    syntax(),
    |_| tr(),
    rest_export,
    r#"
// ExportNamedDeclaration
export var { b, ...c } = asdf2;
// Skip
export var { bb, cc } = ads;
export var [ dd, ee ] = ads;
"#,
    r#"
// ExportNamedDeclaration
var {
  b
} = asdf2,
    c = _objectWithoutProperties(asdf2, ["b"]); // Skip

export { b, c };
export var {
  bb,
  cc
} = ads;
export var [dd, ee] = ads;
"#
);

test!(
    syntax(),
    |_| chain!(
        tr(),
        spread(spread::Config {
            ..Default::default()
        })
    ),
    rest_for_x,
    r#"
// ForXStatement
for (var {a, ...b} of []) {}
for ({a, ...b} of []) {}
async function a() {
  for await ({a, ...b} of []) {}
}

// skip
for ({a} in {}) {}
for ({a} of []) {}
async function a() {
  for await ({a} of []) {}
}

for (a in {}) {}
for (a of []) {}
async function a() {
  for await (a of []) {}
}
"#,
    r#"
// ForXStatement
for (var _ref of []) {
  var {
    a
  } = _ref,
      b = _objectWithoutProperties(_ref, ["a"]);
}

for (var _ref1 of []) {
  var {
    a
  } = _ref1,
      b = _objectWithoutProperties(_ref1, ["a"]);
}

async function a() {
  for await (var _ref2 of []) {
    var {
      a
    } = _ref2,
        b = _objectWithoutProperties(_ref2, ["a"]);
  }
} // skip


for ({
  a
} in {}) {}

for ({
  a
} of []) {}

async function a() {
  for await ({
    a
  } of []) {}
}

for (a in {}) {}

for (a of []) {}

async function a() {
  for await (a of []) {}
}
"#
);

test_exec!(
    ignore,
    syntax(),
    |_| tr(),
    rest_impure_computed_exec,
    r#"
var key, x, y, z;
// impure
key = 1;
var { [key++]: y, ...x } = { 1: 1, a: 1 };
expect(x).toEqual({ a: 1 });
expect(key).toBe(2);
expect(1).toBe(y);

// takes care of the order

key = 1;
var { [++key]: y, [++key]: z, ...rest} = {2: 2, 3: 3};
expect(y).toBe(2);
expect(z).toBe(3);

// pure, computed property should remain as-is
key = 2;
({ [key]: y, z, ...x } = {2: "two", z: "zee"});
expect(y).toBe("two");
expect(x).toEqual({});
expect(z).toBe("zee");
"#
);

test!(
    ignore,
    syntax(),
    |_| tr(),
    rest_impure_computed,
    r#"
var key, x, y, z;
// impure
key = 1;
var { [key++]: y, ...x } = { 1: 1, a: 1 };
expect(x).toEqual({ a: 1 });
expect(key).toBe(2);
expect(y).toBe(1);

// takes care of the order

key = 1;
var { [++key]: y, [++key]: z, ...rest} = {2: 2, 3: 3};
expect(y).toBe(2);
expect(z).toBe(3);

// pure, computed property should remain as-is
key = 2;
({ [key]: y, z, ...x } = {2: "two", z: "zee"});
expect(y).toBe("two");
expect(x).toEqual({});
expect(z).toBe("zee");
"#,
    r#"
var key, x, y, z; // impure

key = 1;

var _$a = {
  1: 1,
  a: 1
},
    _ref = key++,
    {
  [_ref]: y
} = _$a,
    x = _objectWithoutProperties(_$a, [_ref].map(_toPropertyKey));

expect(x).toEqual({
  a: 1
});
expect(key).toBe(2);
expect(y).toBe(1); // takes care of the order

key = 1;

var _$ = {
  2: 2,
  3: 3
},
    _ref2 = ++key,
    _ref3 = ++key,
    {
  [_ref2]: y,
  [_ref3]: z
} = _$,
    rest = _objectWithoutProperties(_$, [_ref2, _ref3].map(_toPropertyKey));

expect(y).toBe(2);
expect(z).toBe(3); // pure, computed property should remain as-is

key = 2;
var _$z = {
  2: "two",
  z: "zee"
};
({
  [key]: y,
  z
} = _$z);
x = _objectWithoutProperties(_$z, [key, "z"].map(_toPropertyKey));
_$z;
expect(y).toBe("two");
expect(x).toEqual({});
expect(z).toBe("zee");"#
);

test!(
    syntax(),
    |_| tr(),
    rest_nested_2,
    r#"
const test = {
  foo: {
    bar: {
      baz: {
        a: {
          x: 1,
          y: 2,
          z: 3,
        },
      },
    },
  },
};

const { foo: { bar: { baz: { a: { x, ...other } } } } } = test;
"#,
    r#"
const test = {
  foo: {
    bar: {
      baz: {
        a: {
          x: 1,
          y: 2,
          z: 3
        }
      }
    }
  }
};
const {
  foo: {
    bar: {
      baz: {
        a: {
          x
        }
      }
    }
  }
} = test,
      other = _objectWithoutProperties(test.foo.bar.baz.a, ["x"]);"#
);

test!(
    syntax(),
    |_| tr(),
    rest_nested_computed_key,
    r#"
const {
  [({ ...rest }) => {
    let { ...b } = {};
  }]: a,
  [({ ...d } = {})]: c,
} = {};
"#,
    r#"
var _tmp;
const _ref = {
}, key = (_param)=>{
    var rest = _extends({
    }, _param);
    let b = _extends({
    }, {
    });
}, key1 = (_tmp = {
}, d = _extends({
}, _tmp), _tmp), { [key]: a , [key1]: c  } = _ref;

"#
);

test_exec!(
    syntax(),
    |_| tr(),
    rest_nested_default_value_exec,
    r#"
const {
  a = ({ ...rest }) => {
    expect(rest).toEqual({})
    let { ...b } = {};
  },
  c = ({ ...d } = {}),
} = {};
a()
expect(c).toEqual({})
expect(d).toEqual({})
"#
);

test!(
    syntax(),
    |_| tr(),
    rest_nested_default_value,
    r#"
const {
  a = ({ ...rest }) => {
    let { ...b } = {};
  },
  c = ({ ...d } = {}),
} = {};
"#,
    r#"
var _tmp;
const _ref = {
}, { a =(_param)=>{
    var rest = _extends({
    }, _param);
    let b = _extends({
    }, {
    });
} , c =(_tmp = {
}, d = _extends({
}, _tmp), _tmp)  } = _ref;

"#
);

test_exec!(
    syntax(),
    |_| tr(),
    rest_nested_order_exec,
    r#"
var result = "";

var obj = {
  get foo() {
    result += "foo"
  },
  a: {
    get bar() {
      result += "bar";
    }
  },
  b: {
    get baz() {
      result += "baz";
    }
  }
};

var { a: { ...bar }, b: { ...baz }, ...foo } = obj;

expect(result).toBe("barbazfoo");
"#
);

test!(
    syntax(),
    |_| tr(),
    rest_nested_order,
    r#"
const { a: { ...bar }, b: { ...baz }, ...foo } = obj;
"#,
    r#"
const bar = _extends({}, obj.a), baz = _extends({}, obj.b), foo =
    _objectWithoutProperties(obj, ['a', 'b']);
"#
);

test!(
    syntax(),
    |_| tr(),
    rest_nested_1,
    r#"
const defunct = {
  outer: {
    inner: {
      three: 'three',
      four: 'four'
    }
  }
}

const { outer: { inner: { three, ...other } } } = defunct
"#,
    r#"
const defunct = {
  outer: {
    inner: {
      three: 'three',
      four: 'four'
    }
  }
};
const {
  outer: {
    inner: {
      three
    }
  }
} = defunct,
      other = _objectWithoutProperties(defunct.outer.inner, ["three"]);
"#
);

test_exec!(
    ignore,
    syntax(),
    |_| tr(),
    rest_non_string_computed_exec,
    r#"
const a = {
  "3": "three",
  "foo": "bar"
}

const {
  [3]: omit,
  ...rest
} = a;

expect(rest).toEqual({"foo": "bar"});
expect(omit).toBe("three");

const [k1, k2, k3, k4, k5] = [null, undefined, true, false, {toString() { return "warrior"; }}];
const c = {
  [k1]: "1",
  [k2]: "2",
  [k3]: "3",
  [k4]: "4",
  [k5]: "5"
};

const {
  [k1]: v1,
  [k2]: v2,
  [k3]: v3,
  [k4]: v4,
  [k5]: v5,
  ...vrest
} = c;

expect(v1).toBe("1");
expect(v2).toBe("2");
expect(v3).toBe("3");
expect(v4).toBe("4");
expect(v5).toBe("5");
expect(vrest).toEqual({});

// shouldn't convert symbols to strings
const sx = Symbol();
const sy = Symbol();

const d = {
  [sx]: "sx",
  [sy]: "sy"
}

const {
  [sx]: dx,
  [sy]: dy
} = d;

expect(dx).toBe("sx");
expect(dy).toBe("sy");
"#
);

test!(
    ignore,
    syntax(),
    |_| tr(),
    rest_non_string_computed,
    r#"
const a = {
  "3": "three",
  "foo": "bar"
}

const {
  [3]: omit,
  ...rest
} = a;

expect(rest).toEqual({"foo": "bar"});
expect(omit).toBe("three");

const [k1, k2, k3, k4, k5] = [null, undefined, true, false, {toString() { return "warrior"; }}];
const c = {
  [k1]: "1",
  [k2]: "2",
  [k3]: "3",
  [k4]: "4",
  [k5]: "5"
};

const {
  [k1]: v1,
  [k2]: v2,
  [k3]: v3,
  [k4]: v4,
  [k5]: v5,
  ...vrest
} = c;

expect(v1).toBe("1");
expect(v2).toBe("2");
expect(v3).toBe("3");
expect(v4).toBe("4");
expect(v5).toBe("5");
expect(vrest).toEqual({});

// shouldn't convert symbols to strings
const sx = Symbol();
const sy = Symbol();

const d = {
  [sx]: "sx",
  [sy]: "sy"
}

const {
  [sx]: dx,
  [sy]: dy
} = d;

expect(dx).toBe("sx");
expect(dy).toBe("sy");"#,
    r#"
const a = {
  "3": "three",
  "foo": "bar"
};
const {
  [3]: omit
} = a,
      rest = _objectWithoutProperties(a, ["3"]);
expect(rest).toEqual({
  "foo": "bar"
});
expect(omit).toBe("three");
const [k1, k2, k3, k4, k5] = [null, undefined, true, false, {
  toString() {
    return "warrior";
  }

}];
const c = {
  [k1]: "1",
  [k2]: "2",
  [k3]: "3",
  [k4]: "4",
  [k5]: "5"
};
const {
  [k1]: v1,
  [k2]: v2,
  [k3]: v3,
  [k4]: v4,
  [k5]: v5
} = c,
      vrest = _objectWithoutProperties(c, [k1, k2, k3, k4, k5].map(_toPropertyKey));
expect(v1).toBe("1");
expect(v2).toBe("2");
expect(v3).toBe("3");
expect(v4).toBe("4");
expect(v5).toBe("5");
expect(vrest).toEqual({}); // shouldn't convert symbols to strings

const sx = Symbol();
const sy = Symbol();
const d = {
  [sx]: "sx",
  [sy]: "sy"
};
const {
  [sx]: dx,
  [sy]: dy
} = d;
expect(dx).toBe("sx");
expect(dy).toBe("sy");"#
);

test!(
    syntax(),
    |_| tr(),
    rest_parameters,
    r#"
function a({ ...a34 }) {}
function a2({a1, ...b1}) {}
function a3({a2, b2, ...c2}) {}
function a4({a3, ...c3}, {a5, ...c5}) {}
function a5({a3, b2: { ba1, ...ba2 }, ...c3}) {}
function a6({a3, b2: { ba1, ...ba2 } }) {}
function a7({a1 = 1, ...b1} = {}) {}
function a8([{...a1}]) {}
function a9([{a1, ...a2}]) {}
function a10([a1, {...a2}]) {}
// Unchanged
function b(a) {}
function b2(a, ...b) {}
function b3({ b }) {}
"#,
    r#"
function a(_param) {
  var a34 = _extends({}, _param);
}

function a2(_param) {
  var {
    a1
  } = _param,
      b1 = _objectWithoutProperties(_param, ["a1"]);
}

function a3(_param) {
  var {
    a2,
    b2
  } = _param,
      c2 = _objectWithoutProperties(_param, ["a2", "b2"]);
}

function a4(_param, _param1) {
  var { a3 } = _param, c3 = _objectWithoutProperties(_param, ['a3']),
    { a5  } = _param1, c5 = _objectWithoutProperties(_param1, ['a5']);

}

function a5(_param) {
  var {
    a3,
    b2: {
      ba1
    }
  } = _param,
      ba2 = _objectWithoutProperties(_param.b2, ["ba1"]),
      c3 = _objectWithoutProperties(_param, ["a3", "b2"]);
}

function a6(_param) {
  var {
    a3,
    b2: {
      ba1
    }
  } = _param,
      ba2 = _objectWithoutProperties(_param.b2, ["ba1"]);
}

function a7(_param = {
}) {
    var { a1 =1  } = _param, b1 = _objectWithoutProperties(_param, ['a1']);
}

function a8([_param]) {
    var a1 = _extends({
    }, _param);
}

function a9([_param]) {
  var { a1 } = _param, a2 = _objectWithoutProperties(_param, ["a1"]);
}

function a10([a1, _param]) {
  var a2 = _extends({}, _param);
}

// Unchanged
function b(a) {}

function b2(a, ...b) {}

function b3({
  b
}) {}"#
);

test_exec!(
    syntax(),
    |_| tr(),
    rest_symbol_exec,
    r#"
const sym = Symbol("test");
const sym2 = Symbol("not enumerable");

const src = { a: "string" };
Object.defineProperty(src, "b", { value: "not enumerable" })
Object.defineProperty(src, sym, { enumerable: true, value: "symbol" });
Object.defineProperty(src, sym2, { value: "not enumerable" });

const {...rest} = src;

expect(rest[sym]).toBe("symbol");
expect(rest.a).toBe("string");
expect(Object.getOwnPropertyNames(rest)).toEqual(["a"]);
expect(Object.getOwnPropertySymbols(rest)).toEqual([sym]);

const { [sym]: dst, ...noSym } = src;

expect(dst).toBe("symbol");
expect(noSym.a).toBe("string");
expect(Object.getOwnPropertySymbols(noSym)).toEqual([]);"#
);

test!(
    ignore,
    syntax(),
    |_| tr(),
    rest_symbol,
    r#"
let {
  [Symbol.for("foo")]: foo,
  ...rest
} = {};

({ [Symbol.for("foo")]: foo, ...rest } = {});

if ({ [Symbol.for("foo")]: foo, ...rest } = {}) {}
"#,
    r#"
var _ref3, _Symbol$for3;

let _ref = {},
    _Symbol$for = Symbol.for("foo"),
    {
  [_Symbol$for]: foo
} = _ref,
    rest = _objectWithoutProperties(_ref, [_Symbol$for].map(_toPropertyKey));

var _ref2 = {};

var _Symbol$for2 = Symbol.for("foo");

({
  [_Symbol$for2]: foo
} = _ref2);
rest = _objectWithoutProperties(_ref2, [_Symbol$for2].map(_toPropertyKey));
_ref2;

if (_ref3 = {}, _Symbol$for3 = Symbol.for("foo"), ({
  [_Symbol$for3]: foo
} = _ref3), rest = _objectWithoutProperties(_ref3,
    [_Symbol$for3].map(_toPropertyKey)), _ref3) {}
"#
);

test!(
    syntax(),
    |_| tr(),
    rest_variable_destructuring_1,
    r#"
var z = {};
var { ...x } = z;
var { ...a } = { a: 1 };
var { ...x } = a.b;
var { ...x } = a();
var {x1, ...y1} = z;
x1++;
var { [a]: b, ...c } = z;
var {x1, ...y1} = z;
let {x2, y2, ...z2} = z;
const {w3, x3, y3, ...z4} = z;
"#,
    r#"
var z = {};
var x = _extends({}, z);
var a = _extends({
}, {
    a: 1
});
var x = _extends({
}, a.b);
var x = _extends({
}, a());
var {
  x1
} = z,
    y1 = _objectWithoutProperties(z, ["x1"]);
x1++;
var {
  [a]: b
} = z,
    c = _objectWithoutProperties(z, [a].map(_toPropertyKey));
var {
  x1
} = z,
    y1 = _objectWithoutProperties(z, ["x1"]);
let {
  x2,
  y2
} = z,
    z2 = _objectWithoutProperties(z, ["x2", "y2"]);
const {
  w3,
  x3,
  y3
} = z,
      z4 = _objectWithoutProperties(z, ["w3", "x3", "y3"]);
"#
);

test!(
    syntax(),
    |_| tr(),
    rest_variable_destructuring_2,
    r#"
let {
  x: { a: xa, [d]: f, ...asdf },
  y: { ...d },
  ...g
} = complex;
"#,
    r#"
let {
  x: {
    a: xa,
    [d]: f
  }
} = complex,
    asdf = _objectWithoutProperties(complex.x, ["a", d].map(_toPropertyKey)),
    d = _extends({}, complex.y),
    g = _objectWithoutProperties(complex, ["x", "y"]);
"#
);

test!(
    syntax(),
    |_| tr(),
    rest_variable_destructuring_3,
    r#"
let { x4: { ...y4 } } = z;
"#,
    r#"
let y4 = _extends({}, z.x4);
"#
);

test_exec!(
    syntax(),
    |_| tr(),
    rest_with_array_rest_exec,
    r#"
let [{ a, ...foo}, ...bar] = [{ a: 1, b:2 }, 2, 3, 4];
expect(a).toBe(1)
expect(foo).toEqual({b: 2});
expect(bar).toEqual([2, 3, 4]);
"#
);

test_exec!(
    syntax(),
    |_| tr(),
    rest_with_array_rest_exec_2,
    r#"
let {
  a: [b, ...arrayRest],
  c = function(...functionRest){},
  ...objectRest
} = {
  a: [1, 2, 3, 4],
  d: "oyez"
};

expect(b).toBe(1);
expect(arrayRest).toEqual([2, 3, 4]);
expect(objectRest).toEqual({d: 'oyez'})
"#
);

test!(
    syntax(),
    |_| tr(),
    rest_with_array_rest,
    r#"
let {
  a: [b, ...arrayRest],
  c = function(...functionRest){},
  ...objectRest
} = {
  a: [1, 2, 3, 4],
  d: "oyez"
};
"#,
    r#"
let _ref = {
     a: [1, 2, 3, 4], d: 'oyez'
}, { a: [b, ...arrayRest] , c =function(...functionRest) {
}  } = _ref, objectRest = _objectWithoutProperties(_ref, ['a', 'c']);
"#
);

test!(
    syntax(),
    |_| tr(),
    spread_assignment,
    r#"
z = { x, ...y };

z = { x, w: { ...y } };
"#,
    r#"
z = _objectSpread({
  x
}, y);
z = {
  x,
  w: _objectSpread({}, y)
};
"#
);

test!(
    syntax(),
    |_| tr(),
    object_spread_expression,
    r#"
({ x, ...y, a, ...b, c });

({ ...Object.prototype });

({ ...{ foo: 'bar' } });

({ ...{ get foo () { return 'foo' } } });
"#,
    r#"
_objectSpread({
  x
}, y, {
  a
}, b, {
  c
});

_objectSpread({}, Object.prototype);

_objectSpread({}, {
  foo: 'bar'
});

_objectSpread({}, {
  get foo() {
    return 'foo';
  }

});
"#
);

test_exec!(
    syntax(),
    |_| tr(),
    spread_no_object_assign_exec,
    r#"
Object.defineProperty(Object.prototype, 'NOSET', {
  set(value) {
    // noop
  },
});

Object.defineProperty(Object.prototype, 'NOWRITE', {
  writable: false,
  value: 'abc',
});

const obj = { NOSET: 123 };
// this wouldn't work as expected if transformed as Object.assign (or equivalent)
// because those trigger object setters (spread don't)
const objSpread = { ...obj };

const obj2 = { NOSET: 123, NOWRITE: 456 };
// this line would throw `TypeError: Cannot assign to read only property 'NOWRITE'`
// if transformed as Object.assign (or equivalent) because
// those use *assignment* for creating properties
// (spread defines them)
const obj2Spread = { ...obj2 };

expect(objSpread).toEqual(obj);
expect(obj2Spread).toEqual(obj2);

const KEY = Symbol('key');
const obj3Spread = { ...{ get foo () { return 'bar' } }, [KEY]: 'symbol' };
expect(Object.getOwnPropertyDescriptor(obj3Spread, 'foo').value).toBe('bar');
expect(Object.getOwnPropertyDescriptor(obj3Spread, KEY).value).toBe('symbol');

const obj4Spread = { ...Object.prototype };
expect(Object.getOwnPropertyDescriptor(obj4Spread, 'hasOwnProperty')).toBeUndefined();

expect(() => ({ ...null, ...undefined })).not.toThrow();

const o = Object.create(null);
o.a = 'foo';
o.__proto__ = [];
const o2 = { ...o };
expect(Array.isArray(Object.getPrototypeOf(o2))).toBe(false);
"#
);

test!(
    syntax(),
    |_| tr(),
    spread_variable_declaration,
    r#"var z = { ...x };"#,
    r#"var z = _objectSpread({}, x);"#
);

// object_spread_assignment
test!(
    syntax(),
    |_| tr(),
    object_spread_assignment,
    r#"
z = { x, ...y };

z = { x, w: { ...y } };

"#,
    r#"
z = _objectSpread({
  x
}, y);
z = {
  x,
  w: _objectSpread({}, y)
};

"#
);

//// regression_gh_7304
//test!(syntax(),|_| tr("{
//  "presets": [
//    [
//      "env",
//      {
//        "shippedProposals": true,
//        "targets": {
//          "node": 8
//        },
//        "useBuiltIns": "usage",
//        "corejs": 3
//      }
//    ]
//  ],
//  "plugins": []
//}
//"), regression_gh_7304, r#"
//export default class {
//  method ({ ...object }) {}
//}
//"#, r#"
//"use strict";
//
//Object.defineProperty(exports, "__esModule", {
//  value: true
//});
//exports.default = void 0;
//
//class _default {
//  method(_ref) {
//    let object = Object.assign({}, _ref);
//  }
//
//}
//
//exports.default = _default;
//
//"#);

//// object_rest_symbol
//test!(syntax(),|_| tr("{
//  "plugins": [
//    "syntax-async-generators",
//    "proposal-object-rest-spread",
//
//  ]
//}
//"), object_rest_symbol, r#"
//let {
//  [Symbol.for("foo")]: foo,
//  ...rest
//} = {};
//
//({ [Symbol.for("foo")]: foo, ...rest } = {});
//
//if ({ [Symbol.for("foo")]: foo, ...rest } = {}) {}
//
//"#, r#"
//var _ref3, _Symbol$for3;
//
//let _ref = {},
//    _Symbol$for = Symbol.for("foo"),
//    {
//  [_Symbol$for]: foo
//} = _ref,
//    rest = _objectWithoutProperties(_ref,
// [_Symbol$for].map(_toPropertyKey));
//
//var _ref2 = {};
//
//var _Symbol$for2 = Symbol.for("foo");
//
//({
//  [_Symbol$for2]: foo
//} = _ref2);
//rest = _objectWithoutProperties(_ref2,
// [_Symbol$for2].map(_toPropertyKey));
//_ref2;
//
//if (_ref3 = {}, _Symbol$for3 = Symbol.for("foo"), ({
//  [_Symbol$for3]: foo
//} = _ref3), rest = _objectWithoutProperties(_ref3,
//} [_Symbol$for3].map(_toPropertyKey)), _ref3) {}
//
//"#);

// object_rest_symbol_exec_exec
test_exec!(
    syntax(),
    |_| tr(),
    object_rest_symbol_exec_exec,
    r#"
const sym = Symbol("test");
const sym2 = Symbol("not enumerable");

const src = { a: "string" };
Object.defineProperty(src, "b", { value: "not enumerable" })
Object.defineProperty(src, sym, { enumerable: true, value: "symbol" });
Object.defineProperty(src, sym2, { value: "not enumerable" });

const {...rest} = src;

expect(rest[sym]).toBe("symbol");
expect(rest.a).toBe("string");
expect(Object.getOwnPropertyNames(rest)).toEqual(["a"]);
expect(Object.getOwnPropertySymbols(rest)).toEqual([sym]);

const { [sym]: dst, ...noSym } = src;

expect(dst).toBe("symbol");
expect(noSym.a).toBe("string");
expect(Object.getOwnPropertySymbols(noSym)).toEqual([]);

"#
);

//// object_rest_impure_computed
//test!(syntax(),|_| tr("{
//  "plugins": [
//    "syntax-async-generators",
//    "proposal-object-rest-spread",
//
//  ]
//}
//"), object_rest_impure_computed, r#"
//var key, x, y, z;
//// impure
//key = 1;
//var { [key++]: y, ...x } = { 1: 1, a: 1 };
//expect(x).toEqual({ a: 1 });
//expect(key).toBe(2);
//expect(y).toBe(1);
//
//// takes care of the order
//
//key = 1;
//var { [++key]: y, [++key]: z, ...rest} = {2: 2, 3: 3};
//expect(y).toBe(2);
//expect(z).toBe(3);
//
//// pure, computed property should remain as-is
//key = 2;
//({ [key]: y, z, ...x } = {2: "two", z: "zee"});
//expect(y).toBe("two");
//expect(x).toEqual({});
//expect(z).toBe("zee");
//
//"#, r#"
//var key, x, y, z; // impure
//
//key = 1;
//
//var _$a = {
//  1: 1,
//  a: 1
//},
//    _ref = key++,
//    {
//  [_ref]: y
//} = _$a,
//    x = _objectWithoutProperties(_$a,
// [_ref].map(_toPropertyKey));
//
//expect(x).toEqual({
//  a: 1
//});
//expect(key).toBe(2);
//expect(y).toBe(1); // takes care of the order
//
//key = 1;
//
//var _$ = {
//  2: 2,
//  3: 3
//},
//    _ref2 = ++key,
//    _ref3 = ++key,
//    {
//  [_ref2]: y,
//  [_ref3]: z
//} = _$,
//    rest = _objectWithoutProperties(_$, [_ref2,
// _ref3].map(_toPropertyKey));
//
//expect(y).toBe(2);
//expect(z).toBe(3); // pure, computed property should remain as-is
//
//key = 2;
//var _$z = {
//  2: "two",
//  z: "zee"
//};
//({
//  [key]: y,
//  z
//} = _$z);
//x = _objectWithoutProperties(_$z, [key,
// "z"].map(_toPropertyKey));
//_$z;
//expect(y).toBe("two");
//expect(x).toEqual({});
//expect(z).toBe("zee");
//
//"#);

//// object_rest_catch_clause
//test!(syntax(),|_| tr("{
//  "plugins": [
//    "syntax-async-generators",
//    "proposal-object-rest-spread",
//
//  ]
//}
//"), object_rest_catch_clause, r#"
//try {} catch({ ...a34 }) {}
//try {} catch({a1, ...b1}) {}
//try {} catch({a2, b2, ...c2}) {}
//try {} catch({a2, b2, c2: { c3, ...c4 }}) {}
//
//// Unchanged
//try {} catch(a) {}
//try {} catch({ b }) {}
//
//"#, r#"
//try {} catch (_ref) {
//  let a34 = _extends({}, _ref);
//}
//
//try {} catch (_ref2) {
//  let {
//    a1
//  } = _ref2,
//      b1 = _objectWithoutProperties(_ref2, ["a1"]);
//}
//
//try {} catch (_ref3) {
//  let {
//    a2,
//    b2
//  } = _ref3,
//      c2 = _objectWithoutProperties(_ref3, ["a2", "b2"]);
//}
//
//try {} catch (_ref4) {
//  let {
//    a2,
//    b2,
//    c2: {
//      c3
//    }
//  } = _ref4,
//      c4 = _objectWithoutProperties(_ref4.c2, ["c3"]);
//} // Unchanged
//
//
//try {} catch (a) {}
//
//try {} catch ({
//  b
//}) {}
//
//"#);

//// object_rest_variable_destructuring
//test!(syntax(),|_| tr("{
//  "plugins": [
//    "syntax-async-generators",
//    "proposal-object-rest-spread",
//
//  ]
//}
//"), object_rest_variable_destructuring, r#"
//var z = {};
//var { ...x } = z;
//var { ...a } = { a: 1 };
//var { ...x } = a.b;
//var { ...x } = a();
//var {x1, ...y1} = z;
//x1++;
//var { [a]: b, ...c } = z;
//var {x1, ...y1} = z;
//let {x2, y2, ...z2} = z;
//const {w3, x3, y3, ...z4} = z;
//
//let {
//  x: { a: xa, [d]: f, ...asdf },
//  y: { ...d },
//  ...g
//} = complex;
//
//let { x4: { ...y4 } } = z;
//
//"#, r#"
//var z = {};
//var x = _extends({}, z);
//var a = _extends({}, {
//  a: 1
//});
//var x = _extends({}, a.b);
//var x = _extends({}, a());
//var {
//  x1
//} = z,
//    y1 = _objectWithoutProperties(z, ["x1"]);
//x1++;
//var {
//  [a]: b
//} = z,
//    c = _objectWithoutProperties(z,
// [a].map(_toPropertyKey)); var {
//  x1
//} = z,
//    y1 = _objectWithoutProperties(z, ["x1"]);
//let {
//  x2,
//  y2
//} = z,
//    z2 = _objectWithoutProperties(z, ["x2", "y2"]);
//const {
//  w3,
//  x3,
//  y3
//} = z,
//      z4 = _objectWithoutProperties(z, ["w3", "x3", "y3"]);
//let {
//  x: {
//    a: xa,
//    [d]: f
//  }
//} = complex,
//    asdf = _objectWithoutProperties(complex.x, ["a",
// d].map(_toPropertyKey)),    d = _extends({},
// complex.y),    g = _objectWithoutProperties(complex, ["x"]);
//let {} = z,
//    y4 = _extends({}, z.x4);
//
//"#);

// object_rest_non_string_computed_exec

//// regression_gh_8323
//test!(syntax(),|_| tr("{
//  "presets": [["env", { "targets": { "node": "8" } }]],
//  "plugins": [["proposal-object-rest-spread", { "loose": true }]]
//}
//"), regression_gh_8323, r#"
//const get = () => {
//  fireTheMissiles();
//  return 3;
//};
//
//const f = ({ a = get(), b, c, ...z }) => {
//  const v = b + 3;
//};
//
//"#, r#"
//const get = () => {
//  fireTheMissiles();
//  return 3;
//};
//
//const f = (_ref) => {
//  let {
//    a = get(),
//    b
//  } = _ref,
//      z = _objectWithoutPropertiesLoose(_ref, ["a", "b", "c"]);
//
//  const v = b + 3;
//};
//
//"#);

// regression_gh_5151
test!(
    syntax(),
    |_| tr(),
    regression_gh_5151,
    r#"
const { x, ...y } = a,
  z = foo(y);

const { ...s } = r,
  t = foo(s);

// ordering is preserved
var l = foo(),
    { m: { n, ...o }, ...p } = bar(),
    q = baz();

"#,
    r#"
const {
  x
} = a,
      y = _objectWithoutProperties(a, ["x"]),
      z = foo(y);
const s = _extends({}, r),
      t = foo(s); // ordering is preserved

var l = foo(),
    _ref = bar(),
    {
  m: {
    n
  }
} = _ref,
    o = _objectWithoutProperties(_ref.m, ["n"]),
    p = _objectWithoutProperties(_ref, ["m"]),
    q = baz();

"#
);

// object_rest_parameters
//test!(syntax(),|_| tr("{
//  "plugins": [
//    "syntax-async-generators",
//    "proposal-object-rest-spread",
//
//  ]
//}
//"), object_rest_parameters, r#"
//function a({ ...a34 }) {}
//function a2({a1, ...b1}) {}
//function a3({a2, b2, ...c2}) {}
//function a4({a3, ...c3}, {a5, ...c5}) {}
//function a5({a3, b2: { ba1, ...ba2 }, ...c3}) {}
//function a6({a3, b2: { ba1, ...ba2 } }) {}
//function a7({a1 = 1, ...b1} = {}) {}
//function a8([{...a1}]) {}
//function a9([{a1, ...a2}]) {}
//function a10([a1, {...a2}]) {}
//// Unchanged
//function b(a) {}
//function b2(a, ...b) {}
//function b3({ b }) {}
//
//"#, r#"
//function a(_ref) {
//  let a34 = _extends({}, _ref);
//}
//
//function a2(_ref2) {
//  let {
//    a1
//  } = _ref2,
//      b1 = _objectWithoutProperties(_ref2, ["a1"]);
//}
//
//function a3(_ref3) {
//  let {
//    a2,
//    b2
//  } = _ref3,
//      c2 = _objectWithoutProperties(_ref3, ["a2", "b2"]);
//}
//
//function a4(_ref5, _ref4) {
//  let {
//    a3
//  } = _ref5,
//      c3 = _objectWithoutProperties(_ref5, ["a3"]);
//  let {
//    a5
//  } = _ref4,
//      c5 = _objectWithoutProperties(_ref4, ["a5"]);
//}
//
//function a5(_ref6) {
//  let {
//    a3,
//    b2: {
//      ba1
//    }
//  } = _ref6,
//      ba2 = _objectWithoutProperties(_ref6.b2, ["ba1"]),
//      c3 = _objectWithoutProperties(_ref6, ["a3", "b2"]);
//}
//
//function a6(_ref7) {
//  let {
//    a3,
//    b2: {
//      ba1
//    }
//  } = _ref7,
//      ba2 = _objectWithoutProperties(_ref7.b2, ["ba1"]);
//}
//
//function a7(_ref8 = {}) {
//  let {
//    a1 = 1
//  } = _ref8,
//      b1 = _objectWithoutProperties(_ref8, ["a1"]);
//}
//
//function a8([_ref9]) {
//  let a1 = _extends({}, _ref9);
//}
//
//function a9([_ref10]) {
//  let {
//    a1
//  } = _ref10,
//      a2 = _objectWithoutProperties(_ref10, ["a1"]);
//}
//
//function a10([a1, _ref11]) {
//  let a2 = _extends({}, _ref11);
//} // Unchanged
//
//
//function b(a) {}
//
//function b2(a, ...b) {}
//
//function b3({
//  b
//}) {}
//
//"#);

//// object_rest_for_x_array_pattern
//test!(syntax(),|_| tr("{
//  "plugins": [
//    "syntax-async-generators",
//    "proposal-object-rest-spread",
//
//  ]
//}
//"), object_rest_for_x_array_pattern, r#"
//// ForXStatement
//for (const [{a, ...b}] of []) {}
//for ([{a, ...b}] of []) {}
//async function a() {
//  for await ([{a, ...b}] of []) {}
//}
//
//// skip
//for ([{a}] in {}) {}
//for ([{a}] of []) {}
//async function a() {
//  for await ([{a}] of []) {}
//}
//
//for ([a, ...b] in {}) {}
//for ([a, ...b] of []) {}
//async function a() {
//  for await ([a, ...b] of []) {}
//}
//
//"#, r#"
//// ForXStatement
//for (const _ref of []) {
//  const [_ref2] = _ref;
//  const {
//    a
//  } = _ref2,
//        b = _objectWithoutProperties(_ref2, ["a"]);
//}
//
//for (var _ref3 of []) {
//  [_ref4] = _ref3;
//  var {
//    a
//  } = _ref4,
//      b = _objectWithoutProperties(_ref4, ["a"]);
//}
//
//async function a() {
//  for await (var _ref5 of []) {
//    [_ref6] = _ref5;
//    var {
//      a
//    } = _ref6,
//        b = _objectWithoutProperties(_ref6, ["a"]);
//  }
//} // skip
//
//
//for ([{
//  a
//}] in {}) {}
//
//for ([{
//  a
//}] of []) {}
//
//async function a() {
//  for await ([{
//    a
//  }] of []) {}
//}
//
//for ([a, ...b] in {}) {}
//
//for ([a, ...b] of []) {}
//
//async function a() {
//  for await ([a, ...b] of []) {}
//}
//
//"#);

//// object_rest_nested_computed_key
//test!(syntax(),|_| tr("{
//  "plugins": [
//    "syntax-async-generators",
//    "proposal-object-rest-spread",
//
//  ]
//}
//"), object_rest_nested_computed_key, r#"
//const {
//  [({ ...rest }) => {
//    let { ...b } = {};
//  }]: a,
//  [({ ...d } = {})]: c,
//} = {};
//"#, r#"
//var _ref2;
//
//const {
//  [(_ref) => {
//    let rest = _extends({}, _ref);
//    let b = _extends({}, {});
//  }]: a,
//  [(_ref2 = {}, ({} = _ref2), d = _extends({}, _ref2), _ref2)]: c
//} = {};
//
//"#);

// regression_gh_7388
test!(
    syntax(),
    |_| tr(),
    regression_gh_7388,
    r#"
function fn0(obj0) {
  const {
    fn1 = (obj1 = {}) => {
      const {
        fn2 = (obj2 = {}) => {
          const {a, ...rest} = obj2;
          console.log(rest);
        }
      } = obj1;
    }
  } = obj0;
}

"#,
    r#"
function fn0(obj0) {
  const {
    fn1 = (obj1 = {}) => {
      const {
        fn2 = (obj2 = {}) => {
          const {
            a
          } = obj2,
                rest = _objectWithoutProperties(obj2, ["a"]);
          console.log(rest);
        }
      } = obj1;
    }
  } = obj0;
}

"#
);

// object_rest_impure_computed_exec

//// object_rest_nested
//test!(syntax(),|_| tr("{
//  "plugins": [
//    "syntax-async-generators",
//    "proposal-object-rest-spread",
//
//  ]
//}
//"), object_rest_nested, r#"
//const defunct = {
//  outer: {
//    inner: {
//      three: 'three',
//      four: 'four'
//    }
//  }
//}
//
//const { outer: { inner: { three, ...other } } } = defunct
//
//"#, r#"
//const defunct = {
//  outer: {
//    inner: {
//      three: 'three',
//      four: 'four'
//    }
//  }
//};
//const {
//  outer: {
//    inner: {
//      three
//    }
//  }
//} = defunct,
//      other = _objectWithoutProperties(defunct.outer.inner,
// ["three"]);
//
//"#);

//// object_rest_with_array_rest
//test!(syntax(),|_| tr("{
//  "plugins": [
//    "syntax-async-generators",
//    "proposal-object-rest-spread",
//
//  ]
//}
//"), object_rest_with_array_rest, r#"
//let {
//  a: [b, ...arrayRest],
//  c = function(...functionRest){},
//  ...objectRest
//} = {
//  a: [1, 2, 3, 4],
//  d: "oyez"
//};
//
//"#, r#"
//let _a$d = {
//  a: [1, 2, 3, 4],
//  d: "oyez"
//},
//    {
//  a: [b, ...arrayRest],
//  c = function (...functionRest) {}
//} = _a$d,
//    objectRest = _objectWithoutProperties(_a$d, ["a", "c"]);
//
//"#);

//// object_rest_nested_array_2
//test!(syntax(),|_| tr("{
//  "plugins": [
//    "syntax-async-generators",
//    "proposal-object-rest-spread",
//
//  ]
//}
//"), object_rest_nested_array_2, r#"
//const [a, [{b, ...c}], {d, ...e}, [{ f, ...g}, {h: [i, {j, ...k}] }]] = x;
//
//"#, r#"
//const [a, [_ref], _ref2, [_ref3, {
//  h: [i, _ref4]
//}]] = x;
//const {
//  b
//} = _ref,
//      c = _objectWithoutProperties(_ref, ["b"]),
//      {
//  d
//} = _ref2,
//      e = _objectWithoutProperties(_ref2, ["d"]),
//      {
//  f
//} = _ref3,
//      g = _objectWithoutProperties(_ref3, ["f"]),
//      {
//  j
//} = _ref4,
//      k = _objectWithoutProperties(_ref4, ["j"]);
//
//"#);

// object_rest_impure_computed_exec_exec
test_exec!(
    syntax(),
    |_| tr(),
    object_rest_impure_computed_exec_exec,
    r#"
var key, x, y, z;
// impure
key = 1;
var { [key++]: y, ...x } = { 1: 1, a: 1 };
expect(x).toEqual({ a: 1 });
expect(key).toBe(2);
expect(1).toBe(y);

// takes care of the order

key = 1;
var { [++key]: y, [++key]: z, ...rest} = {2: 2, 3: 3};
expect(y).toBe(2);
expect(z).toBe(3);

// pure, computed property should remain as-is
key = 2;
({ [key]: y, z, ...x } = {2: "two", z: "zee"});
expect(y).toBe("two");
expect(x).toEqual({});
expect(z).toBe("zee");

// rhs evaluated before lhs
var order = [];
function left() {
  order.push("left");
  return 0;
}
function right() {
  order.push("right");
  return {};
}
var { [left()]: y, ...x} = right();
expect(order).toEqual(["right", "left"]);

"#
);

//// object_rest_duplicate_decl_bug
//test!(syntax(),|_| tr("{
//  "plugins": [
//    "./plugin-clear-scope",
//    "proposal-object-rest-spread",
//
//  ]
//}
//"), object_rest_duplicate_decl_bug, r#"
//it("es7.objectRestSpread", () => {
//  let original = { a: 1, b: 2 };
//  let { ...copy } = original;
//});
//
//"#, r#"
//it("es7.objectRestSpread", () => {
//  let original = {
//    a: 1,
//    b: 2
//  };
//  let copy = _extends({}, original);
//});
//
//"#);

// object_spread_expression_exec
test_exec!(
    // WTF? babel's output is wrong
    ignore,
    syntax(),
    |_| tr(),
    object_spread_expression_exec,
    r#"
var log = [];

var a = {
  ...{ get foo() { log.push(1); } },
  get bar() { log.push(2); }
};

expect(log).toEqual([1]);

"#
);

// object_rest_nested_default_value
//test!(syntax(),|_| tr("{
//  "plugins": [
//    "syntax-async-generators",
//    "proposal-object-rest-spread",
//
//  ]
//}
//"), object_rest_nested_default_value, r#"
//const {
//  a = ({ ...rest }) => {
//    let { ...b } = {};
//  },
//  c = ({ ...d } = {}),
//} = {};
//"#, r#"
//var _ref2;
//
//const {
//  a = (_ref) => {
//    let rest = _extends({}, _ref);
//    let b = _extends({}, {});
//  },
//  c = (_ref2 = {}, ({} = _ref2), d = _extends({}, _ref2), _ref2)
//} = {};
//
//"#);

//// object_rest_export
//test!(syntax(),|_| tr("{
//  "plugins": [
//    "syntax-async-generators",
//    "proposal-object-rest-spread",
//
//  ]
//}
//"), object_rest_export, r#"
//// ExportNamedDeclaration
//export var { b, ...c } = asdf2;
//// Skip
//export var { bb, cc } = ads;
//export var [ dd, ee ] = ads;
//
//"#, r#"
//// ExportNamedDeclaration
//var {
//  b
//} = asdf2,
//    c = _objectWithoutProperties(asdf2, ["b"]); // Skip
//
//export { b, c };
//export var {
//  bb,
//  cc
//} = ads;
//export var [dd, ee] = ads;
//
//"#);

//// object_rest_non_string_computed
//test!(syntax(),|_| tr("{
//  "plugins": [
//    "syntax-async-generators",
//    "proposal-object-rest-spread",
//
//  ]
//}
//"), object_rest_non_string_computed, r#"
//const a = {
//  "3": "three",
//  "foo": "bar"
//}
//
//const {
//  [3]: omit,
//  ...rest
//} = a;
//
//expect(rest).toEqual({"foo": "bar"});
//expect(omit).toBe("three");
//
//const [k1, k2, k3, k4, k5] = [null, undefined, true, false, {toString() {
// return "warrior"; }}]; const c = {
//  [k1]: "1",
//  [k2]: "2",
//  [k3]: "3",
//  [k4]: "4",
//  [k5]: "5"
//};
//
//const {
//  [k1]: v1,
//  [k2]: v2,
//  [k3]: v3,
//  [k4]: v4,
//  [k5]: v5,
//  ...vrest
//} = c;
//
//expect(v1).toBe("1");
//expect(v2).toBe("2");
//expect(v3).toBe("3");
//expect(v4).toBe("4");
//expect(v5).toBe("5");
//expect(vrest).toEqual({});
//
//// shouldn't convert symbols to strings
//const sx = Symbol();
//const sy = Symbol();
//
//const d = {
//  [sx]: "sx",
//  [sy]: "sy"
//}
//
//const {
//  [sx]: dx,
//  [sy]: dy
//} = d;
//
//expect(dx).toBe("sx");
//expect(dy).toBe("sy");
//
//"#, r#"
//const a = {
//  "3": "three",
//  "foo": "bar"
//};
//const {
//  [3]: omit
//} = a,
//      rest = _objectWithoutProperties(a, ["3"]);
//expect(rest).toEqual({
//  "foo": "bar"
//});
//expect(omit).toBe("three");
//const [k1, k2, k3, k4, k5] = [null, undefined, true, false, {
//  toString() {
//    return "warrior";
//  }
//
//}];
//const c = {
//  [k1]: "1",
//  [k2]: "2",
//  [k3]: "3",
//  [k4]: "4",
//  [k5]: "5"
//};
//const {
//  [k1]: v1,
//  [k2]: v2,
//  [k3]: v3,
//  [k4]: v4,
//  [k5]: v5
//} = c,
//      vrest = _objectWithoutProperties(c, [k1, k2, k3, k4,
// k5].map(_toPropertyKey)); expect(v1).toBe("1");
//expect(v2).toBe("2");
//expect(v3).toBe("3");
//expect(v4).toBe("4");
//expect(v5).toBe("5");
//expect(vrest).toEqual({}); // shouldn't convert symbols to strings
//
//const sx = Symbol();
//const sy = Symbol();
//const d = {
//  [sx]: "sx",
//  [sy]: "sy"
//};
//const {
//  [sx]: dx,
//  [sy]: dy
//} = d;
//expect(dx).toBe("sx");
//expect(dy).toBe("sy");
//
//"#);

// object_spread_variable_declaration
test!(
    syntax(),
    |_| tr(),
    object_spread_variable_declaration,
    r#"
var z = { ...x };

"#,
    r#"
var z = _objectSpread({}, x);

"#
);

//// object_rest_nested_array
//test!(syntax(),|_| tr("{
//  "plugins": [
//    "syntax-async-generators",
//    "proposal-object-rest-spread",
//
//  ]
//}
//"), object_rest_nested_array, r#"
//const [a, {b, ...c}] = x;
//
//let [d, {e, ...f}] = x;
//
//[g, {h, ...i}] = x;
//
//
//"#, r#"
//const [a, _ref] = x;
//const {
//  b
//} = _ref,
//      c = _objectWithoutProperties(_ref, ["b"]);
//let [d, _ref2] = x;
//let {
//  e
//} = _ref2,
//    f = _objectWithoutProperties(_ref2, ["e"]);
//[g, _ref3] = x;
//var {
//  h
//} = _ref3,
//    i = _objectWithoutProperties(_ref3, ["h"]);
//
//"#);

// object_spread_no_object_assign_exec_exec
test_exec!(
    syntax(),
    |_| tr(),
    object_spread_no_object_assign_exec_exec,
    r#"
Object.defineProperty(Object.prototype, 'NOSET', {
  set(value) {
    // noop
  },
});

Object.defineProperty(Object.prototype, 'NOWRITE', {
  writable: false,
  value: 'abc',
});

const obj = { NOSET: 123 };
// this wouldn't work as expected if transformed as Object.assign (or equivalent)
// because those trigger object setters (spread don't)
const objSpread = { ...obj };

const obj2 = { NOSET: 123, NOWRITE: 456 };
// this line would throw `TypeError: Cannot assign to read only property 'NOWRITE'`
// if transformed as Object.assign (or equivalent) because those use *assignment* for creating properties
// (spread defines them)
const obj2Spread = { ...obj2 };

expect(objSpread).toEqual(obj);
expect(obj2Spread).toEqual(obj2);

const KEY = Symbol('key');
const obj3Spread = { ...{ get foo () { return 'bar' } }, [KEY]: 'symbol' };
expect(Object.getOwnPropertyDescriptor(obj3Spread, 'foo').value).toBe('bar');
expect(Object.getOwnPropertyDescriptor(obj3Spread, KEY).value).toBe('symbol');

const obj4Spread = { ...Object.prototype };
expect(Object.getOwnPropertyDescriptor(obj4Spread, 'hasOwnProperty')).toBeUndefined();

expect(() => ({ ...null, ...undefined })).not.toThrow();

const o = Object.create(null);
o.a = 'foo';
o.__proto__ = [];
const o2 = { ...o };
expect(Array.isArray(Object.getPrototypeOf(o2))).toBe(false);

"#
);

// object_rest_non_string_computed_exec_exec
test_exec!(
    syntax(),
    |_| tr(),
    object_rest_non_string_computed_exec_exec,
    r#"
const a = {
  "3": "three",
  "foo": "bar"
}

const {
  [3]: omit,
  ...rest
} = a;

expect(rest).toEqual({"foo": "bar"});
expect(omit).toBe("three");

const [k1, k2, k3, k4, k5] = [null, undefined, true, false, {toString() { return "warrior"; }}];
const c = {
  [k1]: "1",
  [k2]: "2",
  [k3]: "3",
  [k4]: "4",
  [k5]: "5"
};

const {
  [k1]: v1,
  [k2]: v2,
  [k3]: v3,
  [k4]: v4,
  [k5]: v5,
  ...vrest
} = c;

expect(v1).toBe("1");
expect(v2).toBe("2");
expect(v3).toBe("3");
expect(v4).toBe("4");
expect(v5).toBe("5");
expect(vrest).toEqual({});

// shouldn't convert symbols to strings
const sx = Symbol();
const sy = Symbol();

const d = {
  [sx]: "sx",
  [sy]: "sy"
}

const {
  [sx]: dx,
  [sy]: dy
} = d;

expect(dx).toBe("sx");
expect(dy).toBe("sy");

"#
);

// object_rest_variable_exec

// object_rest_variable_exec_exec
test_exec!(
    syntax(),
    |_| tr(),
    object_rest_variable_exec_exec,
    r#"
// var { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };

// expect(x).toBe(1);
// expect(y).toBe(2);
// expect(z).toEqual({ a: 3, b: 4 });

// var complex = {
//   x: { a: 1, b: 2, c: 3 },
// };

// var {
//   x: { a: xa, ...xbc }
// } = complex;

// expect(xa).toBe(1);
// expect(xbc).toEqual({ b: 2, c: 3});

// // own properties
// function ownX({ ...properties }) {
//   return properties.x;
// }
// expect(ownX(Object.create({ x: 1 }))).toBeUndefined();

"#
);

//// object_rest_nested_2
//test!(syntax(),|_| tr("{
//  "plugins": [
//    "syntax-async-generators",
//    "proposal-object-rest-spread",
//
//  ]
//}
//"), object_rest_nested_2, r#"
//const test = {
//  foo: {
//    bar: {
//      baz: {
//        a: {
//          x: 1,
//          y: 2,
//          z: 3,
//        },
//      },
//    },
//  },
//};
//
//const { foo: { bar: { baz: { a: { x, ...other } } } } } = test;
//
//"#, r#"
//const test = {
//  foo: {
//    bar: {
//      baz: {
//        a: {
//          x: 1,
//          y: 2,
//          z: 3
//        }
//      }
//    }
//  }
//};
//const {
//  foo: {
//    bar: {
//      baz: {
//        a: {
//          x
//        }
//      }
//    }
//  }
//} = test,
//      other = _objectWithoutProperties(test.foo.bar.baz.a, ["x"]);
//
//"#);

// object_spread_no_getOwnPropertyDescriptors_exec
test_exec!(
    syntax(),
    |_| tr(),
    object_spread_no_get_own_property_descriptors_exec,
    r#"
const oldGOPDs = Object.getOwnPropertyDescriptors;
Object.getOwnPropertyDescriptors = null;

({ ...{ a: 1 }, b: 1, ...{} });

Object.getOwnPropertyDescriptors = oldGOPDs;

"#
);

//// object_rest_for_x
//test!(syntax(),|_| tr("{
//  "plugins": [
//    "syntax-async-generators",
//    "proposal-object-rest-spread",
//
//  ]
//}
//"), object_rest_for_x, r#"
//// ForXStatement
//for (var {a, ...b} of []) {}
//for ({a, ...b} of []) {}
//async function a() {
//  for await ({a, ...b} of []) {}
//}
//
//// skip
//for ({a} in {}) {}
//for ({a} of []) {}
//async function a() {
//  for await ({a} of []) {}
//}
//
//for (a in {}) {}
//for (a of []) {}
//async function a() {
//  for await (a of []) {}
//}
//
//"#, r#"
//// ForXStatement
//for (var _ref of []) {
//  var {
//    a
//  } = _ref,
//      b = _objectWithoutProperties(_ref, ["a"]);
//}
//
//for (var _ref2 of []) {
//  var _ref3 = _ref2;
//  ({
//    a
//  } = _ref3);
//  b = _objectWithoutProperties(_ref3, ["a"]);
//  _ref3;
//}
//
//async function a() {
//  for await (var _ref4 of []) {
//    var _ref5 = _ref4;
//    ({
//      a
//    } = _ref5);
//    b = _objectWithoutProperties(_ref5, ["a"]);
//    _ref5;
//  }
//} // skip
//
//
//for ({
//  a
//} in {}) {}
//
//for ({
//  a
//} of []) {}
//
//async function a() {
//  for await ({
//    a
//  } of []) {}
//}
//
//for (a in {}) {}
//
//for (a of []) {}
//
//async function a() {
//  for await (a of []) {}
//}
//
//"#);

//// object_rest_template_literal_property_allLiterals_false
//test!(syntax(),|_| tr("{
//  "plugins": [
//    "syntax-async-generators",
//    "proposal-object-rest-spread",
//
//  ]
//}
//"), object_rest_template_literal_property_allLiterals_false, r#"
//const input = {};
//
//const {
//  given_name: givenName,
//  'last_name': lastName,
//  [`country`]: country,
//  [prefix + 'state']: state,
//  [`${prefix}consents`]: consents,
//  ...rest
//} = input;
//
//"#, r#"
//const input = {};
//
//const _ref = prefix + 'state',
//      _ref2 = `${prefix}consents`,
//      {
//  given_name: givenName,
//  'last_name': lastName,
//  [`country`]: country,
//  [_ref]: state,
//  [_ref2]: consents
//} = input,
//      rest = _objectWithoutProperties(input, ["given_name",
// "last_name", `country`, _ref, _ref2].map(_toPropertyKey));
//
//"#);

//// object_rest_for_x_completion_record
//test!(syntax(),|_| tr("{
//  "plugins": [
//    "syntax-async-generators",
//    "proposal-object-rest-spread",
//
//  ]
//}
//"), object_rest_for_x_completion_record, r#"
//for ({a, ...b} of []) {}
//
//"#, r#"
//for (var _ref of []) {
//  var _ref2 = _ref;
//  ({
//    a
//  } = _ref2);
//  b = _objectWithoutProperties(_ref2, ["a"]);
//  _ref2;
//  void 0;
//}
//
//"#);

// regression_gh_4904
test!(
    syntax(),
    |_| tr(),
    regression_gh_4904,
    r#"
const { s, ...t } = foo();

const { s: { q1, ...q2 }, ...q3 } = bar();

const { a } = foo(({ b, ...c }) => {
  console.log(b, c);
});

"#,
    r#"
const _ref = foo(), { s  } = _ref, t = _objectWithoutProperties(_ref, ['s']);
const _ref1 = bar(), { s: { q1  }  } = _ref1, q2 = _objectWithoutProperties(_ref1.s, ['q1']), q3 = _objectWithoutProperties(_ref1, ['s']);
const _ref2 = foo((_param)=>{
    var { b  } = _param, c = _objectWithoutProperties(_param, ['b']);
    console.log(b, c);
}), { a  } = _ref2;


"#
);