mirror of
https://github.com/jashkenas/coffeescript.git
synced 2022-11-09 12:23:24 -05:00
27eff5ca77
* Fix #4464: backticked expressions in class body should be left in the body, not hoisted
* Fix #4464: backticked expressions in class body should be left in the body, not hoisted
* Simplify fix for #4464
This uses more of the existing machinery for moving class body
expressions into the initializer.
* Clarify the purpose of Class::addInitializerExpression
* Further clarify the purpose of Class::addInitializerExpression
* Add reference to class fields; format
comment wrapping
* Reapply 1d3af8c432
, that got lost because of rebase/force-push shenanigans
* Updated output
1850 lines
38 KiB
CoffeeScript
1850 lines
38 KiB
CoffeeScript
# Classes
|
|
# -------
|
|
|
|
# * Class Definition
|
|
# * Class Instantiation
|
|
# * Inheritance and Super
|
|
# * ES2015+ Class Interoperability
|
|
|
|
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: ->
|
|
super()
|
|
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'
|
|
|
|
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 "'super' with accessors", ->
|
|
class Base
|
|
m: -> 4
|
|
n: -> 5
|
|
o: -> 6
|
|
|
|
name = 'o'
|
|
class A extends Base
|
|
m: -> super()
|
|
n: -> super.n()
|
|
"#{name}": -> super()
|
|
p: -> super[name]()
|
|
|
|
a = new A
|
|
eq 4, a.m()
|
|
eq 5, a.n()
|
|
eq 6, a.o()
|
|
eq 6, a.p()
|
|
|
|
|
|
test "soaked 'super' invocation", ->
|
|
class Base
|
|
method: -> 2
|
|
|
|
class A extends Base
|
|
method: -> super?()
|
|
noMethod: -> super?()
|
|
|
|
a = new A
|
|
eq 2, a.method()
|
|
eq undefined, a.noMethod()
|
|
|
|
name = 'noMethod'
|
|
class B extends Base
|
|
"#{'method'}": -> super?()
|
|
"#{'noMethod'}": -> super?() ? super['method']()
|
|
|
|
b = new B
|
|
eq 2, b.method()
|
|
eq 2, b.noMethod()
|
|
|
|
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 name
|
|
|
|
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 arguments...
|
|
|
|
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 ->
|
|
Date
|
|
|
|
|
|
test "#1182: a subclass should be able to set its constructor to an external function", ->
|
|
ctor = ->
|
|
@val = 1
|
|
return
|
|
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: -> super()
|
|
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')
|
|
|
|
# #4320: Don't be too eager when checking, though.
|
|
class Test
|
|
arguments: 5
|
|
eq 5, Test::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 "extending native objects works with and without defining a constructor", ->
|
|
class MyArray extends Array
|
|
method: -> 'yes!'
|
|
|
|
myArray = new MyArray
|
|
ok myArray instanceof MyArray
|
|
ok 'yes!', myArray.method()
|
|
|
|
class OverrideArray extends Array
|
|
constructor: -> super()
|
|
method: -> 'yes!'
|
|
|
|
overrideArray = new OverrideArray
|
|
ok overrideArray instanceof OverrideArray
|
|
eq 'yes!', overrideArray.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: ->
|
|
class namespace.A extends Base
|
|
m: -> super()
|
|
|
|
eq 5, (new namespace.A).m()
|
|
namespace.B::m = namespace.A::m
|
|
namespace.A::m = null
|
|
eq 5, (new namespace.B).m()
|
|
|
|
class C
|
|
@a: class extends Base
|
|
m: -> super()
|
|
eq 5, (new C.a).m()
|
|
|
|
|
|
test "#4436 immediately instantiated named class", ->
|
|
ok new class Foo
|
|
|
|
|
|
test "dynamic method names", ->
|
|
class A
|
|
"#{name = 'm'}": -> 1
|
|
eq 1, new A().m()
|
|
|
|
class B extends A
|
|
"#{name = 'm'}": -> super()
|
|
eq 1, new B().m()
|
|
|
|
getName = -> 'm'
|
|
class C
|
|
"#{name = getName()}": -> 1
|
|
eq 1, new C().m()
|
|
|
|
|
|
test "dynamic method names and super", ->
|
|
class Base
|
|
@m: -> 6
|
|
m: -> 5
|
|
m2: -> 4.5
|
|
n: -> 4
|
|
|
|
name = -> count++; 'n'
|
|
count = 0
|
|
|
|
m = 'm'
|
|
class A extends Base
|
|
"#{m}": -> super()
|
|
"#{name()}": -> super()
|
|
|
|
m = 'n'
|
|
eq 5, (new A).m()
|
|
|
|
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()
|
|
|
|
# ES2015+ class interoperability
|
|
# Based on https://github.com/balupton/es6-javascript-class-interop
|
|
# Helper functions to generate true ES classes to extend:
|
|
getBasicClass = ->
|
|
```
|
|
class BasicClass {
|
|
constructor (greeting) {
|
|
this.greeting = greeting || 'hi'
|
|
}
|
|
}
|
|
```
|
|
BasicClass
|
|
|
|
getExtendedClass = (BaseClass) ->
|
|
```
|
|
class ExtendedClass extends BaseClass {
|
|
constructor (greeting, name) {
|
|
super(greeting || 'hello')
|
|
this.name = name
|
|
}
|
|
}
|
|
```
|
|
ExtendedClass
|
|
|
|
test "can instantiate a basic ES class", ->
|
|
BasicClass = getBasicClass()
|
|
i = new BasicClass 'howdy!'
|
|
eq i.greeting, 'howdy!'
|
|
|
|
test "can instantiate an extended ES class", ->
|
|
BasicClass = getBasicClass()
|
|
ExtendedClass = getExtendedClass BasicClass
|
|
i = new ExtendedClass 'yo', 'buddy'
|
|
eq i.greeting, 'yo'
|
|
eq i.name, 'buddy'
|
|
|
|
test "can extend a basic ES class", ->
|
|
BasicClass = getBasicClass()
|
|
class ExtendedClass extends BasicClass
|
|
constructor: (@name) ->
|
|
super()
|
|
i = new ExtendedClass 'dude'
|
|
eq i.name, 'dude'
|
|
|
|
test "can extend an extended ES class", ->
|
|
BasicClass = getBasicClass()
|
|
ExtendedClass = getExtendedClass BasicClass
|
|
|
|
class ExtendedExtendedClass extends ExtendedClass
|
|
constructor: (@value) ->
|
|
super()
|
|
getDoubledValue: ->
|
|
@value * 2
|
|
|
|
i = new ExtendedExtendedClass 7
|
|
eq i.getDoubledValue(), 14
|
|
|
|
test "CoffeeScript class can be extended in ES", ->
|
|
class CoffeeClass
|
|
constructor: (@favoriteDrink = 'latte', @size = 'grande') ->
|
|
getDrinkOrder: ->
|
|
"#{@size} #{@favoriteDrink}"
|
|
|
|
```
|
|
class ECMAScriptClass extends CoffeeClass {
|
|
constructor (favoriteDrink) {
|
|
super(favoriteDrink);
|
|
this.favoriteDrink = this.favoriteDrink + ' with a dash of semicolons';
|
|
}
|
|
}
|
|
```
|
|
|
|
e = new ECMAScriptClass 'coffee'
|
|
eq e.getDrinkOrder(), 'grande coffee with a dash of semicolons'
|
|
|
|
test "extended CoffeeScript class can be extended in ES", ->
|
|
class CoffeeClass
|
|
constructor: (@favoriteDrink = 'latte') ->
|
|
|
|
class CoffeeClassWithDrinkOrder extends CoffeeClass
|
|
constructor: (@favoriteDrink, @size = 'grande') ->
|
|
super()
|
|
getDrinkOrder: ->
|
|
"#{@size} #{@favoriteDrink}"
|
|
|
|
```
|
|
class ECMAScriptClass extends CoffeeClassWithDrinkOrder {
|
|
constructor (favoriteDrink) {
|
|
super(favoriteDrink);
|
|
this.favoriteDrink = this.favoriteDrink + ' with a dash of semicolons';
|
|
}
|
|
}
|
|
```
|
|
|
|
e = new ECMAScriptClass 'coffee'
|
|
eq e.getDrinkOrder(), 'grande coffee with a dash of semicolons'
|
|
|
|
test "`this` access after `super` in extended classes", ->
|
|
class Base
|
|
|
|
class Test extends Base
|
|
constructor: (param, @param) ->
|
|
eq param, nonce
|
|
|
|
result = { super: super(), @param, @method }
|
|
eq result.super, this
|
|
eq result.param, @param
|
|
eq result.method, @method
|
|
ok result.method isnt Test::method
|
|
|
|
method: =>
|
|
|
|
nonce = {}
|
|
new Test nonce, {}
|
|
|
|
test "`@`-params and bound methods with multiple `super` paths (blocks)", ->
|
|
nonce = {}
|
|
|
|
class Base
|
|
constructor: (@name) ->
|
|
|
|
class Test extends Base
|
|
constructor: (param, @param) ->
|
|
if param
|
|
super 'param'
|
|
eq @name, 'param'
|
|
else
|
|
super 'not param'
|
|
eq @name, 'not param'
|
|
eq @param, nonce
|
|
ok @method isnt Test::method
|
|
method: =>
|
|
new Test true, nonce
|
|
new Test false, nonce
|
|
|
|
|
|
test "`@`-params and bound methods with multiple `super` paths (expressions)", ->
|
|
nonce = {}
|
|
|
|
class Base
|
|
constructor: (@name) ->
|
|
|
|
class Test extends Base
|
|
constructor: (param, @param) ->
|
|
# Contrived example: force each path into an expression with inline assertions
|
|
if param
|
|
result = (
|
|
eq (super 'param'), @;
|
|
eq @name, 'param';
|
|
eq @param, nonce;
|
|
ok @method isnt Test::method
|
|
)
|
|
else
|
|
result = (
|
|
eq (super 'not param'), @;
|
|
eq @name, 'not param';
|
|
eq @param, nonce;
|
|
ok @method isnt Test::method
|
|
)
|
|
method: =>
|
|
new Test true, nonce
|
|
new Test false, nonce
|
|
|
|
test "constructor super in arrow functions", ->
|
|
class Test extends (class)
|
|
constructor: (@param) ->
|
|
do => super()
|
|
eq @param, nonce
|
|
|
|
new Test nonce = {}
|
|
|
|
# TODO Some of these tests use CoffeeScript.compile and CoffeeScript.run when they could use
|
|
# regular test mechanics.
|
|
# TODO Some of these tests might be better placed in `test/error_messages.coffee`.
|
|
# TODO Some of these tests are duplicates.
|
|
|
|
# Ensure that we always throw if we experience more than one super()
|
|
# call in a constructor. This ends up being a runtime error.
|
|
# Should be caught at compile time.
|
|
test "multiple super calls", ->
|
|
throwsA = """
|
|
class A
|
|
constructor: (@drink) ->
|
|
make: -> "Making a #{@drink}"
|
|
|
|
class MultiSuper extends A
|
|
constructor: (drink) ->
|
|
super(drink)
|
|
super(drink)
|
|
@newDrink = drink
|
|
new MultiSuper('Late').make()
|
|
"""
|
|
throws -> CoffeeScript.run throwsA, bare: yes
|
|
|
|
# Basic test to ensure we can pass @params in a constuctor and
|
|
# inheritance works correctly
|
|
test "@ params", ->
|
|
class A
|
|
constructor: (@drink, @shots, @flavor) ->
|
|
make: -> "Making a #{@flavor} #{@drink} with #{@shots} shot(s)"
|
|
|
|
a = new A('Machiato', 2, 'chocolate')
|
|
eq a.make(), "Making a chocolate Machiato with 2 shot(s)"
|
|
|
|
class B extends A
|
|
b = new B('Machiato', 2, 'chocolate')
|
|
eq b.make(), "Making a chocolate Machiato with 2 shot(s)"
|
|
|
|
# Ensure we can accept @params with default parameters in a constructor
|
|
test "@ params with defaults in a constructor", ->
|
|
class A
|
|
# Multiple @ params with defaults
|
|
constructor: (@drink = 'Americano', @shots = '1', @flavor = 'caramel') ->
|
|
make: -> "Making a #{@flavor} #{@drink} with #{@shots} shot(s)"
|
|
|
|
a = new A()
|
|
eq a.make(), "Making a caramel Americano with 1 shot(s)"
|
|
|
|
# Ensure we can handle default constructors with class params
|
|
test "@ params with class params", ->
|
|
class Beverage
|
|
drink: 'Americano'
|
|
shots: '1'
|
|
flavor: 'caramel'
|
|
|
|
class A
|
|
# Class creation as a default param with `this`
|
|
constructor: (@drink = new Beverage()) ->
|
|
a = new A()
|
|
eq a.drink.drink, 'Americano'
|
|
|
|
beverage = new Beverage
|
|
class B
|
|
# class costruction with a default external param
|
|
constructor: (@drink = beverage) ->
|
|
|
|
b = new B()
|
|
eq b.drink.drink, 'Americano'
|
|
|
|
class C
|
|
# Default constructor with anonymous empty class
|
|
constructor: (@meta = class) ->
|
|
c = new C()
|
|
ok c.meta instanceof Function
|
|
|
|
test "@ params without super, including errors", ->
|
|
classA = """
|
|
class A
|
|
constructor: (@drink) ->
|
|
make: -> "Making a #{@drink}"
|
|
a = new A('Machiato')
|
|
"""
|
|
|
|
throwsB = """
|
|
class B extends A
|
|
#implied super
|
|
constructor: (@drink) ->
|
|
b = new B('Machiato')
|
|
"""
|
|
throws -> CoffeeScript.compile classA + throwsB, bare: yes
|
|
|
|
test "@ params super race condition", ->
|
|
classA = """
|
|
class A
|
|
constructor: (@drink) ->
|
|
make: -> "Making a #{@drink}"
|
|
"""
|
|
|
|
throwsB = """
|
|
class B extends A
|
|
constructor: (@params) ->
|
|
|
|
b = new B('Machiato')
|
|
"""
|
|
throws -> CoffeeScript.compile classA + throwsB, bare: yes
|
|
|
|
# Race condition with @ and super
|
|
throwsC = """
|
|
class C extends A
|
|
constructor: (@params) ->
|
|
super(@params)
|
|
|
|
c = new C('Machiato')
|
|
"""
|
|
throws -> CoffeeScript.compile classA + throwsC, bare: yes
|
|
|
|
|
|
test "@ with super call", ->
|
|
class D
|
|
make: -> "Making a #{@drink}"
|
|
|
|
class E extends D
|
|
constructor: (@drink) ->
|
|
super()
|
|
|
|
e = new E('Machiato')
|
|
eq e.make(), "Making a Machiato"
|
|
|
|
test "@ with splats and super call", ->
|
|
class A
|
|
make: -> "Making a #{@drink}"
|
|
|
|
class B extends A
|
|
constructor: (@drink...) ->
|
|
super()
|
|
|
|
B = new B('Machiato')
|
|
eq B.make(), "Making a Machiato"
|
|
|
|
|
|
test "super and external constructors", ->
|
|
# external constructor with @ param is allowed
|
|
ctorA = (@drink) ->
|
|
class A
|
|
constructor: ctorA
|
|
make: -> "Making a #{@drink}"
|
|
a = new A('Machiato')
|
|
eq a.make(), "Making a Machiato"
|
|
|
|
# External constructor with super
|
|
throwsC = """
|
|
class B
|
|
constructor: (@drink) ->
|
|
make: -> "Making a #{@drink}"
|
|
|
|
ctorC = (drink) ->
|
|
super(drink)
|
|
|
|
class C extends B
|
|
constructor: ctorC
|
|
c = new C('Machiato')
|
|
"""
|
|
throws -> CoffeeScript.compile throwsC, bare: yes
|
|
|
|
|
|
test "bound functions without super", ->
|
|
# Bound function with @
|
|
# Throw on compile, since bound
|
|
# constructors are illegal
|
|
throwsA = """
|
|
class A
|
|
constructor: (drink) =>
|
|
@drink = drink
|
|
|
|
"""
|
|
throws -> CoffeeScript.compile throwsA, bare: yes
|
|
|
|
test "super in a bound function in a constructor", ->
|
|
throwsB = """
|
|
class A
|
|
class B extends A
|
|
constructor: do => super
|
|
"""
|
|
throws -> CoffeeScript.compile throwsB, bare: yes
|
|
|
|
test "super in a bound function", ->
|
|
class A
|
|
constructor: (@drink) ->
|
|
make: -> "Making a #{@drink}"
|
|
|
|
class B extends A
|
|
make: (@flavor) =>
|
|
super() + " with #{@flavor}"
|
|
|
|
b = new B('Machiato')
|
|
eq b.make('vanilla'), "Making a Machiato with vanilla"
|
|
|
|
# super in a bound function in a bound function
|
|
class C extends A
|
|
make: (@flavor) =>
|
|
func = () =>
|
|
super() + " with #{@flavor}"
|
|
func()
|
|
|
|
c = new C('Machiato')
|
|
eq c.make('vanilla'), "Making a Machiato with vanilla"
|
|
|
|
# bound function in a constructor
|
|
class D extends A
|
|
constructor: (drink) ->
|
|
super(drink)
|
|
x = =>
|
|
eq @drink, "Machiato"
|
|
x()
|
|
d = new D('Machiato')
|
|
eq d.make(), "Making a Machiato"
|
|
|
|
# duplicate
|
|
test "super in a try/catch", ->
|
|
classA = """
|
|
class A
|
|
constructor: (param) ->
|
|
throw "" unless param
|
|
"""
|
|
|
|
throwsB = """
|
|
class B extends A
|
|
constructor: ->
|
|
try
|
|
super()
|
|
"""
|
|
|
|
throwsC = """
|
|
ctor = ->
|
|
try
|
|
super()
|
|
|
|
class C extends A
|
|
constructor: ctor
|
|
"""
|
|
throws -> CoffeeScript.run classA + throwsB, bare: yes
|
|
throws -> CoffeeScript.run classA + throwsC, bare: yes
|
|
|
|
test "mixed ES6 and CS6 classes with a four-level inheritance chain", ->
|
|
# Extended test
|
|
# ES2015+ class interoperability
|
|
|
|
```
|
|
class Base {
|
|
constructor (greeting) {
|
|
this.greeting = greeting || 'hi';
|
|
}
|
|
func (string) {
|
|
return 'zero/' + string;
|
|
}
|
|
static staticFunc (string) {
|
|
return 'static/' + string;
|
|
}
|
|
}
|
|
```
|
|
|
|
class FirstChild extends Base
|
|
func: (string) ->
|
|
super('one/') + string
|
|
|
|
|
|
```
|
|
class SecondChild extends FirstChild {
|
|
func (string) {
|
|
return super.func('two/' + string);
|
|
}
|
|
}
|
|
```
|
|
|
|
thirdCtor = ->
|
|
@array = [1, 2, 3]
|
|
|
|
class ThirdChild extends SecondChild
|
|
constructor: ->
|
|
super()
|
|
thirdCtor.call this
|
|
func: (string) ->
|
|
super('three/') + string
|
|
|
|
result = (new ThirdChild).func 'four'
|
|
ok result is 'zero/one/two/three/four'
|
|
ok Base.staticFunc('word') is 'static/word'
|
|
|
|
# exercise extends in a nested class
|
|
test "nested classes with super", ->
|
|
class Outer
|
|
constructor: ->
|
|
@label = 'outer'
|
|
|
|
class @Inner
|
|
constructor: ->
|
|
@label = 'inner'
|
|
|
|
class @ExtendedInner extends @Inner
|
|
constructor: ->
|
|
tmp = super()
|
|
@label = tmp.label + ' extended'
|
|
|
|
@extender: () =>
|
|
class ExtendedSelf extends @
|
|
constructor: ->
|
|
tmp = super()
|
|
@label = tmp.label + ' from this'
|
|
new ExtendedSelf
|
|
|
|
eq (new Outer).label, 'outer'
|
|
eq (new Outer.Inner).label, 'inner'
|
|
eq (new Outer.ExtendedInner).label, 'inner extended'
|
|
eq (Outer.extender()).label, 'outer from this'
|
|
|
|
test "Static methods generate 'static' keywords", ->
|
|
compile = """
|
|
class CheckStatic
|
|
constructor: (@drink) ->
|
|
@className: -> 'CheckStatic'
|
|
|
|
c = new CheckStatic('Machiato')
|
|
"""
|
|
result = CoffeeScript.compile compile, bare: yes
|
|
ok result.match(' static ')
|
|
|
|
test "Static methods in nested classes", ->
|
|
class Outer
|
|
@name: -> 'Outer'
|
|
|
|
class @Inner
|
|
@name: -> 'Inner'
|
|
|
|
eq Outer.name(), 'Outer'
|
|
eq Outer.Inner.name(), 'Inner'
|
|
|
|
|
|
test "mixed constructors with inheritance and ES6 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 "ES6 static class methods can be overriden", ->
|
|
class A
|
|
@name: -> 'A'
|
|
|
|
class B extends A
|
|
@name: -> 'B'
|
|
|
|
eq A.name(), 'A'
|
|
eq B.name(), 'B'
|
|
|
|
# If creating static by direct assignment rather than ES6 static keyword
|
|
test "ES6 Static methods should set `this` to undefined // ES6 ", ->
|
|
class A
|
|
@test: ->
|
|
eq this, undefined
|
|
|
|
# Ensure that our object prototypes work with ES6
|
|
test "ES6 prototypes can be overriden", ->
|
|
class A
|
|
className: 'classA'
|
|
|
|
```
|
|
class B {
|
|
test () {return "B";};
|
|
}
|
|
```
|
|
b = new B
|
|
a = new A
|
|
eq a.className, 'classA'
|
|
eq b.test(), 'B'
|
|
Object.setPrototypeOf(b, a)
|
|
eq b.className, 'classA'
|
|
# This shouldn't throw,
|
|
# as we only change inheritance not object construction
|
|
# This may be an issue with ES, rather than CS construction?
|
|
#eq b.test(), 'B'
|
|
|
|
class D extends B
|
|
B::test = () -> 'D'
|
|
eq (new D).test(), 'D'
|
|
|
|
# TODO: implement this error check
|
|
# test "ES6 conformance to extending non-classes", ->
|
|
# A = (@title) ->
|
|
# 'Title: ' + @
|
|
|
|
# class B extends A
|
|
# b = new B('caffeinated')
|
|
# eq b.title, 'caffeinated'
|
|
|
|
# # Check inheritance chain
|
|
# A::getTitle = () -> @title
|
|
# eq b.getTitle(), 'caffeinated'
|
|
|
|
# throwsC = """
|
|
# C = {title: 'invalid'}
|
|
# class D extends {}
|
|
# """
|
|
# # This should catch on compile and message should be "class can only extend classes and functions."
|
|
# throws -> CoffeeScript.run throwsC, bare: yes
|
|
|
|
# TODO: Evaluate future compliance with "strict mode";
|
|
# test "Class function environment should be in `strict mode`, ie as if 'use strict' was in use", ->
|
|
# class A
|
|
# # this might be a meaningless test, since these are likely to be runtime errors and different
|
|
# # for every browser. Thoughts?
|
|
# constructor: () ->
|
|
# # Ivalid: prop reassignment
|
|
# @state = {prop: [1], prop: {a: 'a'}}
|
|
# # eval reassignment
|
|
# @badEval = eval;
|
|
|
|
# # Should throw, but doesn't
|
|
# a = new A
|
|
|
|
# TODO: new.target needs support Separate issue
|
|
# test "ES6 support for new.target (functions and constructors)", ->
|
|
# throwsA = """
|
|
# class A
|
|
# constructor: () ->
|
|
# a = new.target.name
|
|
# """
|
|
# throws -> CoffeeScript.compile throwsA, bare: yes
|
|
|
|
test "only one method named constructor allowed", ->
|
|
throwsA = """
|
|
class A
|
|
constructor: (@first) ->
|
|
constructor: (@last) ->
|
|
"""
|
|
throws -> CoffeeScript.compile throwsA, bare: yes
|
|
|
|
test "If the constructor of a child class does not call super,it should return an object.", ->
|
|
nonce = {}
|
|
|
|
class A
|
|
class B extends A
|
|
constructor: ->
|
|
return nonce
|
|
|
|
eq nonce, new B
|
|
|
|
|
|
test "super can only exist in extended classes", ->
|
|
throwsA = """
|
|
class A
|
|
constructor: (@name) ->
|
|
super()
|
|
"""
|
|
throws -> CoffeeScript.compile throwsA, bare: yes
|
|
|
|
# --- CS1 classes compatability breaks ---
|
|
test "CS6 Class extends a CS1 compiled class", ->
|
|
```
|
|
// Generated by CoffeeScript 1.11.1
|
|
var BaseCS1, ExtendedCS1,
|
|
extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
|
|
hasProp = {}.hasOwnProperty;
|
|
|
|
BaseCS1 = (function() {
|
|
function BaseCS1(drink) {
|
|
this.drink = drink;
|
|
}
|
|
|
|
BaseCS1.prototype.make = function() {
|
|
return "making a " + this.drink;
|
|
};
|
|
|
|
BaseCS1.className = function() {
|
|
return 'BaseCS1';
|
|
};
|
|
|
|
return BaseCS1;
|
|
|
|
})();
|
|
|
|
ExtendedCS1 = (function(superClass) {
|
|
extend(ExtendedCS1, superClass);
|
|
|
|
function ExtendedCS1(flavor) {
|
|
this.flavor = flavor;
|
|
ExtendedCS1.__super__.constructor.call(this, 'cafe ole');
|
|
}
|
|
|
|
ExtendedCS1.prototype.make = function() {
|
|
return "making a " + this.drink + " with " + this.flavor;
|
|
};
|
|
|
|
ExtendedCS1.className = function() {
|
|
return 'ExtendedCS1';
|
|
};
|
|
|
|
return ExtendedCS1;
|
|
|
|
})(BaseCS1);
|
|
|
|
```
|
|
class B extends BaseCS1
|
|
eq B.className(), 'BaseCS1'
|
|
b = new B('machiato')
|
|
eq b.make(), "making a machiato"
|
|
|
|
|
|
test "CS6 Class extends an extended CS1 compiled class", ->
|
|
```
|
|
// Generated by CoffeeScript 1.11.1
|
|
var BaseCS1, ExtendedCS1,
|
|
extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
|
|
hasProp = {}.hasOwnProperty;
|
|
|
|
BaseCS1 = (function() {
|
|
function BaseCS1(drink) {
|
|
this.drink = drink;
|
|
}
|
|
|
|
BaseCS1.prototype.make = function() {
|
|
return "making a " + this.drink;
|
|
};
|
|
|
|
BaseCS1.className = function() {
|
|
return 'BaseCS1';
|
|
};
|
|
|
|
return BaseCS1;
|
|
|
|
})();
|
|
|
|
ExtendedCS1 = (function(superClass) {
|
|
extend(ExtendedCS1, superClass);
|
|
|
|
function ExtendedCS1(flavor) {
|
|
this.flavor = flavor;
|
|
ExtendedCS1.__super__.constructor.call(this, 'cafe ole');
|
|
}
|
|
|
|
ExtendedCS1.prototype.make = function() {
|
|
return "making a " + this.drink + " with " + this.flavor;
|
|
};
|
|
|
|
ExtendedCS1.className = function() {
|
|
return 'ExtendedCS1';
|
|
};
|
|
|
|
return ExtendedCS1;
|
|
|
|
})(BaseCS1);
|
|
|
|
```
|
|
class B extends ExtendedCS1
|
|
eq B.className(), 'ExtendedCS1'
|
|
b = new B('vanilla')
|
|
eq b.make(), "making a cafe ole with vanilla"
|
|
|
|
test "CS6 Class extends a CS1 compiled class with super()", ->
|
|
```
|
|
// Generated by CoffeeScript 1.11.1
|
|
var BaseCS1, ExtendedCS1,
|
|
extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
|
|
hasProp = {}.hasOwnProperty;
|
|
|
|
BaseCS1 = (function() {
|
|
function BaseCS1(drink) {
|
|
this.drink = drink;
|
|
}
|
|
|
|
BaseCS1.prototype.make = function() {
|
|
return "making a " + this.drink;
|
|
};
|
|
|
|
BaseCS1.className = function() {
|
|
return 'BaseCS1';
|
|
};
|
|
|
|
return BaseCS1;
|
|
|
|
})();
|
|
|
|
ExtendedCS1 = (function(superClass) {
|
|
extend(ExtendedCS1, superClass);
|
|
|
|
function ExtendedCS1(flavor) {
|
|
this.flavor = flavor;
|
|
ExtendedCS1.__super__.constructor.call(this, 'cafe ole');
|
|
}
|
|
|
|
ExtendedCS1.prototype.make = function() {
|
|
return "making a " + this.drink + " with " + this.flavor;
|
|
};
|
|
|
|
ExtendedCS1.className = function() {
|
|
return 'ExtendedCS1';
|
|
};
|
|
|
|
return ExtendedCS1;
|
|
|
|
})(BaseCS1);
|
|
|
|
```
|
|
class B extends ExtendedCS1
|
|
constructor: (@shots) ->
|
|
super('caramel')
|
|
make: () ->
|
|
super() + " and #{@shots} shots of espresso"
|
|
|
|
eq B.className(), 'ExtendedCS1'
|
|
b = new B('three')
|
|
eq b.make(), "making a cafe ole with caramel and three shots of espresso"
|
|
|
|
test 'Bound method called normally before binding is ok', ->
|
|
class Base
|
|
constructor: ->
|
|
@setProp()
|
|
eq @derivedBound(), 3
|
|
|
|
class Derived extends Base
|
|
setProp: ->
|
|
@prop = 3
|
|
|
|
derivedBound: =>
|
|
@prop
|
|
|
|
d = new Derived
|
|
|
|
test 'Bound method called as callback after super() is ok', ->
|
|
class Base
|
|
|
|
class Derived extends Base
|
|
constructor: (@prop = 3) ->
|
|
super()
|
|
f = @derivedBound
|
|
eq f(), 3
|
|
|
|
derivedBound: =>
|
|
@prop
|
|
|
|
d = new Derived
|
|
{derivedBound} = d
|
|
eq derivedBound(), 3
|
|
|
|
test 'Bound method of base class called as callback is ok', ->
|
|
class Base
|
|
constructor: (@prop = 3) ->
|
|
f = @baseBound
|
|
eq f(), 3
|
|
|
|
baseBound: =>
|
|
@prop
|
|
|
|
b = new Base
|
|
{baseBound} = b
|
|
eq baseBound(), 3
|
|
|
|
test 'Bound method of prop-named class called as callback is ok', ->
|
|
Hive = {}
|
|
class Hive.Bee
|
|
constructor: (@prop = 3) ->
|
|
f = @baseBound
|
|
eq f(), 3
|
|
|
|
baseBound: =>
|
|
@prop
|
|
|
|
b = new Hive.Bee
|
|
{baseBound} = b
|
|
eq baseBound(), 3
|
|
|
|
test 'Bound method of class with expression base class called as callback is ok', ->
|
|
calledB = no
|
|
B = ->
|
|
throw new Error if calledB
|
|
calledB = yes
|
|
class
|
|
class A extends B()
|
|
constructor: (@prop = 3) ->
|
|
super()
|
|
f = @derivedBound
|
|
eq f(), 3
|
|
|
|
derivedBound: =>
|
|
@prop
|
|
|
|
b = new A
|
|
{derivedBound} = b
|
|
eq derivedBound(), 3
|
|
|
|
test 'Bound method of class with expression class name called as callback is ok', ->
|
|
calledF = no
|
|
obj = {}
|
|
B = class
|
|
f = ->
|
|
throw new Error if calledF
|
|
calledF = yes
|
|
obj
|
|
class f().A extends B
|
|
constructor: (@prop = 3) ->
|
|
super()
|
|
g = @derivedBound
|
|
eq g(), 3
|
|
|
|
derivedBound: =>
|
|
@prop
|
|
|
|
a = new obj.A
|
|
{derivedBound} = a
|
|
eq derivedBound(), 3
|
|
|
|
test 'Bound method of anonymous child class called as callback is ok', ->
|
|
f = ->
|
|
B = class
|
|
class extends B
|
|
constructor: (@prop = 3) ->
|
|
super()
|
|
g = @derivedBound
|
|
eq g(), 3
|
|
|
|
derivedBound: =>
|
|
@prop
|
|
|
|
a = new (f())
|
|
{derivedBound} = a
|
|
eq derivedBound(), 3
|
|
|
|
test 'Bound method of immediately instantiated class with expression base class called as callback is ok', ->
|
|
calledF = no
|
|
obj = {}
|
|
B = class
|
|
f = ->
|
|
throw new Error if calledF
|
|
calledF = yes
|
|
obj
|
|
a = new class f().A extends B
|
|
constructor: (@prop = 3) ->
|
|
super()
|
|
g = @derivedBound
|
|
eq g(), 3
|
|
|
|
derivedBound: =>
|
|
@prop
|
|
|
|
{derivedBound} = a
|
|
eq derivedBound(), 3
|
|
|
|
test "#4591: super.x.y, super['x'].y", ->
|
|
class A
|
|
x:
|
|
y: 1
|
|
z: -> 2
|
|
|
|
class B extends A
|
|
constructor: ->
|
|
super()
|
|
|
|
@w = super.x.y
|
|
@v = super['x'].y
|
|
@u = super.x['y']
|
|
@t = super.x.z()
|
|
@s = super['x'].z()
|
|
@r = super.x['z']()
|
|
|
|
b = new B
|
|
eq 1, b.w
|
|
eq 1, b.v
|
|
eq 1, b.u
|
|
eq 2, b.t
|
|
eq 2, b.s
|
|
eq 2, b.r
|
|
|
|
test "#4464: backticked expressions in class body", ->
|
|
class A
|
|
`get x() { return 42; }`
|
|
|
|
class B
|
|
`get x() { return 42; }`
|
|
constructor: ->
|
|
@y = 84
|
|
|
|
a = new A
|
|
eq 42, a.x
|
|
b = new B
|
|
eq 42, b.x
|
|
eq 84, b.y
|