1
0
Fork 0
mirror of https://github.com/jashkenas/coffeescript.git synced 2022-11-09 12:23:24 -05:00
jashkenas--coffeescript/test/classes.coffee
Simon Lydell 76c076db55 Fix #3597: Allow interpolations in object keys
The following is now allowed:

    o =
      a: 1
      b: 2
      "#{'c'}": 3
      "#{'d'}": 4
      e: 5
      "#{'f'}": 6
      g: 7

It compiles to:

    o = (
      obj = {
        a: 1,
        b: 2
      },
      obj["" + 'c'] = 3,
      obj["" + 'd'] = 4,
      obj.e = 5,
      obj["" + 'f'] = 6,
      obj.g = 7,
      obj
    );

- Closes #3039. Empty interpolations in object keys are now _supposed_ to be
  allowed.
- Closes #1131. No need to improve error messages for attempted key
  interpolation anymore.
- Implementing this required fixing the following bug: `("" + a): 1` used to
  error out on the colon, saying "unexpected colon". But really, it is the
  attempted object key that is unexpected. Now the error is on the opening
  parenthesis instead.
- However, the above fix broke some error message tests for regexes. The easiest
  way to fix this was to make a seemingly unrelated change: The error messages
  for unexpected identifiers, numbers, strings and regexes now say for example
  'unexpected string' instead of 'unexpected """some #{really long} string"""'.
  In other words, the tag _name_ is used instead of the tag _value_.
  This was way easier to implement, and is more helpful to the user. Using the
  tag value is good for operators, reserved words and the like, but not for
  tokens which can contain any text. For example, 'unexpected identifier' is
  better than 'unexpected expected' (if a variable called 'expected' was used
  erraneously).
- While writing tests for the above point I found a few minor bugs with string
  locations which have been fixed.
2015-02-09 17:32:37 +01:00

896 lines
16 KiB
CoffeeScript

# Classes
# -------
# * Class Definition
# * Class Instantiation
# * Inheritance and Super
test "classes with a four-level inheritance chain", ->
class Base
func: (string) ->
"zero/#{string}"
@static: (string) ->
"static/#{string}"
class FirstChild extends Base
func: (string) ->
super('one/') + string
SecondChild = class extends FirstChild
func: (string) ->
super('two/') + string
thirdCtor = ->
@array = [1, 2, 3]
class ThirdChild extends SecondChild
constructor: -> thirdCtor.call this
# Gratuitous comment for testing.
func: (string) ->
super('three/') + string
result = (new ThirdChild).func 'four'
ok result is 'zero/one/two/three/four'
ok Base.static('word') is 'static/word'
FirstChild::func = (string) ->
super('one/').length + string
result = (new ThirdChild).func 'four'
ok result is '9two/three/four'
ok (new ThirdChild).array.join(' ') is '1 2 3'
test "constructors with inheritance and super", ->
identity = (f) -> f
class TopClass
constructor: (arg) ->
@prop = 'top-' + arg
class SuperClass extends TopClass
constructor: (arg) ->
identity super 'super-' + arg
class SubClass extends SuperClass
constructor: ->
identity super 'sub'
ok (new SubClass).prop is 'top-super-sub'
test "Overriding the static property new doesn't clobber Function::new", ->
class OneClass
@new: 'new'
function: 'function'
constructor: (name) -> @name = name
class TwoClass extends OneClass
delete TwoClass.new
Function.prototype.new = -> new this arguments...
ok (TwoClass.new('three')).name is 'three'
ok (new OneClass).function is 'function'
ok OneClass.new is 'new'
delete Function.prototype.new
test "basic classes, again, but in the manual prototype style", ->
Base = ->
Base::func = (string) ->
'zero/' + string
Base::['func-func'] = (string) ->
"dynamic-#{string}"
FirstChild = ->
SecondChild = ->
ThirdChild = ->
@array = [1, 2, 3]
this
ThirdChild extends SecondChild extends FirstChild extends Base
FirstChild::func = (string) ->
super('one/') + string
SecondChild::func = (string) ->
super('two/') + string
ThirdChild::func = (string) ->
super('three/') + string
result = (new ThirdChild).func 'four'
ok result is 'zero/one/two/three/four'
ok (new ThirdChild)['func-func']('thing') is 'dynamic-thing'
test "super with plain ol' prototypes", ->
TopClass = ->
TopClass::func = (arg) ->
'top-' + arg
SuperClass = ->
SuperClass extends TopClass
SuperClass::func = (arg) ->
super 'super-' + arg
SubClass = ->
SubClass extends SuperClass
SubClass::func = ->
super 'sub'
eq (new SubClass).func(), 'top-super-sub'
test "'@' referring to the current instance, and not being coerced into a call", ->
class ClassName
amI: ->
@ instanceof ClassName
obj = new ClassName
ok obj.amI()
test "super() calls in constructors of classes that are defined as object properties", ->
class Hive
constructor: (name) -> @name = name
class Hive.Bee extends Hive
constructor: (name) -> super
maya = new Hive.Bee 'Maya'
ok maya.name is 'Maya'
test "classes with JS-keyword properties", ->
class Class
class: 'class'
name: -> @class
instance = new Class
ok instance.class is 'class'
ok instance.name() is 'class'
test "Classes with methods that are pre-bound to the instance, or statically, to the class", ->
class Dog
constructor: (name) ->
@name = name
bark: =>
"#{@name} woofs!"
@static = =>
new this('Dog')
spark = new Dog('Spark')
fido = new Dog('Fido')
fido.bark = spark.bark
ok fido.bark() is 'Spark woofs!'
obj = func: Dog.static
ok obj.func().name is 'Dog'
test "a bound function in a bound function", ->
class Mini
num: 10
generate: =>
for i in [1..3]
=>
@num
m = new Mini
eq (func() for func in m.generate()).join(' '), '10 10 10'
test "contructor called with varargs", ->
class Connection
constructor: (one, two, three) ->
[@one, @two, @three] = [one, two, three]
out: ->
"#{@one}-#{@two}-#{@three}"
list = [3, 2, 1]
conn = new Connection list...
ok conn instanceof Connection
ok conn.out() is '3-2-1'
test "calling super and passing along all arguments", ->
class Parent
method: (args...) -> @args = args
class Child extends Parent
method: -> super
c = new Child
c.method 1, 2, 3, 4
ok c.args.join(' ') is '1 2 3 4'
test "classes wrapped in decorators", ->
func = (klass) ->
klass::prop = 'value'
klass
func class Test
prop2: 'value2'
ok (new Test).prop is 'value'
ok (new Test).prop2 is 'value2'
test "anonymous classes", ->
obj =
klass: class
method: -> 'value'
instance = new obj.klass
ok instance.method() is 'value'
test "Implicit objects as static properties", ->
class Static
@static =
one: 1
two: 2
ok Static.static.one is 1
ok Static.static.two is 2
test "nothing classes", ->
c = class
ok c instanceof Function
test "classes with static-level implicit objects", ->
class A
@static = one: 1
two: 2
class B
@static = one: 1,
two: 2
eq A.static.one, 1
eq A.static.two, undefined
eq (new A).two, 2
eq B.static.one, 1
eq B.static.two, 2
eq (new B).two, undefined
test "classes with value'd constructors", ->
counter = 0
classMaker = ->
inner = ++counter
->
@value = inner
class One
constructor: classMaker()
class Two
constructor: classMaker()
eq (new One).value, 1
eq (new Two).value, 2
eq (new One).value, 1
eq (new Two).value, 2
test "executable class bodies", ->
class A
if true
b: 'b'
else
c: 'c'
a = new A
eq a.b, 'b'
eq a.c, undefined
test "#2502: parenthesizing inner object values", ->
class A
category: (type: 'string')
sections: (type: 'number', default: 0)
eq (new A).category.type, 'string'
eq (new A).sections.default, 0
test "conditional prototype property assignment", ->
debug = false
class Person
if debug
age: -> 10
else
age: -> 20
eq (new Person).age(), 20
test "mild metaprogramming", ->
class Base
@attr: (name) ->
@::[name] = (val) ->
if arguments.length > 0
@["_#{name}"] = val
else
@["_#{name}"]
class Robot extends Base
@attr 'power'
@attr 'speed'
robby = new Robot
ok robby.power() is undefined
robby.power 11
robby.speed Infinity
eq robby.power(), 11
eq robby.speed(), Infinity
test "namespaced classes do not reserve their function name in outside scope", ->
one = {}
two = {}
class one.Klass
@label = "one"
class two.Klass
@label = "two"
eq typeof Klass, 'undefined'
eq one.Klass.label, 'one'
eq two.Klass.label, 'two'
test "nested classes", ->
class Outer
constructor: ->
@label = 'outer'
class @Inner
constructor: ->
@label = 'inner'
eq (new Outer).label, 'outer'
eq (new Outer.Inner).label, 'inner'
test "variables in constructor bodies are correctly scoped", ->
class A
x = 1
constructor: ->
x = 10
y = 20
y = 2
captured: ->
{x, y}
a = new A
eq a.captured().x, 10
eq a.captured().y, 2
test "Issue #924: Static methods in nested classes", ->
class A
@B: class
@c = -> 5
eq A.B.c(), 5
test "`class extends this`", ->
class A
func: -> 'A'
B = null
makeClass = ->
B = class extends this
func: -> super + ' B'
makeClass.call A
eq (new B()).func(), 'A B'
test "ensure that constructors invoked with splats return a new object", ->
args = [1, 2, 3]
Type = (@args) ->
type = new Type args
ok type and type instanceof Type
ok type.args and type.args instanceof Array
ok v is args[i] for v, i in type.args
Type1 = (@a, @b, @c) ->
type1 = new Type1 args...
ok type1 instanceof Type1
eq type1.constructor, Type1
ok type1.a is args[0] and type1.b is args[1] and type1.c is args[2]
# Ensure that constructors invoked with splats cache the function.
called = 0
get = -> if called++ then false else class Type
new get() args...
test "`new` shouldn't add extra parens", ->
ok new Date().constructor is Date
test "`new` works against bare function", ->
eq Date, new ->
eq this, new => this
Date
test "#1182: a subclass should be able to set its constructor to an external function", ->
ctor = ->
@val = 1
class A
class B extends A
constructor: ctor
eq (new B).val, 1
test "#1182: external constructors continued", ->
ctor = ->
class A
class B extends A
method: ->
constructor: ctor
ok B::method
test "#1313: misplaced __extends", ->
nonce = {}
class A
class B extends A
prop: nonce
constructor: ->
eq nonce, B::prop
test "#1182: execution order needs to be considered as well", ->
counter = 0
makeFn = (n) -> eq n, ++counter; ->
class B extends (makeFn 1)
@B: makeFn 2
constructor: makeFn 3
test "#1182: external constructors with bound functions", ->
fn = ->
{one: 1}
this
class B
class A
constructor: fn
method: => this instanceof A
ok (new A).method.call(new B)
test "#1372: bound class methods with reserved names", ->
class C
delete: =>
ok C::delete
test "#1380: `super` with reserved names", ->
class C
do: -> super
ok C::do
class B
0: -> super
ok B::[0]
test "#1464: bound class methods should keep context", ->
nonce = {}
nonce2 = {}
class C
constructor: (@id) ->
@boundStaticColon: => new this(nonce)
@boundStaticEqual= => new this(nonce2)
eq nonce, C.boundStaticColon().id
eq nonce2, C.boundStaticEqual().id
test "#1009: classes with reserved words as determined names", -> (->
eq 'function', typeof (class @for)
ok not /\beval\b/.test (class @eval).toString()
ok not /\barguments\b/.test (class @arguments).toString()
).call {}
test "#1482: classes can extend expressions", ->
id = (x) -> x
nonce = {}
class A then nonce: nonce
class B extends id A
eq nonce, (new B).nonce
test "#1598: super works for static methods too", ->
class Parent
method: ->
'NO'
@method: ->
'yes'
class Child extends Parent
@method: ->
'pass? ' + super
eq Child.method(), 'pass? yes'
test "#1842: Regression with bound functions within bound class methods", ->
class Store
@bound: =>
do =>
eq this, Store
Store.bound()
# And a fancier case:
class Store
eq this, Store
@bound: =>
do =>
eq this, Store
@unbound: ->
eq this, Store
instance: =>
ok this instanceof Store
Store.bound()
Store.unbound()
(new Store).instance()
test "#1876: Class @A extends A", ->
class A
class @A extends A
ok (new @A) instanceof A
test "#1813: Passing class definitions as expressions", ->
ident = (x) -> x
result = ident class A then x = 1
eq result, A
result = ident class B extends A
x = 1
eq result, B
test "#1966: external constructors should produce their return value", ->
ctor = -> {}
class A then constructor: ctor
ok (new A) not instanceof A
test "#1980: regression with an inherited class with static function members", ->
class A
class B extends A
@static: => 'value'
eq B.static(), 'value'
test "#1534: class then 'use strict'", ->
# [14.1 Directive Prologues and the Use Strict Directive](http://es5.github.com/#x14.1)
nonce = {}
error = 'do -> ok this'
strictTest = "do ->'use strict';#{error}"
return unless (try CoffeeScript.run strictTest, bare: yes catch e then nonce) is nonce
throws -> CoffeeScript.run "class then 'use strict';#{error}", bare: yes
doesNotThrow -> CoffeeScript.run "class then #{error}", bare: yes
doesNotThrow -> CoffeeScript.run "class then #{error};'use strict'", bare: yes
# comments are ignored in the Directive Prologue
comments = ["""
class
### comment ###
'use strict'
#{error}""",
"""
class
### comment 1 ###
### comment 2 ###
'use strict'
#{error}""",
"""
class
### comment 1 ###
### comment 2 ###
'use strict'
#{error}
### comment 3 ###"""
]
throws (-> CoffeeScript.run comment, bare: yes) for comment in comments
# [ES5 §14.1](http://es5.github.com/#x14.1) allows for other directives
directives = ["""
class
'directive 1'
'use strict'
#{error}""",
"""
class
'use strict'
'directive 2'
#{error}""",
"""
class
### comment 1 ###
'directive 1'
'use strict'
#{error}""",
"""
class
### comment 1 ###
'directive 1'
### comment 2 ###
'use strict'
#{error}"""
]
throws (-> CoffeeScript.run directive, bare: yes) for directive in directives
test "#2052: classes should work in strict mode", ->
try
do ->
'use strict'
class A
catch e
ok no
test "directives in class with extends ", ->
strictTest = """
class extends Object
### comment ###
'use strict'
do -> eq this, undefined
"""
CoffeeScript.run strictTest, bare: yes
test "#2630: class bodies can't reference arguments", ->
throws ->
CoffeeScript.compile('class Test then arguments')
test "#2319: fn class n extends o.p [INDENT] x = 123", ->
first = ->
base = onebase: ->
first class OneKeeper extends base.onebase
one = 1
one: -> one
eq new OneKeeper().one(), 1
test "#2599: other typed constructors should be inherited", ->
class Base
constructor: -> return {}
class Derived extends Base
ok (new Derived) not instanceof Derived
ok (new Derived) not instanceof Base
ok (new Base) not instanceof Base
test "#2359: extending native objects that use other typed constructors requires defining a constructor", ->
class BrokenArray extends Array
method: -> 'no one will call me'
brokenArray = new BrokenArray
ok brokenArray not instanceof BrokenArray
ok typeof brokenArray.method is 'undefined'
class WorkingArray extends Array
constructor: -> super
method: -> 'yes!'
workingArray = new WorkingArray
ok workingArray instanceof WorkingArray
eq 'yes!', workingArray.method()
test "#2782: non-alphanumeric-named bound functions", ->
class A
'b:c': =>
'd'
eq (new A)['b:c'](), 'd'
test "#2781: overriding bound functions", ->
class A
a: ->
@b()
b: =>
1
class B extends A
b: =>
2
b = (new A).b
eq b(), 1
b = (new B).b
eq b(), 2
test "#2791: bound function with destructured argument", ->
class Foo
method: ({a}) => 'Bar'
eq (new Foo).method({a: 'Bar'}), 'Bar'
test "#2796: ditto, ditto, ditto", ->
answer = null
outsideMethod = (func) ->
func.call message: 'wrong!'
class Base
constructor: ->
@message = 'right!'
outsideMethod @echo
echo: =>
answer = @message
new Base
eq answer, 'right!'
test "#3063: Class bodies cannot contain pure statements", ->
throws -> CoffeeScript.compile """
class extends S
return if S.f
@f: => this
"""
test "#2949: super in static method with reserved name", ->
class Foo
@static: -> 'baz'
class Bar extends Foo
@static: -> super
eq Bar.static(), 'baz'
test "#3232: super in static methods (not object-assigned)", ->
class Foo
@baz = -> true
@qux = -> true
class Bar extends Foo
@baz = -> super
Bar.qux = -> super
ok Bar.baz()
ok Bar.qux()
test "#1392 calling `super` in methods defined on namespaced classes", ->
class Base
m: -> 5
n: -> 4
namespace =
A: ->
B: ->
namespace.A extends Base
namespace.A::m = -> super
eq 5, (new namespace.A).m()
namespace.B::m = namespace.A::m
namespace.A::m = null
eq 5, (new namespace.B).m()
count = 0
getNamespace = -> count++; namespace
getNamespace().A::n = -> super
eq 4, (new namespace.A).n()
eq 1, count
class C
@a: ->
@a extends Base
@a::m = -> super
eq 5, (new C.a).m()
test "dynamic method names and super", ->
class Base
@m: -> 6
m: -> 5
m2: -> 4.5
n: -> 4
A = ->
A extends Base
m = 'm'
A::[m] = -> super
m = 'n'
eq 5, (new A).m()
name = -> count++; 'n'
count = 0
A::[name()] = -> super
eq 4, (new A).n()
eq 1, count
m = 'm'
m2 = 'm2'
count = 0
class B extends Base
@[name()] = -> super
@::[m] = -> super
"#{m2}": -> super
b = new B
m = m2 = 'n'
eq 6, B.m()
eq 5, b.m()
eq 4.5, b.m2()
eq 1, count
class C extends B
m: -> super
eq 5, (new C).m()