# Object Literals
# ---------------

# TODO: refactor object literal tests
# TODO: add indexing and method invocation tests: {a}['a'] is a, {a}.a()

trailingComma = {k1: "v1", k2: 4, k3: (-> true),}
ok trailingComma.k3() and (trailingComma.k2 is 4) and (trailingComma.k1 is "v1")

ok {a: (num) -> num is 10 }.a 10

moe = {
  name:  'Moe'
  greet: (salutation) ->
    salutation + " " + @name
  hello: ->
    @['greet'] "Hello"
  10: 'number'
}
ok moe.hello() is "Hello Moe"
ok moe[10] is 'number'
moe.hello = ->
  this['greet'] "Hello"
ok moe.hello() is 'Hello Moe'

obj = {
  is:     -> yes,
  'not':  -> no,
}
ok obj.is()
ok not obj.not()

### Top-level object literal... ###
obj: 1
### ...doesn't break things. ###

# Object literals should be able to include keywords.
obj = {class: 'höt'}
obj.function = 'dog'
ok obj.class + obj.function is 'hötdog'

# Implicit objects as part of chained calls.
pluck = (x) -> x.a
eq 100, pluck pluck pluck a: a: a: 100


test "YAML-style object literals", ->
  obj =
    a: 1
    b: 2
  eq 1, obj.a
  eq 2, obj.b

  config =
    development:
      server: 'localhost'
      timeout: 10

    production:
      server: 'dreamboat'
      timeout: 1000

  ok config.development.server  is 'localhost'
  ok config.production.server   is 'dreamboat'
  ok config.development.timeout is 10
  ok config.production.timeout  is 1000

obj =
  a: 1,
  b: 2,
ok obj.a is 1
ok obj.b is 2

# Implicit objects nesting.
obj =
  options:
    value: yes
  fn: ->
    {}
    null
ok obj.options.value is yes
ok obj.fn() is null

# Implicit objects with wacky indentation:
obj =
  'reverse': (obj) ->
    Array.prototype.reverse.call obj
  abc: ->
    @reverse(
      @reverse @reverse ['a', 'b', 'c'].reverse()
    )
  one: [1, 2,
    a: 'b'
  3, 4]
  red:
    orange:
          yellow:
                  green: 'blue'
    indigo: 'violet'
  misdent: [[],
  [],
                  [],
      []]
ok obj.abc().join(' ') is 'a b c'
ok obj.one.length is 5
ok obj.one[4] is 4
ok obj.one[2].a is 'b'
ok (key for key of obj.red).length is 2
ok obj.red.orange.yellow.green is 'blue'
ok obj.red.indigo is 'violet'
ok obj.misdent.toString() is ',,,'

#542: Objects leading expression statement should be parenthesized.
{f: -> ok yes }.f() + 1

# String-keyed objects shouldn't suppress newlines.
one =
  '>!': 3
six: -> 10
ok not one.six

# Shorthand objects with property references.
obj =
  ### comment one ###
  ### comment two ###
  one: 1
  two: 2
  object: -> {@one, @two}
  list:   -> [@one, @two]
result = obj.object()
eq result.one, 1
eq result.two, 2
eq result.two, obj.list()[1]

third = (a, b, c) -> c
obj =
  one: 'one'
  two: third 'one', 'two', 'three'
ok obj.one is 'one'
ok obj.two is 'three'

test "invoking functions with implicit object literals", ->
  generateGetter = (prop) -> (obj) -> obj[prop]
  getA = generateGetter 'a'
  getArgs = -> arguments
  a = b = 30

  result = getA
    a: 10
  eq 10, result

  result = getA
    "a": 20
  eq 20, result

  result = getA a,
    b:1
  eq undefined, result

  result = getA b:1,
  a:43
  eq 43, result

  result = getA b:1,
    a:62
  eq undefined, result

  result = getA
    b:1
    a
  eq undefined, result

  result = getA
    a:
      b:2
    b:1
  eq 2, result.b

  result = getArgs
    a:1
    b
    c:1
  ok result.length is 3
  ok result[2].c is 1

  result = getA b: 13, a: 42, 2
  eq 42, result

  result = getArgs a:1, (1 + 1)
  ok result[1] is 2

  result = getArgs a:1, b
  ok result.length is 2
  ok result[1] is 30

  result = getArgs a:1, b, b:1, a
  ok result.length is 4
  ok result[2].b is 1

  throws -> CoffeeScript.compile "a = b:1, c"

test "some weird indentation in YAML-style object literals", ->
  two = (a, b) -> b
  obj = then two 1,
    1: 1
    a:
      b: ->
        fn c,
          d: e
    f: 1
  eq 1, obj[1]

test "#1274: `{} = a()` compiles to `false` instead of `a()`", ->
  a = false
  fn = -> a = true
  {} = fn()
  ok a

test "#1436: `for` etc. work as normal property names", ->
  obj = {}
  eq no, obj.hasOwnProperty 'for'
  obj.for = 'foo' of obj
  eq yes, obj.hasOwnProperty 'for'

test "#2706, Un-bracketed object as argument causes inconsistent behavior", ->
  foo = (x, y) -> y
  bar = baz: yes

  eq yes, foo x: 1, bar.baz

test "#2608, Allow inline objects in arguments to be followed by more arguments", ->
  foo = (x, y) -> y

  eq yes, foo x: 1, y: 2, yes

test "#2308, a: b = c:1", ->
  foo = a: b = c: yes
  eq b.c, yes
  eq foo.a.c, yes

test "#2317, a: b c: 1", ->
  foo = (x) -> x
  bar = a: foo c: yes
  eq bar.a.c, yes

test "#1896, a: func b, {c: d}", ->
  first = (x) -> x
  second = (x, y) -> y
  third = (x, y, z) -> z

  one = 1
  two = 2
  three = 3
  four = 4

  foo = a: second one, {c: two}
  eq foo.a.c, two

  bar = a: second one, c: two
  eq bar.a.c, two

  baz = a: second one, {c: two}, e: first first h: three
  eq baz.a.c, two

  qux = a: third one, {c: two}, e: first first h: three
  eq qux.a.e.h, three

  quux = a: third one, {c: two}, e: first(three), h: four
  eq quux.a.e, three
  eq quux.a.h, four

  corge = a: third one, {c: two}, e: second three, h: four
  eq corge.a.e.h, four

test "Implicit objects, functions and arrays", ->
  first  = (x) -> x
  second = (x, y) -> y

  foo = [
    1
    one: 1
    two: 2
    three: 3
    more:
      four: 4
      five: 5, six: 6
    2, 3, 4
    5]
  eq foo[2], 2
  eq foo[1].more.six, 6

  bar = [
    1
    first first first second 1,
      one: 1, twoandthree: twoandthree: two: 2, three: 3
      2,
    2
    one: 1
    two: 2
    three: first second ->
      no
    , ->
      3
    3
    4]
  eq bar[2], 2
  eq bar[1].twoandthree.twoandthree.two, 2
  eq bar[3].three(), 3
  eq bar[4], 3

test "#2549, Brace-less Object Literal as a Second Operand on a New Line", ->
  foo = no or
    one: 1
    two: 2
    three: 3
  eq foo.one, 1

  bar = yes and one: 1
  eq bar.one, 1

  baz = null ?
    one: 1
    two: 2
  eq baz.two, 2

test "#2757, Nested", ->
  foo =
    bar:
      one: 1,
  eq foo.bar.one, 1

  baz =
    qux:
      one: 1,
    corge:
      two: 2,
      three: three: three: 3,
    xyzzy:
      thud:
        four:
          four: 4,
      five: 5,

  eq baz.qux.one, 1
  eq baz.corge.three.three.three, 3
  eq baz.xyzzy.thud.four.four, 4
  eq baz.xyzzy.five, 5

test "#1865, syntax regression 1.1.3", ->
  foo = (x, y) -> y

  bar = a: foo (->),
    c: yes
  eq bar.a.c, yes

  baz = a: foo (->), c: yes
  eq baz.a.c, yes


test "#1322: implicit call against implicit object with block comments", ->
  ((obj, arg) ->
    eq obj.x * obj.y, 6
    ok not arg
  )
    ###
    x
    ###
    x: 2
    ### y ###
    y: 3

test "#1513: Top level bare objs need to be wrapped in parens for unary and existence ops", ->
  doesNotThrow -> CoffeeScript.run "{}?", bare: true
  doesNotThrow -> CoffeeScript.run "{}.a++", bare: true

test "#1871: Special case for IMPLICIT_END in the middle of an implicit object", ->
  result = 'result'
  ident = (x) -> x

  result = ident one: 1 if false

  eq result, 'result'

  result = ident
    one: 1
    two: 2 for i in [1..3]

  eq result.two.join(' '), '2 2 2'

test "#1871: implicit object closed by IMPLICIT_END in implicit returns", ->
  ob = do ->
    a: 1 if no
  eq ob, undefined

  # instead these return an object
  func = ->
    key:
      i for i in [1, 2, 3]

  eq func().key.join(' '), '1 2 3'

  func = ->
    key: (i for i in [1, 2, 3])

  eq func().key.join(' '), '1 2 3'

test "#1961, #1974, regression with compound assigning to an implicit object", ->

  obj = null

  obj ?=
    one: 1
    two: 2

  eq obj.two, 2

  obj = null

  obj or=
    three: 3
    four: 4

  eq obj.four, 4

test "#2207: Immediate implicit closes don't close implicit objects", ->
  func = ->
    key: for i in [1, 2, 3] then i

  eq func().key.join(' '), '1 2 3'

test "#3216: For loop declaration as a value of an implicit object", ->
  test = [0..2]
  ob =
    a: for v, i in test then i
    b: for v, i in test then i
    c: for v in test by 1 then v
    d: for v in test when true then v
  arrayEq ob.a, test
  arrayEq ob.b, test
  arrayEq ob.c, test
  arrayEq ob.d, test
  byFirstKey =
    a: for v in test by 1 then v
  arrayEq byFirstKey.a, test
  whenFirstKey =
    a: for v in test when true then v
  arrayEq whenFirstKey.a, test

test 'inline implicit object literals within multiline implicit object literals', ->
  x =
    a: aa: 0
    b: 0
  eq 0, x.b
  eq 0, x.a.aa

test "object keys with interpolations: simple cases", ->
  a = 'a'
  obj = "#{a}": yes
  eq obj.a, yes
  obj = {"#{a}": yes}
  eq obj.a, yes
  obj = {"#{a}"}
  eq obj.a, 'a'
  obj = {"#{5}"}
  eq obj[5], '5' # Note that the value is a string, just like the key.

test "object keys with interpolations: commas in implicit object", ->
  obj = "#{'a'}": 1, b: 2
  deepEqual obj, {a: 1, b: 2}
  obj = a: 1, "#{'b'}": 2
  deepEqual obj, {a: 1, b: 2}
  obj = "#{'a'}": 1, "#{'b'}": 2
  deepEqual obj, {a: 1, b: 2}

test "object keys with interpolations: commas in explicit object", ->
  obj = {"#{'a'}": 1, b: 2}
  deepEqual obj, {a: 1, b: 2}
  obj = {a: 1, "#{'b'}": 2}
  deepEqual obj, {a: 1, b: 2}
  obj = {"#{'a'}": 1, "#{'b'}": 2}
  deepEqual obj, {a: 1, b: 2}

test "object keys with interpolations: commas after key with interpolation", ->
  obj = {"#{'a'}": yes,}
  eq obj.a, yes
  obj = {
    "#{'a'}": 1,
    "#{'b'}": 2,
    ### herecomment ###
    "#{'c'}": 3,
  }
  deepEqual obj, {a: 1, b: 2, c: 3}
  obj =
    "#{'a'}": 1,
    "#{'b'}": 2,
    ### herecomment ###
    "#{'c'}": 3,
  deepEqual obj, {a: 1, b: 2, c: 3}
  obj =
    "#{'a'}": 1,
    "#{'b'}": 2,
    ### herecomment ###
    "#{'c'}": 3, "#{'d'}": 4,
  deepEqual obj, {a: 1, b: 2, c: 3, d: 4}

test "object keys with interpolations: key with interpolation mixed with `@prop`", ->
  deepEqual (-> {@a, "#{'b'}": 2}).call(a: 1), {a: 1, b: 2}

test "object keys with interpolations: evaluate only once", ->
  count = 0
  a = -> count++; 'a'
  obj = {"#{a()}"}
  eq obj.a, 'a'
  eq count, 1

test "object keys with interpolations: evaluation order", ->
  arr = []
  obj =
    a: arr.push 1
    b: arr.push 2
    "#{'c'}": arr.push 3
    "#{'d'}": arr.push 4
    e: arr.push 5
    "#{'f'}": arr.push 6
    g: arr.push 7
  arrayEq arr, [1..7]
  deepEqual obj, {a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7}

test "object keys with interpolations: object starting with dynamic key", ->
  obj =
    "#{'a'}": 1
    b: 2
  deepEqual obj, {a: 1, b: 2}

test "object keys with interpolations: comments in implicit object", ->
  obj =
    ### leading comment ###
    "#{'a'}": 1

    ### middle ###

    "#{'b'}": 2
    # regular comment
    'c': 3
    ### foo ###
    d: 4
    "#{'e'}": 5
  deepEqual obj, {a: 1, b: 2, c: 3, d: 4, e: 5}

  # Comments in explicit object.
  obj = {
    ### leading comment ###
    "#{'a'}": 1

    ### middle ###

    "#{'b'}": 2
    # regular comment
    'c': 3
    ### foo ###
    d: 4
    "#{'e'}": 5
  }
  deepEqual obj, {a: 1, b: 2, c: 3, d: 4, e: 5}

test "object keys with interpolations: more complicated case", ->
  obj = {
    "#{'interpolated'}":
      """
        #{ '''nested''' }
      """: 123: 456
  }
  deepEqual obj,
    interpolated:
      nested:
        123: 456

test "#4324: Shorthand after interpolated key", ->
  a = 2
  obj = {"#{1}": 1, a}
  eq 1, obj[1]
  eq 2, obj.a

test "computed property keys: simple cases", ->
  a = 'a'
  obj = [a]: yes
  eq obj.a, yes
  obj = {[a]: yes}
  eq obj.a, yes
  obj = {[a]}
  eq obj.a, 'a'
  obj = {[5]}
  eq obj[5], 5
  obj = {['5']}
  eq obj['5'], '5'

test "computed property keys: commas in implicit object", ->
  obj = ['a']: 1, b: 2
  deepEqual obj, {a: 1, b: 2}
  obj = a: 1, ['b']: 2
  deepEqual obj, {a: 1, b: 2}
  obj = ['a']: 1, ['b']: 2
  deepEqual obj, {a: 1, b: 2}

test "computed property keys: commas in explicit object", ->
  obj = {['a']: 1, b: 2}
  deepEqual obj, {a: 1, b: 2}
  obj = {a: 1, ['b']: 2}
  deepEqual obj, {a: 1, b: 2}
  obj = {['a']: 1, ['b']: 2}
  deepEqual obj, {a: 1, b: 2}

test "computed property keys: commas after key with interpolation", ->
  obj = {['a']: yes,}
  eq obj.a, yes
  obj = {
    ['a']: 1,
    ['b']: 2,
    ### herecomment ###
    ['c']: 3,
  }
  deepEqual obj, {a: 1, b: 2, c: 3}
  obj =
    ['a']: 1,
    ['b']: 2,
    ### herecomment ###
    ['c']: 3,
  deepEqual obj, {a: 1, b: 2, c: 3}
  obj =
    ['a']: 1,
    ['b']: 2,
    ### herecomment ###
    ['c']: 3, ['d']: 4,
  deepEqual obj, {a: 1, b: 2, c: 3, d: 4}

test "computed property keys: key with interpolation mixed with `@prop`", ->
  deepEqual (-> {@a, ['b']: 2}).call(a: 1), {a: 1, b: 2}

test "computed property keys: evaluate only once", ->
  count = 0
  a = -> count++; 'a'
  obj = {[a()]}
  eq obj.a, 'a'
  eq count, 1

test "computed property keys: evaluation order", ->
  arr = []
  obj =
    a: arr.push 1
    b: arr.push 2
    ['c']: arr.push 3
    ['d']: arr.push 4
    e: arr.push 5
    ['f']: arr.push 6
    g: arr.push 7
  arrayEq arr, [1..7]
  deepEqual obj, {a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7}

test "computed property keys: object starting with dynamic key", ->
  obj =
    ['a']: 1
    b: 2
  deepEqual obj, {a: 1, b: 2}

test "computed property keys: comments in implicit object", ->
  obj =
    ### leading comment ###
    ['a']: 1

    ### middle ###

    ['b']: 2
    # regular comment
    'c': 3
    ### foo ###
    d: 4
    ['e']: 5
  deepEqual obj, {a: 1, b: 2, c: 3, d: 4, e: 5}

  obj = {
    ### leading comment ###
    ['a']: 1

    ### middle ###

    ['b']: 2
    # regular comment
    'c': 3
    ### foo ###
    d: 4
    ['e']: 5
  }
  deepEqual obj, {a: 1, b: 2, c: 3, d: 4, e: 5}

test "computed property keys: more complicated case", ->
  obj = {
    ['interpolated']:
       ['nested']:
         123: 456
  }
  deepEqual obj,
    interpolated:
      nested:
        123: 456

test "computed property keys: empty array as key", ->
  o1 = { [[]] }
  deepEqual o1, { [[]]: [] }
  arrayEq o1[[]], []
  o2 = { [[]]: 1 }
  deepEqual o2, { [[]]: 1 }
  eq o2[[]], 1
  o3 = [[]]: 1
  deepEqual o3, { [[]]: 1 }
  deepEqual o3, { [[]]: 1 }
  eq o3[[]], 1
  o4 = a: 1, [[]]: 2
  deepEqual o4, { a: 1, [[]]: 2 }
  eq o4.a, 1,
  eq o4[[]], 2
  o5 = { a: 1, [[]]: 2 }
  deepEqual o5, { a: 1, [[]]: 2 }
  eq o5.a, 1,
  eq o5[[]], 2

test "computed property keys: shorthand after computed property key", ->
  a = 2
  obj = {[1]: 1, a}
  eq 1, obj[1]
  eq 2, obj.a

test "computed property keys: shorthand computed property key", ->
  a = 'b'
  o = {[a]}
  p = {a}
  r = {['a']}
  eq o.b, 'b'
  eq p.a, o.b
  eq r.a, 'a'

  foo = -> "a"
  obj = { [foo()] }
  eq obj.a, 'a'

test "computed property keys: arrays", ->
  b = 'b'
  f = (c) -> "#{c}1"
  obj =
    ['a']: [1, 2, 3]
    [b]: [4, 5, 6]
    [f(b)]: [7, 8, 9]
  arrayEq obj.a, [1, 2, 3]
  arrayEq obj.b, [4, 5, 6]
  arrayEq obj.b1, [7, 8, 9]

test "computed property keys: examples from developer.mozilla.org (Object initializer)", ->
  i = 0
  obj =
    ['foo' + ++i]: i
    ['foo' + ++i]: i
    ['foo' + ++i]: i
  eq obj.foo1, 1
  eq obj.foo2, 2
  eq obj.foo3, 3

  param = 'size'
  config =
    [param]: 12,
    ['mobile' + param.charAt(0).toUpperCase() + param.slice(1)]: 4
  deepEqual config, {size: 12,  mobileSize: 4}

test "computed property keys: [Symbol.iterator]", ->
  obj =
    [Symbol.iterator]: ->
      yield "hello"
      yield "world"
  arrayEq [obj...], ['hello', 'world']

test "computed property keys: Class property", ->
  increment_method = "increment"
  decrement_method = "decrement"
  class Obs
    constructor: (@count) ->
    [increment_method]: -> @count += 1
    [decrement_method]: -> @count -= 1
  ob = new Obs 2
  eq ob.increment(), 3
  eq ob.decrement(), 2

test "#1263: Braceless object return", ->
  fn = ->
    return
      a: 1
      b: 2
      c: -> 3

  obj = fn()
  eq 1, obj.a
  eq 2, obj.b
  eq 3, obj.c()

test "#4564: indent should close implicit object", ->
  f = (x) -> x

  arrayEq ['a'],
    for key of f a: 1
      key

  g = null
  if f a: 1
    g = 3
  eq g, 3

  h = null
  if a: (i for i in [1, 2, 3])
    h = 4
  eq h, 4

test "#4544: Postfix conditionals in first line of implicit object literals", ->
  two =
    foo:
      bar: 42 if yes
      baz: 1337
  eq 42, two.foo.bar
  eq 1337, two.foo.baz

  f = (x) -> x

  three =
    foo: f
      bar: 42 if yes
      baz: 1337
  eq 42, three.foo.bar
  eq 1337, three.foo.baz

  four =
    f
      foo:
        bar: 42 if yes
      baz: 1337
  eq 42, four.foo.bar
  eq 1337, four.baz

  x = bar: 42 if no
  baz: 1337
  ok not x?

  # Example from #2051
  a = null
  _alert = (arg) -> a = arg
  _alert
    val3: "works" if true
    val: "hello"
    val2: "all good"
  eq a.val2, "all good"

test "#4579: Postfix for/while/until in first line of implicit object literals", ->
  two =
    foo:
      bar1: x for x in [1, 2, 3]
      bar2: x + y for x, y in [1, 2, 3]
      baz: 1337
  arrayEq [1, 2, 3], two.foo.bar1
  arrayEq [1, 3, 5], two.foo.bar2
  eq 1337, two.foo.baz

  f = (x) -> x

  three =
    foo: f
      bar1: x + y for x, y of a: 'b', c: 'd'
      bar2: x + 'c' for x of a: 1, b: 2
      baz: 1337
  arrayEq ['ab', 'cd'], three.foo.bar1
  arrayEq ['ac', 'bc'], three.foo.bar2
  eq 1337, three.foo.baz

  four =
    f
      foo:
        "bar_#{x}": x for x of a: 1, b: 2
      baz: 1337
  eq 'a', four.foo[0].bar_a
  eq 'b', four.foo[1].bar_b
  eq 1337, four.baz

  x = bar: 42 for y in [1]
  baz: 1337
  eq x.bar, 42

  i = 5
  five =
    foo:
      bar: i while i-- > 0
      baz: 1337
  arrayEq [4, 3, 2, 1, 0], five.foo.bar
  eq 1337, five.foo.baz

  i = 5
  six =
    foo:
      bar: i until i-- <= 0
      baz: 1337
  arrayEq [4, 3, 2, 1, 0], six.foo.bar
  eq 1337, six.foo.baz
