# 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