jashkenas--coffeescript/docs/v1/test.html

11762 lines
253 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<title>CoffeeScript Test Suite</title>
<script src="browser-compiler/coffee-script.js"></script>
<script src="https://cdn.jsdelivr.net/underscorejs/1.8.3/underscore-min.js"></script>
<style>
body, pre {
font-family: Consolas, Menlo, Monaco, monospace;
}
body {
margin: 1em;
}
h1 {
font-size: 1.3em;
}
div {
margin: 0.6em;
}
.good {
color: #22b24c
}
.bad {
color: #eb6864
}
.subtle {
font-size: 0.7em;
color: #999999
}
</style>
</head>
<body>
<h1>CoffeeScript Test Suite</h1>
<pre id="stdout"></pre>
<script type="text/coffeescript">
@testingBrowser = yes
@global = window
stdout = document.getElementById 'stdout'
start = new Date
success = total = done = failed = 0
say = (msg, className) ->
div = document.createElement 'div'
div.className = className if className?
div.appendChild document.createTextNode msg
stdout.appendChild div
msg
@test = (description, fn) ->
++total
try
fn.call(fn)
++success
catch exception
say "#{description}:", 'bad'
say fn.toString(), 'subtle' if fn.toString?
say exception, 'bad'
console.error exception
@ok = (good, msg = 'Error') ->
throw Error msg unless good
# Polyfill Node assert's fail
@fail = ->
ok no
# Polyfill Node assert's deepEqual with Underscore's isEqual
@deepEqual = (a, b) ->
ok _.isEqual(a, b), "Expected #{JSON.stringify a} to deep equal #{JSON.stringify b}"
# See http://wiki.ecmascript.org/doku.php?id=harmony:egal
egal = (a, b) ->
if a is b
a isnt 0 or 1/a is 1/b
else
a isnt a and b isnt b
# A recursive functional equivalence helper; uses egal for testing equivalence.
arrayEgal = (a, b) ->
if egal a, b then yes
else if a instanceof Array and b instanceof Array
return no unless a.length is b.length
return no for el, idx in a when not arrayEgal el, b[idx]
yes
@eq = (a, b, msg) -> ok egal(a, b), msg or "Expected #{a} to equal #{b}"
@arrayEq = (a, b, msg) -> ok arrayEgal(a,b), msg or "Expected #{a} to deep equal #{b}"
@toJS = (str) ->
CoffeeScript.compile str, bare: yes
.replace /^\s+|\s+$/g, '' # Trim leading/trailing whitespace
@doesNotThrow = (fn) ->
fn()
ok yes
@throws = (fun, err, msg) ->
try
fun()
catch e
if err
if typeof err is 'function' and e instanceof err # Handle comparing exceptions
ok yes
else if e.toString().indexOf('[stdin]') is 0 # Handle comparing error messages
ok err e
else
eq e, err
else
ok yes
return
ok no
# Run the tests
for test in document.getElementsByClassName 'test'
say '\u2714 ' + test.id
options = {}
options.literate = yes if test.type is 'text/x-literate-coffeescript'
CoffeeScript.run test.innerHTML, options
# Finish up
yay = success is total and not failed
sec = (new Date - start) / 1000
msg = "passed #{success} tests in #{ sec.toFixed 2 } seconds"
msg = "failed #{ total - success } tests and #{msg}" unless yay
say msg, (if yay then 'good' else 'bad')
</script>
<script type="text/x-coffeescript" class="test" id="arrays">
# Array Literals
# --------------
# * Array Literals
# * Splats in Array Literals
# TODO: add indexing and method invocation tests: [1][0] is 1, [].toString()
test "trailing commas", ->
trailingComma = [1, 2, 3,]
ok (trailingComma[0] is 1) and (trailingComma[2] is 3) and (trailingComma.length is 3)
trailingComma = [
1, 2, 3,
4, 5, 6
7, 8, 9,
]
(sum = (sum or 0) + n) for n in trailingComma
a = [((x) -> x), ((x) -> x * x)]
ok a.length is 2
test "incorrect indentation without commas", ->
result = [['a']
{b: 'c'}]
ok result[0][0] is 'a'
ok result[1]['b'] is 'c'
# Splats in Array Literals
test "array splat expansions with assignments", ->
nums = [1, 2, 3]
list = [a = 0, nums..., b = 4]
eq 0, a
eq 4, b
arrayEq [0,1,2,3,4], list
test "mixed shorthand objects in array lists", ->
arr = [
a:1
'b'
c:1
]
ok arr.length is 3
ok arr[2].c is 1
arr = [b: 1, a: 2, 100]
eq arr[1], 100
arr = [a:0, b:1, (1 + 1)]
eq arr[1], 2
arr = [a:1, 'a', b:1, 'b']
eq arr.length, 4
eq arr[2].b, 1
eq arr[3], 'b'
test "array splats with nested arrays", ->
nonce = {}
a = [nonce]
list = [1, 2, a...]
eq list[0], 1
eq list[2], nonce
a = [[nonce]]
list = [1, 2, a...]
arrayEq list, [1, 2, [nonce]]
test "#1274: `[] = a()` compiles to `false` instead of `a()`", ->
a = false
fn = -> a = true
[] = fn()
ok a
test "#3194: string interpolation in array", ->
arr = [ "a"
key: 'value'
]
eq 2, arr.length
eq 'a', arr[0]
eq 'value', arr[1].key
b = 'b'
arr = [ "a#{b}"
key: 'value'
]
eq 2, arr.length
eq 'ab', arr[0]
eq 'value', arr[1].key
test "regex interpolation in array", ->
arr = [ /a/
key: 'value'
]
eq 2, arr.length
eq 'a', arr[0].source
eq 'value', arr[1].key
b = 'b'
arr = [ ///a#{b}///
key: 'value'
]
eq 2, arr.length
eq 'ab', arr[0].source
eq 'value', arr[1].key
test "for-from loops over Array", ->
array1 = [50, 30, 70, 20]
array2 = []
for x from array1
array2.push(x)
arrayEq array1, array2
array1 = [[20, 30], [40, 50]]
array2 = []
for [a, b] from array1
array2.push(b)
array2.push(a)
arrayEq array2, [30, 20, 50, 40]
array1 = [{a: 10, b: 20, c: 30}, {a: 40, b: 50, c: 60}]
array2 = []
for {a: a, b, c: d} from array1
array2.push([a, b, d])
arrayEq array2, [[10, 20, 30], [40, 50, 60]]
array1 = [[10, 20, 30, 40, 50]]
for [a, b..., c] from array1
eq 10, a
arrayEq [20, 30, 40], b
eq 50, c
test "for-from comprehensions over Array", ->
array1 = (x + 10 for x from [10, 20, 30])
ok array1.join(' ') is '20 30 40'
array2 = (x for x from [30, 41, 57] when x %% 3 is 0)
ok array2.join(' ') is '30 57'
array1 = (b + 5 for [a, b] from [[20, 30], [40, 50]])
ok array1.join(' ') is '35 55'
array2 = (a + b for [a, b] from [[10, 20], [30, 40], [50, 60]] when a + b >= 70)
ok array2.join(' ') is '70 110'
</script>
<script type="text/x-coffeescript" class="test" id="assignment">
# Assignment
# ----------
# * Assignment
# * Compound Assignment
# * Destructuring Assignment
# * Context Property (@) Assignment
# * Existential Assignment (?=)
# * Assignment to variables similar to generated variables
test "context property assignment (using @)", ->
nonce = {}
addMethod = ->
@method = -> nonce
this
eq nonce, addMethod.call({}).method()
test "unassignable values", ->
nonce = {}
for nonref in ['', '""', '0', 'f()'].concat CoffeeScript.RESERVED
eq nonce, (try CoffeeScript.compile "#{nonref} = v" catch e then nonce)
# Compound Assignment
test "boolean operators", ->
nonce = {}
a = 0
a or= nonce
eq nonce, a
b = 1
b or= nonce
eq 1, b
c = 0
c and= nonce
eq 0, c
d = 1
d and= nonce
eq nonce, d
# ensure that RHS is treated as a group
e = f = false
e and= f or true
eq false, e
test "compound assignment as a sub expression", ->
[a, b, c] = [1, 2, 3]
eq 6, (a + b += c)
eq 1, a
eq 5, b
eq 3, c
# *note: this test could still use refactoring*
test "compound assignment should be careful about caching variables", ->
count = 0
list = []
list[++count] or= 1
eq 1, list[1]
eq 1, count
list[++count] ?= 2
eq 2, list[2]
eq 2, count
list[count++] and= 6
eq 6, list[2]
eq 3, count
base = ->
++count
base
base().four or= 4
eq 4, base.four
eq 4, count
base().five ?= 5
eq 5, base.five
eq 5, count
eq 5, base().five ?= 6
eq 6, count
test "compound assignment with implicit objects", ->
obj = undefined
obj ?=
one: 1
eq 1, obj.one
obj and=
two: 2
eq undefined, obj.one
eq 2, obj.two
test "compound assignment (math operators)", ->
num = 10
num -= 5
eq 5, num
num *= 10
eq 50, num
num /= 10
eq 5, num
num %= 3
eq 2, num
test "more compound assignment", ->
a = {}
val = undefined
val ||= a
val ||= true
eq a, val
b = {}
val &&= true
eq val, true
val &&= b
eq b, val
c = {}
val = null
val ?= c
val ?= true
eq c, val
test "#1192: assignment starting with object literals", ->
doesNotThrow (-> CoffeeScript.run "{}.p = 0")
doesNotThrow (-> CoffeeScript.run "{}.p++")
doesNotThrow (-> CoffeeScript.run "{}[0] = 1")
doesNotThrow (-> CoffeeScript.run """{a: 1, 'b', "#{1}": 2}.p = 0""")
doesNotThrow (-> CoffeeScript.run "{a:{0:{}}}.a[0] = 0")
# Destructuring Assignment
test "empty destructuring assignment", ->
{} = [] = undefined
test "chained destructuring assignments", ->
[a] = {0: b} = {'0': c} = [nonce={}]
eq nonce, a
eq nonce, b
eq nonce, c
test "variable swapping to verify caching of RHS values when appropriate", ->
a = nonceA = {}
b = nonceB = {}
c = nonceC = {}
[a, b, c] = [b, c, a]
eq nonceB, a
eq nonceC, b
eq nonceA, c
[a, b, c] = [b, c, a]
eq nonceC, a
eq nonceA, b
eq nonceB, c
fn = ->
[a, b, c] = [b, c, a]
arrayEq [nonceA,nonceB,nonceC], fn()
eq nonceA, a
eq nonceB, b
eq nonceC, c
test "#713: destructuring assignment should return right-hand-side value", ->
nonces = [nonceA={},nonceB={}]
eq nonces, [a, b] = [c, d] = nonces
eq nonceA, a
eq nonceA, c
eq nonceB, b
eq nonceB, d
test "destructuring assignment with splats", ->
a = {}; b = {}; c = {}; d = {}; e = {}
[x,y...,z] = [a,b,c,d,e]
eq a, x
arrayEq [b,c,d], y
eq e, z
test "deep destructuring assignment with splats", ->
a={}; b={}; c={}; d={}; e={}; f={}; g={}; h={}; i={}
[u, [v, w..., x], y..., z] = [a, [b, c, d, e], f, g, h, i]
eq a, u
eq b, v
arrayEq [c,d], w
eq e, x
arrayEq [f,g,h], y
eq i, z
test "destructuring assignment with objects", ->
a={}; b={}; c={}
obj = {a,b,c}
{a:x, b:y, c:z} = obj
eq a, x
eq b, y
eq c, z
test "deep destructuring assignment with objects", ->
a={}; b={}; c={}; d={}
obj = {
a
b: {
'c': {
d: [
b
{e: c, f: d}
]
}
}
}
{a: w, 'b': {c: d: [x, {'f': z, e: y}]}} = obj
eq a, w
eq b, x
eq c, y
eq d, z
test "destructuring assignment with objects and splats", ->
a={}; b={}; c={}; d={}
obj = a: b: [a, b, c, d]
{a: b: [y, z...]} = obj
eq a, y
arrayEq [b,c,d], z
test "destructuring assignment against an expression", ->
a={}; b={}
[y, z] = if true then [a, b] else [b, a]
eq a, y
eq b, z
test "bracket insertion when necessary", ->
[a] = [0] ? [1]
eq a, 0
# for implicit destructuring assignment in comprehensions, see the comprehension tests
test "destructuring assignment with context (@) properties", ->
a={}; b={}; c={}; d={}; e={}
obj =
fn: () ->
local = [a, {b, c}, d, e]
[@a, {b: @b, c: @c}, @d, @e] = local
eq undefined, obj[key] for key in ['a','b','c','d','e']
obj.fn()
eq a, obj.a
eq b, obj.b
eq c, obj.c
eq d, obj.d
eq e, obj.e
test "#1024: destructure empty assignments to produce javascript-like results", ->
eq 2 * [] = 3 + 5, 16
test "#1005: invalid identifiers allowed on LHS of destructuring assignment", ->
disallowed = ['eval', 'arguments'].concat CoffeeScript.RESERVED
throws (-> CoffeeScript.compile "[#{disallowed.join ', '}] = x"), null, 'all disallowed'
throws (-> CoffeeScript.compile "[#{disallowed.join '..., '}...] = x"), null, 'all disallowed as splats'
t = tSplat = null
for v in disallowed when v isnt 'class' # `class` by itself is an expression
throws (-> CoffeeScript.compile t), null, t = "[#{v}] = x"
throws (-> CoffeeScript.compile tSplat), null, tSplat = "[#{v}...] = x"
doesNotThrow ->
for v in disallowed
CoffeeScript.compile "[a.#{v}] = x"
CoffeeScript.compile "[a.#{v}...] = x"
CoffeeScript.compile "[@#{v}] = x"
CoffeeScript.compile "[@#{v}...] = x"
test "#2055: destructuring assignment with `new`", ->
{length} = new Array
eq 0, length
test "#156: destructuring with expansion", ->
array = [1..5]
[first, ..., last] = array
eq 1, first
eq 5, last
[..., lastButOne, last] = array
eq 4, lastButOne
eq 5, last
[first, second, ..., last] = array
eq 2, second
[..., last] = 'strings as well -> x'
eq 'x', last
throws (-> CoffeeScript.compile "[1, ..., 3]"), null, "prohibit expansion outside of assignment"
throws (-> CoffeeScript.compile "[..., a, b...] = c"), null, "prohibit expansion and a splat"
throws (-> CoffeeScript.compile "[...] = c"), null, "prohibit lone expansion"
test "destructuring with dynamic keys", ->
{"#{'a'}": a, """#{'b'}""": b, c} = {a: 1, b: 2, c: 3}
eq 1, a
eq 2, b
eq 3, c
throws -> CoffeeScript.compile '{"#{a}"} = b'
test "simple array destructuring defaults", ->
[a = 1] = []
eq 1, a
[a = 2] = [undefined]
eq 2, a
[a = 3] = [null]
eq 3, a
[a = 4] = [0]
eq 0, a
arr = [a = 5]
eq 5, a
arrayEq [5], arr
test "simple object destructuring defaults", ->
{b = 1} = {}
eq b, 1
{b = 2} = {b: undefined}
eq b, 2
{b = 3} = {b: null}
eq b, 3
{b = 4} = {b: 0}
eq b, 0
{b: c = 1} = {}
eq c, 1
{b: c = 2} = {b: undefined}
eq c, 2
{b: c = 3} = {b: null}
eq c, 3
{b: c = 4} = {b: 0}
eq c, 0
test "multiple array destructuring defaults", ->
[a = 1, b = 2, c] = [null, 12, 13]
eq a, 1
eq b, 12
eq c, 13
[a, b = 2, c = 3] = [null, 12, 13]
eq a, null
eq b, 12
eq c, 13
[a = 1, b, c = 3] = [11, 12]
eq a, 11
eq b, 12
eq c, 3
test "multiple object destructuring defaults", ->
{a = 1, b: bb = 2, 'c': c = 3, "#{0}": d = 4} = {"#{'b'}": 12}
eq a, 1
eq bb, 12
eq c, 3
eq d, 4
test "array destructuring defaults with splats", ->
[..., a = 9] = []
eq a, 9
[..., b = 9] = [19]
eq b, 19
test "deep destructuring assignment with defaults", ->
[a, [{b = 1, c = 3}] = [c: 2]] = [0]
eq a, 0
eq b, 1
eq c, 2
test "destructuring assignment with context (@) properties and defaults", ->
a={}; b={}; c={}; d={}; e={}
obj =
fn: () ->
local = [a, {b, c: null}, d]
[@a, {b: @b = b, @c = c}, @d, @e = e] = local
eq undefined, obj[key] for key in ['a','b','c','d','e']
obj.fn()
eq a, obj.a
eq b, obj.b
eq c, obj.c
eq d, obj.d
eq e, obj.e
test "destructuring assignment with defaults single evaluation", ->
callCount = 0
fn = -> callCount++
[a = fn()] = []
eq 0, a
eq 1, callCount
[a = fn()] = [10]
eq 10, a
eq 1, callCount
{a = fn(), b: c = fn()} = {a: 20, b: null}
eq 20, a
eq c, 1
eq callCount, 2
# Existential Assignment
test "existential assignment", ->
nonce = {}
a = false
a ?= nonce
eq false, a
b = undefined
b ?= nonce
eq nonce, b
c = null
c ?= nonce
eq nonce, c
test "#1627: prohibit conditional assignment of undefined variables", ->
throws (-> CoffeeScript.compile "x ?= 10"), null, "prohibit (x ?= 10)"
throws (-> CoffeeScript.compile "x ||= 10"), null, "prohibit (x ||= 10)"
throws (-> CoffeeScript.compile "x or= 10"), null, "prohibit (x or= 10)"
throws (-> CoffeeScript.compile "do -> x ?= 10"), null, "prohibit (do -> x ?= 10)"
throws (-> CoffeeScript.compile "do -> x ||= 10"), null, "prohibit (do -> x ||= 10)"
throws (-> CoffeeScript.compile "do -> x or= 10"), null, "prohibit (do -> x or= 10)"
doesNotThrow (-> CoffeeScript.compile "x = null; x ?= 10"), "allow (x = null; x ?= 10)"
doesNotThrow (-> CoffeeScript.compile "x = null; x ||= 10"), "allow (x = null; x ||= 10)"
doesNotThrow (-> CoffeeScript.compile "x = null; x or= 10"), "allow (x = null; x or= 10)"
doesNotThrow (-> CoffeeScript.compile "x = null; do -> x ?= 10"), "allow (x = null; do -> x ?= 10)"
doesNotThrow (-> CoffeeScript.compile "x = null; do -> x ||= 10"), "allow (x = null; do -> x ||= 10)"
doesNotThrow (-> CoffeeScript.compile "x = null; do -> x or= 10"), "allow (x = null; do -> x or= 10)"
throws (-> CoffeeScript.compile "-> -> -> x ?= 10"), null, "prohibit (-> -> -> x ?= 10)"
doesNotThrow (-> CoffeeScript.compile "x = null; -> -> -> x ?= 10"), "allow (x = null; -> -> -> x ?= 10)"
test "more existential assignment", ->
global.temp ?= 0
eq global.temp, 0
global.temp or= 100
eq global.temp, 100
delete global.temp
test "#1348, #1216: existential assignment compilation", ->
nonce = {}
a = nonce
b = (a ?= 0)
eq nonce, b
#the first ?= compiles into a statement; the second ?= compiles to a ternary expression
eq a ?= b ?= 1, nonce
if a then a ?= 2 else a = 3
eq a, nonce
test "#1591, #1101: splatted expressions in destructuring assignment must be assignable", ->
nonce = {}
for nonref in ['', '""', '0', 'f()', '(->)'].concat CoffeeScript.RESERVED
eq nonce, (try CoffeeScript.compile "[#{nonref}...] = v" catch e then nonce)
test "#1643: splatted accesses in destructuring assignments should not be declared as variables", ->
nonce = {}
accesses = ['o.a', 'o["a"]', '(o.a)', '(o.a).a', '@o.a', 'C::a', 'C::', 'f().a', 'o?.a', 'o?.a.b', 'f?().a']
for access in accesses
for i,j in [1,2,3] #position can matter
code =
"""
nonce = {}; nonce2 = {}; nonce3 = {};
@o = o = new (class C then a:{}); f = -> o
[#{new Array(i).join('x,')}#{access}...] = [#{new Array(i).join('0,')}nonce, nonce2, nonce3]
unless #{access}[0] is nonce and #{access}[1] is nonce2 and #{access}[2] is nonce3 then throw new Error('[...]')
"""
eq nonce, unless (try CoffeeScript.run code, bare: true catch e then true) then nonce
# subpatterns like `[[a]...]` and `[{a}...]`
subpatterns = ['[sub, sub2, sub3]', '{0: sub, 1: sub2, 2: sub3}']
for subpattern in subpatterns
for i,j in [1,2,3]
code =
"""
nonce = {}; nonce2 = {}; nonce3 = {};
[#{new Array(i).join('x,')}#{subpattern}...] = [#{new Array(i).join('0,')}nonce, nonce2, nonce3]
unless sub is nonce and sub2 is nonce2 and sub3 is nonce3 then throw new Error('[sub...]')
"""
eq nonce, unless (try CoffeeScript.run code, bare: true catch e then true) then nonce
test "#1838: Regression with variable assignment", ->
name =
'dave'
eq name, 'dave'
test '#2211: splats in destructured parameters', ->
doesNotThrow -> CoffeeScript.compile '([a...]) ->'
doesNotThrow -> CoffeeScript.compile '([a...],b) ->'
doesNotThrow -> CoffeeScript.compile '([a...],[b...]) ->'
throws -> CoffeeScript.compile '([a...,[a...]]) ->'
doesNotThrow -> CoffeeScript.compile '([a...,[b...]]) ->'
test '#2213: invocations within destructured parameters', ->
throws -> CoffeeScript.compile '([a()])->'
throws -> CoffeeScript.compile '([a:b()])->'
throws -> CoffeeScript.compile '([a:b.c()])->'
throws -> CoffeeScript.compile '({a()})->'
throws -> CoffeeScript.compile '({a:b()})->'
throws -> CoffeeScript.compile '({a:b.c()})->'
test '#2532: compound assignment with terminator', ->
doesNotThrow -> CoffeeScript.compile """
a = "hello"
a +=
"
world
!
"
"""
test "#2613: parens on LHS of destructuring", ->
a = {}
[(a).b] = [1, 2, 3]
eq a.b, 1
test "#2181: conditional assignment as a subexpression", ->
a = false
false && a or= true
eq false, a
eq false, not a or= true
test "#1500: Assignment to variables similar to generated variables", ->
len = 0
x = ((results = null; n) for n in [1, 2, 3])
arrayEq [1, 2, 3], x
eq 0, len
for x in [1, 2, 3]
f = ->
i = 0
f()
eq 'undefined', typeof i
ref = 2
x = ref * 2 ? 1
eq x, 4
eq 'undefined', typeof ref1
x = {}
base = -> x
name = -1
base()[-name] ?= 2
eq x[1], 2
eq base(), x
eq name, -1
f = (@a, a) -> [@a, a]
arrayEq [1, 2], f.call scope = {}, 1, 2
eq 1, scope.a
try throw 'foo'
catch error
eq error, 'foo'
eq error, 'foo'
doesNotThrow -> CoffeeScript.compile '(@slice...) ->'
test "Assignment to variables similar to helper functions", ->
f = (slice...) -> slice
arrayEq [1, 2, 3], f 1, 2, 3
eq 'undefined', typeof slice1
class A
class B extends A
extend = 3
hasProp = 4
value: 5
method: (bind, bind1) => [bind, bind1, extend, hasProp, @value]
{method} = new B
arrayEq [1, 2, 3, 4, 5], method 1, 2
modulo = -1 %% 3
eq 2, modulo
indexOf = [1, 2, 3]
ok 2 in indexOf
</script>
<script type="text/x-coffeescript" class="test" id="booleans">
# Boolean Literals
# ----------------
# TODO: add method invocation tests: true.toString() is "true"
test "#764 Booleans should be indexable", ->
toString = Boolean::toString
eq toString, true['toString']
eq toString, false['toString']
eq toString, yes['toString']
eq toString, no['toString']
eq toString, on['toString']
eq toString, off['toString']
eq toString, true.toString
eq toString, false.toString
eq toString, yes.toString
eq toString, no.toString
eq toString, on.toString
eq toString, off.toString
</script>
<script type="text/x-coffeescript" class="test" id="classes">
# 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')
# #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 "#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()
</script>
<script type="text/x-coffeescript" class="test" id="cluster">
# Cluster Module
# ---------
return if testingBrowser?
cluster = require 'cluster'
if cluster.isMaster
test "#2737 - cluster module can spawn workers from a coffeescript process", ->
cluster.once 'exit', (worker, code) ->
eq code, 0
cluster.fork()
else
process.exit 0
</script>
<script type="text/x-coffeescript" class="test" id="comments">
# Comments
# --------
# * Single-Line Comments
# * Block Comments
# Note: awkward spacing seen in some tests is likely intentional.
test "comments in objects", ->
obj1 = {
# comment
# comment
# comment
one: 1
# comment
two: 2
# comment
}
ok Object::hasOwnProperty.call(obj1,'one')
eq obj1.one, 1
ok Object::hasOwnProperty.call(obj1,'two')
eq obj1.two, 2
test "comments in YAML-style objects", ->
obj2 =
# comment
# comment
# comment
three: 3
# comment
four: 4
# comment
ok Object::hasOwnProperty.call(obj2,'three')
eq obj2.three, 3
ok Object::hasOwnProperty.call(obj2,'four')
eq obj2.four, 4
test "comments following operators that continue lines", ->
sum =
1 +
1 + # comment
1
eq 3, sum
test "comments in functions", ->
fn = ->
# comment
false
false # comment
false
# comment
# comment
true
ok fn()
fn2 = -> #comment
fn()
# comment
ok fn2()
test "trailing comment before an outdent", ->
nonce = {}
fn3 = ->
if true
undefined # comment
nonce
eq nonce, fn3()
test "comments in a switch", ->
nonce = {}
result = switch nonce #comment
# comment
when false then undefined
# comment
when null #comment
undefined
else nonce # comment
eq nonce, result
test "comment with conditional statements", ->
nonce = {}
result = if false # comment
undefined
#comment
else # comment
nonce
# comment
eq nonce, result
test "spaced comments with conditional statements", ->
nonce = {}
result = if false
undefined
# comment
else if false
undefined
# comment
else
nonce
eq nonce, result
# Block Comments
###
This is a here-comment.
Kind of like a heredoc.
###
test "block comments in objects", ->
a = {}
b = {}
obj = {
a: a
###
comment
###
b: b
}
eq a, obj.a
eq b, obj.b
test "block comments in YAML-style", ->
a = {}
b = {}
obj =
a: a
###
comment
###
b: b
eq a, obj.a
eq b, obj.b
test "block comments in functions", ->
nonce = {}
fn1 = ->
true
###
false
###
ok fn1()
fn2 = ->
###
block comment
###
nonce
eq nonce, fn2()
fn3 = ->
nonce
###
block comment
###
eq nonce, fn3()
fn4 = ->
one = ->
###
block comment
###
two = ->
three = ->
nonce
eq nonce, fn4()()()()
test "block comments inside class bodies", ->
class A
a: ->
###
Comment
###
b: ->
ok A.prototype.b instanceof Function
class B
###
Comment
###
a: ->
b: ->
ok B.prototype.a instanceof Function
test "#2037: herecomments shouldn't imply line terminators", ->
do (-> ### ###; fail)
test "#2916: block comment before implicit call with implicit object", ->
fn = (obj) -> ok obj.a
### ###
fn
a: yes
test "#3132: Format single-line block comment nicely", ->
input = """
### Single-line block comment without additional space here => ###"""
result = """
/* Single-line block comment without additional space here => */
"""
eq CoffeeScript.compile(input, bare: on), result
test "#3132: Format multi-line block comment nicely", ->
input = """
###
# Multi-line
# block
# comment
###"""
result = """
/*
* Multi-line
* block
* comment
*/
"""
eq CoffeeScript.compile(input, bare: on), result
test "#3132: Format simple block comment nicely", ->
input = """
###
No
Preceding hash
###"""
result = """
/*
No
Preceding hash
*/
"""
eq CoffeeScript.compile(input, bare: on), result
test "#3132: Format indented block-comment nicely", ->
input = """
fn = () ->
###
# Indented
Multiline
###
1"""
result = """
var fn;
fn = function() {
/*
* Indented
Multiline
*/
return 1;
};
"""
eq CoffeeScript.compile(input, bare: on), result
# Although adequately working, block comment-placement is not yet perfect.
# (Considering a case where multiple variables have been declared )
test "#3132: Format jsdoc-style block-comment nicely", ->
input = """
###*
# Multiline for jsdoc-"@doctags"
#
# @type {Function}
###
fn = () -> 1
"""
result = """
/**
* Multiline for jsdoc-"@doctags"
*
* @type {Function}
*/
var fn;
fn = function() {
return 1;
};
"""
eq CoffeeScript.compile(input, bare: on), result
# Although adequately working, block comment-placement is not yet perfect.
# (Considering a case where multiple variables have been declared )
test "#3132: Format hand-made (raw) jsdoc-style block-comment nicely", ->
input = """
###*
* Multiline for jsdoc-"@doctags"
*
* @type {Function}
###
fn = () -> 1
"""
result = """
/**
* Multiline for jsdoc-"@doctags"
*
* @type {Function}
*/
var fn;
fn = function() {
return 1;
};
"""
eq CoffeeScript.compile(input, bare: on), result
# Although adequately working, block comment-placement is not yet perfect.
# (Considering a case where multiple variables have been declared )
test "#3132: Place block-comments nicely", ->
input = """
###*
# A dummy class definition
#
# @class
###
class DummyClass
###*
# @constructor
###
constructor: ->
###*
# Singleton reference
#
# @type {DummyClass}
###
@instance = new DummyClass()
"""
result = """
/**
* A dummy class definition
*
* @class
*/
var DummyClass;
DummyClass = (function() {
/**
* @constructor
*/
function DummyClass() {}
/**
* Singleton reference
*
* @type {DummyClass}
*/
DummyClass.instance = new DummyClass();
return DummyClass;
})();
"""
eq CoffeeScript.compile(input, bare: on), result
test "#3638: Demand a whitespace after # symbol", ->
input = """
###
#No
#whitespace
###"""
result = """
/*
#No
#whitespace
*/
"""
eq CoffeeScript.compile(input, bare: on), result
test "#3761: Multiline comment at end of an object", ->
anObject =
x: 3
###
#Comment
###
ok anObject.x is 3
test "#4375: UTF-8 characters in comments", ->
# 智に働けば角が立つ情に掉させば流される
ok yes
</script>
<script type="text/x-coffeescript" class="test" id="compilation">
# Compilation
# -----------
# helper to assert that a string should fail compilation
cantCompile = (code) ->
throws -> CoffeeScript.compile code
test "ensure that carriage returns don't break compilation on Windows", ->
doesNotThrow -> CoffeeScript.compile 'one\r\ntwo', bare: on
test "#3089 - don't mutate passed in options to compile", ->
opts = {}
CoffeeScript.compile '1 + 1', opts
ok !opts.scope
test "--bare", ->
eq -1, CoffeeScript.compile('x = y', bare: on).indexOf 'function'
ok 'passed' is CoffeeScript.eval '"passed"', bare: on, filename: 'test'
test "header (#1778)", ->
header = "// Generated by CoffeeScript #{CoffeeScript.VERSION}\n"
eq 0, CoffeeScript.compile('x = y', header: on).indexOf header
test "header is disabled by default", ->
header = "// Generated by CoffeeScript #{CoffeeScript.VERSION}\n"
eq -1, CoffeeScript.compile('x = y').indexOf header
test "multiple generated references", ->
a = {b: []}
a.b[true] = -> this == a.b
c = 0
d = []
ok a.b[0<++c<2] d...
test "splat on a line by itself is invalid", ->
cantCompile "x 'a'\n...\n"
test "Issue 750", ->
cantCompile 'f(->'
cantCompile 'a = (break)'
cantCompile 'a = (return 5 for item in list)'
cantCompile 'a = (return 5 while condition)'
cantCompile 'a = for x in y\n return 5'
test "Issue #986: Unicode identifiers", ->
λ = 5
eq λ, 5
test "#2516: Unicode spaces should not be part of identifiers", ->
a = (x) -> x * 2
b = 3
eq 6, a b # U+00A0 NO-BREAK SPACE
eq 6, ab # U+1680 OGHAM SPACE MARK
eq 6, a b # U+2000 EN QUAD
eq 6, ab # U+2001 EM QUAD
eq 6, ab # U+2002 EN SPACE
eq 6, ab # U+2003 EM SPACE
eq 6, ab # U+2004 THREE-PER-EM SPACE
eq 6, ab # U+2005 FOUR-PER-EM SPACE
eq 6, ab # U+2006 SIX-PER-EM SPACE
eq 6, ab # U+2007 FIGURE SPACE
eq 6, ab # U+2008 PUNCTUATION SPACE
eq 6, ab # U+2009 THIN SPACE
eq 6, ab # U+200A HAIR SPACE
eq 6, ab # U+202F NARROW NO-BREAK SPACE
eq 6, ab # U+205F MEDIUM MATHEMATICAL SPACE
eq 6, a b # U+3000 IDEOGRAPHIC SPACE
# #3560: Non-breaking space (U+00A0) (before `'c'`)
eq 5, {c: 5}[ 'c' ]
# A line where every space in non-breaking
  eq 1 + 1, 2  
test "don't accidentally stringify keywords", ->
ok (-> this == 'this')() is false
test "#1026: no if/else/else allowed", ->
cantCompile '''
if a
b
else
c
else
d
'''
test "#1050: no closing asterisk comments from within block comments", ->
cantCompile "### */ ###"
test "#1273: escaping quotes at the end of heredocs", ->
cantCompile '"""\\"""' # """\"""
cantCompile '"""\\\\\\"""' # """\\\"""
test "#1106: __proto__ compilation", ->
object = eq
@["__proto__"] = true
ok __proto__
test "reference named hasOwnProperty", ->
CoffeeScript.compile 'hasOwnProperty = 0; a = 1'
test "#1055: invalid keys in real (but not work-product) objects", ->
cantCompile "@key: value"
test "#1066: interpolated strings are not implicit functions", ->
cantCompile '"int#{er}polated" arg'
test "#2846: while with empty body", ->
CoffeeScript.compile 'while 1 then', {sourceMap: true}
test "#2944: implicit call with a regex argument", ->
CoffeeScript.compile 'o[key] /regex/'
test "#3001: `own` shouldn't be allowed in a `for`-`in` loop", ->
cantCompile "a for own b in c"
test "#2994: single-line `if` requires `then`", ->
cantCompile "if b else x"
</script>
<script type="text/x-coffeescript" class="test" id="comprehensions">
# Comprehensions
# --------------
# * Array Comprehensions
# * Range Comprehensions
# * Object Comprehensions
# * Implicit Destructuring Assignment
# * Comprehensions with Nonstandard Step
# TODO: refactor comprehension tests
test "Basic array comprehensions.", ->
nums = (n * n for n in [1, 2, 3] when n & 1)
results = (n * 2 for n in nums)
ok results.join(',') is '2,18'
test "Basic object comprehensions.", ->
obj = {one: 1, two: 2, three: 3}
names = (prop + '!' for prop of obj)
odds = (prop + '!' for prop, value of obj when value & 1)
ok names.join(' ') is "one! two! three!"
ok odds.join(' ') is "one! three!"
test "Basic range comprehensions.", ->
nums = (i * 3 for i in [1..3])
negs = (x for x in [-20..-5*2])
negs = negs[0..2]
result = nums.concat(negs).join(', ')
ok result is '3, 6, 9, -20, -19, -18'
test "With range comprehensions, you can loop in steps.", ->
results = (x for x in [0...15] by 5)
ok results.join(' ') is '0 5 10'
results = (x for x in [0..100] by 10)
ok results.join(' ') is '0 10 20 30 40 50 60 70 80 90 100'
test "And can loop downwards, with a negative step.", ->
results = (x for x in [5..1])
ok results.join(' ') is '5 4 3 2 1'
ok results.join(' ') is [(10-5)..(-2+3)].join(' ')
results = (x for x in [10..1])
ok results.join(' ') is [10..1].join(' ')
results = (x for x in [10...0] by -2)
ok results.join(' ') is [10, 8, 6, 4, 2].join(' ')
test "Range comprehension gymnastics.", ->
eq "#{i for i in [5..1]}", '5,4,3,2,1'
eq "#{i for i in [5..-5] by -5}", '5,0,-5'
a = 6
b = 0
c = -2
eq "#{i for i in [a..b]}", '6,5,4,3,2,1,0'
eq "#{i for i in [a..b] by c}", '6,4,2,0'
test "Multiline array comprehension with filter.", ->
evens = for num in [1, 2, 3, 4, 5, 6] when not (num & 1)
num *= -1
num -= 2
num * -1
eq evens + '', '4,6,8'
test "The in operator still works, standalone.", ->
ok 2 of evens
test "all isn't reserved.", ->
all = 1
test "Ensure that the closure wrapper preserves local variables.", ->
obj = {}
for method in ['one', 'two', 'three'] then do (method) ->
obj[method] = ->
"I'm " + method
ok obj.one() is "I'm one"
ok obj.two() is "I'm two"
ok obj.three() is "I'm three"
test "Index values at the end of a loop.", ->
i = 0
for i in [1..3]
-> 'func'
break if false
ok i is 4
test "Ensure that local variables are closed over for range comprehensions.", ->
funcs = for i in [1..3]
do (i) ->
-> -i
eq (func() for func in funcs).join(' '), '-1 -2 -3'
ok i is 4
test "Even when referenced in the filter.", ->
list = ['one', 'two', 'three']
methods = for num, i in list when num isnt 'two' and i isnt 1
do (num, i) ->
-> num + ' ' + i
ok methods.length is 2
ok methods[0]() is 'one 0'
ok methods[1]() is 'three 2'
test "Even a convoluted one.", ->
funcs = []
for i in [1..3]
do (i) ->
x = i * 2
((z)->
funcs.push -> z + ' ' + i
)(x)
ok (func() for func in funcs).join(', ') is '2 1, 4 2, 6 3'
funcs = []
results = for i in [1..3]
do (i) ->
z = (x * 3 for x in [1..i])
((a, b, c) -> [a, b, c].join(' ')).apply this, z
ok results.join(', ') is '3 , 3 6 , 3 6 9'
test "Naked ranges are expanded into arrays.", ->
array = [0..10]
ok(num % 2 is 0 for num in array by 2)
test "Nested shared scopes.", ->
foo = ->
for i in [0..7]
do (i) ->
for j in [0..7]
do (j) ->
-> i + j
eq foo()[3][4](), 7
test "Scoped loop pattern matching.", ->
a = [[0], [1]]
funcs = []
for [v] in a
do (v) ->
funcs.push -> v
eq funcs[0](), 0
eq funcs[1](), 1
test "Nested comprehensions.", ->
multiLiner =
for x in [3..5]
for y in [3..5]
[x, y]
singleLiner =
(([x, y] for y in [3..5]) for x in [3..5])
ok multiLiner.length is singleLiner.length
ok 5 is multiLiner[2][2][1]
ok 5 is singleLiner[2][2][1]
test "Comprehensions within parentheses.", ->
result = null
store = (obj) -> result = obj
store (x * 2 for x in [3, 2, 1])
ok result.join(' ') is '6 4 2'
test "Closure-wrapped comprehensions that refer to the 'arguments' object.", ->
expr = ->
result = (item * item for item in arguments)
ok expr(2, 4, 8).join(' ') is '4 16 64'
test "Fast object comprehensions over all properties, including prototypal ones.", ->
class Cat
constructor: -> @name = 'Whiskers'
breed: 'tabby'
hair: 'cream'
whiskers = new Cat
own = (value for own key, value of whiskers)
all = (value for key, value of whiskers)
ok own.join(' ') is 'Whiskers'
ok all.sort().join(' ') is 'Whiskers cream tabby'
test "Optimized range comprehensions.", ->
exxes = ('x' for [0...10])
ok exxes.join(' ') is 'x x x x x x x x x x'
test "#3671: Allow step in optimized range comprehensions.", ->
exxes = ('x' for [0...10] by 2)
eq exxes.join(' ') , 'x x x x x'
test "#3671: Disallow guard in optimized range comprehensions.", ->
throws -> CoffeeScript.compile "exxes = ('x' for [0...10] when a)"
test "Loop variables should be able to reference outer variables", ->
outer = 1
do ->
null for outer in [1, 2, 3]
eq outer, 3
test "Lenient on pure statements not trying to reach out of the closure", ->
val = for i in [1]
for j in [] then break
i
ok val[0] is i
test "Comprehensions only wrap their last line in a closure, allowing other lines
to have pure expressions in them.", ->
func = -> for i in [1]
break if i is 2
j for j in [1]
ok func()[0][0] is 1
i = 6
odds = while i--
continue unless i & 1
i
ok odds.join(', ') is '5, 3, 1'
test "Issue #897: Ensure that plucked function variables aren't leaked.", ->
facets = {}
list = ['one', 'two']
(->
for entity in list
facets[entity] = -> entity
)()
eq typeof entity, 'undefined'
eq facets['two'](), 'two'
test "Issue #905. Soaks as the for loop subject.", ->
a = {b: {c: [1, 2, 3]}}
for d in a.b?.c
e = d
eq e, 3
test "Issue #948. Capturing loop variables.", ->
funcs = []
list = ->
[1, 2, 3]
for y in list()
do (y) ->
z = y
funcs.push -> "y is #{y} and z is #{z}"
eq funcs[1](), "y is 2 and z is 2"
test "Cancel the comprehension if there's a jump inside the loop.", ->
result = try
for i in [0...10]
continue if i < 5
i
eq result, 10
test "Comprehensions over break.", ->
arrayEq (break for [1..10]), []
test "Comprehensions over continue.", ->
arrayEq (continue for [1..10]), []
test "Comprehensions over function literals.", ->
a = 0
for f in [-> a = 1]
do (f) ->
do f
eq a, 1
test "Comprehensions that mention arguments.", ->
list = [arguments: 10]
args = for f in list
do (f) ->
f.arguments
eq args[0], 10
test "expression conversion under explicit returns", ->
nonce = {}
fn = ->
return (nonce for x in [1,2,3])
arrayEq [nonce,nonce,nonce], fn()
fn = ->
return [nonce for x in [1,2,3]][0]
arrayEq [nonce,nonce,nonce], fn()
fn = ->
return [(nonce for x in [1..3])][0]
arrayEq [nonce,nonce,nonce], fn()
test "implicit destructuring assignment in object of objects", ->
a={}; b={}; c={}
obj = {
a: { d: a },
b: { d: b }
c: { d: c }
}
result = ([y,z] for y, { d: z } of obj)
arrayEq [['a',a],['b',b],['c',c]], result
test "implicit destructuring assignment in array of objects", ->
a={}; b={}; c={}; d={}; e={}; f={}
arr = [
{ a: a, b: { c: b } },
{ a: c, b: { c: d } },
{ a: e, b: { c: f } }
]
result = ([y,z] for { a: y, b: { c: z } } in arr)
arrayEq [[a,b],[c,d],[e,f]], result
test "implicit destructuring assignment in array of arrays", ->
a={}; b={}; c={}; d={}; e={}; f={}
arr = [[a, [b]], [c, [d]], [e, [f]]]
result = ([y,z] for [y, [z]] in arr)
arrayEq [[a,b],[c,d],[e,f]], result
test "issue #1124: don't assign a variable in two scopes", ->
lista = [1, 2, 3, 4, 5]
listb = (_i + 1 for _i in lista)
arrayEq [2, 3, 4, 5, 6], listb
test "#1326: `by` value is uncached", ->
a = [0,1,2]
fi = gi = hi = 0
f = -> ++fi
g = -> ++gi
h = -> ++hi
forCompile = []
rangeCompileSimple = []
#exercises For.compile
for v, i in a by f()
forCompile.push i
#exercises Range.compileSimple
rangeCompileSimple = (i for i in [0..2] by g())
arrayEq a, forCompile
arrayEq a, rangeCompileSimple
#exercises Range.compile
eq "#{i for i in [0..2] by h()}", '0,1,2'
test "#1669: break/continue should skip the result only for that branch", ->
ns = for n in [0..99]
if n > 9
break
else if n & 1
continue
else
n
eq "#{ns}", '0,2,4,6,8'
# `else undefined` is implied.
ns = for n in [1..9]
if n % 2
continue unless n % 5
n
eq "#{ns}", "1,,3,,,7,,9"
# Ditto.
ns = for n in [1..9]
switch
when n % 2
continue unless n % 5
n
eq "#{ns}", "1,,3,,,7,,9"
test "#1850: inner `for` should not be expression-ized if `return`ing", ->
eq '3,4,5', do ->
for a in [1..9] then \
for b in [1..9]
c = Math.sqrt a*a + b*b
return String [a, b, c] unless c % 1
test "#1910: loop index should be mutable within a loop iteration and immutable between loop iterations", ->
n = 1
iterations = 0
arr = [0..n]
for v, k in arr
++iterations
v = k = 5
eq 5, k
eq 2, k
eq 2, iterations
iterations = 0
for v in [0..n]
++iterations
eq 2, k
eq 2, iterations
arr = ([v, v + 1] for v in [0..5])
iterations = 0
for [v0, v1], k in arr when v0
k += 3
++iterations
eq 6, k
eq 5, iterations
test "#2007: Return object literal from comprehension", ->
y = for x in [1, 2]
foo: "foo" + x
eq 2, y.length
eq "foo1", y[0].foo
eq "foo2", y[1].foo
x = 2
y = while x
x: --x
eq 2, y.length
eq 1, y[0].x
eq 0, y[1].x
test "#2274: Allow @values as loop variables", ->
obj = {
item: null
method: ->
for @item in [1, 2, 3]
null
}
eq obj.item, null
obj.method()
eq obj.item, 3
test "#4411: Allow @values as loop indices", ->
obj =
index: null
get: -> @index
method: ->
@get() for _, @index in [1, 2, 3]
eq obj.index, null
arrayEq obj.method(), [0, 1, 2]
eq obj.index, 3
test "#2525, #1187, #1208, #1758, looping over an array forwards", ->
list = [0, 1, 2, 3, 4]
ident = (x) -> x
arrayEq (i for i in list), list
arrayEq (index for i, index in list), list
arrayEq (i for i in list by 1), list
arrayEq (i for i in list by ident 1), list
arrayEq (i for i in list by ident(1) * 2), [0, 2, 4]
arrayEq (index for i, index in list by ident(1) * 2), [0, 2, 4]
test "#2525, #1187, #1208, #1758, looping over an array backwards", ->
list = [0, 1, 2, 3, 4]
backwards = [4, 3, 2, 1, 0]
ident = (x) -> x
arrayEq (i for i in list by -1), backwards
arrayEq (index for i, index in list by -1), backwards
arrayEq (i for i in list by ident -1), backwards
arrayEq (i for i in list by ident(-1) * 2), [4, 2, 0]
arrayEq (index for i, index in list by ident(-1) * 2), [4, 2, 0]
test "splats in destructuring in comprehensions", ->
list = [[0, 1, 2], [2, 3, 4], [4, 5, 6]]
arrayEq (seq for [rep, seq...] in list), [[1, 2], [3, 4], [5, 6]]
test "#156: expansion in destructuring in comprehensions", ->
list = [[0, 1, 2], [2, 3, 4], [4, 5, 6]]
arrayEq (last for [..., last] in list), [2, 4, 6]
test "#3778: Consistently always cache for loop range boundaries and steps, even
if they are simple identifiers", ->
a = 1; arrayEq [1, 2, 3], (for n in [1, 2, 3] by a then a = 4; n)
a = 1; arrayEq [1, 2, 3], (for n in [1, 2, 3] by +a then a = 4; n)
a = 1; arrayEq [1, 2, 3], (for n in [a..3] then a = 4; n)
a = 1; arrayEq [1, 2, 3], (for n in [+a..3] then a = 4; n)
a = 3; arrayEq [1, 2, 3], (for n in [1..a] then a = 4; n)
a = 3; arrayEq [1, 2, 3], (for n in [1..+a] then a = 4; n)
a = 1; arrayEq [1, 2, 3], (for n in [1..3] by a then a = 4; n)
a = 1; arrayEq [1, 2, 3], (for n in [1..3] by +a then a = 4; n)
</script>
<script type="text/x-coffeescript" class="test" id="control_flow">
# Control Flow
# ------------
# * Conditionals
# * Loops
# * For
# * While
# * Until
# * Loop
# * Switch
# * Throw
# TODO: make sure postfix forms and expression coercion are properly tested
# shared identity function
id = (_) -> if arguments.length is 1 then _ else Array::slice.call(arguments)
# Conditionals
test "basic conditionals", ->
if false
ok false
else if false
ok false
else
ok true
if true
ok true
else if true
ok false
else
ok true
unless true
ok false
else unless true
ok false
else
ok true
unless false
ok true
else unless false
ok false
else
ok true
test "single-line conditional", ->
if false then ok false else ok true
unless false then ok true else ok false
test "nested conditionals", ->
nonce = {}
eq nonce, (if true
unless false
if false then false else
if true
nonce)
test "nested single-line conditionals", ->
nonce = {}
a = if false then undefined else b = if 0 then undefined else nonce
eq nonce, a
eq nonce, b
c = if false then undefined else (if 0 then undefined else nonce)
eq nonce, c
d = if true then id(if false then undefined else nonce)
eq nonce, d
test "empty conditional bodies", ->
eq undefined, (if false
else if false
else)
test "conditional bodies containing only comments", ->
eq undefined, (if true
###
block comment
###
else
# comment
)
eq undefined, (if false
# comment
else if true
###
block comment
###
else)
test "return value of if-else is from the proper body", ->
nonce = {}
eq nonce, if false then undefined else nonce
test "return value of unless-else is from the proper body", ->
nonce = {}
eq nonce, unless true then undefined else nonce
test "assign inside the condition of a conditional statement", ->
nonce = {}
if a = nonce then 1
eq nonce, a
1 if b = nonce
eq nonce, b
# Interactions With Functions
test "single-line function definition with single-line conditional", ->
fn = -> if 1 < 0.5 then 1 else -1
ok fn() is -1
test "function resturns conditional value with no `else`", ->
fn = ->
return if false then true
eq undefined, fn()
test "function returns a conditional value", ->
a = {}
fnA = ->
return if false then undefined else a
eq a, fnA()
b = {}
fnB = ->
return unless false then b else undefined
eq b, fnB()
test "passing a conditional value to a function", ->
nonce = {}
eq nonce, id if false then undefined else nonce
test "unmatched `then` should catch implicit calls", ->
a = 0
trueFn = -> true
if trueFn undefined then a++
eq 1, a
# if-to-ternary
test "if-to-ternary with instanceof requires parentheses", ->
nonce = {}
eq nonce, (if {} instanceof Object
nonce
else
undefined)
test "if-to-ternary as part of a larger operation requires parentheses", ->
ok 2, 1 + if false then 0 else 1
# Odd Formatting
test "if-else indented within an assignment", ->
nonce = {}
result =
if false
undefined
else
nonce
eq nonce, result
test "suppressed indentation via assignment", ->
nonce = {}
result =
if false then undefined
else if no then undefined
else if 0 then undefined
else if 1 < 0 then undefined
else id(
if false then undefined
else nonce
)
eq nonce, result
test "tight formatting with leading `then`", ->
nonce = {}
eq nonce,
if true
then nonce
else undefined
test "#738: inline function defintion", ->
nonce = {}
fn = if true then -> nonce
eq nonce, fn()
test "#748: trailing reserved identifiers", ->
nonce = {}
obj = delete: true
result = if obj.delete
nonce
eq nonce, result
test 'if-else within an assignment, condition parenthesized', ->
result = if (1 is 1) then 'correct'
eq result, 'correct'
result = if ('whatever' ? no) then 'correct'
eq result, 'correct'
f = -> 'wrong'
result = if (f?()) then 'correct' else 'wrong'
eq result, 'correct'
# Postfix
test "#3056: multiple postfix conditionals", ->
temp = 'initial'
temp = 'ignored' unless true if false
eq temp, 'initial'
# Loops
test "basic `while` loops", ->
i = 5
list = while i -= 1
i * 2
ok list.join(' ') is "8 6 4 2"
i = 5
list = (i * 3 while i -= 1)
ok list.join(' ') is "12 9 6 3"
i = 5
func = (num) -> i -= num
assert = -> ok i < 5 > 0
results = while func 1
assert()
i
ok results.join(' ') is '4 3 2 1'
i = 10
results = while i -= 1 when i % 2 is 0
i * 2
ok results.join(' ') is '16 12 8 4'
test "Issue 759: `if` within `while` condition", ->
2 while if 1 then 0
test "assignment inside the condition of a `while` loop", ->
nonce = {}
count = 1
a = nonce while count--
eq nonce, a
count = 1
while count--
b = nonce
eq nonce, b
test "While over break.", ->
i = 0
result = while i < 10
i++
break
arrayEq result, []
test "While over continue.", ->
i = 0
result = while i < 10
i++
continue
arrayEq result, []
test "Basic `until`", ->
value = false
i = 0
results = until value
value = true if i is 5
i++
ok i is 6
test "Basic `loop`", ->
i = 5
list = []
loop
i -= 1
break if i is 0
list.push i * 2
ok list.join(' ') is '8 6 4 2'
test "break at the top level", ->
for i in [1,2,3]
result = i
if i == 2
break
eq 2, result
test "break *not* at the top level", ->
someFunc = ->
i = 0
while ++i < 3
result = i
break if i > 1
result
eq 2, someFunc()
# Switch
test "basic `switch`", ->
num = 10
result = switch num
when 5 then false
when 'a'
true
true
false
when 10 then true
# Mid-switch comment with whitespace
# and multi line
when 11 then false
else false
ok result
func = (num) ->
switch num
when 2, 4, 6
true
when 1, 3, 5
false
ok func(2)
ok func(6)
ok !func(3)
eq func(8), undefined
test "Ensure that trailing switch elses don't get rewritten.", ->
result = false
switch "word"
when "one thing"
doSomething()
else
result = true unless false
ok result
result = false
switch "word"
when "one thing"
doSomething()
when "other thing"
doSomething()
else
result = true unless false
ok result
test "Should be able to handle switches sans-condition.", ->
result = switch
when null then 0
when !1 then 1
when '' not of {''} then 2
when [] not instanceof Array then 3
when true is false then 4
when 'x' < 'y' > 'z' then 5
when 'a' in ['b', 'c'] then 6
when 'd' in (['e', 'f']) then 7
else ok
eq result, ok
test "Should be able to use `@properties` within the switch clause.", ->
obj = {
num: 101
func: ->
switch @num
when 101 then '101!'
else 'other'
}
ok obj.func() is '101!'
test "Should be able to use `@properties` within the switch cases.", ->
obj = {
num: 101
func: (yesOrNo) ->
result = switch yesOrNo
when yes then @num
else 'other'
result
}
ok obj.func(yes) is 101
test "Switch with break as the return value of a loop.", ->
i = 10
results = while i > 0
i--
switch i % 2
when 1 then i
when 0 then break
eq results.join(', '), '9, 7, 5, 3, 1'
test "Issue #997. Switch doesn't fallthrough.", ->
val = 1
switch true
when true
if false
return 5
else
val = 2
eq val, 1
# Throw
test "Throw should be usable as an expression.", ->
try
false or throw 'up'
throw new Error 'failed'
catch e
ok e is 'up'
test "#2555, strange function if bodies", ->
success = -> ok true
failure = -> ok false
success() if do ->
yes
failure() if try
false
test "#1057: `catch` or `finally` in single-line functions", ->
ok do -> try throw 'up' catch then yes
ok do -> try yes finally 'nothing'
test "#2367: super in for-loop", ->
class Foo
sum: 0
add: (val) -> @sum += val
class Bar extends Foo
add: (vals...) ->
super val for val in vals
@sum
eq 10, (new Bar).add 2, 3, 5
test "#4267: lots of for-loops in the same scope", ->
# This used to include the invalid JavaScript `var do = 0`.
code = """
do ->
#{Array(200).join('for [0..0] then\n ')}
true
"""
ok CoffeeScript.eval(code)
</script>
<script type="text/x-coffeescript" class="test" id="error_messages">
# Error Formatting
# ----------------
# Ensure that errors of different kinds (lexer, parser and compiler) are shown
# in a consistent way.
assertErrorFormat = (code, expectedErrorFormat) ->
throws (-> CoffeeScript.run code), (err) ->
err.colorful = no
eq expectedErrorFormat, "#{err}"
yes
test "lexer errors formatting", ->
assertErrorFormat '''
normalObject = {}
insideOutObject = }{
''',
'''
[stdin]:2:19: error: unmatched }
insideOutObject = }{
^
'''
test "parser error formatting", ->
assertErrorFormat '''
foo in bar or in baz
''',
'''
[stdin]:1:15: error: unexpected in
foo in bar or in baz
^^
'''
test "compiler error formatting", ->
assertErrorFormat '''
evil = (foo, eval, bar) ->
''',
'''
[stdin]:1:14: error: 'eval' can't be assigned
evil = (foo, eval, bar) ->
^^^^
'''
test "compiler error formatting with mixed tab and space", ->
assertErrorFormat """
\t if a
\t test
""",
'''
[stdin]:1:4: error: unexpected if
\t if a
\t ^^
'''
if require?
os = require 'os'
fs = require 'fs'
path = require 'path'
test "patchStackTrace line patching", ->
err = new Error 'error'
ok err.stack.match /test[\/\\]error_messages\.coffee:\d+:\d+\b/
test "patchStackTrace stack prelude consistent with V8", ->
err = new Error
ok err.stack.match /^Error\n/ # Notice no colon when no message.
err = new Error 'error'
ok err.stack.match /^Error: error\n/
test "#2849: compilation error in a require()d file", ->
# Create a temporary file to require().
tempFile = path.join os.tmpdir(), 'syntax-error.coffee'
ok not fs.existsSync tempFile
fs.writeFileSync tempFile, 'foo in bar or in baz'
try
assertErrorFormat """
require '#{tempFile}'
""",
"""
#{fs.realpathSync tempFile}:1:15: error: unexpected in
foo in bar or in baz
^^
"""
finally
fs.unlinkSync tempFile
test "#3890 Error.prepareStackTrace doesn't throw an error if a compiled file is deleted", ->
# Adapted from https://github.com/atom/coffee-cash/blob/master/spec/coffee-cash-spec.coffee
filePath = path.join os.tmpdir(), 'PrepareStackTraceTestFile.coffee'
fs.writeFileSync filePath, "module.exports = -> throw new Error('hello world')"
throwsAnError = require filePath
fs.unlinkSync filePath
try
throwsAnError()
catch error
eq error.message, 'hello world'
doesNotThrow(-> error.stack)
notEqual error.stack.toString().indexOf(filePath), -1
test "#4418 stack traces for compiled files reference the correct line number", ->
filePath = path.join os.tmpdir(), 'StackTraceLineNumberTestFile.coffee'
fileContents = """
testCompiledFileStackTraceLineNumber = ->
# `a` on the next line is undefined and should throw a ReferenceError
console.log a if true
do testCompiledFileStackTraceLineNumber
"""
fs.writeFileSync filePath, fileContents
try
require filePath
catch error
fs.unlinkSync filePath
# Make sure the line number reported is line 3 (the original Coffee source)
# and not line 6 (the generated JavaScript).
eq /StackTraceLineNumberTestFile.coffee:(\d)/.exec(error.stack.toString())[1], '3'
test "#4418 stack traces for compiled strings reference the correct line number", ->
try
CoffeeScript.run """
testCompiledStringStackTraceLineNumber = ->
# `a` on the next line is undefined and should throw a ReferenceError
console.log a if true
do testCompiledStringStackTraceLineNumber
"""
catch error
# Make sure the line number reported is line 3 (the original Coffee source)
# and not line 6 (the generated JavaScript).
eq /at testCompiledStringStackTraceLineNumber.*:(\d):/.exec(error.stack.toString())[1], '3'
test "#1096: unexpected generated tokens", ->
# Implicit ends
assertErrorFormat 'a:, b', '''
[stdin]:1:3: error: unexpected ,
a:, b
^
'''
# Explicit ends
assertErrorFormat '(a:)', '''
[stdin]:1:4: error: unexpected )
(a:)
^
'''
# Unexpected end of file
assertErrorFormat 'a:', '''
[stdin]:1:3: error: unexpected end of input
a:
^
'''
assertErrorFormat 'a +', '''
[stdin]:1:4: error: unexpected end of input
a +
^
'''
# Unexpected key in implicit object (an implicit object itself is _not_
# unexpected here)
assertErrorFormat '''
for i in [1]:
1
''', '''
[stdin]:1:10: error: unexpected [
for i in [1]:
^
'''
# Unexpected regex
assertErrorFormat '{/a/i: val}', '''
[stdin]:1:2: error: unexpected regex
{/a/i: val}
^^^^
'''
assertErrorFormat '{///a///i: val}', '''
[stdin]:1:2: error: unexpected regex
{///a///i: val}
^^^^^^^^
'''
assertErrorFormat '{///#{a}///i: val}', '''
[stdin]:1:2: error: unexpected regex
{///#{a}///i: val}
^^^^^^^^^^^
'''
# Unexpected string
assertErrorFormat 'import foo from "lib-#{version}"', '''
[stdin]:1:17: error: the name of the module to be imported from must be an uninterpolated string
import foo from "lib-#{version}"
^^^^^^^^^^^^^^^^
'''
# Unexpected number
assertErrorFormat '"a"0x00Af2', '''
[stdin]:1:4: error: unexpected number
"a"0x00Af2
^^^^^^^
'''
test "#1316: unexpected end of interpolation", ->
assertErrorFormat '''
"#{+}"
''', '''
[stdin]:1:5: error: unexpected end of interpolation
"#{+}"
^
'''
assertErrorFormat '''
"#{++}"
''', '''
[stdin]:1:6: error: unexpected end of interpolation
"#{++}"
^
'''
assertErrorFormat '''
"#{-}"
''', '''
[stdin]:1:5: error: unexpected end of interpolation
"#{-}"
^
'''
assertErrorFormat '''
"#{--}"
''', '''
[stdin]:1:6: error: unexpected end of interpolation
"#{--}"
^
'''
assertErrorFormat '''
"#{~}"
''', '''
[stdin]:1:5: error: unexpected end of interpolation
"#{~}"
^
'''
assertErrorFormat '''
"#{!}"
''', '''
[stdin]:1:5: error: unexpected end of interpolation
"#{!}"
^
'''
assertErrorFormat '''
"#{not}"
''', '''
[stdin]:1:7: error: unexpected end of interpolation
"#{not}"
^
'''
assertErrorFormat '''
"#{5) + (4}_"
''', '''
[stdin]:1:5: error: unmatched )
"#{5) + (4}_"
^
'''
# #2918
assertErrorFormat '''
"#{foo.}"
''', '''
[stdin]:1:8: error: unexpected end of interpolation
"#{foo.}"
^
'''
test "#3325: implicit indentation errors", ->
assertErrorFormat '''
i for i in a then i
''', '''
[stdin]:1:14: error: unexpected then
i for i in a then i
^^^^
'''
test "explicit indentation errors", ->
assertErrorFormat '''
a = b
c
''', '''
[stdin]:2:1: error: unexpected indentation
c
^^
'''
test "unclosed strings", ->
assertErrorFormat '''
'
''', '''
[stdin]:1:1: error: missing '
'
^
'''
assertErrorFormat '''
"
''', '''
[stdin]:1:1: error: missing "
"
^
'''
assertErrorFormat """
'''
""", """
[stdin]:1:1: error: missing '''
'''
^^^
"""
assertErrorFormat '''
"""
''', '''
[stdin]:1:1: error: missing """
"""
^^^
'''
assertErrorFormat '''
"#{"
''', '''
[stdin]:1:4: error: missing "
"#{"
^
'''
assertErrorFormat '''
"""#{"
''', '''
[stdin]:1:6: error: missing "
"""#{"
^
'''
assertErrorFormat '''
"#{"""
''', '''
[stdin]:1:4: error: missing """
"#{"""
^^^
'''
assertErrorFormat '''
"""#{"""
''', '''
[stdin]:1:6: error: missing """
"""#{"""
^^^
'''
assertErrorFormat '''
///#{"""
''', '''
[stdin]:1:6: error: missing """
///#{"""
^^^
'''
assertErrorFormat '''
"a
#{foo """
bar
#{ +'12 }
baz
"""} b"
''', '''
[stdin]:4:11: error: missing '
#{ +'12 }
^
'''
# https://github.com/jashkenas/coffeescript/issues/3301#issuecomment-31735168
assertErrorFormat '''
# Note the double escaping; this would be `"""a\"""` real code.
"""a\\"""
''', '''
[stdin]:2:1: error: missing """
"""a\\"""
^^^
'''
test "unclosed heregexes", ->
assertErrorFormat '''
///
''', '''
[stdin]:1:1: error: missing ///
///
^^^
'''
# https://github.com/jashkenas/coffeescript/issues/3301#issuecomment-31735168
assertErrorFormat '''
# Note the double escaping; this would be `///a\///` real code.
///a\\///
''', '''
[stdin]:2:1: error: missing ///
///a\\///
^^^
'''
test "unexpected token after string", ->
# Parsing error.
assertErrorFormat '''
'foo'bar
''', '''
[stdin]:1:6: error: unexpected identifier
'foo'bar
^^^
'''
assertErrorFormat '''
"foo"bar
''', '''
[stdin]:1:6: error: unexpected identifier
"foo"bar
^^^
'''
# Lexing error.
assertErrorFormat '''
'foo'bar'
''', '''
[stdin]:1:9: error: missing '
'foo'bar'
^
'''
assertErrorFormat '''
"foo"bar"
''', '''
[stdin]:1:9: error: missing "
"foo"bar"
^
'''
test "#3348: Location data is wrong in interpolations with leading whitespace", ->
assertErrorFormat '''
"#{ * }"
''', '''
[stdin]:1:5: error: unexpected *
"#{ * }"
^
'''
test "octal escapes", ->
assertErrorFormat '''
"a\\0\\tb\\\\\\07c"
''', '''
[stdin]:1:10: error: octal escape sequences are not allowed \\07
"a\\0\\tb\\\\\\07c"
\ \ \ \ ^\^^
'''
assertErrorFormat '''
"a
#{b} \\1"
''', '''
[stdin]:2:8: error: octal escape sequences are not allowed \\1
#{b} \\1"
^\^
'''
assertErrorFormat '''
/a\\0\\tb\\\\\\07c/
''', '''
[stdin]:1:10: error: octal escape sequences are not allowed \\07
/a\\0\\tb\\\\\\07c/
\ \ \ \ ^\^^
'''
assertErrorFormat '''
/a\\1\\tb\\\\\\07c/
''', '''
[stdin]:1:10: error: octal escape sequences are not allowed \\07
/a\\1\\tb\\\\\\07c/
\ \ \ \ ^\^^
'''
assertErrorFormat '''
///a
#{b} \\01///
''', '''
[stdin]:2:8: error: octal escape sequences are not allowed \\01
#{b} \\01///
^\^^
'''
test "#3795: invalid escapes", ->
assertErrorFormat '''
"a\\0\\tb\\\\\\x7g"
''', '''
[stdin]:1:10: error: invalid escape sequence \\x7g
"a\\0\\tb\\\\\\x7g"
\ \ \ \ ^\^^^
'''
assertErrorFormat '''
"a
#{b} \\uA02
c"
''', '''
[stdin]:2:8: error: invalid escape sequence \\uA02
#{b} \\uA02
^\^^^^
'''
assertErrorFormat '''
/a\\u002space/
''', '''
[stdin]:1:3: error: invalid escape sequence \\u002s
/a\\u002space/
^\^^^^^
'''
assertErrorFormat '''
///a \\u002 0 space///
''', '''
[stdin]:1:6: error: invalid escape sequence \\u002 \n\
///a \\u002 0 space///
^\^^^^^
'''
assertErrorFormat '''
///a
#{b} \\x0
c///
''', '''
[stdin]:2:8: error: invalid escape sequence \\x0
#{b} \\x0
^\^^
'''
assertErrorFormat '''
/ab\\u/
''', '''
[stdin]:1:4: error: invalid escape sequence \\u
/ab\\u/
^\^
'''
test "illegal herecomment", ->
assertErrorFormat '''
###
Regex: /a*/g
###
''', '''
[stdin]:2:12: error: block comments cannot contain */
Regex: /a*/g
^^
'''
test "#1724: regular expressions beginning with *", ->
assertErrorFormat '''
/* foo/
''', '''
[stdin]:1:2: error: regular expressions cannot begin with *
/* foo/
^
'''
assertErrorFormat '''
///
* foo
///
''', '''
[stdin]:2:3: error: regular expressions cannot begin with *
* foo
^
'''
test "invalid regex flags", ->
assertErrorFormat '''
/a/ii
''', '''
[stdin]:1:4: error: invalid regular expression flags ii
/a/ii
^^
'''
assertErrorFormat '''
/a/G
''', '''
[stdin]:1:4: error: invalid regular expression flags G
/a/G
^
'''
assertErrorFormat '''
/a/gimi
''', '''
[stdin]:1:4: error: invalid regular expression flags gimi
/a/gimi
^^^^
'''
assertErrorFormat '''
/a/g_
''', '''
[stdin]:1:4: error: invalid regular expression flags g_
/a/g_
^^
'''
assertErrorFormat '''
///a///ii
''', '''
[stdin]:1:8: error: invalid regular expression flags ii
///a///ii
^^
'''
doesNotThrow -> CoffeeScript.compile '/a/ymgi'
test "missing `)`, `}`, `]`", ->
assertErrorFormat '''
(
''', '''
[stdin]:1:1: error: missing )
(
^
'''
assertErrorFormat '''
{
''', '''
[stdin]:1:1: error: missing }
{
^
'''
assertErrorFormat '''
[
''', '''
[stdin]:1:1: error: missing ]
[
^
'''
assertErrorFormat '''
obj = {a: [1, (2+
''', '''
[stdin]:1:15: error: missing )
obj = {a: [1, (2+
^
'''
assertErrorFormat '''
"#{
''', '''
[stdin]:1:3: error: missing }
"#{
^
'''
assertErrorFormat '''
"""
foo#{ bar "#{1}"
''', '''
[stdin]:2:7: error: missing }
foo#{ bar "#{1}"
^
'''
test "unclosed regexes", ->
assertErrorFormat '''
/
''', '''
[stdin]:1:1: error: missing / (unclosed regex)
/
^
'''
assertErrorFormat '''
# Note the double escaping; this would be `/a\/` real code.
/a\\/
''', '''
[stdin]:2:1: error: missing / (unclosed regex)
/a\\/
^
'''
assertErrorFormat '''
/// ^
a #{""" ""#{if /[/].test "|" then 1 else 0}"" """}
///
''', '''
[stdin]:2:18: error: missing / (unclosed regex)
a #{""" ""#{if /[/].test "|" then 1 else 0}"" """}
^
'''
test "duplicate function arguments", ->
assertErrorFormat '''
(foo, bar, foo) ->
''', '''
[stdin]:1:12: error: multiple parameters named foo
(foo, bar, foo) ->
^^^
'''
assertErrorFormat '''
(@foo, bar, @foo) ->
''', '''
[stdin]:1:13: error: multiple parameters named @foo
(@foo, bar, @foo) ->
^^^^
'''
test "reserved words", ->
assertErrorFormat '''
case
''', '''
[stdin]:1:1: error: reserved word 'case'
case
^^^^
'''
assertErrorFormat '''
case = 1
''', '''
[stdin]:1:1: error: reserved word 'case'
case = 1
^^^^
'''
assertErrorFormat '''
for = 1
''', '''
[stdin]:1:1: error: keyword 'for' can't be assigned
for = 1
^^^
'''
assertErrorFormat '''
unless = 1
''', '''
[stdin]:1:1: error: keyword 'unless' can't be assigned
unless = 1
^^^^^^
'''
assertErrorFormat '''
for += 1
''', '''
[stdin]:1:1: error: keyword 'for' can't be assigned
for += 1
^^^
'''
assertErrorFormat '''
for &&= 1
''', '''
[stdin]:1:1: error: keyword 'for' can't be assigned
for &&= 1
^^^
'''
# Make sure token look-behind doesn't go out of range.
assertErrorFormat '''
&&= 1
''', '''
[stdin]:1:1: error: unexpected &&=
&&= 1
^^^
'''
# #2306: Show unaliased name in error messages.
assertErrorFormat '''
on = 1
''', '''
[stdin]:1:1: error: keyword 'on' can't be assigned
on = 1
^^
'''
test "strict mode errors", ->
assertErrorFormat '''
eval = 1
''', '''
[stdin]:1:1: error: 'eval' can't be assigned
eval = 1
^^^^
'''
assertErrorFormat '''
class eval
''', '''
[stdin]:1:7: error: 'eval' can't be assigned
class eval
^^^^
'''
assertErrorFormat '''
arguments++
''', '''
[stdin]:1:1: error: 'arguments' can't be assigned
arguments++
^^^^^^^^^
'''
assertErrorFormat '''
--arguments
''', '''
[stdin]:1:3: error: 'arguments' can't be assigned
--arguments
^^^^^^^^^
'''
test "invalid numbers", ->
assertErrorFormat '''
0X0
''', '''
[stdin]:1:2: error: radix prefix in '0X0' must be lowercase
0X0
^
'''
assertErrorFormat '''
10E0
''', '''
[stdin]:1:3: error: exponential notation in '10E0' must be indicated with a lowercase 'e'
10E0
^
'''
assertErrorFormat '''
018
''', '''
[stdin]:1:1: error: decimal literal '018' must not be prefixed with '0'
018
^^^
'''
assertErrorFormat '''
010
''', '''
[stdin]:1:1: error: octal literal '010' must be prefixed with '0o'
010
^^^
'''
test "unexpected object keys", ->
assertErrorFormat '''
{[[]]}
''', '''
[stdin]:1:2: error: unexpected [
{[[]]}
^
'''
assertErrorFormat '''
{[[]]: 1}
''', '''
[stdin]:1:2: error: unexpected [
{[[]]: 1}
^
'''
assertErrorFormat '''
[[]]: 1
''', '''
[stdin]:1:1: error: unexpected [
[[]]: 1
^
'''
assertErrorFormat '''
{(a + "b")}
''', '''
[stdin]:1:2: error: unexpected (
{(a + "b")}
^
'''
assertErrorFormat '''
{(a + "b"): 1}
''', '''
[stdin]:1:2: error: unexpected (
{(a + "b"): 1}
^
'''
assertErrorFormat '''
(a + "b"): 1
''', '''
[stdin]:1:1: error: unexpected (
(a + "b"): 1
^
'''
assertErrorFormat '''
a: 1, [[]]: 2
''', '''
[stdin]:1:7: error: unexpected [
a: 1, [[]]: 2
^
'''
assertErrorFormat '''
{a: 1, [[]]: 2}
''', '''
[stdin]:1:8: error: unexpected [
{a: 1, [[]]: 2}
^
'''
test "invalid object keys", ->
assertErrorFormat '''
@a: 1
''', '''
[stdin]:1:1: error: invalid object key
@a: 1
^^
'''
assertErrorFormat '''
f
@a: 1
''', '''
[stdin]:2:3: error: invalid object key
@a: 1
^^
'''
assertErrorFormat '''
{a=2}
''', '''
[stdin]:1:3: error: unexpected =
{a=2}
^
'''
test "invalid destructuring default target", ->
assertErrorFormat '''
{'a' = 2} = obj
''', '''
[stdin]:1:6: error: unexpected =
{'a' = 2} = obj
^
'''
test "#4070: lone expansion", ->
assertErrorFormat '''
[...] = a
''', '''
[stdin]:1:2: error: Destructuring assignment has no target
[...] = a
^^^
'''
assertErrorFormat '''
[ ..., ] = a
''', '''
[stdin]:1:3: error: Destructuring assignment has no target
[ ..., ] = a
^^^
'''
test "#3926: implicit object in parameter list", ->
assertErrorFormat '''
(a: b) ->
''', '''
[stdin]:1:3: error: unexpected :
(a: b) ->
^
'''
assertErrorFormat '''
(one, two, {three, four: five}, key: value) ->
''', '''
[stdin]:1:36: error: unexpected :
(one, two, {three, four: five}, key: value) ->
^
'''
test "#4130: unassignable in destructured param", ->
assertErrorFormat '''
fun = ({
@param : null
}) ->
console.log "Oh hello!"
''', '''
[stdin]:2:12: error: keyword 'null' can't be assigned
@param : null
^^^^
'''
assertErrorFormat '''
({a: null}) ->
''', '''
[stdin]:1:6: error: keyword 'null' can't be assigned
({a: null}) ->
^^^^
'''
assertErrorFormat '''
({a: 1}) ->
''', '''
[stdin]:1:6: error: '1' can't be assigned
({a: 1}) ->
^
'''
assertErrorFormat '''
({1}) ->
''', '''
[stdin]:1:3: error: '1' can't be assigned
({1}) ->
^
'''
assertErrorFormat '''
({a: true = 1}) ->
''', '''
[stdin]:1:6: error: keyword 'true' can't be assigned
({a: true = 1}) ->
^^^^
'''
test "`yield` outside of a function", ->
assertErrorFormat '''
yield 1
''', '''
[stdin]:1:1: error: yield can only occur inside functions
yield 1
^^^^^^^
'''
assertErrorFormat '''
yield return
''', '''
[stdin]:1:1: error: yield can only occur inside functions
yield return
^^^^^^^^^^^^
'''
test "#4097: `yield return` as an expression", ->
assertErrorFormat '''
-> (yield return)
''', '''
[stdin]:1:5: error: cannot use a pure statement in an expression
-> (yield return)
^^^^^^^^^^^^
'''
test "`&&=` and `||=` with a space in-between", ->
assertErrorFormat '''
a = 0
a && = 1
''', '''
[stdin]:2:6: error: unexpected =
a && = 1
^
'''
assertErrorFormat '''
a = 0
a and = 1
''', '''
[stdin]:2:7: error: unexpected =
a and = 1
^
'''
assertErrorFormat '''
a = 0
a || = 1
''', '''
[stdin]:2:6: error: unexpected =
a || = 1
^
'''
assertErrorFormat '''
a = 0
a or = 1
''', '''
[stdin]:2:6: error: unexpected =
a or = 1
^
'''
test "anonymous functions cannot be exported", ->
assertErrorFormat '''
export ->
console.log 'hello, world!'
''', '''
[stdin]:1:8: error: unexpected ->
export ->
^^
'''
test "anonymous classes cannot be exported", ->
assertErrorFormat '''
export class
constructor: ->
console.log 'hello, world!'
''', '''
[stdin]:1:8: error: anonymous classes cannot be exported
export class
^^^^^
'''
test "unless enclosed by curly braces, only * can be aliased", ->
assertErrorFormat '''
import foo as bar from 'lib'
''', '''
[stdin]:1:12: error: unexpected as
import foo as bar from 'lib'
^^
'''
test "unwrapped imports must follow constrained syntax", ->
assertErrorFormat '''
import foo, bar from 'lib'
''', '''
[stdin]:1:13: error: unexpected identifier
import foo, bar from 'lib'
^^^
'''
assertErrorFormat '''
import foo, bar, baz from 'lib'
''', '''
[stdin]:1:13: error: unexpected identifier
import foo, bar, baz from 'lib'
^^^
'''
assertErrorFormat '''
import foo, bar as baz from 'lib'
''', '''
[stdin]:1:13: error: unexpected identifier
import foo, bar as baz from 'lib'
^^^
'''
test "cannot export * without a module to export from", ->
assertErrorFormat '''
export *
''', '''
[stdin]:1:9: error: unexpected end of input
export *
^
'''
test "imports and exports must be top-level", ->
assertErrorFormat '''
if foo
import { bar } from 'lib'
''', '''
[stdin]:2:3: error: import statements must be at top-level scope
import { bar } from 'lib'
^^^^^^^^^^^^^^^^^^^^^^^^^
'''
assertErrorFormat '''
foo = ->
export { bar }
''', '''
[stdin]:2:3: error: export statements must be at top-level scope
export { bar }
^^^^^^^^^^^^^^
'''
test "cannot import the same member more than once", ->
assertErrorFormat '''
import { foo, foo } from 'lib'
''', '''
[stdin]:1:15: error: 'foo' has already been declared
import { foo, foo } from 'lib'
^^^
'''
assertErrorFormat '''
import { foo, bar, foo } from 'lib'
''', '''
[stdin]:1:20: error: 'foo' has already been declared
import { foo, bar, foo } from 'lib'
^^^
'''
assertErrorFormat '''
import { foo, bar as foo } from 'lib'
''', '''
[stdin]:1:15: error: 'foo' has already been declared
import { foo, bar as foo } from 'lib'
^^^^^^^^^^
'''
assertErrorFormat '''
import foo, { foo } from 'lib'
''', '''
[stdin]:1:15: error: 'foo' has already been declared
import foo, { foo } from 'lib'
^^^
'''
assertErrorFormat '''
import foo, { bar as foo } from 'lib'
''', '''
[stdin]:1:15: error: 'foo' has already been declared
import foo, { bar as foo } from 'lib'
^^^^^^^^^^
'''
assertErrorFormat '''
import foo from 'libA'
import foo from 'libB'
''', '''
[stdin]:2:8: error: 'foo' has already been declared
import foo from 'libB'
^^^
'''
assertErrorFormat '''
import * as foo from 'libA'
import { foo } from 'libB'
''', '''
[stdin]:2:10: error: 'foo' has already been declared
import { foo } from 'libB'
^^^
'''
test "imported members cannot be reassigned", ->
assertErrorFormat '''
import { foo } from 'lib'
foo = 'bar'
''', '''
[stdin]:2:1: error: 'foo' is read-only
foo = 'bar'
^^^
'''
assertErrorFormat '''
import { foo } from 'lib'
export default foo = 'bar'
''', '''
[stdin]:2:16: error: 'foo' is read-only
export default foo = 'bar'
^^^
'''
assertErrorFormat '''
import { foo } from 'lib'
export foo = 'bar'
''', '''
[stdin]:2:8: error: 'foo' is read-only
export foo = 'bar'
^^^
'''
test "CoffeeScript keywords cannot be used as unaliased names in import lists", ->
assertErrorFormat """
import { unless, baz as bar } from 'lib'
bar.barMethod()
""", '''
[stdin]:1:10: error: unexpected unless
import { unless, baz as bar } from 'lib'
^^^^^^
'''
test "CoffeeScript keywords cannot be used as local names in import list aliases", ->
assertErrorFormat """
import { bar as unless, baz as bar } from 'lib'
bar.barMethod()
""", '''
[stdin]:1:17: error: unexpected unless
import { bar as unless, baz as bar } from 'lib'
^^^^^^
'''
test "indexes are not supported in for-from loops", ->
assertErrorFormat "x for x, i from [1, 2, 3]", '''
[stdin]:1:10: error: cannot use index with for-from
x for x, i from [1, 2, 3]
^
'''
test "own is not supported in for-from loops", ->
assertErrorFormat "x for own x from [1, 2, 3]", '''
[stdin]:1:7: error: cannot use own with for-from
x for own x from [1, 2, 3]
^^^
'''
test "tagged template literals must be called by an identifier", ->
assertErrorFormat "1''", '''
[stdin]:1:1: error: literal is not a function
1''
^
'''
assertErrorFormat '1""', '''
[stdin]:1:1: error: literal is not a function
1""
^
'''
assertErrorFormat "1'b'", '''
[stdin]:1:1: error: literal is not a function
1'b'
^
'''
assertErrorFormat '1"b"', '''
[stdin]:1:1: error: literal is not a function
1"b"
^
'''
assertErrorFormat "1'''b'''", """
[stdin]:1:1: error: literal is not a function
1'''b'''
^
"""
assertErrorFormat '1"""b"""', '''
[stdin]:1:1: error: literal is not a function
1"""b"""
^
'''
assertErrorFormat '1"#{b}"', '''
[stdin]:1:1: error: literal is not a function
1"#{b}"
^
'''
assertErrorFormat '1"""#{b}"""', '''
[stdin]:1:1: error: literal is not a function
1"""#{b}"""
^
'''
test "can't use pattern matches for loop indices", ->
assertErrorFormat 'a for b, {c} in d', '''
[stdin]:1:10: error: index cannot be a pattern matching expression
a for b, {c} in d
^^^
'''
test "#4248: Unicode code point escapes", ->
assertErrorFormat '''
"a
#{b} \\u{G02}
c"
''', '''
[stdin]:2:8: error: invalid escape sequence \\u{G02}
#{b} \\u{G02}
^\^^^^^^
'''
assertErrorFormat '''
/a\\u{}b/
''', '''
[stdin]:1:3: error: invalid escape sequence \\u{}
/a\\u{}b/
^\^^^
'''
assertErrorFormat '''
///a \\u{01abc///
''', '''
[stdin]:1:6: error: invalid escape sequence \\u{01abc
///a \\u{01abc///
^\^^^^^^^
'''
assertErrorFormat '''
/\\u{123} \\u{110000}/
''', '''
[stdin]:1:10: error: unicode code point escapes greater than \\u{10ffff} are not allowed
/\\u{123} \\u{110000}/
\ ^\^^^^^^^^^
'''
assertErrorFormat '''
///abc\\\\\\u{123456}///u
''', '''
[stdin]:1:9: error: unicode code point escapes greater than \\u{10ffff} are not allowed
///abc\\\\\\u{123456}///u
\ \^\^^^^^^^^^
'''
assertErrorFormat '''
"""
\\u{123}
a
\\u{00110000}
#{ 'b' }
"""
''', '''
[stdin]:4:5: error: unicode code point escapes greater than \\u{10ffff} are not allowed
\\u{00110000}
^\^^^^^^^^^^^
'''
assertErrorFormat '''
'\\u{a}\\u{1111110000}'
''', '''
[stdin]:1:7: error: unicode code point escapes greater than \\u{10ffff} are not allowed
'\\u{a}\\u{1111110000}'
\ ^\^^^^^^^^^^^^^
'''
test "#4283: error message for implicit call", ->
assertErrorFormat '''
console.log {search, users, contacts users_to_display}
''', '''
[stdin]:1:29: error: unexpected implicit function call
console.log {search, users, contacts users_to_display}
^^^^^^^^
'''
test "#3199: error message for call indented non-object", ->
assertErrorFormat '''
fn = ->
fn
1
''', '''
[stdin]:3:1: error: unexpected indentation
1
^^
'''
test "#3199: error message for call indented comprehension", ->
assertErrorFormat '''
fn = ->
fn
x for x in [1, 2, 3]
''', '''
[stdin]:3:1: error: unexpected indentation
x for x in [1, 2, 3]
^^
'''
test "#3199: error message for return indented non-object", ->
assertErrorFormat '''
return
1
''', '''
[stdin]:2:3: error: unexpected number
1
^
'''
test "#3199: error message for return indented comprehension", ->
assertErrorFormat '''
return
x for x in [1, 2, 3]
''', '''
[stdin]:2:3: error: unexpected identifier
x for x in [1, 2, 3]
^
'''
</script>
<script type="text/x-coffeescript" class="test" id="eval">
if vm = require? 'vm'
test "CoffeeScript.eval runs in the global context by default", ->
global.punctuation = '!'
code = '''
global.fhqwhgads = "global superpower#{global.punctuation}"
'''
result = CoffeeScript.eval code
eq result, 'global superpower!'
eq fhqwhgads, 'global superpower!'
test "CoffeeScript.eval can run in, and modify, a Script context sandbox", ->
createContext = vm.Script.createContext ? vm.createContext
sandbox = createContext()
sandbox.foo = 'bar'
code = '''
global.foo = 'not bar!'
'''
result = CoffeeScript.eval code, {sandbox}
eq result, 'not bar!'
eq sandbox.foo, 'not bar!'
test "CoffeeScript.eval can run in, but cannot modify, an ordinary object sandbox", ->
sandbox = {foo: 'bar'}
code = '''
global.foo = 'not bar!'
'''
result = CoffeeScript.eval code, {sandbox}
eq result, 'not bar!'
eq sandbox.foo, 'bar'
</script>
<script type="text/x-coffeescript" class="test" id="exception_handling">
# Exception Handling
# ------------------
# shared nonce
nonce = {}
# Throw
test "basic exception throwing", ->
throws (-> throw 'error'), 'error'
# Empty Try/Catch/Finally
test "try can exist alone", ->
try
test "try/catch with empty try, empty catch", ->
try
# nothing
catch err
# nothing
test "single-line try/catch with empty try, empty catch", ->
try catch err
test "try/finally with empty try, empty finally", ->
try
# nothing
finally
# nothing
test "single-line try/finally with empty try, empty finally", ->
try finally
test "try/catch/finally with empty try, empty catch, empty finally", ->
try
catch err
finally
test "single-line try/catch/finally with empty try, empty catch, empty finally", ->
try catch err then finally
# Try/Catch/Finally as an Expression
test "return the result of try when no exception is thrown", ->
result = try
nonce
catch err
undefined
finally
undefined
eq nonce, result
test "single-line result of try when no exception is thrown", ->
result = try nonce catch err then undefined
eq nonce, result
test "return the result of catch when an exception is thrown", ->
fn = ->
try
throw ->
catch err
nonce
doesNotThrow fn
eq nonce, fn()
test "single-line result of catch when an exception is thrown", ->
fn = ->
try throw (->) catch err then nonce
doesNotThrow fn
eq nonce, fn()
test "optional catch", ->
fn = ->
try throw ->
nonce
doesNotThrow fn
eq nonce, fn()
# Try/Catch/Finally Interaction With Other Constructs
test "try/catch with empty catch as last statement in a function body", ->
fn = ->
try nonce
catch err
eq nonce, fn()
# Catch leads to broken scoping: #1595
test "try/catch with a reused variable name.", ->
do ->
try
inner = 5
catch inner
# nothing
eq typeof inner, 'undefined'
# Allowed to destructure exceptions: #2580
test "try/catch with destructuring the exception object", ->
result = try
missing.object
catch {message}
message
eq message, 'missing is not defined'
test "Try catch finally as implicit arguments", ->
first = (x) -> x
foo = no
try
first try iamwhoiam() finally foo = yes
catch e
eq foo, yes
bar = no
try
first try iamwhoiam() catch e finally
bar = yes
catch e
eq bar, yes
# Catch Should Not Require Param: #2900
test "parameter-less catch clause", ->
try
throw new Error 'failed'
catch
ok true
try throw new Error 'failed' catch finally ok true
ok try throw new Error 'failed' catch then true
</script>
<script type="text/x-coffeescript" class="test" id="formatting">
# Formatting
# ----------
# TODO: maybe this file should be split up into their respective sections:
# operators -> operators
# array literals -> array literals
# string literals -> string literals
# function invocations -> function invocations
doesNotThrow -> CoffeeScript.compile "a = then b"
test "multiple semicolon-separated statements in parentheticals", ->
nonce = {}
eq nonce, (1; 2; nonce)
eq nonce, (-> return (1; 2; nonce))()
# * Line Continuation
# * Property Accesss
# * Operators
# * Array Literals
# * Function Invocations
# * String Literals
# Property Access
test "chained accesses split on period/newline, backwards and forwards", ->
str = 'abc'
result = str.
split('').
reverse().
reverse().
reverse()
arrayEq ['c','b','a'], result
arrayEq ['c','b','a'], str.
split('').
reverse().
reverse().
reverse()
result = str
.split('')
.reverse()
.reverse()
.reverse()
arrayEq ['c','b','a'], result
arrayEq ['c','b','a'],
str
.split('')
.reverse()
.reverse()
.reverse()
arrayEq ['c','b','a'],
str.
split('')
.reverse().
reverse()
.reverse()
# Operators
test "newline suppression for operators", ->
six =
1 +
2 +
3
eq 6, six
test "`?.` and `::` should continue lines", ->
ok not (
Date
::
?.foo
)
#eq Object::toString, Date?.
#prototype
#::
#?.foo
doesNotThrow -> CoffeeScript.compile """
oh. yes
oh?. true
oh:: return
"""
doesNotThrow -> CoffeeScript.compile """
a?[b..]
a?[...b]
a?[b..c]
"""
# Array Literals
test "indented array literals don't trigger whitespace rewriting", ->
getArgs = -> arguments
result = getArgs(
[[[[[],
[]],
[[]]]],
[]])
eq 1, result.length
# Function Invocations
doesNotThrow -> CoffeeScript.compile """
obj = then fn 1,
1: 1
a:
b: ->
fn c,
d: e
f: 1
"""
# String Literals
test "indented heredoc", ->
result = ((_) -> _)(
"""
abc
""")
eq "abc", result
# Chaining - all open calls are closed by property access starting a new line
# * chaining after
# * indented argument
# * function block
# * indented object
#
# * single line arguments
# * inline function literal
# * inline object literal
#
# * chaining inside
# * implicit object literal
test "chaining after outdent", ->
id = (x) -> x
# indented argument
ff = id parseInt "ff",
16
.toString()
eq '255', ff
# function block
str = 'abc'
zero = parseInt str.replace /\w/, (letter) ->
0
.toString()
eq '0', zero
# indented object
a = id id
a: 1
.a
eq 1, a
test "#1495, method call chaining", ->
str = 'abc'
result = str.split ''
.join ', '
eq 'a, b, c', result
result = str
.split ''
.join ', '
eq 'a, b, c', result
eq 'a, b, c', (str
.split ''
.join ', '
)
eq 'abc',
'aaabbbccc'.replace /(\w)\1\1/g, '$1$1'
.replace /([abc])\1/g, '$1'
# Nested calls
result = [1..3]
.slice Math.max 0, 1
.concat [3]
arrayEq [2, 3, 3], result
# Single line function arguments
result = [1..6]
.map (x) -> x * x
.filter (x) -> x % 2 is 0
.reverse()
arrayEq [36, 16, 4], result
# Single line implicit objects
id = (x) -> x
result = id a: 1
.a
eq 1, result
# The parens are forced
result = str.split(''.
split ''
.join ''
).join ', '
eq 'a, b, c', result
test "chaining should not wrap spilling ternary", ->
throws -> CoffeeScript.compile """
if 0 then 1 else g
a: 42
.h()
"""
test "chaining should wrap calls containing spilling ternary", ->
f = (x) -> h: x
id = (x) -> x
result = f if true then 42 else id
a: 2
.h
eq 42, result
test "chaining should work within spilling ternary", ->
f = (x) -> h: x
id = (x) -> x
result = f if false then 1 else id
a: 3
.a
eq 3, result.h
test "method call chaining inside objects", ->
f = (x) -> c: 42
result =
a: f 1
b: f a: 1
.c
eq 42, result.b
test "#4568: refine sameLine implicit object tagging", ->
condition = yes
fn = -> yes
x =
fn bar: {
foo: 123
} if not condition
eq x, undefined
# Nested blocks caused by paren unwrapping
test "#1492: Nested blocks don't cause double semicolons", ->
js = CoffeeScript.compile '(0;0)'
eq -1, js.indexOf ';;'
test "#1195 Ignore trailing semicolons (before newlines or as the last char in a program)", ->
preNewline = (numSemicolons) ->
"""
nonce = {}; nonce2 = {}
f = -> nonce#{Array(numSemicolons+1).join(';')}
nonce2
unless f() is nonce then throw new Error('; before linebreak should = newline')
"""
CoffeeScript.run(preNewline(n), bare: true) for n in [1,2,3]
lastChar = '-> lastChar;'
doesNotThrow -> CoffeeScript.compile lastChar, bare: true
test "#1299: Disallow token misnesting", ->
try
CoffeeScript.compile '''
[{
]}
'''
ok no
catch e
eq 'unmatched ]', e.message
test "#2981: Enforce initial indentation", ->
try
CoffeeScript.compile ' a\nb-'
ok no
catch e
eq 'missing indentation', e.message
test "'single-line' expression containing multiple lines", ->
doesNotThrow -> CoffeeScript.compile """
(a, b) -> if a
-a
else if b
then -b
else null
"""
test "#1275: allow indentation before closing brackets", ->
array = [
1
2
3
]
eq array, array
do ->
(
a = 1
)
eq 1, a
test "#3199: return multiline implicit object", ->
y = do ->
if no then return
type: 'a'
msg: 'b'
eq undefined, y
</script>
<script type="text/x-coffeescript" class="test" id="function_invocation">
# Function Invocation
# -------------------
# * Function Invocation
# * Splats in Function Invocations
# * Implicit Returns
# * Explicit Returns
# shared identity function
id = (_) -> if arguments.length is 1 then _ else [arguments...]
# helper to assert that a string should fail compilation
cantCompile = (code) ->
throws -> CoffeeScript.compile code
test "basic argument passing", ->
a = {}
b = {}
c = {}
eq 1, (id 1)
eq 2, (id 1, 2)[1]
eq a, (id a)
eq c, (id a, b, c)[2]
test "passing arguments on separate lines", ->
a = {}
b = {}
c = {}
ok(id(
a
b
c
)[1] is b)
eq(0, id(
0
10
)[0])
eq(a,id(
a
))
eq b,
(id b)
test "optional parens can be used in a nested fashion", ->
call = (func) -> func()
add = (a,b) -> a + b
result = call ->
inner = call ->
add 5, 5
ok result is 10
test "hanging commas and semicolons in argument list", ->
fn = () -> arguments.length
eq 2, fn(0,1,)
eq 3, fn 0, 1,
2
eq 2, fn(0, 1;)
# TODO: this test fails (the string compiles), but should it?
#throws -> CoffeeScript.compile "fn(0,1,;)"
throws -> CoffeeScript.compile "fn(0,1,;;)"
throws -> CoffeeScript.compile "fn(0, 1;,)"
throws -> CoffeeScript.compile "fn(,0)"
throws -> CoffeeScript.compile "fn(;0)"
test "function invocation", ->
func = ->
return if true
eq undefined, func()
result = ("hello".slice) 3
ok result is 'lo'
test "And even with strange things like this:", ->
funcs = [((x) -> x), ((x) -> x * x)]
result = funcs[1] 5
ok result is 25
test "More fun with optional parens.", ->
fn = (arg) -> arg
ok fn(fn {prop: 101}).prop is 101
okFunc = (f) -> ok(f())
okFunc -> true
test "chained function calls", ->
nonce = {}
identityWrap = (x) ->
-> x
eq nonce, identityWrap(identityWrap(nonce))()()
eq nonce, (identityWrap identityWrap nonce)()()
test "Multi-blocks with optional parens.", ->
fn = (arg) -> arg
result = fn( ->
fn ->
"Wrapped"
)
ok result()() is 'Wrapped'
test "method calls", ->
fnId = (fn) -> -> fn.apply this, arguments
math = {
add: (a, b) -> a + b
anonymousAdd: (a, b) -> a + b
fastAdd: fnId (a, b) -> a + b
}
ok math.add(5, 5) is 10
ok math.anonymousAdd(10, 10) is 20
ok math.fastAdd(20, 20) is 40
test "Ensure that functions can have a trailing comma in their argument list", ->
mult = (x, mids..., y) ->
x *= n for n in mids
x *= y
#ok mult(1, 2,) is 2
#ok mult(1, 2, 3,) is 6
ok mult(10, (i for i in [1..6])...) is 7200
test "`@` and `this` should both be able to invoke a method", ->
nonce = {}
fn = (arg) -> eq nonce, arg
fn.withAt = -> @ nonce
fn.withThis = -> this nonce
fn.withAt()
fn.withThis()
test "Trying an implicit object call with a trailing function.", ->
a = null
meth = (arg, obj, func) -> a = [obj.a, arg, func()].join ' '
meth 'apple', b: 1, a: 13, ->
'orange'
ok a is '13 apple orange'
test "Ensure that empty functions don't return mistaken values.", ->
obj =
func: (@param, @rest...) ->
ok obj.func(101, 102, 103, 104) is undefined
ok obj.param is 101
ok obj.rest.join(' ') is '102 103 104'
test "Passing multiple functions without paren-wrapping is legal, and should compile.", ->
sum = (one, two) -> one() + two()
result = sum ->
7 + 9
, ->
1 + 3
ok result is 20
test "Implicit call with a trailing if statement as a param.", ->
func = -> arguments[1]
result = func 'one', if false then 100 else 13
ok result is 13
test "Test more function passing:", ->
sum = (one, two) -> one() + two()
result = sum( ->
1 + 2
, ->
2 + 1
)
ok result is 6
sum = (a, b) -> a + b
result = sum(1
, 2)
ok result is 3
test "Chained blocks, with proper indentation levels:", ->
counter =
results: []
tick: (func) ->
@results.push func()
this
counter
.tick ->
3
.tick ->
2
.tick ->
1
arrayEq [3,2,1], counter.results
test "This is a crazy one.", ->
x = (obj, func) -> func obj
ident = (x) -> x
result = x {one: ident 1}, (obj) ->
inner = ident(obj)
ident inner
ok result.one is 1
test "More paren compilation tests:", ->
reverse = (obj) -> obj.reverse()
ok reverse([1, 2].concat 3).join(' ') is '3 2 1'
test "Test for inline functions with parentheses and implicit calls.", ->
combine = (func, num) -> func() * num
result = combine (-> 1 + 2), 3
ok result is 9
test "Test for calls/parens/multiline-chains.", ->
f = (x) -> x
result = (f 1).toString()
.length
ok result is 1
test "Test implicit calls in functions in parens:", ->
result = ((val) ->
[].push val
val
)(10)
ok result is 10
test "Ensure that chained calls with indented implicit object literals below are alright.", ->
result = null
obj =
method: (val) -> this
second: (hash) -> result = hash.three
obj
.method(
101
).second(
one:
two: 2
three: 3
)
eq result, 3
test "Test newline-supressed call chains with nested functions.", ->
obj =
call: -> this
func = ->
obj
.call ->
one two
.call ->
three four
101
eq func(), 101
test "Implicit objects with number arguments.", ->
func = (x, y) -> y
obj =
prop: func "a", 1
ok obj.prop is 1
test "Non-spaced unary and binary operators should cause a function call.", ->
func = (val) -> val + 1
ok (func +5) is 6
ok (func -5) is -4
test "Prefix unary assignment operators are allowed in parenless calls.", ->
func = (val) -> val + 1
val = 5
ok (func --val) is 5
test "#855: execution context for `func arr...` should be `null`", ->
contextTest = -> eq @, if window? then window else global
array = []
contextTest array
contextTest.apply null, array
contextTest array...
test "#904: Destructuring function arguments with same-named variables in scope", ->
a = b = nonce = {}
fn = ([a,b]) -> {a:a,b:b}
result = fn([c={},d={}])
eq c, result.a
eq d, result.b
eq nonce, a
eq nonce, b
test "Simple Destructuring function arguments with same-named variables in scope", ->
x = 1
f = ([x]) -> x
eq f([2]), 2
eq x, 1
test "caching base value", ->
obj =
index: 0
0: {method: -> this is obj[0]}
ok obj[obj.index++].method([]...)
test "passing splats to functions", ->
arrayEq [0..4], id id [0..4]...
fn = (a, b, c..., d) -> [a, b, c, d]
range = [0..3]
[first, second, others, last] = fn range..., 4, [5...8]...
eq 0, first
eq 1, second
arrayEq [2..6], others
eq 7, last
test "splat variables are local to the function", ->
outer = "x"
clobber = (avar, outer...) -> outer
clobber "foo", "bar"
eq "x", outer
test "Issue 894: Splatting against constructor-chained functions.", ->
x = null
class Foo
bar: (y) -> x = y
new Foo().bar([101]...)
eq x, 101
test "Functions with splats being called with too few arguments.", ->
pen = null
method = (first, variable..., penultimate, ultimate) ->
pen = penultimate
method 1, 2, 3, 4, 5, 6, 7, 8, 9
ok pen is 8
method 1, 2, 3
ok pen is 2
method 1, 2
ok pen is 2
test "splats with super() within classes.", ->
class Parent
meth: (args...) ->
args
class Child extends Parent
meth: ->
nums = [3, 2, 1]
super nums...
ok (new Child).meth().join(' ') is '3 2 1'
test "#1011: passing a splat to a method of a number", ->
eq '1011', 11.toString [2]...
eq '1011', (31).toString [3]...
eq '1011', 69.0.toString [4]...
eq '1011', (131.0).toString [5]...
test "splats and the `new` operator: functions that return `null` should construct their instance", ->
args = []
child = new (constructor = -> null) args...
ok child instanceof constructor
test "splats and the `new` operator: functions that return functions should construct their return value", ->
args = []
fn = ->
child = new (constructor = -> fn) args...
ok child not instanceof constructor
eq fn, child
test "implicit return", ->
eq ok, new ->
ok
### Should `return` implicitly ###
### even with trailing comments. ###
test "implicit returns with multiple branches", ->
nonce = {}
fn = ->
if false
for a in b
return c if d
else
nonce
eq nonce, fn()
test "implicit returns with switches", ->
nonce = {}
fn = ->
switch nonce
when nonce then nonce
else return undefined
eq nonce, fn()
test "preserve context when generating closure wrappers for expression conversions", ->
nonce = {}
obj =
property: nonce
method: ->
this.result = if false
10
else
"a"
"b"
this.property
eq nonce, obj.method()
eq nonce, obj.property
test "don't wrap 'pure' statements in a closure", ->
nonce = {}
items = [0, 1, 2, 3, nonce, 4, 5]
fn = (items) ->
for item in items
return item if item is nonce
eq nonce, fn items
test "usage of `new` is careful about where the invocation parens end up", ->
eq 'object', typeof new try Array
eq 'object', typeof new do -> ->
test "implicit call against control structures", ->
result = null
save = (obj) -> result = obj
save switch id false
when true
'true'
when false
'false'
eq result, 'false'
save if id false
'false'
else
'true'
eq result, 'true'
save unless id false
'true'
else
'false'
eq result, 'true'
save try
doesnt exist
catch error
'caught'
eq result, 'caught'
save try doesnt(exist) catch error then 'caught2'
eq result, 'caught2'
test "#1420: things like `(fn() ->)`; there are no words for this one", ->
fn = -> (f) -> f()
nonce = {}
eq nonce, (fn() -> nonce)
test "#1416: don't omit one 'new' when compiling 'new new'", ->
nonce = {}
obj = new new -> -> {prop: nonce}
eq obj.prop, nonce
test "#1416: don't omit one 'new' when compiling 'new new fn()()'", ->
nonce = {}
argNonceA = {}
argNonceB = {}
fn = (a) -> (b) -> {a, b, prop: nonce}
obj = new new fn(argNonceA)(argNonceB)
eq obj.prop, nonce
eq obj.a, argNonceA
eq obj.b, argNonceB
test "#1840: accessing the `prototype` after function invocation should compile", ->
doesNotThrow -> CoffeeScript.compile 'fn()::prop'
nonce = {}
class Test then id: nonce
dotAccess = -> Test::
protoAccess = -> Test
eq dotAccess().id, nonce
eq protoAccess()::id, nonce
test "#960: improved 'do'", ->
do (nonExistent = 'one') ->
eq nonExistent, 'one'
overridden = 1
do (overridden = 2) ->
eq overridden, 2
two = 2
do (one = 1, two, three = 3) ->
eq one, 1
eq two, 2
eq three, 3
ret = do func = (two) ->
eq two, 2
func
eq ret, func
test "#2617: implicit call before unrelated implicit object", ->
pass = ->
true
result = if pass 1
one: 1
eq result.one, 1
test "#2292, b: f (z),(x)", ->
f = (x, y) -> y
one = 1
two = 2
o = b: f (one),(two)
eq o.b, 2
test "#2297, Different behaviors on interpreting literal", ->
foo = (x, y) -> y
bar =
baz: foo 100, on
eq bar.baz, on
qux = (x) -> x
quux = qux
corge: foo 100, true
eq quux.corge, on
xyzzy =
e: 1
f: foo
a: 1
b: 2
,
one: 1
two: 2
three: 3
g:
a: 1
b: 2
c: foo 2,
one: 1
two: 2
three: 3
d: 3
four: 4
h: foo one: 1, two: 2, three: three: three: 3,
2
eq xyzzy.f.two, 2
eq xyzzy.g.c.three, 3
eq xyzzy.four, 4
eq xyzzy.h, 2
test "#2715, Chained implicit calls", ->
first = (x) -> x
second = (x, y) -> y
foo = first first
one: 1
eq foo.one, 1
bar = first second
one: 1, 2
eq bar, 2
baz = first second
one: 1,
2
eq baz, 2
test "Implicit calls and new", ->
first = (x) -> x
foo = (@x) ->
bar = first new foo first 1
eq bar.x, 1
third = (x, y, z) -> z
baz = first new foo new foo third
one: 1
two: 2
1
three: 3
2
eq baz.x.x.three, 3
test "Loose tokens inside of explicit call lists", ->
first = (x) -> x
second = (x, y) -> y
one = 1
foo = second( one
2)
eq foo, 2
bar = first( first
one: 1)
eq bar.one, 1
test "Non-callable literals shouldn't compile", ->
cantCompile '1(2)'
cantCompile '1 2'
cantCompile '/t/(2)'
cantCompile '/t/ 2'
cantCompile '///t///(2)'
cantCompile '///t/// 2'
cantCompile "''(2)"
cantCompile "'' 2"
cantCompile '""(2)'
cantCompile '"" 2'
cantCompile '""""""(2)'
cantCompile '"""""" 2'
cantCompile '{}(2)'
cantCompile '{} 2'
cantCompile '[](2)'
cantCompile '[] 2'
cantCompile '[2..9] 2'
cantCompile '[2..9](2)'
cantCompile '[1..10][2..9] 2'
cantCompile '[1..10][2..9](2)'
test 'implicit invocation with implicit object literal', ->
f = (obj) -> eq 1, obj.a
f
a: 1
obj =
if f
a: 2
else
a: 1
eq 2, obj.a
f
"a": 1
obj =
if f
"a": 2
else
"a": 1
eq 2, obj.a
# #3935: Implicit call when the first key of an implicit object has interpolation.
a = 'a'
f
"#{a}": 1
obj =
if f
"#{a}": 2
else
"#{a}": 1
eq 2, obj.a
</script>
<script type="text/x-coffeescript" class="test" id="functions">
# Function Literals
# -----------------
# TODO: add indexing and method invocation tests: (->)[0], (->).call()
# * Function Definition
# * Bound Function Definition
# * Parameter List Features
# * Splat Parameters
# * Context (@) Parameters
# * Parameter Destructuring
# * Default Parameters
# Function Definition
x = 1
y = {}
y.x = -> 3
ok x is 1
ok typeof(y.x) is 'function'
ok y.x instanceof Function
ok y.x() is 3
# The empty function should not cause a syntax error.
->
() ->
# Multiple nested function declarations mixed with implicit calls should not
# cause a syntax error.
(one) -> (two) -> three four, (five) -> six seven, eight, (nine) ->
# with multiple single-line functions on the same line.
func = (x) -> (x) -> (x) -> x
ok func(1)(2)(3) is 3
# Make incorrect indentation safe.
func = ->
obj = {
key: 10
}
obj.key - 5
eq func(), 5
# Ensure that functions with the same name don't clash with helper functions.
del = -> 5
ok del() is 5
# Bound Function Definition
obj =
bound: ->
(=> this)()
unbound: ->
(-> this)()
nested: ->
(=>
(=>
(=> this)()
)()
)()
eq obj, obj.bound()
ok obj isnt obj.unbound()
eq obj, obj.nested()
test "even more fancy bound functions", ->
obj =
one: ->
do =>
return this.two()
two: ->
do =>
do =>
do =>
return this.three
three: 3
eq obj.one(), 3
test "self-referencing functions", ->
changeMe = ->
changeMe = 2
changeMe()
eq changeMe, 2
test "#2009: don't touch `` `this` ``", ->
nonceA = {}
nonceB = {}
fn = null
(->
fn = => this is nonceA and `this` is nonceB
).call nonceA
ok fn.call nonceB
# Parameter List Features
test "splats", ->
arrayEq [0, 1, 2], (((splat...) -> splat) 0, 1, 2)
arrayEq [2, 3], (((_, _1, splat...) -> splat) 0, 1, 2, 3)
arrayEq [0, 1], (((splat..., _, _1) -> splat) 0, 1, 2, 3)
arrayEq [2], (((_, _1, splat..., _2) -> splat) 0, 1, 2, 3)
test "destructured splatted parameters", ->
arr = [0,1,2]
splatArray = ([a...]) -> a
splatArrayRest = ([a...],b...) -> arrayEq(a,b); b
arrayEq splatArray(arr), arr
arrayEq splatArrayRest(arr,0,1,2), arr
test "@-parameters: automatically assign an argument's value to a property of the context", ->
nonce = {}
((@prop) ->).call context = {}, nonce
eq nonce, context.prop
# allow splats along side the special argument
((splat..., @prop) ->).apply context = {}, [0, 0, nonce]
eq nonce, context.prop
# allow the argument itself to be a splat
((@prop...) ->).call context = {}, 0, nonce, 0
eq nonce, context.prop[1]
# the argument should not be able to be referenced normally
code = '((@prop) -> prop).call {}'
doesNotThrow -> CoffeeScript.compile code
throws (-> CoffeeScript.run code), ReferenceError
code = '((@prop) -> _at_prop).call {}'
doesNotThrow -> CoffeeScript.compile code
throws (-> CoffeeScript.run code), ReferenceError
test "@-parameters and splats with constructors", ->
a = {}
b = {}
class Klass
constructor: (@first, splat..., @last) ->
obj = new Klass a, 0, 0, b
eq a, obj.first
eq b, obj.last
test "destructuring in function definition", ->
(([{a: [b], c}]...) ->
eq 1, b
eq 2, c
) {a: [1], c: 2}
context = {}
(([{a: [b, c = 2], @d, e = 4}]...) ->
eq 1, b
eq 2, c
eq @d, 3
eq context.d, 3
eq e, 4
).call context, {a: [1], d: 3}
(({a: aa = 1, b: bb = 2}) ->
eq 5, aa
eq 2, bb
) {a: 5}
ajax = (url, {
async = true,
beforeSend = (->),
cache = true,
method = 'get',
data = {}
}) ->
{url, async, beforeSend, cache, method, data}
fn = ->
deepEqual ajax('/home', beforeSend: fn, cache: null, method: 'post'), {
url: '/home', async: true, beforeSend: fn, cache: true, method: 'post', data: {}
}
test "#4005: `([a = {}]..., b) ->` weirdness", ->
fn = ([a = {}]..., b) -> [a, b]
deepEqual fn(5), [{}, 5]
test "default values", ->
nonceA = {}
nonceB = {}
a = (_,_1,arg=nonceA) -> arg
eq nonceA, a()
eq nonceA, a(0)
eq nonceB, a(0,0,nonceB)
eq nonceA, a(0,0,undefined)
eq nonceA, a(0,0,null)
eq false , a(0,0,false)
eq nonceB, a(undefined,undefined,nonceB,undefined)
b = (_,arg=nonceA,_1,_2) -> arg
eq nonceA, b()
eq nonceA, b(0)
eq nonceB, b(0,nonceB)
eq nonceA, b(0,undefined)
eq nonceA, b(0,null)
eq false , b(0,false)
eq nonceB, b(undefined,nonceB,undefined)
c = (arg=nonceA,_,_1) -> arg
eq nonceA, c()
eq 0, c(0)
eq nonceB, c(nonceB)
eq nonceA, c(undefined)
eq nonceA, c(null)
eq false , c(false)
eq nonceB, c(nonceB,undefined,undefined)
test "default values with @-parameters", ->
a = {}
b = {}
obj = f: (q = a, @p = b) -> q
eq a, obj.f()
eq b, obj.p
test "default values with splatted arguments", ->
withSplats = (a = 2, b..., c = 3, d = 5) -> a * (b.length + 1) * c * d
eq 30, withSplats()
eq 15, withSplats(1)
eq 5, withSplats(1,1)
eq 1, withSplats(1,1,1)
eq 2, withSplats(1,1,1,1)
test "#156: parameter lists with expansion", ->
expandArguments = (first, ..., lastButOne, last) ->
eq 1, first
eq 4, lastButOne
last
eq 5, expandArguments 1, 2, 3, 4, 5
throws (-> CoffeeScript.compile "(..., a, b...) ->"), null, "prohibit expansion and a splat"
throws (-> CoffeeScript.compile "(...) ->"), null, "prohibit lone expansion"
test "#156: parameter lists with expansion in array destructuring", ->
expandArray = (..., [..., last]) ->
last
eq 3, expandArray 1, 2, 3, [1, 2, 3]
test "#3502: variable definitions and expansion", ->
a = b = 0
f = (a, ..., b) -> [a, b]
arrayEq [1, 5], f 1, 2, 3, 4, 5
eq 0, a
eq 0, b
test "variable definitions and splat", ->
a = b = 0
f = (a, middle..., b) -> [a, middle, b]
arrayEq [1, [2, 3, 4], 5], f 1, 2, 3, 4, 5
eq 0, a
eq 0, b
test "default values with function calls", ->
doesNotThrow -> CoffeeScript.compile "(x = f()) ->"
test "arguments vs parameters", ->
doesNotThrow -> CoffeeScript.compile "f(x) ->"
f = (g) -> g()
eq 5, f (x) -> 5
test "reserved keyword as parameters", ->
f = (_case, @case) -> [_case, @case]
[a, b] = f(1, 2)
eq 1, a
eq 2, b
f = (@case, _case...) -> [@case, _case...]
[a, b, c] = f(1, 2, 3)
eq 1, a
eq 2, b
eq 3, c
test "reserved keyword at-splat", ->
f = (@case...) -> @case
[a, b] = f(1, 2)
eq 1, a
eq 2, b
test "#1574: Destructuring and a parameter named _arg", ->
f = ({a, b}, _arg, _arg1) -> [a, b, _arg, _arg1]
arrayEq [1, 2, 3, 4], f a: 1, b: 2, 3, 4
test "#1844: bound functions in nested comprehensions causing empty var statements", ->
a = ((=>) for a in [0] for b in [0])
eq 1, a.length
test "#1859: inline function bodies shouldn't modify prior postfix ifs", ->
list = [1, 2, 3]
ok true if list.some (x) -> x is 2
test "#2258: allow whitespace-style parameter lists in function definitions", ->
func = (
a, b, c
) -> c
eq func(1, 2, 3), 3
func = (
a
b
c
) -> b
eq func(1, 2, 3), 2
test "#2621: fancy destructuring in parameter lists", ->
func = ({ prop1: { key1 }, prop2: { key2, key3: [a, b, c] } }) ->
eq(key2, 'key2')
eq(a, 'a')
func({prop1: {key1: 'key1'}, prop2: {key2: 'key2', key3: ['a', 'b', 'c']}})
test "#1435 Indented property access", ->
rec = -> rec: rec
eq 1, do ->
rec()
.rec ->
rec()
.rec ->
rec.rec()
.rec()
1
test "#1038 Optimize trailing return statements", ->
compile = (code) -> CoffeeScript.compile(code, bare: yes).trim().replace(/\s+/g, " ")
eq "(function() {});", compile("->")
eq "(function() {});", compile("-> return")
eq "(function() { return void 0; });", compile("-> undefined")
eq "(function() { return void 0; });", compile("-> return undefined")
eq "(function() { foo(); });", compile("""
->
foo()
return
""")
</script>
<script type="text/x-coffeescript" class="test" id="generators">
# Generators
# -----------------
#
# * Generator Definition
test "most basic generator support", ->
ok -> yield
test "empty generator", ->
x = do -> yield return
y = x.next()
ok y.value is undefined and y.done is true
test "generator iteration", ->
x = do ->
yield 0
yield
yield 2
3
y = x.next()
ok y.value is 0 and y.done is false
y = x.next()
ok y.value is undefined and y.done is false
y = x.next()
ok y.value is 2 and y.done is false
y = x.next()
ok y.value is 3 and y.done is true
test "last line yields are returned", ->
x = do ->
yield 3
y = x.next()
ok y.value is 3 and y.done is false
y = x.next 42
ok y.value is 42 and y.done is true
test "yield return can be used anywhere in the function body", ->
x = do ->
if 2 is yield 1
yield return 42
throw new Error "this code shouldn't be reachable"
y = x.next()
ok y.value is 1 and y.done is false
y = x.next 2
ok y.value is 42 and y.done is true
test "bound generator", ->
obj =
bound: ->
do =>
yield this
unbound: ->
do ->
yield this
nested: ->
do =>
yield do =>
yield do =>
yield this
eq obj, obj.bound().next().value
ok obj isnt obj.unbound().next().value
eq obj, obj.nested().next().value.next().value.next().value
test "`yield from` support", ->
x = do ->
yield from do ->
yield i for i in [3..4]
y = x.next()
ok y.value is 3 and y.done is false
y = x.next 1
ok y.value is 4 and y.done is false
y = x.next 2
arrayEq y.value, [1, 2]
ok y.done is true
test "error if `yield from` occurs outside of a function", ->
throws -> CoffeeScript.compile 'yield from 1'
test "`yield from` at the end of a function errors", ->
throws -> CoffeeScript.compile 'x = -> x = 1; yield from'
test "yield in if statements", ->
x = do -> if 1 is yield 2 then 3 else 4
y = x.next()
ok y.value is 2 and y.done is false
y = x.next 1
ok y.value is 3 and y.done is true
test "yielding if statements", ->
x = do -> yield if true then 3 else 4
y = x.next()
ok y.value is 3 and y.done is false
y = x.next 42
ok y.value is 42 and y.done is true
test "yield in for loop expressions", ->
x = do ->
y = for i in [1..3]
yield i * 2
z = x.next()
ok z.value is 2 and z.done is false
z = x.next 10
ok z.value is 4 and z.done is false
z = x.next 20
ok z.value is 6 and z.done is false
z = x.next 30
arrayEq z.value, [10, 20, 30]
ok z.done is true
test "yield in switch expressions", ->
x = do ->
y = switch yield 1
when 2 then yield 1337
else 1336
z = x.next()
ok z.value is 1 and z.done is false
z = x.next 2
ok z.value is 1337 and z.done is false
z = x.next 3
ok z.value is 3 and z.done is true
test "yielding switch expressions", ->
x = do ->
yield switch 1337
when 1337 then 1338
else 1336
y = x.next()
ok y.value is 1338 and y.done is false
y = x.next 42
ok y.value is 42 and y.done is true
test "yield in try expressions", ->
x = do ->
try yield 1 catch
y = x.next()
ok y.value is 1 and y.done is false
y = x.next 42
ok y.value is 42 and y.done is true
test "yielding try expressions", ->
x = do ->
yield try 1
y = x.next()
ok y.value is 1 and y.done is false
y = x.next 42
ok y.value is 42 and y.done is true
test "`yield` can be thrown", ->
x = do ->
throw yield null
x.next()
throws -> x.next new Error "boom"
test "`throw` can be yielded", ->
x = do ->
yield throw new Error "boom"
throws -> x.next()
test "symbolic operators has precedence over the `yield`", ->
symbolic = '+ - * / << >> & | || && ** ^ // or and'.split ' '
compound = ("#{op}=" for op in symbolic)
relations = '< > == != <= >= is isnt'.split ' '
operators = [symbolic..., '=', compound..., relations...]
collect = (gen) -> ref.value until (ref = gen.next()).done
values = [0, 1, 2, 3]
for op in operators
expression = "i #{op} 2"
yielded = CoffeeScript.eval "(arr) -> yield #{expression} for i in arr"
mapped = CoffeeScript.eval "(arr) -> (#{expression} for i in arr)"
arrayEq mapped(values), collect yielded values
test "yield handles 'this' correctly", ->
x = ->
yield switch
when true then yield => this
array = for item in [1]
yield => this
yield array
yield if true then yield => this
yield try throw yield => this
throw yield => this
y = x.call [1, 2, 3]
z = y.next()
arrayEq z.value(), [1, 2, 3]
ok z.done is false
z = y.next 123
ok z.value is 123 and z.done is false
z = y.next()
arrayEq z.value(), [1, 2, 3]
ok z.done is false
z = y.next 42
arrayEq z.value, [42]
ok z.done is false
z = y.next()
arrayEq z.value(), [1, 2, 3]
ok z.done is false
z = y.next 456
ok z.value is 456 and z.done is false
z = y.next()
arrayEq z.value(), [1, 2, 3]
ok z.done is false
z = y.next new Error "ignore me"
ok z.value is undefined and z.done is false
z = y.next()
arrayEq z.value(), [1, 2, 3]
ok z.done is false
throws -> y.next new Error "boom"
test "for-from loops over generators", ->
array1 = [50, 30, 70, 20]
gen = -> yield from array1
array2 = []
array3 = []
array4 = []
iterator = gen()
for x from iterator
array2.push(x)
break if x is 30
for x from iterator
array3.push(x)
for x from iterator
array4.push(x)
arrayEq array2, [50, 30]
# Different JS engines have different opinions on the value of array3:
# https://github.com/jashkenas/coffeescript/pull/4306#issuecomment-257066877
# As a temporary measure, either result is accepted.
ok array3.length is 0 or array3.join(',') is '70,20'
arrayEq array4, []
test "for-from comprehensions over generators", ->
gen = ->
yield from [30, 41, 51, 60]
iterator = gen()
array1 = (x for x from iterator when x %% 2 is 1)
array2 = (x for x from iterator)
ok array1.join(' ') is '41 51'
ok array2.length is 0
test "from as an iterable variable name in a for loop declaration", ->
from = [1, 2, 3]
out = []
for i from from
out.push i
arrayEq from, out
test "from as an iterator variable name in a for loop declaration", ->
a = [1, 2, 3]
b = []
for from from a
b.push from
arrayEq a, b
test "from as a destructured object variable name in a for loop declaration", ->
a = [
from: 1
to: 2
,
from: 3
to: 4
]
b = []
for {from, to} in a
b.push from
arrayEq b, [1, 3]
c = []
for {to, from} in a
c.push from
arrayEq c, [1, 3]
test "from as a destructured, aliased object variable name in a for loop declaration", ->
a = [
b: 1
c: 2
,
b: 3
c: 4
]
out = []
for {b: from} in a
out.push from
arrayEq out, [1, 3]
test "from as a destructured array variable name in a for loop declaration", ->
a = [
[1, 2]
[3, 4]
]
b = []
for [from, to] from a
b.push from
arrayEq b, [1, 3]
</script>
<script type="text/x-coffeescript" class="test" id="helpers">
# Helpers
# -------
# pull the helpers from `CoffeeScript.helpers` into local variables
{starts, ends, repeat, compact, count, merge, extend, flatten, del, baseFileName} = CoffeeScript.helpers
# `starts`
test "the `starts` helper tests if a string starts with another string", ->
ok starts('01234', '012')
ok not starts('01234', '123')
test "the `starts` helper can take an optional offset", ->
ok starts('01234', '34', 3)
ok not starts('01234', '01', 1)
# `ends`
test "the `ends` helper tests if a string ends with another string", ->
ok ends('01234', '234')
ok not ends('01234', '012')
test "the `ends` helper can take an optional offset", ->
ok ends('01234', '012', 2)
ok not ends('01234', '234', 6)
# `repeat`
test "the `repeat` helper concatenates a given number of times", ->
eq 'asdasdasd', repeat('asd', 3)
test "`repeat`ing a string 0 times always returns the empty string", ->
eq '', repeat('whatever', 0)
# `compact`
test "the `compact` helper removes falsey values from an array, preserves truthy ones", ->
allValues = [1, 0, false, obj={}, [], '', ' ', -1, null, undefined, true]
truthyValues = [1, obj, [], ' ', -1, true]
arrayEq truthyValues, compact(allValues)
# `count`
test "the `count` helper counts the number of occurrences of a string in another string", ->
eq 1/0, count('abc', '')
eq 0, count('abc', 'z')
eq 1, count('abc', 'a')
eq 1, count('abc', 'b')
eq 2, count('abcdc', 'c')
eq 2, count('abcdabcd','abc')
# `merge`
test "the `merge` helper makes a new object with all properties of the objects given as its arguments", ->
ary = [0, 1, 2, 3, 4]
obj = {}
merged = merge obj, ary
ok merged isnt obj
ok merged isnt ary
for own key, val of ary
eq val, merged[key]
# `extend`
test "the `extend` helper performs a shallow copy", ->
ary = [0, 1, 2, 3]
obj = {}
# should return the object being extended
eq obj, extend(obj, ary)
# should copy the other object's properties as well (obviously)
eq 2, obj[2]
# `flatten`
test "the `flatten` helper flattens an array", ->
success = yes
(success and= typeof n is 'number') for n in flatten [0, [[[1]], 2], 3, [4]]
ok success
# `del`
test "the `del` helper deletes a property from an object and returns the deleted value", ->
obj = [0, 1, 2]
eq 1, del(obj, 1)
ok 1 not of obj
# `baseFileName`
test "the `baseFileName` helper returns the file name to write to", ->
ext = '.js'
sourceToCompiled =
'.coffee': ext
'a.coffee': 'a' + ext
'b.coffee': 'b' + ext
'coffee.coffee': 'coffee' + ext
'.litcoffee': ext
'a.litcoffee': 'a' + ext
'b.litcoffee': 'b' + ext
'coffee.litcoffee': 'coffee' + ext
'.lit': ext
'a.lit': 'a' + ext
'b.lit': 'b' + ext
'coffee.lit': 'coffee' + ext
'.coffee.md': ext
'a.coffee.md': 'a' + ext
'b.coffee.md': 'b' + ext
'coffee.coffee.md': 'coffee' + ext
for sourceFileName, expectedFileName of sourceToCompiled
name = baseFileName sourceFileName, yes
filename = name + ext
eq filename, expectedFileName
</script>
<script type="text/x-coffeescript" class="test" id="importing">
# Importing
# ---------
unless window? or testingBrowser?
test "coffeescript modules can be imported and executed", ->
magicKey = __filename
magicValue = 0xFFFF
if global[magicKey]?
if exports?
local = magicValue
exports.method = -> local
else
global[magicKey] = {}
if require?.extensions?
ok require(__filename).method() is magicValue
delete global[magicKey]
test "javascript modules can be imported", ->
magicVal = 1
for module in 'import.js import2 .import2 import.extension.js import.unknownextension .coffee .coffee.md'.split ' '
ok require("./importing/#{module}").value?() is magicVal, module
test "coffeescript modules can be imported", ->
magicVal = 2
for module in '.import.coffee import.coffee import.extension.coffee'.split ' '
ok require("./importing/#{module}").value?() is magicVal, module
test "literate coffeescript modules can be imported", ->
magicVal = 3
# Leading space intentional to check for index.coffee.md
for module in ' .import.coffee.md import.coffee.md import.litcoffee import.extension.coffee.md'.split ' '
ok require("./importing/#{module}").value?() is magicVal, module
</script>
<script type="text/x-coffeescript" class="test" id="interpolation">
# Interpolation
# -------------
# * String Interpolation
# * Regular Expression Interpolation
# String Interpolation
# TODO: refactor string interpolation tests
eq 'multiline nested "interpolations" work', """multiline #{
"nested #{
ok true
"\"interpolations\""
}"
} work"""
# Issue #923: Tricky interpolation.
eq "#{ "{" }", "{"
eq "#{ '#{}}' } }", '#{}} }'
eq "#{"'#{ ({a: "b#{1}"}['a']) }'"}", "'b1'"
# Issue #1150: String interpolation regression
eq "#{'"/'}", '"/'
eq "#{"/'"}", "/'"
eq "#{/'"/}", '/\'"/'
eq "#{"'/" + '/"' + /"'/}", '\'//"/"\'/'
eq "#{"'/"}#{'/"'}#{/"'/}", '\'//"/"\'/'
eq "#{6 / 2}", '3'
eq "#{6 / 2}#{6 / 2}", '33' # parsed as division
eq "#{6 + /2}#{6/ + 2}", '6/2}#{6/2' # parsed as a regex
eq "#{6/2}
#{6/2}", '3 3' # newline cannot be part of a regex, so it's division
eq "#{/// "'/'"/" ///}", '/"\'\\/\'"\\/"/' # heregex, stuffed with spicy characters
eq "#{/\\'/}", "/\\\\'/"
# Issue #2321: Regex/division conflict in interpolation
eq "#{4/2}/", '2/'
curWidth = 4
eq "<i style='left:#{ curWidth/2 }%;'></i>", "<i style='left:2%;'></i>"
throws -> CoffeeScript.compile '''
"<i style='left:#{ curWidth /2 }%;'></i>"'''
# valid regex--^^^^^^^^^^^ ^--unclosed string
eq "<i style='left:#{ curWidth/2 }%;'></i>", "<i style='left:2%;'></i>"
eq "<i style='left:#{ curWidth/ 2 }%;'></i>", "<i style='left:2%;'></i>"
eq "<i style='left:#{ curWidth / 2 }%;'></i>", "<i style='left:2%;'></i>"
hello = 'Hello'
world = 'World'
ok '#{hello} #{world}!' is '#{hello} #{world}!'
ok "#{hello} #{world}!" is 'Hello World!'
ok "[#{hello}#{world}]" is '[HelloWorld]'
ok "#{hello}##{world}" is 'Hello#World'
ok "Hello #{ 1 + 2 } World" is 'Hello 3 World'
ok "#{hello} #{ 1 + 2 } #{world}" is "Hello 3 World"
ok 1 + "#{2}px" is '12px'
ok isNaN "a#{2}" * 2
ok "#{2}" is '2'
ok "#{2}#{2}" is '22'
[s, t, r, i, n, g] = ['s', 't', 'r', 'i', 'n', 'g']
ok "#{s}#{t}#{r}#{i}#{n}#{g}" is 'string'
ok "\#{s}\#{t}\#{r}\#{i}\#{n}\#{g}" is '#{s}#{t}#{r}#{i}#{n}#{g}'
ok "\#{string}" is '#{string}'
ok "\#{Escaping} first" is '#{Escaping} first'
ok "Escaping \#{in} middle" is 'Escaping #{in} middle'
ok "Escaping \#{last}" is 'Escaping #{last}'
ok "##" is '##'
ok "#{}" is ''
ok "#{}A#{} #{} #{}B#{}" is 'A B'
ok "\\\#{}" is '\\#{}'
ok "I won ##{20} last night." is 'I won #20 last night.'
ok "I won ##{'#20'} last night." is 'I won ##20 last night.'
ok "#{hello + world}" is 'HelloWorld'
ok "#{hello + ' ' + world + '!'}" is 'Hello World!'
list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
ok "values: #{list.join(', ')}, length: #{list.length}." is 'values: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, length: 10.'
ok "values: #{list.join ' '}" is 'values: 0 1 2 3 4 5 6 7 8 9'
obj = {
name: 'Joe'
hi: -> "Hello #{@name}."
cya: -> "Hello #{@name}.".replace('Hello','Goodbye')
}
ok obj.hi() is "Hello Joe."
ok obj.cya() is "Goodbye Joe."
ok "With #{"quotes"}" is 'With quotes'
ok 'With #{"quotes"}' is 'With #{"quotes"}'
ok "Where is #{obj["name"] + '?'}" is 'Where is Joe?'
ok "Where is #{"the nested #{obj["name"]}"}?" is 'Where is the nested Joe?'
ok "Hello #{world ? "#{hello}"}" is 'Hello World'
ok "Hello #{"#{"#{obj["name"]}" + '!'}"}" is 'Hello Joe!'
a = """
Hello #{ "Joe" }
"""
ok a is "Hello Joe"
a = 1
b = 2
c = 3
ok "#{a}#{b}#{c}" is '123'
result = null
stash = (str) -> result = str
stash "a #{ ('aa').replace /a/g, 'b' } c"
ok result is 'a bb c'
foo = "hello"
ok "#{foo.replace("\"", "")}" is 'hello'
val = 10
a = """
basic heredoc #{val}
on two lines
"""
b = '''
basic heredoc #{val}
on two lines
'''
ok a is "basic heredoc 10\non two lines"
ok b is "basic heredoc \#{val}\non two lines"
eq 'multiline nested "interpolations" work', """multiline #{
"nested #{(->
ok yes
"\"interpolations\""
)()}"
} work"""
eq 'function(){}', "#{->}".replace /\s/g, ''
ok /^a[\s\S]+b$/.test "a#{=>}b"
ok /^a[\s\S]+b$/.test "a#{ (x) -> x ** 2 }b"
# Regular Expression Interpolation
# TODO: improve heregex interpolation tests
test "heregex interpolation", ->
eq /\\#{}\\"/ + '', ///
#{
"#{ '\\' }" # normal comment
}
# regex comment
\#{}
\\ "
/// + ''
</script>
<script type="text/x-coffeescript" class="test" id="javascript_literals">
# JavaScript Literals
# -------------------
test "inline JavaScript is evaluated", ->
eq '\\`', `
// Inline JS
"\\\\\`"
`
test "escaped backticks are output correctly", ->
`var a = \`2 + 2 = ${4}\``
eq a, '2 + 2 = 4'
test "backslashes before a newline dont break JavaScript blocks", ->
`var a = \`To be, or not\\
to be.\``
eq a, '''
To be, or not\\
to be.'''
test "block inline JavaScript is evaluated", ->
```
var a = 1;
var b = 2;
```
c = 3
```var d = 4;```
eq a + b + c + d, 10
test "block inline JavaScript containing backticks", ->
```
// This is a comment with `backticks`
var a = 42;
var b = `foo ${'bar'}`;
var c = 3;
var d = 'foo`bar`';
```
eq a + c, 45
eq b, 'foo bar'
eq d, 'foo`bar`'
test "block JavaScript can end with an escaped backtick character", ->
```var a = \`hello\````
```
var b = \`world${'!'}\````
eq a, 'hello'
eq b, 'world!'
test "JavaScript block only escapes backslashes followed by backticks", ->
eq `'\\\n'`, '\\\n'
test "escaped JavaScript blocks speed round", ->
# The following has escaped backslashes because theyre required in strings, but the intent is this:
# `hello` hello;
# `\`hello\`` `hello`;
# `\`Escaping backticks in JS: \\\`hello\\\`\`` `Escaping backticks in JS: \`hello\``;
# `Single backslash: \ ` Single backslash: \ ;
# `Double backslash: \\ ` Double backslash: \\ ;
# `Single backslash at EOS: \\` Single backslash at EOS: \;
# `Double backslash at EOS: \\\\` Double backslash at EOS: \\;
for [input, output] in [
['`hello`', 'hello;']
['`\\`hello\\``', '`hello`;']
['`\\`Escaping backticks in JS: \\\\\\`hello\\\\\\`\\``', '`Escaping backticks in JS: \\`hello\\``;']
['`Single backslash: \\ `', 'Single backslash: \\ ;']
['`Double backslash: \\\\ `', 'Double backslash: \\\\ ;']
['`Single backslash at EOS: \\\\`', 'Single backslash at EOS: \\;']
['`Double backslash at EOS: \\\\\\\\`', 'Double backslash at EOS: \\\\;']
]
eq CoffeeScript.compile(input, bare: yes), "#{output}\n\n"
</script>
<script type="text/x-literate-coffeescript" class="test" id="literate">
Literate CoffeeScript Test
--------------------------
comment comment
test "basic literate CoffeeScript parsing", ->
ok yes
now with a...
test "broken up indentation", ->
... broken up ...
do ->
... nested block.
ok yes
Code must be separated from text by a blank line.
test "code blocks must be preceded by a blank line", ->
The next line is part of the text and will not be executed.
fail()
ok yes
Code in `backticks is not parsed` and...
test "comments in indented blocks work", ->
do ->
do ->
# Regular comment.
###
Block comment.
###
ok yes
Regular [Markdown](http://example.com/markdown) features, like links
and unordered lists, are fine:
* I
* Am
* A
* List
Tabs work too:
test "tabbed code", ->
ok yes
</script>
<script type="text/x-coffeescript" class="test" id="location">
testScript = '''
if true
x = 6
console.log "A console #{x + 7} log"
foo = "bar"
z = /// ^ (a#{foo}) ///
x = () ->
try
console.log "foo"
catch err
# Rewriter will generate explicit indentation here.
return null
'''
test "Verify location of generated tokens", ->
tokens = CoffeeScript.tokens "a = 79"
eq tokens.length, 4
[aToken, equalsToken, numberToken] = tokens
eq aToken[2].first_line, 0
eq aToken[2].first_column, 0
eq aToken[2].last_line, 0
eq aToken[2].last_column, 0
eq equalsToken[2].first_line, 0
eq equalsToken[2].first_column, 2
eq equalsToken[2].last_line, 0
eq equalsToken[2].last_column, 2
eq numberToken[2].first_line, 0
eq numberToken[2].first_column, 4
eq numberToken[2].last_line, 0
eq numberToken[2].last_column, 5
test "Verify location of generated tokens (with indented first line)", ->
tokens = CoffeeScript.tokens " a = 83"
eq tokens.length, 4
[aToken, equalsToken, numberToken] = tokens
eq aToken[2].first_line, 0
eq aToken[2].first_column, 2
eq aToken[2].last_line, 0
eq aToken[2].last_column, 2
eq equalsToken[2].first_line, 0
eq equalsToken[2].first_column, 4
eq equalsToken[2].last_line, 0
eq equalsToken[2].last_column, 4
eq numberToken[2].first_line, 0
eq numberToken[2].first_column, 6
eq numberToken[2].last_line, 0
eq numberToken[2].last_column, 7
getMatchingTokens = (str, wantedTokens...) ->
tokens = CoffeeScript.tokens str
matchingTokens = []
i = 0
for token in tokens
if token[1].replace(/^'|'$/g, '"') is wantedTokens[i]
i++
matchingTokens.push token
eq wantedTokens.length, matchingTokens.length
matchingTokens
test 'Verify locations in string interpolation (in "string")', ->
[a, b, c] = getMatchingTokens '"a#{b}c"', '"a"', 'b', '"c"'
eq a[2].first_line, 0
eq a[2].first_column, 0
eq a[2].last_line, 0
eq a[2].last_column, 1
eq b[2].first_line, 0
eq b[2].first_column, 4
eq b[2].last_line, 0
eq b[2].last_column, 4
eq c[2].first_line, 0
eq c[2].first_column, 6
eq c[2].last_line, 0
eq c[2].last_column, 7
test 'Verify locations in string interpolation (in "string", multiple interpolation)', ->
[a, b, c] = getMatchingTokens '"#{a}b#{c}"', 'a', '"b"', 'c'
eq a[2].first_line, 0
eq a[2].first_column, 3
eq a[2].last_line, 0
eq a[2].last_column, 3
eq b[2].first_line, 0
eq b[2].first_column, 5
eq b[2].last_line, 0
eq b[2].last_column, 5
eq c[2].first_line, 0
eq c[2].first_column, 8
eq c[2].last_line, 0
eq c[2].last_column, 8
test 'Verify locations in string interpolation (in "string", multiple interpolation and line breaks)', ->
[a, b, c] = getMatchingTokens '"#{a}\nb\n#{c}"', 'a', '" b "', 'c'
eq a[2].first_line, 0
eq a[2].first_column, 3
eq a[2].last_line, 0
eq a[2].last_column, 3
eq b[2].first_line, 0
eq b[2].first_column, 5
eq b[2].last_line, 1
eq b[2].last_column, 1
eq c[2].first_line, 2
eq c[2].first_column, 2
eq c[2].last_line, 2
eq c[2].last_column, 2
test 'Verify locations in string interpolation (in "string", multiple interpolation and starting with line breaks)', ->
[a, b, c] = getMatchingTokens '"\n#{a}\nb\n#{c}"', 'a', '" b "', 'c'
eq a[2].first_line, 1
eq a[2].first_column, 2
eq a[2].last_line, 1
eq a[2].last_column, 2
eq b[2].first_line, 1
eq b[2].first_column, 4
eq b[2].last_line, 2
eq b[2].last_column, 1
eq c[2].first_line, 3
eq c[2].first_column, 2
eq c[2].last_line, 3
eq c[2].last_column, 2
test 'Verify locations in string interpolation (in "string", multiple interpolation and starting with line breaks)', ->
[a, b, c] = getMatchingTokens '"\n\n#{a}\n\nb\n\n#{c}"', 'a', '" b "', 'c'
eq a[2].first_line, 2
eq a[2].first_column, 2
eq a[2].last_line, 2
eq a[2].last_column, 2
eq b[2].first_line, 2
eq b[2].first_column, 4
eq b[2].last_line, 5
eq b[2].last_column, 0
eq c[2].first_line, 6
eq c[2].first_column, 2
eq c[2].last_line, 6
eq c[2].last_column, 2
test 'Verify locations in string interpolation (in "string", multiple interpolation and starting with line breaks)', ->
[a, b, c] = getMatchingTokens '"\n\n\n#{a}\n\n\nb\n\n\n#{c}"', 'a', '" b "', 'c'
eq a[2].first_line, 3
eq a[2].first_column, 2
eq a[2].last_line, 3
eq a[2].last_column, 2
eq b[2].first_line, 3
eq b[2].first_column, 4
eq b[2].last_line, 8
eq b[2].last_column, 0
eq c[2].first_line, 9
eq c[2].first_column, 2
eq c[2].last_line, 9
eq c[2].last_column, 2
test 'Verify locations in string interpolation (in """string""", line breaks)', ->
[a, b, c] = getMatchingTokens '"""a\n#{b}\nc"""', '"a\\n"', 'b', '"\\nc"'
eq a[2].first_line, 0
eq a[2].first_column, 0
eq a[2].last_line, 0
eq a[2].last_column, 4
eq b[2].first_line, 1
eq b[2].first_column, 2
eq b[2].last_line, 1
eq b[2].last_column, 2
eq c[2].first_line, 1
eq c[2].first_column, 4
eq c[2].last_line, 2
eq c[2].last_column, 3
test 'Verify locations in string interpolation (in """string""", starting with a line break)', ->
[b, c] = getMatchingTokens '"""\n#{b}\nc"""', 'b', '"\\nc"'
eq b[2].first_line, 1
eq b[2].first_column, 2
eq b[2].last_line, 1
eq b[2].last_column, 2
eq c[2].first_line, 1
eq c[2].first_column, 4
eq c[2].last_line, 2
eq c[2].last_column, 3
test 'Verify locations in string interpolation (in """string""", starting with line breaks)', ->
[a, b, c] = getMatchingTokens '"""\n\n#{b}\nc"""', '"\\n"', 'b', '"\\nc"'
eq a[2].first_line, 0
eq a[2].first_column, 0
eq a[2].last_line, 1
eq a[2].last_column, 0
eq b[2].first_line, 2
eq b[2].first_column, 2
eq b[2].last_line, 2
eq b[2].last_column, 2
eq c[2].first_line, 2
eq c[2].first_column, 4
eq c[2].last_line, 3
eq c[2].last_column, 3
test 'Verify locations in string interpolation (in """string""", multiple interpolation)', ->
[a, b, c] = getMatchingTokens '"""#{a}\nb\n#{c}"""', 'a', '"\\nb\\n"', 'c'
eq a[2].first_line, 0
eq a[2].first_column, 5
eq a[2].last_line, 0
eq a[2].last_column, 5
eq b[2].first_line, 0
eq b[2].first_column, 7
eq b[2].last_line, 1
eq b[2].last_column, 1
eq c[2].first_line, 2
eq c[2].first_column, 2
eq c[2].last_line, 2
eq c[2].last_column, 2
test 'Verify locations in string interpolation (in """string""", multiple interpolation, and starting with line breaks)', ->
[a, b, c] = getMatchingTokens '"""\n\n#{a}\n\nb\n\n#{c}"""', 'a', '"\\n\\nb\\n\\n"', 'c'
eq a[2].first_line, 2
eq a[2].first_column, 2
eq a[2].last_line, 2
eq a[2].last_column, 2
eq b[2].first_line, 2
eq b[2].first_column, 4
eq b[2].last_line, 5
eq b[2].last_column, 0
eq c[2].first_line, 6
eq c[2].first_column, 2
eq c[2].last_line, 6
eq c[2].last_column, 2
test 'Verify locations in string interpolation (in """string""", multiple interpolation, and starting with line breaks)', ->
[a, b, c] = getMatchingTokens '"""\n\n\n#{a}\n\n\nb\n\n\n#{c}"""', 'a', '"\\n\\n\\nb\\n\\n\\n"', 'c'
eq a[2].first_line, 3
eq a[2].first_column, 2
eq a[2].last_line, 3
eq a[2].last_column, 2
eq b[2].first_line, 3
eq b[2].first_column, 4
eq b[2].last_line, 8
eq b[2].last_column, 0
eq c[2].first_line, 9
eq c[2].first_column, 2
eq c[2].last_line, 9
eq c[2].last_column, 2
test 'Verify locations in heregex interpolation (in ///regex///, multiple interpolation)', ->
[a, b, c] = getMatchingTokens '///#{a}b#{c}///', 'a', '"b"', 'c'
eq a[2].first_line, 0
eq a[2].first_column, 5
eq a[2].last_line, 0
eq a[2].last_column, 5
eq b[2].first_line, 0
eq b[2].first_column, 7
eq b[2].last_line, 0
eq b[2].last_column, 7
eq c[2].first_line, 0
eq c[2].first_column, 10
eq c[2].last_line, 0
eq c[2].last_column, 10
test 'Verify locations in heregex interpolation (in ///regex///, multiple interpolation)', ->
[a, b, c] = getMatchingTokens '///a#{b}c///', '"a"', 'b', '"c"'
eq a[2].first_line, 0
eq a[2].first_column, 0
eq a[2].last_line, 0
eq a[2].last_column, 3
eq b[2].first_line, 0
eq b[2].first_column, 6
eq b[2].last_line, 0
eq b[2].last_column, 6
eq c[2].first_line, 0
eq c[2].first_column, 8
eq c[2].last_line, 0
eq c[2].last_column, 11
test 'Verify locations in heregex interpolation (in ///regex///, multiple interpolation and line breaks)', ->
[a, b, c] = getMatchingTokens '///#{a}\nb\n#{c}///', 'a', '"b"', 'c'
eq a[2].first_line, 0
eq a[2].first_column, 5
eq a[2].last_line, 0
eq a[2].last_column, 5
eq b[2].first_line, 0
eq b[2].first_column, 7
eq b[2].last_line, 1
eq b[2].last_column, 1
eq c[2].first_line, 2
eq c[2].first_column, 2
eq c[2].last_line, 2
eq c[2].last_column, 2
test 'Verify locations in heregex interpolation (in ///regex///, multiple interpolation and line breaks)', ->
[a, b, c] = getMatchingTokens '///#{a}\n\n\nb\n\n\n#{c}///', 'a', '"b"', 'c'
eq a[2].first_line, 0
eq a[2].first_column, 5
eq a[2].last_line, 0
eq a[2].last_column, 5
eq b[2].first_line, 0
eq b[2].first_column, 7
eq b[2].last_line, 5
eq b[2].last_column, 0
eq c[2].first_line, 6
eq c[2].first_column, 2
eq c[2].last_line, 6
eq c[2].last_column, 2
test 'Verify locations in heregex interpolation (in ///regex///, multiple interpolation and line breaks)', ->
[a, b, c] = getMatchingTokens '///a\n\n\n#{b}\n\n\nc///', '"a"', 'b', '"c"'
eq a[2].first_line, 0
eq a[2].first_column, 0
eq a[2].last_line, 2
eq a[2].last_column, 0
eq b[2].first_line, 3
eq b[2].first_column, 2
eq b[2].last_line, 3
eq b[2].last_column, 2
eq c[2].first_line, 3
eq c[2].first_column, 4
eq c[2].last_line, 6
eq c[2].last_column, 3
test 'Verify locations in heregex interpolation (in ///regex///, multiple interpolation and line breaks and starting with linebreak)', ->
[a, b, c] = getMatchingTokens '///\n#{a}\nb\n#{c}///', 'a', '"b"', 'c'
eq a[2].first_line, 1
eq a[2].first_column, 2
eq a[2].last_line, 1
eq a[2].last_column, 2
eq b[2].first_line, 1
eq b[2].first_column, 4
eq b[2].last_line, 2
eq b[2].last_column, 1
eq c[2].first_line, 3
eq c[2].first_column, 2
eq c[2].last_line, 3
eq c[2].last_column, 2
test 'Verify locations in heregex interpolation (in ///regex///, multiple interpolation and line breaks and starting with linebreak)', ->
[a, b, c] = getMatchingTokens '///\n\n\n#{a}\n\n\nb\n\n\n#{c}///', 'a', '"b"', 'c'
eq a[2].first_line, 3
eq a[2].first_column, 2
eq a[2].last_line, 3
eq a[2].last_column, 2
eq b[2].first_line, 3
eq b[2].first_column, 4
eq b[2].last_line, 8
eq b[2].last_column, 0
eq c[2].first_line, 9
eq c[2].first_column, 2
eq c[2].last_line, 9
eq c[2].last_column, 2
test 'Verify locations in heregex interpolation (in ///regex///, multiple interpolation and line breaks and starting with linebreak)', ->
[a, b, c] = getMatchingTokens '///\n\n\na\n\n\n#{b}\n\n\nc///', '"a"', 'b', '"c"'
eq a[2].first_line, 0
eq a[2].first_column, 0
eq a[2].last_line, 5
eq a[2].last_column, 0
eq b[2].first_line, 6
eq b[2].first_column, 2
eq b[2].last_line, 6
eq b[2].last_column, 2
eq c[2].first_line, 6
eq c[2].first_column, 4
eq c[2].last_line, 9
eq c[2].last_column, 3
test "#3822: Simple string/regex start/end should include delimiters", ->
[stringToken] = CoffeeScript.tokens "'string'"
eq stringToken[2].first_line, 0
eq stringToken[2].first_column, 0
eq stringToken[2].last_line, 0
eq stringToken[2].last_column, 7
[regexToken] = CoffeeScript.tokens "/regex/"
eq regexToken[2].first_line, 0
eq regexToken[2].first_column, 0
eq regexToken[2].last_line, 0
eq regexToken[2].last_column, 6
test "#3621: Multiline regex and manual `Regex` call with interpolation should
result in the same tokens", ->
tokensA = CoffeeScript.tokens '(RegExp(".*#{a}[0-9]"))'
tokensB = CoffeeScript.tokens '///.*#{a}[0-9]///'
eq tokensA.length, tokensB.length
for i in [0...tokensA.length] by 1
tokenA = tokensA[i]
tokenB = tokensB[i]
eq tokenA[0], tokenB[0] unless tokenB[0] in ['REGEX_START', 'REGEX_END']
eq tokenA[1], tokenB[1]
unless tokenA[0] is 'STRING_START' or tokenB[0] is 'REGEX_START'
eq tokenA.origin?[1], tokenB.origin?[1]
eq tokenA.stringEnd, tokenB.stringEnd
test "Verify tokens have locations that are in order", ->
source = '''
a {
b: ->
return c d,
if e
f
}
g
'''
tokens = CoffeeScript.tokens source
lastToken = null
for token in tokens
if lastToken
ok token[2].first_line >= lastToken[2].last_line
if token[2].first_line == lastToken[2].last_line
ok token[2].first_column >= lastToken[2].last_column
lastToken = token
test "Verify OUTDENT tokens are located at the end of the previous token", ->
source = '''
SomeArr = [ ->
if something
lol =
count: 500
]
'''
tokens = CoffeeScript.tokens source
[..., number, curly, outdent1, outdent2, outdent3, bracket, terminator] = tokens
eq number[0], 'NUMBER'
for outdent in [outdent1, outdent2, outdent3]
eq outdent[0], 'OUTDENT'
eq outdent[2].first_line, number[2].last_line
eq outdent[2].first_column, number[2].last_column
eq outdent[2].last_line, number[2].last_line
eq outdent[2].last_column, number[2].last_column
test "Verify OUTDENT and CALL_END tokens are located at the end of the previous token", ->
source = '''
a = b {
c: ->
d e,
if f
g {},
if h
i {}
}
'''
tokens = CoffeeScript.tokens source
[..., closeCurly1, callEnd1, outdent1, outdent2, callEnd2, outdent3, outdent4,
callEnd3, outdent5, outdent6, closeCurly2, callEnd4, terminator] = tokens
eq closeCurly1[0], '}'
assertAtCloseCurly = (token) ->
eq token[2].first_line, closeCurly1[2].last_line
eq token[2].first_column, closeCurly1[2].last_column
eq token[2].last_line, closeCurly1[2].last_line
eq token[2].last_column, closeCurly1[2].last_column
for token in [outdent1, outdent2, outdent3, outdent4, outdent5, outdent6]
eq token[0], 'OUTDENT'
assertAtCloseCurly(token)
for token in [callEnd1, callEnd2, callEnd3]
eq token[0], 'CALL_END'
assertAtCloseCurly(token)
test "Verify generated } tokens are located at the end of the previous token", ->
source = '''
a(b, ->
c: () ->
if d
e
)
'''
tokens = CoffeeScript.tokens source
[..., identifier, outdent1, outdent2, closeCurly, outdent3, callEnd,
terminator] = tokens
eq identifier[0], 'IDENTIFIER'
assertAtIdentifier = (token) ->
eq token[2].first_line, identifier[2].last_line
eq token[2].first_column, identifier[2].last_column
eq token[2].last_line, identifier[2].last_line
eq token[2].last_column, identifier[2].last_column
for token in [outdent1, outdent2, closeCurly, outdent3]
assertAtIdentifier(token)
test "Verify real CALL_END tokens have the right position", ->
source = '''
a()
'''
tokens = CoffeeScript.tokens source
[identifier, callStart, callEnd, terminator] = tokens
startIndex = identifier[2].first_column
eq identifier[2].last_column, startIndex
eq callStart[2].first_column, startIndex + 1
eq callStart[2].last_column, startIndex + 1
eq callEnd[2].first_column, startIndex + 2
eq callEnd[2].last_column, startIndex + 2
test "Verify normal heredocs have the right position", ->
source = '''
"""
a"""
'''
[stringToken] = CoffeeScript.tokens source
eq stringToken[2].first_line, 0
eq stringToken[2].first_column, 0
eq stringToken[2].last_line, 1
eq stringToken[2].last_column, 3
test "Verify heredocs ending with a newline have the right position", ->
source = '''
"""
a
"""
'''
[stringToken] = CoffeeScript.tokens source
eq stringToken[2].first_line, 0
eq stringToken[2].first_column, 0
eq stringToken[2].last_line, 2
eq stringToken[2].last_column, 2
test "Verify indented heredocs have the right position", ->
source = '''
->
"""
a
"""
'''
[arrow, indent, stringToken] = CoffeeScript.tokens source
eq stringToken[2].first_line, 1
eq stringToken[2].first_column, 2
eq stringToken[2].last_line, 3
eq stringToken[2].last_column, 4
test "Verify heregexes with interpolations have the right ending position", ->
source = '''
[a ///#{b}///g]
'''
[..., stringEnd, comma, flagsString, regexCallEnd, regexEnd, fnCallEnd,
arrayEnd, terminator] = CoffeeScript.tokens source
eq comma[0], ','
eq arrayEnd[0], ']'
assertColumn = (token, column) ->
eq token[2].first_line, 0
eq token[2].first_column, column
eq token[2].last_line, 0
eq token[2].last_column, column
arrayEndColumn = arrayEnd[2].first_column
for token in [comma, flagsString]
assertColumn token, arrayEndColumn - 2
for token in [regexCallEnd, regexEnd, fnCallEnd]
assertColumn token, arrayEndColumn - 1
assertColumn arrayEnd, arrayEndColumn
test "Verify all tokens get a location", ->
doesNotThrow ->
tokens = CoffeeScript.tokens testScript
for token in tokens
ok !!token[2]
</script>
<script type="text/x-coffeescript" class="test" id="modules">
# Modules, a.k.a. ES2015 import/export
# ------------------------------------
#
# Remember, were not *resolving* modules, just outputting valid ES2015 syntax.
# This is the CoffeeScript import and export syntax, closely modeled after the ES2015 syntax
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export
# import "module-name"
# import defaultMember from "module-name"
# import * as name from "module-name"
# import { } from "module-name"
# import { member } from "module-name"
# import { member as alias } from "module-name"
# import { member1, member2 as alias2, } from "module-name"
# import defaultMember, * as name from "module-name"
# import defaultMember, { } from "module-name"
# export default expression
# export class name
# export { }
# export { name }
# export { name as exportedName }
# export { name as default }
# export { name1, name2 as exportedName2, name3 as default, }
#
# export * from "module-name"
# export { } from "module-name"
#
# As a subsitute for `export var name = …` and `export function name {}`,
# CoffeeScript also supports:
# export name =
# CoffeeScript also supports optional commas within `{ … }`.
# Import statements
test "backticked import statement", ->
input = """
if Meteor.isServer
`import { foo, bar as baz } from 'lib'`"""
output = """
if (Meteor.isServer) {
import { foo, bar as baz } from 'lib';
}"""
eq toJS(input), output
test "import an entire module for side effects only, without importing any bindings", ->
input = "import 'lib'"
output = "import 'lib';"
eq toJS(input), output
test "import default member from module, adding the member to the current scope", ->
input = """
import foo from 'lib'
foo.fooMethod()"""
output = """
import foo from 'lib';
foo.fooMethod();"""
eq toJS(input), output
test "import an entire module's contents as an alias, adding the alias to the current scope", ->
input = """
import * as foo from 'lib'
foo.fooMethod()"""
output = """
import * as foo from 'lib';
foo.fooMethod();"""
eq toJS(input), output
test "import empty object", ->
input = "import { } from 'lib'"
output = "import {} from 'lib';"
eq toJS(input), output
test "import empty object", ->
input = "import {} from 'lib'"
output = "import {} from 'lib';"
eq toJS(input), output
test "import a single member of a module, adding the member to the current scope", ->
input = """
import { foo } from 'lib'
foo.fooMethod()"""
output = """
import {
foo
} from 'lib';
foo.fooMethod();"""
eq toJS(input), output
test "import a single member of a module as an alias, adding the alias to the current scope", ->
input = """
import { foo as bar } from 'lib'
bar.barMethod()"""
output = """
import {
foo as bar
} from 'lib';
bar.barMethod();"""
eq toJS(input), output
test "import multiple members of a module, adding the members to the current scope", ->
input = """
import { foo, bar } from 'lib'
foo.fooMethod()
bar.barMethod()"""
output = """
import {
foo,
bar
} from 'lib';
foo.fooMethod();
bar.barMethod();"""
eq toJS(input), output
test "import multiple members of a module where some are aliased, adding the members or aliases to the current scope", ->
input = """
import { foo, bar as baz } from 'lib'
foo.fooMethod()
baz.bazMethod()"""
output = """
import {
foo,
bar as baz
} from 'lib';
foo.fooMethod();
baz.bazMethod();"""
eq toJS(input), output
test "import default member and other members of a module, adding the members to the current scope", ->
input = """
import foo, { bar, baz as qux } from 'lib'
foo.fooMethod()
bar.barMethod()
qux.quxMethod()"""
output = """
import foo, {
bar,
baz as qux
} from 'lib';
foo.fooMethod();
bar.barMethod();
qux.quxMethod();"""
eq toJS(input), output
test "import default member from a module as well as the entire module's contents as an alias, adding the member and alias to the current scope", ->
input = """
import foo, * as bar from 'lib'
foo.fooMethod()
bar.barMethod()"""
output = """
import foo, * as bar from 'lib';
foo.fooMethod();
bar.barMethod();"""
eq toJS(input), output
test "multiline simple import", ->
input = """
import {
foo,
bar as baz
} from 'lib'"""
output = """
import {
foo,
bar as baz
} from 'lib';"""
eq toJS(input), output
test "multiline complex import", ->
input = """
import foo, {
bar,
baz as qux
} from 'lib'"""
output = """
import foo, {
bar,
baz as qux
} from 'lib';"""
eq toJS(input), output
test "import with optional commas", ->
input = "import { foo, bar, } from 'lib'"
output = """
import {
foo,
bar
} from 'lib';"""
eq toJS(input), output
test "multiline import without commas", ->
input = """
import {
foo
bar
} from 'lib'"""
output = """
import {
foo,
bar
} from 'lib';"""
eq toJS(input), output
test "multiline import with optional commas", ->
input = """
import {
foo,
bar,
} from 'lib'"""
output = """
import {
foo,
bar
} from 'lib';"""
eq toJS(input), output
test "a variable can be assigned after an import", ->
input = """
import { foo } from 'lib'
bar = 5"""
output = """
var bar;
import {
foo
} from 'lib';
bar = 5;"""
eq toJS(input), output
test "variables can be assigned before and after an import", ->
input = """
foo = 5
import { bar } from 'lib'
baz = 7"""
output = """
var baz, foo;
foo = 5;
import {
bar
} from 'lib';
baz = 7;"""
eq toJS(input), output
# Export statements
test "export empty object", ->
input = "export { }"
output = "export {};"
eq toJS(input), output
test "export empty object", ->
input = "export {}"
output = "export {};"
eq toJS(input), output
test "export named members within an object", ->
input = "export { foo, bar }"
output = """
export {
foo,
bar
};"""
eq toJS(input), output
test "export named members as aliases, within an object", ->
input = "export { foo as bar, baz as qux }"
output = """
export {
foo as bar,
baz as qux
};"""
eq toJS(input), output
test "export named members within an object, with an optional comma", ->
input = "export { foo, bar, }"
output = """
export {
foo,
bar
};"""
eq toJS(input), output
test "multiline export named members within an object", ->
input = """
export {
foo,
bar
}"""
output = """
export {
foo,
bar
};"""
eq toJS(input), output
test "multiline export named members within an object, with an optional comma", ->
input = """
export {
foo,
bar,
}"""
output = """
export {
foo,
bar
};"""
eq toJS(input), output
test "export default string", ->
input = "export default 'foo'"
output = "export default 'foo';"
eq toJS(input), output
test "export default number", ->
input = "export default 5"
output = "export default 5;"
eq toJS(input), output
test "export default object", ->
input = "export default { foo: 'bar', baz: 'qux' }"
output = """
export default {
foo: 'bar',
baz: 'qux'
};"""
eq toJS(input), output
test "export default implicit object", ->
input = "export default foo: 'bar', baz: 'qux'"
output = """
export default {
foo: 'bar',
baz: 'qux'
};"""
eq toJS(input), output
test "export default multiline implicit object", ->
input = """
export default
foo: 'bar',
baz: 'qux'
"""
output = """
export default {
foo: 'bar',
baz: 'qux'
};"""
eq toJS(input), output
test "export default assignment expression", ->
input = "export default foo = 'bar'"
output = """
var foo;
export default foo = 'bar';"""
eq toJS(input), output
test "export assignment expression", ->
input = "export foo = 'bar'"
output = "export var foo = 'bar';"
eq toJS(input), output
test "export multiline assignment expression", ->
input = """
export foo =
'bar'"""
output = "export var foo = 'bar';"
eq toJS(input), output
test "export multiline indented assignment expression", ->
input = """
export foo =
'bar'"""
output = "export var foo = 'bar';"
eq toJS(input), output
test "export default function", ->
input = "export default ->"
output = "export default function() {};"
eq toJS(input), output
test "export default multiline function", ->
input = """
export default (foo) ->
console.log foo"""
output = """
export default function(foo) {
return console.log(foo);
};"""
eq toJS(input), output
test "export assignment function", ->
input = """
export foo = (bar) ->
console.log bar"""
output = """
export var foo = function(bar) {
return console.log(bar);
};"""
eq toJS(input), output
test "export assignment function which contains assignments in its body", ->
input = """
export foo = (bar) ->
baz = '!'
console.log bar + baz"""
output = """
export var foo = function(bar) {
var baz;
baz = '!';
return console.log(bar + baz);
};"""
eq toJS(input), output
test "export default predefined function", ->
input = """
foo = (bar) ->
console.log bar
export default foo"""
output = """
var foo;
foo = function(bar) {
return console.log(bar);
};
export default foo;"""
eq toJS(input), output
# Uncomment this test once ES2015+ `class` support is added
# test "export default class", ->
# input = """
# export default class foo extends bar
# baz: ->
# console.log 'hello, world!'"""
# output = """
# export default class foo extends bar {
# baz: function {
# return console.log('hello, world!');
# }
# }"""
# eq toJS(input), output
# Very limited tests for now, testing that `export class foo` either compiles
# identically (ES2015+) or at least into some function, leaving the specifics
# vague in case the CoffeeScript `class` interpretation changes
test "export class", ->
input = """
export class foo
baz: ->
console.log 'hello, world!'"""
output = toJS input
ok /^export (class foo|var foo = \(function)/.test toJS input
test "export class that extends", ->
input = """
export class foo extends bar
baz: ->
console.log 'hello, world!'"""
output = toJS input
ok /export (class foo|var foo = \(function)/.test(output) and \
not /var foo(;|,)/.test output
test "export default class that extends", ->
input = """
export default class foo extends bar
baz: ->
console.log 'hello, world!'"""
ok /export default (class foo|foo = \(function)/.test toJS input
test "export default named member, within an object", ->
input = "export { foo as default, bar }"
output = """
export {
foo as default,
bar
};"""
eq toJS(input), output
# Import and export in the same statement
test "export an entire module's contents", ->
input = "export * from 'lib'"
output = "export * from 'lib';"
eq toJS(input), output
test "export members imported from another module", ->
input = "export { foo, bar } from 'lib'"
output = """
export {
foo,
bar
} from 'lib';"""
eq toJS(input), output
test "export as aliases members imported from another module", ->
input = "export { foo as bar, baz as qux } from 'lib'"
output = """
export {
foo as bar,
baz as qux
} from 'lib';"""
eq toJS(input), output
test "export list can contain CoffeeScript keywords", ->
input = "export { unless } from 'lib'"
output = """
export {
unless
} from 'lib';"""
eq toJS(input), output
test "export list can contain CoffeeScript keywords when aliasing", ->
input = "export { when as bar, baz as unless } from 'lib'"
output = """
export {
when as bar,
baz as unless
} from 'lib';"""
eq toJS(input), output
# Edge cases
test "multiline import with comments", ->
input = """
import {
foo, # Not as good as bar
bar as baz # I prefer qux
} from 'lib'"""
output = """
import {
foo,
bar as baz
} from 'lib';"""
eq toJS(input), output
test "`from` not part of an import or export statement can still be assigned", ->
from = 5
eq 5, from
test "a variable named `from` can be assigned after an import", ->
input = """
import { foo } from 'lib'
from = 5"""
output = """
var from;
import {
foo
} from 'lib';
from = 5;"""
eq toJS(input), output
test "`from` can be assigned after a multiline import", ->
input = """
import {
foo
} from 'lib'
from = 5"""
output = """
var from;
import {
foo
} from 'lib';
from = 5;"""
eq toJS(input), output
test "`from` can be imported as a member name", ->
input = "import { from } from 'lib'"
output = """
import {
from
} from 'lib';"""
eq toJS(input), output
test "`from` can be imported as a member name and aliased", ->
input = "import { from as foo } from 'lib'"
output = """
import {
from as foo
} from 'lib';"""
eq toJS(input), output
test "`from` can be used as an alias name", ->
input = "import { foo as from } from 'lib'"
output = """
import {
foo as from
} from 'lib';"""
eq toJS(input), output
test "`as` can be imported as a member name", ->
input = "import { as } from 'lib'"
output = """
import {
as
} from 'lib';"""
eq toJS(input), output
test "`as` can be imported as a member name and aliased", ->
input = "import { as as foo } from 'lib'"
output = """
import {
as as foo
} from 'lib';"""
eq toJS(input), output
test "`as` can be used as an alias name", ->
input = "import { foo as as } from 'lib'"
output = """
import {
foo as as
} from 'lib';"""
eq toJS(input), output
test "CoffeeScript keywords can be used as imported names in import lists", ->
input = """
import { unless as bar } from 'lib'
bar.barMethod()"""
output = """
import {
unless as bar
} from 'lib';
bar.barMethod();"""
eq toJS(input), output
test "`*` can be used in an expression on the same line as an export keyword", ->
input = "export foo = (x) -> x * x"
output = """
export var foo = function(x) {
return x * x;
};"""
eq toJS(input), output
input = "export default foo = (x) -> x * x"
output = """
var foo;
export default foo = function(x) {
return x * x;
};"""
eq toJS(input), output
test "`*` and `from` can be used in an export default expression", ->
input = """
export default foo.extend
bar: ->
from = 5
from = from * 3"""
output = """
export default foo.extend({
bar: function() {
var from;
from = 5;
return from = from * 3;
}
});"""
eq toJS(input), output
test "wrapped members can be imported multiple times if aliased", ->
input = "import { foo, foo as bar } from 'lib'"
output = """
import {
foo,
foo as bar
} from 'lib';"""
eq toJS(input), output
test "default and wrapped members can be imported multiple times if aliased", ->
input = "import foo, { foo as bar } from 'lib'"
output = """
import foo, {
foo as bar
} from 'lib';"""
eq toJS(input), output
test "import a member named default", ->
input = "import { default } from 'lib'"
output = """
import {
default
} from 'lib';"""
eq toJS(input), output
test "import an aliased member named default", ->
input = "import { default as def } from 'lib'"
output = """
import {
default as def
} from 'lib';"""
eq toJS(input), output
test "export a member named default", ->
input = "export { default }"
output = """
export {
default
};"""
eq toJS(input), output
test "export an aliased member named default", ->
input = "export { def as default }"
output = """
export {
def as default
};"""
eq toJS(input), output
test "import an imported member named default", ->
input = "import { default } from 'lib'"
output = """
import {
default
} from 'lib';"""
eq toJS(input), output
test "import an imported aliased member named default", ->
input = "import { default as def } from 'lib'"
output = """
import {
default as def
} from 'lib';"""
eq toJS(input), output
test "export an imported member named default", ->
input = "export { default } from 'lib'"
output = """
export {
default
} from 'lib';"""
eq toJS(input), output
test "export an imported aliased member named default", ->
input = "export { default as def } from 'lib'"
output = """
export {
default as def
} from 'lib';"""
eq toJS(input), output
test "#4394: export shouldn't prevent variable declarations", ->
input = """
x = 1
export { x }
"""
output = """
var x;
x = 1;
export {
x
};
"""
eq toJS(input), output
test "#4451: `default` in an export statement is only treated as a keyword when it follows `export` or `as`", ->
input = "export default { default: 1 }"
output = """
export default {
"default": 1
};
"""
eq toJS(input), output
test "#4491: import- and export-specific lexing should stop after import/export statement", ->
input = """
import {
foo,
bar as baz
} from 'lib'
foo as
3 * as 4
from 'foo'
"""
output = """
import {
foo,
bar as baz
} from 'lib';
foo(as);
3 * as(4);
from('foo');
"""
eq toJS(input), output
input = """
import { foo, bar as baz } from 'lib'
foo as
3 * as 4
from 'foo'
"""
output = """
import {
foo,
bar as baz
} from 'lib';
foo(as);
3 * as(4);
from('foo');
"""
eq toJS(input), output
input = """
import * as lib from 'lib'
foo as
3 * as 4
from 'foo'
"""
output = """
import * as lib from 'lib';
foo(as);
3 * as(4);
from('foo');
"""
eq toJS(input), output
input = """
export {
foo,
bar
}
foo as
3 * as 4
from 'foo'
"""
output = """
export {
foo,
bar
};
foo(as);
3 * as(4);
from('foo');
"""
eq toJS(input), output
input = """
export * from 'lib'
foo as
3 * as 4
from 'foo'
"""
output = """
export * from 'lib';
foo(as);
3 * as(4);
from('foo');
"""
eq toJS(input), output
</script>
<script type="text/x-coffeescript" class="test" id="numbers">
# Number Literals
# ---------------
# * Decimal Integer Literals
# * Octal Integer Literals
# * Hexadecimal Integer Literals
# * Scientific Notation Integer Literals
# * Scientific Notation Non-Integer Literals
# * Non-Integer Literals
# * Binary Integer Literals
# Binary Integer Literals
# Binary notation is understood as would be decimal notation.
test "Parser recognises binary numbers", ->
eq 4, 0b100
# Decimal Integer Literals
test "call methods directly on numbers", ->
eq 4, 4.valueOf()
eq '11', 4.toString 3
eq -1, 3 -4
#764: Numbers should be indexable
eq Number::toString, 42['toString']
eq Number::toString, 42.toString
eq Number::toString, 2e308['toString'] # Infinity
# Non-Integer Literals
# Decimal number literals.
value = .25 + .75
ok value is 1
value = 0.0 + -.25 - -.75 + 0.0
ok value is 0.5
#764: Numbers should be indexable
eq Number::toString, 4['toString']
eq Number::toString, 4.2['toString']
eq Number::toString, .42['toString']
eq Number::toString, (4)['toString']
eq Number::toString, 4.toString
eq Number::toString, 4.2.toString
eq Number::toString, .42.toString
eq Number::toString, (4).toString
test '#1168: leading floating point suppresses newline', ->
eq 1, do ->
1
.5 + 0.5
test "Python-style octal literal notation '0o777'", ->
eq 511, 0o777
eq 1, 0o1
eq 1, 0o00001
eq parseInt('0777', 8), 0o777
eq '777', 0o777.toString 8
eq 4, 0o4.valueOf()
eq Number::toString, 0o777['toString']
eq Number::toString, 0o777.toString
test "#2060: Disallow uppercase radix prefixes and exponential notation", ->
for char in ['b', 'o', 'x', 'e']
program = "0#{char}0"
doesNotThrow -> CoffeeScript.compile program, bare: yes
throws -> CoffeeScript.compile program.toUpperCase(), bare: yes
test "#2224: hex literals with 0b or B or E", ->
eq 176, 0x0b0
eq 177, 0x0B1
eq 225, 0xE1
test "Infinity", ->
eq Infinity, CoffeeScript.eval "0b#{Array(1024 + 1).join('1')}"
eq Infinity, CoffeeScript.eval "0o#{Array(342 + 1).join('7')}"
eq Infinity, CoffeeScript.eval "0x#{Array(256 + 1).join('f')}"
eq Infinity, CoffeeScript.eval Array(500 + 1).join('9')
eq Infinity, 2e308
test "NaN", ->
ok isNaN 1/NaN
</script>
<script type="text/x-coffeescript" class="test" id="objects">
# 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
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.
# 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}
# 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}
# 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}
# Key with interpolation mixed with `@prop`.
deepEqual (-> {@a, "#{'b'}": 2}).call(a: 1), {a: 1, b: 2}
# Evaluate only once.
count = 0
b = -> count++; 'b'
obj = {"#{b()}"}
eq obj.b, 'b'
eq count, 1
# 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}
# Object starting with dynamic key.
obj =
"#{'a'}": 1
b: 2
deepEqual obj, {a: 1, b: 2}
# 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}
# A 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 "#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()
</script>
<script type="text/x-coffeescript" class="test" id="operators">
# Operators
# ---------
# * Operators
# * Existential Operator (Binary)
# * Existential Operator (Unary)
# * Aliased Operators
# * [not] in/of
# * Chained Comparison
test "binary (2-ary) math operators do not require spaces", ->
a = 1
b = -1
eq +1, a*-b
eq -1, a*+b
eq +1, a/-b
eq -1, a/+b
test "operators should respect new lines as spaced", ->
a = 123 +
456
eq 579, a
b = "1#{2}3" +
"456"
eq '123456', b
test "multiple operators should space themselves", ->
eq (+ +1), (- -1)
test "compound operators on successive lines", ->
a = 1
a +=
1
eq a, 2
test "bitwise operators", ->
eq 2, (10 & 3)
eq 11, (10 | 3)
eq 9, (10 ^ 3)
eq 80, (10 << 3)
eq 1, (10 >> 3)
eq 1, (10 >>> 3)
num = 10; eq 2, (num &= 3)
num = 10; eq 11, (num |= 3)
num = 10; eq 9, (num ^= 3)
num = 10; eq 80, (num <<= 3)
num = 10; eq 1, (num >>= 3)
num = 10; eq 1, (num >>>= 3)
test "`instanceof`", ->
ok new String instanceof String
ok new Boolean instanceof Boolean
# `instanceof` supports negation by prefixing the operator with `not`
ok new Number not instanceof String
ok new Array not instanceof Boolean
test "use `::` operator on keywords `this` and `@`", ->
nonce = {}
obj =
withAt: -> @::prop
withThis: -> this::prop
obj.prototype = prop: nonce
eq nonce, obj.withAt()
eq nonce, obj.withThis()
# Existential Operator (Binary)
test "binary existential operator", ->
nonce = {}
b = a ? nonce
eq nonce, b
a = null
b = undefined
b = a ? nonce
eq nonce, b
a = false
b = a ? nonce
eq false, b
a = 0
b = a ? nonce
eq 0, b
test "binary existential operator conditionally evaluates second operand", ->
i = 1
func = -> i -= 1
result = func() ? func()
eq result, 0
test "binary existential operator with negative number", ->
a = null ? - 1
eq -1, a
# Existential Operator (Unary)
test "postfix existential operator", ->
ok (if nonexistent? then false else true)
defined = true
ok defined?
defined = false
ok defined?
test "postfix existential operator only evaluates its operand once", ->
semaphore = 0
fn = ->
ok false if semaphore
++semaphore
ok(if fn()? then true else false)
test "negated postfix existential operator", ->
ok !nothing?.value
test "postfix existential operator on expressions", ->
eq true, (1 or 0)?, true
# `is`,`isnt`,`==`,`!=`
test "`==` and `is` should be interchangeable", ->
a = b = 1
ok a is 1 and b == 1
ok a == b
ok a is b
test "`!=` and `isnt` should be interchangeable", ->
a = 0
b = 1
ok a isnt 1 and b != 0
ok a != b
ok a isnt b
# [not] in/of
# - `in` should check if an array contains a value using `indexOf`
# - `of` should check if a property is defined on an object using `in`
test "in, of", ->
arr = [1]
ok 0 of arr
ok 1 in arr
# prefixing `not` to `in and `of` should negate them
ok 1 not of arr
ok 0 not in arr
test "`in` should be able to operate on an array literal", ->
ok 2 in [0, 1, 2, 3]
ok 4 not in [0, 1, 2, 3]
arr = [0, 1, 2, 3]
ok 2 in arr
ok 4 not in arr
# should cache the value used to test the array
arr = [0]
val = 0
ok val++ in arr
ok val++ not in arr
val = 0
ok val++ of arr
ok val++ not of arr
test "`of` and `in` should be able to operate on instance variables", ->
obj = {
list: [2,3]
in_list: (value) -> value in @list
not_in_list: (value) -> value not in @list
of_list: (value) -> value of @list
not_of_list: (value) -> value not of @list
}
ok obj.in_list 3
ok obj.not_in_list 1
ok obj.of_list 0
ok obj.not_of_list 2
test "#???: `in` with cache and `__indexOf` should work in argument lists", ->
eq 1, [Object() in Array()].length
test "#737: `in` should have higher precedence than logical operators", ->
eq 1, 1 in [1] and 1
test "#768: `in` should preserve evaluation order", ->
share = 0
a = -> share++ if share is 0
b = -> share++ if share is 1
c = -> share++ if share is 2
ok a() not in [b(),c()]
eq 3, share
test "#1099: empty array after `in` should compile to `false`", ->
eq 1, [5 in []].length
eq false, do -> return 0 in []
test "#1354: optimized `in` checks should not happen when splats are present", ->
a = [6, 9]
eq 9 in [3, a...], true
test "#1100: precedence in or-test compilation of `in`", ->
ok 0 in [1 and 0]
ok 0 in [1, 1 and 0]
ok not (0 in [1, 0 or 1])
test "#1630: `in` should check `hasOwnProperty`", ->
ok undefined not in length: 1
test "#1714: lexer bug with raw range `for` followed by `in`", ->
0 for [1..2]
ok not ('a' in ['b'])
0 for [1..2]; ok not ('a' in ['b'])
0 for [1..10] # comment ending
ok not ('a' in ['b'])
# lexer state (specifically @seenFor) should be reset before each compilation
CoffeeScript.compile "0 for [1..2]"
CoffeeScript.compile "'a' in ['b']"
test "#1099: statically determined `not in []` reporting incorrect result", ->
ok 0 not in []
test "#1099: make sure expression tested gets evaluted when array is empty", ->
a = 0
(do -> a = 1) in []
eq a, 1
# Chained Comparison
test "chainable operators", ->
ok 100 > 10 > 1 > 0 > -1
ok -1 < 0 < 1 < 10 < 100
test "`is` and `isnt` may be chained", ->
ok true is not false is true is not false
ok 0 is 0 isnt 1 is 1
test "different comparison operators (`>`,`<`,`is`,etc.) may be combined", ->
ok 1 < 2 > 1
ok 10 < 20 > 2+3 is 5
test "some chainable operators can be negated by `unless`", ->
ok (true unless 0==10!=100)
test "operator precedence: `|` lower than `<`", ->
eq 1, 1 | 2 < 3 < 4
test "preserve references", ->
a = b = c = 1
# `a == b <= c` should become `a === b && b <= c`
# (this test does not seem to test for this)
ok a == b <= c
test "chained operations should evaluate each value only once", ->
a = 0
ok 1 > a++ < 1
test "#891: incorrect inversion of chained comparisons", ->
ok (true unless 0 > 1 > 2)
ok (true unless (this.NaN = 0/0) < 0/0 < this.NaN)
test "#1234: Applying a splat to :: applies the splat to the wrong object", ->
nonce = {}
class C
method: -> @nonce
nonce: nonce
arr = []
eq nonce, C::method arr... # should be applied to `C::`
test "#1102: String literal prevents line continuation", ->
eq "': '", '' +
"': '"
test "#1703, ---x is invalid JS", ->
x = 2
eq (- --x), -1
test "Regression with implicit calls against an indented assignment", ->
eq 1, a =
1
eq a, 1
test "#2155 ... conditional assignment to a closure", ->
x = null
func = -> x ?= (-> if true then 'hi')
func()
eq x(), 'hi'
test "#2197: Existential existential double trouble", ->
counter = 0
func = -> counter++
func()? ? 100
eq counter, 1
test "#2567: Optimization of negated existential produces correct result", ->
a = 1
ok !(!a?)
ok !b?
test "#2508: Existential access of the prototype", ->
eq NonExistent?::nothing, undefined
ok Object?::toString
test "power operator", ->
eq 27, 3 ** 3
test "power operator has higher precedence than other maths operators", ->
eq 55, 1 + 3 ** 3 * 2
eq -4, -2 ** 2
eq false, !2 ** 2
eq 0, (!2) ** 2
eq -2, ~1 ** 5
test "power operator is right associative", ->
eq 2, 2 ** 1 ** 3
test "power operator compound assignment", ->
a = 2
a **= 3
eq 8, a
test "floor division operator", ->
eq 2, 7 // 3
eq -3, -7 // 3
eq NaN, 0 // 0
test "floor division operator compound assignment", ->
a = 7
a //= 1 + 1
eq 3, a
test "modulo operator", ->
check = (a, b, expected) ->
eq expected, a %% b, "expected #{a} %%%% #{b} to be #{expected}"
check 0, 1, 0
check 0, -1, -0
check 1, 0, NaN
check 1, 2, 1
check 1, -2, -1
check 1, 3, 1
check 2, 3, 2
check 3, 3, 0
check 4, 3, 1
check -1, 3, 2
check -2, 3, 1
check -3, 3, 0
check -4, 3, 2
check 5.5, 2.5, 0.5
check -5.5, 2.5, 2.0
test "modulo operator compound assignment", ->
a = -2
a %%= 5
eq 3, a
test "modulo operator converts arguments to numbers", ->
eq 1, 1 %% '42'
eq 1, '1' %% 42
eq 1, '1' %% '42'
test "#3361: Modulo operator coerces right operand once", ->
count = 0
res = 42 %% valueOf: -> count += 1
eq 1, count
eq 0, res
test "#3363: Modulo operator coercing order", ->
count = 2
a = valueOf: -> count *= 2
b = valueOf: -> count += 1
eq 4, a %% b
eq 5, count
test "#3598: Unary + and - coerce the operand once when it is an identifier", ->
# Unary + and - do not generate `_ref`s when the operand is a number, for
# readability. To make sure that they do when the operand is an identifier,
# test that they are consistent with another unary operator as well as another
# complex expression.
# Tip: Making one of the tests temporarily fail lets you easily inspect the
# compiled JavaScript.
assertOneCoercion = (fn) ->
count = 0
value = valueOf: -> count++; 1
fn value
eq 1, count
eq 1, 1 ? 0
eq 1, +1 ? 0
eq -1, -1 ? 0
assertOneCoercion (a) ->
eq 1, +a ? 0
assertOneCoercion (a) ->
eq -1, -a ? 0
assertOneCoercion (a) ->
eq -2, ~a ? 0
assertOneCoercion (a) ->
eq 0.5, a / 2 ? 0
ok -2 <= 1 < 2
ok -2 <= +1 < 2
ok -2 <= -1 < 2
assertOneCoercion (a) ->
ok -2 <= +a < 2
assertOneCoercion (a) ->
ok -2 <= -a < 2
assertOneCoercion (a) ->
ok -2 <= ~a < 2
assertOneCoercion (a) ->
ok -2 <= a / 2 < 2
arrayEq [0], (n for n in [0] by 1)
arrayEq [0], (n for n in [0] by +1)
arrayEq [0], (n for n in [0] by -1)
assertOneCoercion (a) ->
arrayEq [0], (n for n in [0] by +a)
assertOneCoercion (a) ->
arrayEq [0], (n for n in [0] by -a)
assertOneCoercion (a) ->
arrayEq [0], (n for n in [0] by ~a)
assertOneCoercion (a) ->
arrayEq [0], (n for n in [0] by a * 2 / 2)
ok 1 in [0, 1]
ok +1 in [0, 1]
ok -1 in [0, -1]
assertOneCoercion (a) ->
ok +a in [0, 1]
assertOneCoercion (a) ->
ok -a in [0, -1]
assertOneCoercion (a) ->
ok ~a in [0, -2]
assertOneCoercion (a) ->
ok a / 2 in [0, 0.5]
</script>
<script type="text/x-coffeescript" class="test" id="option_parser">
# Option Parser
# -------------
# TODO: refactor option parser tests
# Ensure that the OptionParser handles arguments correctly.
return unless require?
{OptionParser} = require './../lib/coffee-script/optparse'
opt = new OptionParser [
['-r', '--required [DIR]', 'desc required']
['-o', '--optional', 'desc optional']
['-l', '--list [FILES*]', 'desc list']
]
test "basic arguments", ->
args = ['one', 'two', 'three', '-r', 'dir']
result = opt.parse args
arrayEq args, result.arguments
eq undefined, result.required
test "boolean and parameterised options", ->
result = opt.parse ['--optional', '-r', 'folder', 'one', 'two']
ok result.optional
eq 'folder', result.required
arrayEq ['one', 'two'], result.arguments
test "list options", ->
result = opt.parse ['-l', 'one.txt', '-l', 'two.txt', 'three']
arrayEq ['one.txt', 'two.txt'], result.list
arrayEq ['three'], result.arguments
test "-- and interesting combinations", ->
result = opt.parse ['-o','-r','a','-r','b','-o','--','-a','b','--c','d']
arrayEq ['-a', 'b', '--c', 'd'], result.arguments
ok result.optional
eq 'b', result.required
args = ['--','-o','a','-r','c','-o','--','-a','arg0','-b','arg1']
result = opt.parse args
eq undefined, result.optional
eq undefined, result.required
arrayEq args[1..], result.arguments
</script>
<script type="text/x-coffeescript" class="test" id="parser">
# Parser
# ---------
test "operator precedence for logical operators", ->
source = '''
a or b and c
'''
block = CoffeeScript.nodes source
[expression] = block.expressions
eq expression.first.base.value, 'a'
eq expression.operator, '||'
eq expression.second.first.base.value, 'b'
eq expression.second.operator, '&&'
eq expression.second.second.base.value, 'c'
test "operator precedence for bitwise operators", ->
source = '''
a | b ^ c & d
'''
block = CoffeeScript.nodes source
[expression] = block.expressions
eq expression.first.base.value, 'a'
eq expression.operator, '|'
eq expression.second.first.base.value, 'b'
eq expression.second.operator, '^'
eq expression.second.second.first.base.value, 'c'
eq expression.second.second.operator, '&'
eq expression.second.second.second.base.value, 'd'
test "operator precedence for binary ? operator", ->
source = '''
a ? b and c
'''
block = CoffeeScript.nodes source
[expression] = block.expressions
eq expression.first.base.value, 'a'
eq expression.operator, '?'
eq expression.second.first.base.value, 'b'
eq expression.second.operator, '&&'
eq expression.second.second.base.value, 'c'
test "new calls have a range including the new", ->
source = '''
a = new B().c(d)
'''
block = CoffeeScript.nodes source
assertColumnRange = (node, firstColumn, lastColumn) ->
eq node.locationData.first_line, 0
eq node.locationData.first_column, firstColumn
eq node.locationData.last_line, 0
eq node.locationData.last_column, lastColumn
[assign] = block.expressions
outerCall = assign.value
innerValue = outerCall.variable
innerCall = innerValue.base
assertColumnRange assign, 0, 15
assertColumnRange outerCall, 4, 15
assertColumnRange innerValue, 4, 12
assertColumnRange innerCall, 4, 10
test "location data is properly set for nested `new`", ->
source = '''
new new A()()
'''
block = CoffeeScript.nodes source
assertColumnRange = (node, firstColumn, lastColumn) ->
eq node.locationData.first_line, 0
eq node.locationData.first_column, firstColumn
eq node.locationData.last_line, 0
eq node.locationData.last_column, lastColumn
[outerCall] = block.expressions
innerCall = outerCall.variable
assertColumnRange outerCall, 0, 12
assertColumnRange innerCall, 4, 10
</script>
<script type="text/x-coffeescript" class="test" id="ranges">
# Range Literals
# --------------
# TODO: add indexing and method invocation tests: [1..4][0] is 1, [0...3].toString()
# shared array
shared = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
test "basic inclusive ranges", ->
arrayEq [1, 2, 3] , [1..3]
arrayEq [0, 1, 2] , [0..2]
arrayEq [0, 1] , [0..1]
arrayEq [0] , [0..0]
arrayEq [-1] , [-1..-1]
arrayEq [-1, 0] , [-1..0]
arrayEq [-1, 0, 1], [-1..1]
test "basic exclusive ranges", ->
arrayEq [1, 2, 3] , [1...4]
arrayEq [0, 1, 2] , [0...3]
arrayEq [0, 1] , [0...2]
arrayEq [0] , [0...1]
arrayEq [-1] , [-1...0]
arrayEq [-1, 0] , [-1...1]
arrayEq [-1, 0, 1], [-1...2]
arrayEq [], [1...1]
arrayEq [], [0...0]
arrayEq [], [-1...-1]
test "downward ranges", ->
arrayEq shared, [9..0].reverse()
arrayEq [5, 4, 3, 2] , [5..2]
arrayEq [2, 1, 0, -1], [2..-1]
arrayEq [3, 2, 1] , [3..1]
arrayEq [2, 1, 0] , [2..0]
arrayEq [1, 0] , [1..0]
arrayEq [0] , [0..0]
arrayEq [-1] , [-1..-1]
arrayEq [0, -1] , [0..-1]
arrayEq [1, 0, -1] , [1..-1]
arrayEq [0, -1, -2], [0..-2]
arrayEq [4, 3, 2], [4...1]
arrayEq [3, 2, 1], [3...0]
arrayEq [2, 1] , [2...0]
arrayEq [1] , [1...0]
arrayEq [] , [0...0]
arrayEq [] , [-1...-1]
arrayEq [0] , [0...-1]
arrayEq [0, -1] , [0...-2]
arrayEq [1, 0] , [1...-1]
arrayEq [2, 1, 0], [2...-1]
test "ranges with variables as enpoints", ->
[a, b] = [1, 3]
arrayEq [1, 2, 3], [a..b]
arrayEq [1, 2] , [a...b]
b = -2
arrayEq [1, 0, -1, -2], [a..b]
arrayEq [1, 0, -1] , [a...b]
test "ranges with expressions as endpoints", ->
[a, b] = [1, 3]
arrayEq [2, 3, 4, 5, 6], [(a+1)..2*b]
arrayEq [2, 3, 4, 5] , [(a+1)...2*b]
test "large ranges are generated with looping constructs", ->
down = [99..0]
eq 100, (len = down.length)
eq 0, down[len - 1]
up = [0...100]
eq 100, (len = up.length)
eq 99, up[len - 1]
test "for-from loops over ranges", ->
array1 = []
for x from [20..30]
array1.push(x)
break if x is 25
arrayEq array1, [20, 21, 22, 23, 24, 25]
test "for-from comprehensions over ranges", ->
array1 = (x + 10 for x from [20..25])
ok array1.join(' ') is '30 31 32 33 34 35'
array2 = (x for x from [20..30] when x %% 2 == 0)
ok array2.join(' ') is '20 22 24 26 28 30'
test "#1012 slices with arguments object", ->
expected = [0..9]
argsAtStart = (-> [arguments[0]..9]) 0
arrayEq expected, argsAtStart
argsAtEnd = (-> [0..arguments[0]]) 9
arrayEq expected, argsAtEnd
argsAtBoth = (-> [arguments[0]..arguments[1]]) 0, 9
arrayEq expected, argsAtBoth
test "#1409: creating large ranges outside of a function body", ->
CoffeeScript.eval '[0..100]'
</script>
<script type="text/x-coffeescript" class="test" id="regexps">
# Regular Expression Literals
# ---------------------------
# TODO: add method invocation tests: /regex/.toString()
# * Regexen
# * Heregexen
test "basic regular expression literals", ->
ok 'a'.match(/a/)
ok 'a'.match /a/
ok 'a'.match(/a/g)
ok 'a'.match /a/g
test "division is not confused for a regular expression", ->
# Any spacing around the slash is allowed when it cannot be a regex.
eq 2, 4 / 2 / 1
eq 2, 4/2/1
eq 2, 4/ 2 / 1
eq 2, 4 /2 / 1
eq 2, 4 / 2/ 1
eq 2, 4 / 2 /1
eq 2, 4 /2/ 1
a = (regex) -> regex.test 'a b c'
a.valueOf = -> 4
b = 2
g = 1
eq 2, a / b/g
eq 2, a/ b/g
eq 2, a / b/ g
eq 2, a / b/g # Tabs.
eq 2, a / b/g # Non-breaking spaces.
eq true, a /b/g
# Use parentheses to disambiguate.
eq true, a(/ b/g)
eq true, a(/ b/)
eq true, a (/ b/)
# Escape to disambiguate.
eq true, a /\ b/g
eq false, a /\ b/g
eq true, a /\ b/
obj = method: -> 2
two = 2
eq 2, (obj.method()/two + obj.method()/two)
i = 1
eq 2, (4)/2/i
eq 1, i/i/i
a = ''
a += ' ' until / /.test a
eq a, ' '
a = if /=/.test '=' then yes else no
eq a, yes
a = if !/=/.test '=' then yes else no
eq a, no
#3182:
match = 'foo=bar'.match /=/
eq match[0], '='
#3410:
ok ' '.match(/ /)[0] is ' '
test "division vs regex after a callable token", ->
b = 2
g = 1
r = (r) -> r.test 'b'
a = 4
eq 2, a / b/g
eq 2, a/b/g
eq 2, a/ b/g
eq true, r /b/g
eq 2, (1 + 3) / b/g
eq 2, (1 + 3)/b/g
eq 2, (1 + 3)/ b/g
eq true, (r) /b/g
eq 2, [4][0] / b/g
eq 2, [4][0]/b/g
eq 2, [4][0]/ b/g
eq true, [r][0] /b/g
eq 0.5, 4? / b/g
eq 0.5, 4?/b/g
eq 0.5, 4?/ b/g
eq true, r? /b/g
(->
eq 2, @ / b/g
eq 2, @/b/g
eq 2, @/ b/g
).call 4
(->
eq true, @ /b/g
).call r
(->
eq 2, this / b/g
eq 2, this/b/g
eq 2, this/ b/g
).call 4
(->
eq true, this /b/g
).call r
class A
p: (regex) -> if regex then r regex else 4
class B extends A
p: ->
eq 2, super / b/g
eq 2, super/b/g
eq 2, super/ b/g
eq true, super /b/g
new B().p()
test "always division and never regex after some tokens", ->
b = 2
g = 1
eq 2, 4 / b/g
eq 2, 4/b/g
eq 2, 4/ b/g
eq 2, 4 /b/g
eq 2, "4" / b/g
eq 2, "4"/b/g
eq 2, "4"/ b/g
eq 2, "4" /b/g
eq 20, "4#{0}" / b/g
eq 20, "4#{0}"/b/g
eq 20, "4#{0}"/ b/g
eq 20, "4#{0}" /b/g
ok isNaN /a/ / b/g
ok isNaN /a/i / b/g
ok isNaN /a//b/g
ok isNaN /a/i/b/g
ok isNaN /a// b/g
ok isNaN /a/i/ b/g
ok isNaN /a/ /b/g
ok isNaN /a/i /b/g
eq 0.5, true / b/g
eq 0.5, true/b/g
eq 0.5, true/ b/g
eq 0.5, true /b/g
eq 0, false / b/g
eq 0, false/b/g
eq 0, false/ b/g
eq 0, false /b/g
eq 0, null / b/g
eq 0, null/b/g
eq 0, null/ b/g
eq 0, null /b/g
ok isNaN undefined / b/g
ok isNaN undefined/b/g
ok isNaN undefined/ b/g
ok isNaN undefined /b/g
ok isNaN {a: 4} / b/g
ok isNaN {a: 4}/b/g
ok isNaN {a: 4}/ b/g
ok isNaN {a: 4} /b/g
o = prototype: 4
eq 2, o:: / b/g
eq 2, o::/b/g
eq 2, o::/ b/g
eq 2, o:: /b/g
i = 4
eq 2.0, i++ / b/g
eq 2.5, i++/b/g
eq 3.0, i++/ b/g
eq 3.5, i++ /b/g
eq 4.0, i-- / b/g
eq 3.5, i--/b/g
eq 3.0, i--/ b/g
eq 2.5, i-- /b/g
test "compound division vs regex", ->
c = 4
i = 2
a = 10
b = a /= c / i
eq a, 5
a = 10
b = a /= c /i
eq a, 5
a = 10
b = a /= c /i # Tabs.
eq a, 5
a = 10
b = a /= c /i # Non-breaking spaces.
eq a, 5
a = 10
b = a/= c /i
eq a, 5
a = 10
b = a/=c/i
eq a, 5
a = (regex) -> regex.test '=C '
b = a /=c /i
eq b, true
a = (regex) -> regex.test '= C '
# Use parentheses to disambiguate.
b = a(/= c /i)
eq b, true
b = a(/= c /)
eq b, false
b = a (/= c /)
eq b, false
# Escape to disambiguate.
b = a /\= c /i
eq b, true
b = a /\= c /
eq b, false
test "#764: regular expressions should be indexable", ->
eq /0/['source'], ///#{0}///['source']
test "#584: slashes are allowed unescaped in character classes", ->
ok /^a\/[/]b$/.test 'a//b'
test "does not allow to escape newlines", ->
throws -> CoffeeScript.compile '/a\\\nb/'
# Heregexe(n|s)
test "a heregex will ignore whitespace and comments", ->
eq /^I'm\x20+[a]\s+Heregex?\/\/\//gim + '', ///
^ I'm \x20+ [a] \s+
Heregex? / // # or not
///gim + ''
test "an empty heregex will compile to an empty, non-capturing group", ->
eq /(?:)/ + '', /// /// + ''
eq /(?:)/ + '', ////// + ''
test "heregex starting with slashes", ->
ok /////a/\////.test ' //a// '
test '#2388: `///` in heregex interpolations', ->
ok ///a#{///b///}c///.test ' /a/b/c/ '
ws = ' \t'
scan = (regex) -> regex.exec('\t foo')[0]
eq '/\t /', /// #{scan /// [#{ws}]* ///} /// + ''
test "regexes are not callable", ->
throws -> CoffeeScript.compile '/a/()'
throws -> CoffeeScript.compile '///a#{b}///()'
throws -> CoffeeScript.compile '/a/ 1'
throws -> CoffeeScript.compile '///a#{b}/// 1'
throws -> CoffeeScript.compile '''
/a/
k: v
'''
throws -> CoffeeScript.compile '''
///a#{b}///
k: v
'''
test "backreferences", ->
ok /(a)(b)\2\1/.test 'abba'
test "#3795: Escape otherwise invalid characters", ->
ok (//).test '\u2028'
ok (//).test '\u2029'
ok ///\///.test '\u2028'
ok ///\///.test '\u2029'
ok ///ab///.test 'ab' # The space is U+2028.
ok ///ab///.test 'ab' # The space is U+2029.
ok ///\0
1///.test '\x001'
a = 'a'
ok ///#{a}b///.test 'ab' # The space is U+2028.
ok ///#{a}b///.test 'ab' # The space is U+2029.
ok ///#{a}\///.test 'a\u2028'
ok ///#{a}\///.test 'a\u2029'
ok ///#{a}\0
1///.test 'a\x001'
test "#4248: Unicode code point escapes", ->
# Support for the `u` flag in regexes was added in Node 6.
return if new RegExp().unicode is undefined
ok /a\u{1ab}c/u.test 'a\u01abc'
ok ///#{ 'a' }\u{000001ab}c///u.test 'a\u{1ab}c'
ok ///a\u{000001ab}c///u.test 'a\u{1ab}c'
ok /a\u{12345}c/u.test 'a\ud808\udf45c'
# and now without u flag
ok /a\u{1ab}c/.test 'a\u01abc'
ok ///#{ 'a' }\u{000001ab}c///.test 'a\u{1ab}c'
ok ///a\u{000001ab}c///.test 'a\u{1ab}c'
ok /a\u{12345}c/.test 'a\ud808\udf45c'
# rewrite code point escapes
input = """
/\\u{bcdef}\\u{abc}/u
"""
output = """
/\\udab3\\uddef\\u0abc/u;
"""
eq toJS(input), output
input = """
///#{ 'a' }\\u{bcdef}///
"""
output = """
/a\\udab3\\uddef/;
"""
eq toJS(input), output
</script>
<script type="text/x-coffeescript" class="test" id="repl">
return if global.testingBrowser
os = require 'os'
fs = require 'fs'
path = require 'path'
# REPL
# ----
Stream = require 'stream'
class MockInputStream extends Stream
constructor: ->
@readable = true
resume: ->
emitLine: (val) ->
@emit 'data', new Buffer("#{val}\n")
class MockOutputStream extends Stream
constructor: ->
@writable = true
@written = []
write: (data) ->
#console.log 'output write', arguments
@written.push data
lastWrite: (fromEnd = -1) ->
@written[@written.length - 1 + fromEnd].replace /\r?\n$/, ''
# Create a dummy history file
historyFile = path.join os.tmpdir(), '.coffee_history_test'
fs.writeFileSync historyFile, '1 + 2\n'
testRepl = (desc, fn) ->
input = new MockInputStream
output = new MockOutputStream
repl = Repl.start {input, output, historyFile}
test desc, -> fn input, output, repl
ctrlV = { ctrl: true, name: 'v'}
testRepl 'reads history file', (input, output, repl) ->
input.emitLine repl.rli.history[0]
eq '3', output.lastWrite()
testRepl "starts with coffee prompt", (input, output) ->
eq 'coffee> ', output.lastWrite(0)
testRepl "writes eval to output", (input, output) ->
input.emitLine '1+1'
eq '2', output.lastWrite()
testRepl "comments are ignored", (input, output) ->
input.emitLine '1 + 1 #foo'
eq '2', output.lastWrite()
testRepl "output in inspect mode", (input, output) ->
input.emitLine '"1 + 1\\n"'
eq "'1 + 1\\n'", output.lastWrite()
testRepl "variables are saved", (input, output) ->
input.emitLine "foo = 'foo'"
input.emitLine 'foobar = "#{foo}bar"'
eq "'foobar'", output.lastWrite()
testRepl "empty command evaluates to undefined", (input, output) ->
# A regression fixed in Node 5.11.0 broke the handling of pressing enter in
# the Node REPL; see https://github.com/nodejs/node/pull/6090 and
# https://github.com/jashkenas/coffeescript/issues/4502.
# Just skip this test for versions of Node < 6.
return if parseInt(process.versions.node.split('.')[0], 10) < 6
input.emitLine ''
eq 'undefined', output.lastWrite()
testRepl "ctrl-v toggles multiline prompt", (input, output) ->
input.emit 'keypress', null, ctrlV
eq '------> ', output.lastWrite(0)
input.emit 'keypress', null, ctrlV
eq 'coffee> ', output.lastWrite(0)
testRepl "multiline continuation changes prompt", (input, output) ->
input.emit 'keypress', null, ctrlV
input.emitLine ''
eq '....... ', output.lastWrite(0)
testRepl "evaluates multiline", (input, output) ->
# Stubs. Could assert on their use.
output.cursorTo = (pos) ->
output.clearLine = ->
input.emit 'keypress', null, ctrlV
input.emitLine 'do ->'
input.emitLine ' 1 + 1'
input.emit 'keypress', null, ctrlV
eq '2', output.lastWrite()
testRepl "variables in scope are preserved", (input, output) ->
input.emitLine 'a = 1'
input.emitLine 'do -> a = 2'
input.emitLine 'a'
eq '2', output.lastWrite()
testRepl "existential assignment of previously declared variable", (input, output) ->
input.emitLine 'a = null'
input.emitLine 'a ?= 42'
eq '42', output.lastWrite()
testRepl "keeps running after runtime error", (input, output) ->
input.emitLine 'a = b'
input.emitLine 'a'
eq 'undefined', output.lastWrite()
process.on 'exit', ->
try
fs.unlinkSync historyFile
catch exception # Already deleted, nothing else to do.
</script>
<script type="text/x-coffeescript" class="test" id="scope">
# Scope
# -----
# * Variable Safety
# * Variable Shadowing
# * Auto-closure (`do`)
# * Global Scope Leaks
test "reference `arguments` inside of functions", ->
sumOfArgs = ->
sum = (a,b) -> a + b
sum = 0
sum += num for num in arguments
sum
eq 10, sumOfArgs(0, 1, 2, 3, 4)
test "assignment to an Object.prototype-named variable should not leak to outer scope", ->
# FIXME: fails on IE
(->
constructor = 'word'
)()
ok constructor isnt 'word'
test "siblings of splat parameters shouldn't leak to surrounding scope", ->
x = 10
oops = (x, args...) ->
oops(20, 1, 2, 3)
eq x, 10
test "catch statements should introduce their argument to scope", ->
try throw ''
catch e
do -> e = 5
eq 5, e
test "loop variable should be accessible after for-of loop", ->
d = (x for x of {1:'a',2:'b'})
ok x in ['1','2']
test "loop variable should be accessible after for-in loop", ->
d = (x for x in [1,2])
eq x, 2
test "loop variable should be accessible after for-from loop", ->
d = (x for x from [1,2])
eq x, 2
class Array then slice: fail # needs to be global
class Object then hasOwnProperty: fail
test "#1973: redefining Array/Object constructors shouldn't confuse __X helpers", ->
arr = [1..4]
arrayEq [3, 4], arr[2..]
obj = {arr}
for own k of obj
eq arr, obj[k]
test "#2255: global leak with splatted @-params", ->
ok not x?
arrayEq [0], ((@x...) -> @x).call {}, 0
ok not x?
test "#1183: super + fat arrows", ->
dolater = (cb) -> cb()
class A
constructor: ->
@_i = 0
foo : (cb) ->
dolater =>
@_i += 1
cb()
class B extends A
constructor : ->
super
foo : (cb) ->
dolater =>
dolater =>
@_i += 2
super cb
b = new B
b.foo => eq b._i, 3
test "#1183: super + wrap", ->
class A
m : -> 10
class B extends A
constructor : -> super
B::m = -> r = try super()
eq (new B).m(), 10
test "#1183: super + closures", ->
class A
constructor: ->
@i = 10
foo : -> @i
class B extends A
foo : ->
ret = switch 1
when 0 then 0
when 1 then super()
ret
eq (new B).foo(), 10
test "#2331: bound super regression", ->
class A
@value = 'A'
method: -> @constructor.value
class B extends A
method: => super
eq (new B).method(), 'A'
test "#3259: leak with @-params within destructured parameters", ->
fn = ({@foo}, [@bar], [{@baz}]) ->
foo = bar = baz = false
fn.call {}, {foo: 'foo'}, ['bar'], [{baz: 'baz'}]
eq 'undefined', typeof foo
eq 'undefined', typeof bar
eq 'undefined', typeof baz
</script>
<script type="text/x-coffeescript" class="test" id="slicing_and_splicing">
# Slicing and Splicing
# --------------------
# * Slicing
# * Splicing
# shared array
shared = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# Slicing
test "basic slicing", ->
arrayEq [7, 8, 9] , shared[7..9]
arrayEq [2, 3] , shared[2...4]
arrayEq [2, 3, 4, 5], shared[2...6]
test "slicing with variables as endpoints", ->
[a, b] = [1, 4]
arrayEq [1, 2, 3, 4], shared[a..b]
arrayEq [1, 2, 3] , shared[a...b]
test "slicing with expressions as endpoints", ->
[a, b] = [1, 3]
arrayEq [2, 3, 4, 5, 6], shared[(a+1)..2*b]
arrayEq [2, 3, 4, 5] , shared[a+1...(2*b)]
test "unbounded slicing", ->
arrayEq [7, 8, 9] , shared[7..]
arrayEq [8, 9] , shared[-2..]
arrayEq [9] , shared[-1...]
arrayEq [0, 1, 2] , shared[...3]
arrayEq [0, 1, 2, 3], shared[..-7]
arrayEq shared , shared[..-1]
arrayEq shared[0..8], shared[...-1]
for a in [-shared.length..shared.length]
arrayEq shared[a..] , shared[a...]
for a in [-shared.length+1...shared.length]
arrayEq shared[..a][...-1] , shared[...a]
arrayEq [1, 2, 3], [1, 2, 3][..]
test "#930, #835, #831, #746 #624: inclusive slices to -1 should slice to end", ->
arrayEq shared, shared[0..-1]
arrayEq shared, shared[..-1]
arrayEq shared.slice(1,shared.length), shared[1..-1]
test "string slicing", ->
str = "abcdefghijklmnopqrstuvwxyz"
ok str[1...1] is ""
ok str[1..1] is "b"
ok str[1...5] is "bcde"
ok str[0..4] is "abcde"
ok str[-5..] is "vwxyz"
test "#1722: operator precedence in unbounded slice compilation", ->
list = [0..9]
n = 2 # some truthy number in `list`
arrayEq [0..n], list[..n]
arrayEq [0..n], list[..n or 0]
arrayEq [0..n], list[..if n then n else 0]
test "#2349: inclusive slicing to numeric strings", ->
arrayEq [0, 1], [0..10][.."1"]
# Splicing
test "basic splicing", ->
ary = [0..9]
ary[5..9] = [0, 0, 0]
arrayEq [0, 1, 2, 3, 4, 0, 0, 0], ary
ary = [0..9]
ary[2...8] = []
arrayEq [0, 1, 8, 9], ary
test "unbounded splicing", ->
ary = [0..9]
ary[3..] = [9, 8, 7]
arrayEq [0, 1, 2, 9, 8, 7]. ary
ary[...3] = [7, 8, 9]
arrayEq [7, 8, 9, 9, 8, 7], ary
ary[..] = [1, 2, 3]
arrayEq [1, 2, 3], ary
test "splicing with variables as endpoints", ->
[a, b] = [1, 8]
ary = [0..9]
ary[a..b] = [2, 3]
arrayEq [0, 2, 3, 9], ary
ary = [0..9]
ary[a...b] = [5]
arrayEq [0, 5, 8, 9], ary
test "splicing with expressions as endpoints", ->
[a, b] = [1, 3]
ary = [0..9]
ary[ a+1 .. 2*b+1 ] = [4]
arrayEq [0, 1, 4, 8, 9], ary
ary = [0..9]
ary[a+1...2*b+1] = [4]
arrayEq [0, 1, 4, 7, 8, 9], ary
test "splicing to the end, against a one-time function", ->
ary = null
fn = ->
if ary
throw 'err'
else
ary = [1, 2, 3]
fn()[0..] = 1
arrayEq ary, [1]
test "the return value of a splice literal should be the RHS", ->
ary = [0, 0, 0]
eq (ary[0..1] = 2), 2
ary = [0, 0, 0]
eq (ary[0..] = 3), 3
arrayEq [ary[0..0] = 0], [0]
test "#1723: operator precedence in unbounded splice compilation", ->
n = 4 # some truthy number in `list`
list = [0..9]
list[..n] = n
arrayEq [n..9], list
list = [0..9]
list[..n or 0] = n
arrayEq [n..9], list
list = [0..9]
list[..if n then n else 0] = n
arrayEq [n..9], list
test "#2953: methods on endpoints in assignment from array splice literal", ->
list = [0..9]
Number.prototype.same = -> this
list[1.same()...9.same()] = 5
delete Number.prototype.same
arrayEq [0, 5, 9], list
</script>
<script type="text/x-coffeescript" class="test" id="soaks">
# Soaks
# -----
# * Soaked Property Access
# * Soaked Method Invocation
# * Soaked Function Invocation
# Soaked Property Access
test "soaked property access", ->
nonce = {}
obj = a: b: nonce
eq nonce , obj?.a.b
eq nonce , obj?['a'].b
eq nonce , obj.a?.b
eq nonce , obj?.a?['b']
eq undefined, obj?.a?.non?.existent?.property
test "soaked property access caches method calls", ->
nonce ={}
obj = fn: -> a: nonce
eq nonce , obj.fn()?.a
eq undefined, obj.fn()?.b
test "soaked property access caching", ->
nonce = {}
counter = 0
fn = ->
counter++
'self'
obj =
self: -> @
prop: nonce
eq nonce, obj[fn()]()[fn()]()[fn()]()?.prop
eq 3, counter
test "method calls on soaked methods", ->
nonce = {}
obj = null
eq undefined, obj?.a().b()
obj = a: -> b: -> nonce
eq nonce , obj?.a().b()
test "postfix existential operator mixes well with soaked property accesses", ->
eq false, nonexistent?.property?
test "function invocation with soaked property access", ->
id = (_) -> _
eq undefined, id nonexistent?.method()
test "if-to-ternary should safely parenthesize soaked property accesses", ->
ok (if nonexistent?.property then false else true)
test "#726: don't check for a property on a conditionally-referenced nonexistent thing", ->
eq undefined, nonexistent?[Date()]
test "#756: conditional assignment edge cases", ->
# TODO: improve this test
a = null
ok isNaN a?.b.c + 1
eq undefined, a?.b.c += 1
eq undefined, ++a?.b.c
eq undefined, delete a?.b.c
test "operations on soaked properties", ->
# TODO: improve this test
a = b: {c: 0}
eq 1, a?.b.c + 1
eq 1, a?.b.c += 1
eq 2, ++a?.b.c
eq yes, delete a?.b.c
# Soaked Method Invocation
test "soaked method invocation", ->
nonce = {}
counter = 0
obj =
self: -> @
increment: -> counter++; @
eq obj , obj.self?()
eq undefined, obj.method?()
eq nonce , obj.self?().property = nonce
eq undefined, obj.method?().property = nonce
eq obj , obj.increment().increment().self?()
eq 2 , counter
test "#733: conditional assignments", ->
a = b: {c: null}
eq a.b?.c?(), undefined
a.b?.c or= (it) -> it
eq a.b?.c?(1), 1
eq a.b?.c?([2, 3]...), 2
# Soaked Function Invocation
test "soaked function invocation", ->
nonce = {}
id = (_) -> _
eq nonce , id?(nonce)
eq nonce , (id? nonce)
eq undefined, nonexistent?(nonce)
eq undefined, (nonexistent? nonce)
test "soaked function invocation with generated functions", ->
nonce = {}
id = (_) -> _
maybe = (fn, arg) -> if typeof fn is 'function' then () -> fn(arg)
eq maybe(id, nonce)?(), nonce
eq (maybe id, nonce)?(), nonce
eq (maybe false, nonce)?(), undefined
test "soaked constructor invocation", ->
eq 42 , +new Number? 42
eq undefined, new Other? 42
test "soaked constructor invocations with caching and property access", ->
semaphore = 0
nonce = {}
class C
constructor: ->
ok false if semaphore
semaphore++
prop: nonce
eq nonce, (new C())?.prop
eq 1, semaphore
test "soaked function invocation safe on non-functions", ->
eq undefined, (0)?(1)
eq undefined, (0)? 1, 2
</script>
<script type="text/x-coffeescript" class="test" id="sourcemap">
return if global.testingBrowser
SourceMap = require '../src/sourcemap'
vlqEncodedValues = [
[1, "C"],
[-1, "D"],
[2, "E"],
[-2, "F"],
[0, "A"],
[16, "gB"],
[948, "o7B"]
]
test "encodeVlq tests", ->
for pair in vlqEncodedValues
eq ((new SourceMap).encodeVlq pair[0]), pair[1]
test "SourceMap tests", ->
map = new SourceMap
map.add [0, 0], [0, 0]
map.add [1, 5], [2, 4]
map.add [1, 6], [2, 7]
map.add [1, 9], [2, 8]
map.add [3, 0], [3, 4]
testWithFilenames = map.generate {
sourceRoot: ""
sourceFiles: ["source.coffee"]
generatedFile: "source.js"
}
deepEqual testWithFilenames, {
version: 3
file: "source.js"
sourceRoot: ""
sources: ["source.coffee"]
names: []
mappings: "AAAA;;IACK,GAAC,CAAG;IAET"
}
deepEqual map.generate(), {
version: 3
file: ""
sourceRoot: ""
sources: [""]
names: []
mappings: "AAAA;;IACK,GAAC,CAAG;IAET"
}
# Look up a generated column - should get back the original source position.
arrayEq map.sourceLocation([2,8]), [1,9]
# Look up a point further along on the same line - should get back the same source position.
arrayEq map.sourceLocation([2,10]), [1,9]
</script>
<script type="text/x-coffeescript" class="test" id="strict">
# Strict Early Errors
# -------------------
# The following are prohibited under ES5's `strict` mode
# * `Octal Integer Literals`
# * `Octal Escape Sequences`
# * duplicate property definitions in `Object Literal`s
# * duplicate formal parameter
# * `delete` operand is a variable
# * `delete` operand is a parameter
# * `delete` operand is `undefined`
# * `Future Reserved Word`s as identifiers: implements, interface, let, package, private, protected, public, static, yield
# * `eval` or `arguments` as `catch` identifier
# * `eval` or `arguments` as formal parameter
# * `eval` or `arguments` as function declaration identifier
# * `eval` or `arguments` as LHS of assignment
# * `eval` or `arguments` as the operand of a post/pre-fix inc/dec-rement expression
# helper to assert that code complies with strict prohibitions
strict = (code, msg) ->
throws (-> CoffeeScript.compile code), null, msg ? code
strictOk = (code, msg) ->
doesNotThrow (-> CoffeeScript.compile code), msg ? code
test "octal integer literals prohibited", ->
strict '01'
strict '07777'
# decimals with a leading '0' are also prohibited
strict '09'
strict '079'
strictOk '`01`'
test "octal escape sequences prohibited", ->
strict '"\\1"'
strict '"\\7"'
strict '"\\001"'
strict '"\\777"'
strict '"_\\1"'
strict '"\\1_"'
strict '"_\\1_"'
strict '"\\\\\\1"'
strictOk '"\\0"'
eq "\x00", "\0"
strictOk '"\\08"'
eq "\x008", "\08"
strictOk '"\\0\\8"'
eq "\x008", "\0\8"
strictOk '"\\8"'
eq "8", "\8"
strictOk '"\\\\1"'
eq "\\" + "1", "\\1"
strictOk '"\\\\\\\\1"'
eq "\\\\" + "1", "\\\\1"
strictOk "`'\\1'`"
eq "\\" + "1", `"\\1"`
# Also test other string types.
strict "'\\\\\\1'"
eq "\x008", '\08'
eq "\\\\" + "1", '\\\\1'
strict "'''\\\\\\1'''"
eq "\x008", '''\08'''
eq "\\\\" + "1", '''\\\\1'''
strict '"""\\\\\\1"""'
eq "\x008", """\08"""
eq "\\\\" + "1", """\\\\1"""
test "duplicate formal parameters are prohibited", ->
nonce = {}
# a Param can be an Identifier, ThisProperty( @-param ), Array, or Object
# a Param can also be a splat (...) or an assignment (param=value)
# the following function expressions should throw errors
strict '(_,_)->', 'param, param'
strict '(_,_...)->', 'param, param...'
strict '(_,_ = true)->', 'param, param='
strict '(@_,@_)->', 'two @params'
strict '(@case,@case)->', 'two @reserved'
strict '(_,{_})->', 'param, {param}'
strict '(_,{_=true})->', 'param, {param=}'
strict '({_,_})->', '{param, param}'
strict '({_=true,_})->', '{param=, param}'
strict '(_,[_])->', 'param, [param]'
strict '(_,[_=true])->', 'param, [param=]'
strict '([_,_])->', '[param, param]'
strict '([_=true,_])->', '[param=, param]'
strict '(_,[_]=true)->', 'param, [param]='
strict '(_,[_=true]=true)->', 'param, [param=]='
strict '(_,[@_,{_}])->', 'param, [@param, {param}]'
strict '(_,[_,{@_}])->', 'param, [param, {@param}]'
strict '(_,[_,{@_=true}])->', 'param, [param, {@param=}]'
strict '(_,[_,{_}])->', 'param, [param, {param}]'
strict '(_,[_,{__}])->', 'param, [param, {param2}]'
strict '(_,[__,{_}])->', 'param, [param2, {param}]'
strict '(__,[_,{_}])->', 'param, [param2, {param2}]'
strict '({0:a,1:a})->', '{0:param,1:param}'
strict '(a=b=true,a)->', 'param=assignment, param'
strict '({a=b=true},a)->', '{param=assignment}, param'
# the following function expressions should **not** throw errors
strictOk '(_,@_)->'
strictOk '(@_,_...)->'
strictOk '(_,@_ = true)->'
strictOk '(@_,{_})->'
strictOk '({_,@_})->'
strictOk '({_,@_ = true})->'
strictOk '([_,@_])->'
strictOk '([_,@_ = true])->'
strictOk '({},_arg)->'
strictOk '({},{})->'
strictOk '([]...,_arg)->'
strictOk '({}...,_arg)->'
strictOk '({}...,[],_arg)->'
strictOk '([]...,{},_arg)->'
strictOk '(@case,_case)->'
strictOk '(@case,_case...)->'
strictOk '(@case...,_case)->'
strictOk '(_case,@case)->'
strictOk '(_case,@case...)->'
strictOk '({a:a})->'
strictOk '({a:a,a:b})->'
test "`delete` operand restrictions", ->
strict 'a = 1; delete a'
strictOk 'delete a' #noop
strict '(a) -> delete a'
strict '(a...) -> delete a'
strict '(a = 1) -> delete a'
strict '([a]) -> delete a'
strict '({a}) -> delete a'
test "`Future Reserved Word`s, `eval` and `arguments` restrictions", ->
access = (keyword, check = strict) ->
check "#{keyword}.a = 1"
check "#{keyword}[0] = 1"
assign = (keyword, check = strict) ->
check "#{keyword} = 1"
check "#{keyword} += 1"
check "#{keyword} -= 1"
check "#{keyword} *= 1"
check "#{keyword} /= 1"
check "#{keyword} ?= 1"
check "#{keyword}++"
check "++#{keyword}"
check "#{keyword}--"
check "--#{keyword}"
destruct = (keyword, check = strict) ->
check "{#{keyword}}"
check "o = {#{keyword}}"
invoke = (keyword, check = strict) ->
check "#{keyword} yes"
check "do #{keyword}"
fnDecl = (keyword, check = strict) ->
check "class #{keyword}"
param = (keyword, check = strict) ->
check "(#{keyword}) ->"
check "({#{keyword}}) ->"
prop = (keyword, check = strict) ->
check "a.#{keyword} = 1"
tryCatch = (keyword, check = strict) ->
check "try new Error catch #{keyword}"
future = 'implements interface let package private protected public static'.split ' '
for keyword in future
access keyword
assign keyword
destruct keyword
invoke keyword
fnDecl keyword
param keyword
prop keyword, strictOk
tryCatch keyword
for keyword in ['eval', 'arguments']
access keyword, strictOk
assign keyword
destruct keyword, strictOk
invoke keyword, strictOk
fnDecl keyword
param keyword
prop keyword, strictOk
tryCatch keyword
</script>
<script type="text/x-coffeescript" class="test" id="strings">
# String Literals
# ---------------
# TODO: refactor string literal tests
# TODO: add indexing and method invocation tests: "string"["toString"] is String::toString, "string".toString() is "string"
# * Strings
# * Heredocs
test "backslash escapes", ->
eq "\\/\\\\", /\/\\/.source
eq '(((dollars)))', '\(\(\(dollars\)\)\)'
eq 'one two three', "one
two
three"
eq "four five", 'four
five'
test "#3229, multiline strings", ->
# Separate lines by default by a single space in literal strings.
eq 'one
two', 'one two'
eq "one
two", 'one two'
eq '
a
b
', 'a b'
eq "
a
b
", 'a b'
eq 'one
two', 'one two'
eq "one
two", 'one two'
eq '
indentation
doesn\'t
matter', 'indentation doesn\'t matter'
eq 'trailing ws
doesn\'t matter', 'trailing ws doesn\'t matter'
# Use backslashes at the end of a line to specify whitespace between lines.
eq 'a \
b\
c \
d', 'a bc d'
eq "a \
b\
c \
d", 'a bc d'
eq 'ignore \
trailing whitespace', 'ignore trailing whitespace'
# Backslash at the beginning of a literal string.
eq '\
ok', 'ok'
eq ' \
ok', ' ok'
# #1273, empty strings.
eq '\
', ''
eq '
', ''
eq '
', ''
eq ' ', ' '
# Same behavior in interpolated strings.
eq "interpolation #{1}
follows #{2} \
too #{3}\
!", 'interpolation 1 follows 2 too 3!'
eq "a #{
'string ' + "inside
interpolation"
}", "a string inside interpolation"
eq "
#{1}
", '1'
# Handle escaped backslashes correctly.
eq '\\', `'\\'`
eq 'escaped backslash at EOL\\
next line', 'escaped backslash at EOL\\ next line'
eq '\\
next line', '\\ next line'
eq '\\
', '\\'
eq '\\\\\\
', '\\\\\\'
eq "#{1}\\
after interpolation", '1\\ after interpolation'
eq 'escaped backslash before slash\\ \
next line', 'escaped backslash before slash\\ next line'
eq 'triple backslash\\\
next line', 'triple backslash\\next line'
eq 'several escaped backslashes\\\\\\
ok', 'several escaped backslashes\\\\\\ ok'
eq 'several escaped backslashes slash\\\\\\\
ok', 'several escaped backslashes slash\\\\\\ok'
eq 'several escaped backslashes with trailing ws \\\\\\
ok', 'several escaped backslashes with trailing ws \\\\\\ ok'
# Backslashes at beginning of lines.
eq 'first line
\ backslash at BOL', 'first line \ backslash at BOL'
eq 'first line\
\ backslash at BOL', 'first line\ backslash at BOL'
# Backslashes at end of strings.
eq 'first line \ ', 'first line '
eq 'first line
second line \
', 'first line second line '
eq 'first line
second line
\
', 'first line second line'
eq 'first line
second line
\
', 'first line second line'
# Edge case.
eq 'lone
\
backslash', 'lone backslash'
test "#3249, escape newlines in heredocs with backslashes", ->
# Ignore escaped newlines
eq '''
Set whitespace \
<- this is ignored\
none
normal indentation
''', 'Set whitespace <- this is ignorednone\n normal indentation'
eq """
Set whitespace \
<- this is ignored\
none
normal indentation
""", 'Set whitespace <- this is ignorednone\n normal indentation'
# Changed from #647, trailing backslash.
eq '''
Hello, World\
''', 'Hello, World'
eq '''
\\
''', '\\'
# Backslash at the beginning of a literal string.
eq '''\
ok''', 'ok'
eq ''' \
ok''', ' ok'
# Same behavior in interpolated strings.
eq """
interpolation #{1}
follows #{2} \
too #{3}\
!
""", 'interpolation 1\n follows 2 too 3!'
eq """
#{1} #{2}
""", '\n1 2\n'
# Handle escaped backslashes correctly.
eq '''
escaped backslash at EOL\\
next line
''', 'escaped backslash at EOL\\\n next line'
eq '''\\
''', '\\\n'
# Backslashes at beginning of lines.
eq '''first line
\ backslash at BOL''', 'first line\n\ backslash at BOL'
eq """first line\
\ backslash at BOL""", 'first line\ backslash at BOL'
# Backslashes at end of strings.
eq '''first line \ ''', 'first line '
eq '''
first line
second line \
''', 'first line\nsecond line '
eq '''
first line
second line
\
''', 'first line\nsecond line'
eq '''
first line
second line
\
''', 'first line\nsecond line\n'
# Edge cases.
eq '''lone
\
backslash''', 'lone\n\n backslash'
eq '''\
''', ''
test '#2388: `"""` in heredoc interpolations', ->
eq """a heredoc #{
"inside \
interpolation"
}""", "a heredoc inside interpolation"
eq """a#{"""b"""}c""", 'abc'
eq """#{""""""}""", ''
test "trailing whitespace", ->
testTrailing = (str, expected) ->
eq CoffeeScript.eval(str.replace /\|$/gm, ''), expected
testTrailing '''" |
|
a |
|
"''', 'a'
testTrailing """''' |
|
a |
|
'''""", ' \na \n '
#647
eq "''Hello, World\\''", '''
'\'Hello, World\\\''
'''
eq '""Hello, World\\""', """
"\"Hello, World\\\""
"""
test "#1273, escaping quotes at the end of heredocs.", ->
# """\""" no longer compiles
eq """\\""", '\\'
eq """\\\"""", '\\\"'
a = """
basic heredoc
on two lines
"""
ok a is "basic heredoc\non two lines"
a = '''
a
"b
c
'''
ok a is "a\n \"b\nc"
a = """
a
b
c
"""
ok a is "a\n b\n c"
a = '''one-liner'''
ok a is 'one-liner'
a = """
out
here
"""
ok a is "out\nhere"
a = '''
a
b
c
'''
ok a is " a\n b\nc"
a = '''
a
b c
'''
ok a is "a\n\n\nb c"
a = '''more"than"one"quote'''
ok a is 'more"than"one"quote'
a = '''here's an apostrophe'''
ok a is "here's an apostrophe"
a = """""surrounded by two quotes"\""""
ok a is '""surrounded by two quotes""'
a = '''''surrounded by two apostrophes'\''''
ok a is "''surrounded by two apostrophes''"
# The indentation detector ignores blank lines without trailing whitespace
a = """
one
two
"""
ok a is "one\ntwo\n"
eq ''' line 0
should not be relevant
to the indent level
''', ' line 0\nshould not be relevant\n to the indent level'
eq """
interpolation #{
"contents"
}
should not be relevant
to the indent level
""", 'interpolation contents\nshould not be relevant\n to the indent level'
eq ''' '\\\' ''', " '\\' "
eq """ "\\\" """, ' "\\" '
eq ''' <- keep these spaces -> ''', ' <- keep these spaces -> '
eq '''undefined''', 'undefined'
eq """undefined""", 'undefined'
test "#1046, empty string interpolations", ->
eq "#{ }", ''
test "strings are not callable", ->
throws -> CoffeeScript.compile '"a"()'
throws -> CoffeeScript.compile '"a#{b}"()'
throws -> CoffeeScript.compile '"a" 1'
throws -> CoffeeScript.compile '"a#{b}" 1'
throws -> CoffeeScript.compile '''
"a"
k: v
'''
throws -> CoffeeScript.compile '''
"a#{b}"
k: v
'''
test "#3795: Escape otherwise invalid characters", ->
eq '', '\u2028'
eq '', '\u2029'
eq '\0\
1', '\x001'
eq "", '\u2028'
eq "", '\u2029'
eq "\0\
1", '\x001'
eq '''''', '\u2028'
eq '''''', '\u2029'
eq '''\0\
1''', '\x001'
eq """""", '\u2028'
eq """""", '\u2029'
eq """\0\
1""", '\x001'
a = 'a'
eq "#{a}", 'a\u2028'
eq "#{a}", 'a\u2029'
eq "#{a}\0\
1", 'a\0' + '1'
eq """#{a}""", 'a\u2028'
eq """#{a}""", 'a\u2029'
eq """#{a}\0\
1""", 'a\0' + '1'
test "#4314: Whitespace less than or equal to stripped indentation", ->
# The odd indentation is intentional here, to test 1-space indentation.
eq ' ', """
#{} #{}
"""
eq '1 2 3 4 5 end\na 0 b', """
#{1} #{2} #{3} #{4} #{5} end
a #{0} b"""
test "#4248: Unicode code point escapes", ->
eq '\u01ab\u00cd', '\u{1ab}\u{cd}'
eq '\u01ab', '\u{000001ab}'
eq 'a\u01ab', "#{ 'a' }\u{1ab}"
eq '\u01abc', '''\u{01ab}c'''
eq '\u01abc', """\u{1ab}#{ 'c' }"""
eq '\udab3\uddef', '\u{bcdef}'
eq '\udab3\uddef', '\u{0000bcdef}'
eq 'a\udab3\uddef', "#{ 'a' }\u{bcdef}"
eq '\udab3\uddefc', '''\u{0bcdef}c'''
eq '\udab3\uddefc', """\u{bcdef}#{ 'c' }"""
eq '\\u{123456}', "#{'\\'}#{'u{123456}'}"
# rewrite code point escapes
input = """
'\\u{bcdef}\\u{abc}'
"""
output = """
'\\udab3\\uddef\\u0abc';
"""
eq toJS(input), output
input = """
"#{ 'a' }\\u{bcdef}"
"""
output = """
"a\\udab3\\uddef";
"""
eq toJS(input), output
</script>
<script type="text/x-coffeescript" class="test" id="tagged_template_literals">
# Tagged template literals
# ------------------------
# NOTES:
# A tagged template literal is a string that is passed to a prefixing function for
# post-processing. There's a bunch of different angles that need testing:
# - Prefixing function, which can be any form of function call:
# - function: func'Hello'
# - object property with dot notation: outerobj.obj.func'Hello'
# - object property with bracket notation: outerobj['obj']['func']'Hello'
# - String form: single quotes, double quotes and block strings
# - String is single-line or multi-line
# - String is interpolated or not
func = (text, expressions...) ->
"text: [#{text.join '|'}] expressions: [#{expressions.join '|'}]"
outerobj =
obj:
func: func
# Example use
test "tagged template literal for html templating", ->
html = (htmlFragments, expressions...) ->
htmlFragments.reduce (fullHtml, htmlFragment, i) ->
fullHtml + "#{expressions[i - 1]}#{htmlFragment}"
state =
name: 'Greg'
adjective: 'awesome'
eq """
<p>
Hi Greg. You're looking awesome!
</p>
""",
html"""
<p>
Hi #{state.name}. You're looking #{state.adjective}!
</p>
"""
# Simple, non-interpolated strings
test "tagged template literal with a single-line single-quote string", ->
eq 'text: [single-line single quotes] expressions: []',
func'single-line single quotes'
test "tagged template literal with a single-line double-quote string", ->
eq 'text: [single-line double quotes] expressions: []',
func"single-line double quotes"
test "tagged template literal with a single-line single-quote block string", ->
eq 'text: [single-line block string] expressions: []',
func'''single-line block string'''
test "tagged template literal with a single-line double-quote block string", ->
eq 'text: [single-line block string] expressions: []',
func"""single-line block string"""
test "tagged template literal with a multi-line single-quote string", ->
eq 'text: [multi-line single quotes] expressions: []',
func'multi-line
single quotes'
test "tagged template literal with a multi-line double-quote string", ->
eq 'text: [multi-line double quotes] expressions: []',
func"multi-line
double quotes"
test "tagged template literal with a multi-line single-quote block string", ->
eq 'text: [multi-line\nblock string] expressions: []',
func'''
multi-line
block string
'''
test "tagged template literal with a multi-line double-quote block string", ->
eq 'text: [multi-line\nblock string] expressions: []',
func"""
multi-line
block string
"""
# Interpolated strings with expressions
test "tagged template literal with a single-line double-quote interpolated string", ->
eq 'text: [single-line | double quotes | interpolation] expressions: [36|42]',
func"single-line #{6 * 6} double quotes #{6 * 7} interpolation"
test "tagged template literal with a single-line double-quote block interpolated string", ->
eq 'text: [single-line | block string | interpolation] expressions: [incredible|48]',
func"""single-line #{'incredible'} block string #{6 * 8} interpolation"""
test "tagged template literal with a multi-line double-quote interpolated string", ->
eq 'text: [multi-line | double quotes | interpolation] expressions: [2|awesome]',
func"multi-line #{4/2}
double quotes #{'awesome'} interpolation"
test "tagged template literal with a multi-line double-quote block interpolated string", ->
eq 'text: [multi-line |\nblock string |] expressions: [/abc/|32]',
func"""
multi-line #{/abc/}
block string #{2 * 16}
"""
# Tagged template literal must use a callable function
test "tagged template literal dot notation recognized as a callable function", ->
eq 'text: [dot notation] expressions: []',
outerobj.obj.func'dot notation'
test "tagged template literal bracket notation recognized as a callable function", ->
eq 'text: [bracket notation] expressions: []',
outerobj['obj']['func']'bracket notation'
test "tagged template literal mixed dot and bracket notation recognized as a callable function", ->
eq 'text: [mixed notation] expressions: []',
outerobj['obj'].func'mixed notation'
# Edge cases
test "tagged template literal with an empty string", ->
eq 'text: [] expressions: []',
func''
test "tagged template literal with an empty interpolated string", ->
eq 'text: [] expressions: []',
func"#{}"
test "tagged template literal as single interpolated expression", ->
eq 'text: [|] expressions: [3]',
func"#{3}"
test "tagged template literal with an interpolated string that itself contains an interpolated string", ->
eq 'text: [inner | string] expressions: [interpolated]',
func"inner #{"#{'inter'}polated"} string"
test "tagged template literal with an interpolated string that contains a tagged template literal", ->
eq 'text: [inner tagged | literal] expressions: [text: [|] expressions: [template]]',
func"inner tagged #{func"#{'template'}"} literal"
test "tagged template literal with backticks", ->
eq 'text: [ES template literals look like this: `foo bar`] expressions: []',
func"ES template literals look like this: `foo bar`"
test "tagged template literal with escaped backticks", ->
eq 'text: [ES template literals look like this: \\`foo bar\\`] expressions: []',
func"ES template literals look like this: \\`foo bar\\`"
test "tagged template literal with unnecessarily escaped backticks", ->
eq 'text: [ES template literals look like this: `foo bar`] expressions: []',
func"ES template literals look like this: \`foo bar\`"
test "tagged template literal with ES interpolation", ->
eq 'text: [ES template literals also look like this: `3 + 5 = ${3+5}`] expressions: []',
func"ES template literals also look like this: `3 + 5 = ${3+5}`"
test "tagged template literal with both ES and CoffeeScript interpolation", ->
eq "text: [ES template literals also look like this: `3 + 5 = ${3+5}` which equals |] expressions: [8]",
func"ES template literals also look like this: `3 + 5 = ${3+5}` which equals #{3+5}"
test "tagged template literal with escaped ES interpolation", ->
eq 'text: [ES template literals also look like this: `3 + 5 = \\${3+5}`] expressions: []',
func"ES template literals also look like this: `3 + 5 = \\${3+5}`"
test "tagged template literal with unnecessarily escaped ES interpolation", ->
eq 'text: [ES template literals also look like this: `3 + 5 = ${3+5}`] expressions: []',
func"ES template literals also look like this: `3 + 5 = \${3+5}`"
test "tagged template literal special escaping", ->
eq 'text: [` ` \\` \\` \\\\` $ { ${ ${ \\${ \\${ \\\\${ | ` ${] expressions: [1]',
func"` \` \\` \\\` \\\\` $ { ${ \${ \\${ \\\${ \\\\${ #{1} ` ${"
</script>
</body>
</html>