diff --git a/lib/coffee-script/helpers.js b/lib/coffee-script/helpers.js index f13f7ded..121af5ca 100644 --- a/lib/coffee-script/helpers.js +++ b/lib/coffee-script/helpers.js @@ -165,7 +165,7 @@ } parts = file.split('.'); parts.pop(); - if (parts[parts.length - 1] === 'coffee') { + if (parts[parts.length - 1] === 'coffee' && parts.length > 1) { parts.pop(); } return parts.join('.'); diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index 22f0e355..823da469 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -1394,18 +1394,12 @@ }; Class.prototype.addBoundFunctions = function(o) { - var body, bound, func, lhs, name, rhs, _i, _len, _ref4, _ref5; - if (this.boundFuncs.length) { - o.scope.assign('_this', 'this'); - _ref4 = this.boundFuncs; - for (_i = 0, _len = _ref4.length; _i < _len; _i++) { - _ref5 = _ref4[_i], name = _ref5[0], func = _ref5[1]; - lhs = new Value(new Literal("this"), [new Access(name)]); - body = new Block([new Return(new Literal("" + this.ctor.name + ".prototype." + name.value + ".apply(_this, arguments)"))]); - rhs = new Code(func.params, body, 'boundfunc'); - bound = new Assign(lhs, rhs); - this.ctor.body.push(bound); - } + var bvar, lhs, _i, _len, _ref4; + _ref4 = this.boundFuncs; + for (_i = 0, _len = _ref4.length; _i < _len; _i++) { + bvar = _ref4[_i]; + lhs = (new Value(new Literal("this"), [new Access(bvar)])).compile(o); + this.ctor.body.unshift(new Literal("" + lhs + " = " + (utility('bind')) + "(" + lhs + ", this)")); } }; @@ -1442,7 +1436,7 @@ } else { assign.variable = new Value(new Literal(name), [new Access(new Literal('prototype')), new Access(base)]); if (func instanceof Code && func.bound) { - this.boundFuncs.push([base, func]); + this.boundFuncs.push(base); func.bound = false; } } @@ -3048,6 +3042,9 @@ "extends": function() { return "function(child, parent) { for (var key in parent) { if (" + (utility('hasProp')) + ".call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }"; }, + bind: function() { + return 'function(fn, me){ return function(){ return fn.apply(me, arguments); }; }'; + }, indexOf: function() { return "[].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }"; }, diff --git a/src/helpers.coffee b/src/helpers.coffee index 183b123a..9f4919e1 100644 --- a/src/helpers.coffee +++ b/src/helpers.coffee @@ -120,7 +120,7 @@ exports.baseFileName = (file, stripExt = no) -> return file unless stripExt parts = file.split('.') parts.pop() - parts.pop() if parts[parts.length - 1] is 'coffee' + parts.pop() if parts[parts.length - 1] is 'coffee' and parts.length > 1 parts.join('.') # Determine if a filename represents a CoffeeScript file. diff --git a/src/nodes.coffee b/src/nodes.coffee index 967b98c3..db536d0d 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -983,14 +983,9 @@ exports.Class = class Class extends Base # Ensure that all functions bound to the instance are proxied in the # constructor. addBoundFunctions: (o) -> - if @boundFuncs.length - o.scope.assign '_this', 'this' - for [name, func] in @boundFuncs - lhs = new Value (new Literal "this"), [new Access name] - body = new Block [new Return new Literal "#{@ctor.name}.prototype.#{name.value}.apply(_this, arguments)"] - rhs = new Code func.params, body, 'boundfunc' - bound = new Assign lhs, rhs - @ctor.body.push bound + for bvar in @boundFuncs + lhs = (new Value (new Literal "this"), [new Access bvar]).compile o + @ctor.body.unshift new Literal "#{lhs} = #{utility 'bind'}(#{lhs}, this)" return # Merge the properties from a top-level object as prototypal properties @@ -1020,7 +1015,7 @@ exports.Class = class Class extends Base else assign.variable = new Value(new Literal(name), [(new Access new Literal 'prototype'), new Access base ]) if func instanceof Code and func.bound - @boundFuncs.push [base, func] + @boundFuncs.push base func.bound = no assign compact exprs @@ -2122,6 +2117,11 @@ UTILITIES = function(child, parent) { for (var key in parent) { if (#{utility 'hasProp'}.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; } """ + # Create a function bound to the current value of "this". + bind: -> ''' + function(fn, me){ return function(){ return fn.apply(me, arguments); }; } + ''' + # Discover if an item is in an array. indexOf: -> """ [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; } diff --git a/test/classes.coffee b/test/classes.coffee index e2604345..ffe95fe2 100644 --- a/test/classes.coffee +++ b/test/classes.coffee @@ -730,23 +730,52 @@ test "#2359: extending native objects that use other typed constructors requires eq 'yes!', workingArray.method() -test "#2489: removing __bind", -> +test "#2782: non-alphanumeric-named bound functions", -> + class A + 'b:c': => + 'd' - class Thing - foo: (a, b, c) -> - bar: (a, b, c) => - - thing = new Thing - - eq thing.foo.length, 3 - eq thing.bar.length, 3 + eq (new A)['b:c'](), 'd' -test "#2773: overriding bound functions", -> +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: => 'Foo' + method: ({a}) => 'Bar' - class Bar extends Foo - method: => 'Bar' + eq (new Foo).method({a: 'Bar'}), 'Bar' - eq (new Bar).method(), '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!' diff --git a/test/helpers.coffee b/test/helpers.coffee index 66819084..7c5fa53b 100644 --- a/test/helpers.coffee +++ b/test/helpers.coffee @@ -2,7 +2,7 @@ # ------- # pull the helpers from `CoffeeScript.helpers` into local variables -{starts, ends, compact, count, merge, extend, flatten, del, last} = CoffeeScript.helpers +{starts, ends, compact, count, merge, extend, flatten, del, last, baseFileName} = CoffeeScript.helpers # `starts` @@ -94,3 +94,33 @@ test "the `last` helper returns the last item of an array-like object", -> test "the `last` helper allows one to specify an optional offset", -> ary = [0, 1, 2, 3, 4] eq 2, last(ary, 2) + +# `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