diff --git a/documentation/coffee/array_comprehensions.coffee b/documentation/coffee/array_comprehensions.coffee index b9d9e166..8050de6e 100644 --- a/documentation/coffee/array_comprehensions.coffee +++ b/documentation/coffee/array_comprehensions.coffee @@ -1,7 +1,2 @@ # Eat lunch. -lunch = eat food for food in ['toast', 'cheese', 'wine'] - -# Naive collision detection. -for roid, pos in asteroids - for roid2 in asteroids when roid isnt roid2 - roid.explode() if roid.overlaps roid2 \ No newline at end of file +eat food for food in ['toast', 'cheese', 'wine'] diff --git a/documentation/index.html.erb b/documentation/index.html.erb index 6e985acf..7b45d6f2 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -517,6 +517,15 @@ coffee --bare --print --stdio end of your comprehension.

<%= code_for('range_comprehensions', 'countdown') %> +

+ Note how because we are assigning the value of the comprehensions to a + variable in the example above, CoffeeScript is collecting the result of + each iteration into an array. Sometimes functions end with loops that are + intended to run only for their side-effects. Be careful that you're not + accidentally returning the results of the comprehension in these cases, + by adding a meaningful return value, like true, or null, + to the bottom of your function. +

Comprehensions can also be used to iterate over the keys and values in an object. Use of to signal comprehension over the properties of diff --git a/documentation/js/array_comprehensions.js b/documentation/js/array_comprehensions.js index bbbe4c90..07f136c7 100644 --- a/documentation/js/array_comprehensions.js +++ b/documentation/js/array_comprehensions.js @@ -1,17 +1,6 @@ -var food, lunch, pos, roid, roid2, _i, _j, _len, _len2, _len3, _ref; +var food, _i, _len, _ref; _ref = ['toast', 'cheese', 'wine']; for (_i = 0, _len = _ref.length; _i < _len; _i++) { food = _ref[_i]; - lunch = eat(food); -} -for (pos = 0, _len2 = asteroids.length; pos < _len2; pos++) { - roid = asteroids[pos]; - for (_j = 0, _len3 = asteroids.length; _j < _len3; _j++) { - roid2 = asteroids[_j]; - if (roid !== roid2) { - if (roid.overlaps(roid2)) { - roid.explode(); - } - } - } + eat(food); } \ No newline at end of file diff --git a/documentation/js/splices.js b/documentation/js/splices.js index ed47b9b2..a6f916f1 100644 --- a/documentation/js/splices.js +++ b/documentation/js/splices.js @@ -1,3 +1,3 @@ -var numbers, _ref; +var numbers; numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; -([].splice.apply(numbers, [3, 6 - 3 + 1].concat(_ref = [-3, -4, -5, -6])), _ref); \ No newline at end of file +[].splice.apply(numbers, [3, 4].concat([-3, -4, -5, -6])); \ No newline at end of file diff --git a/index.html b/index.html index 75c421e0..42126459 100644 --- a/index.html +++ b/index.html @@ -854,28 +854,12 @@ lyrics = function() { would use a loop, each/forEach, map, or select/filter.

# Eat lunch.
-lunch = eat food for food in ['toast', 'cheese', 'wine']
-
-# Naive collision detection.
-for roid, pos in asteroids
-  for roid2 in asteroids when roid isnt roid2
-    roid.explode() if roid.overlaps roid2
-
var food, lunch, pos, roid, roid2, _i, _j, _len, _len2, _len3, _ref;
+eat food for food in ['toast', 'cheese', 'wine']
+
var food, _i, _len, _ref;
 _ref = ['toast', 'cheese', 'wine'];
 for (_i = 0, _len = _ref.length; _i < _len; _i++) {
   food = _ref[_i];
-  lunch = eat(food);
-}
-for (pos = 0, _len2 = asteroids.length; pos < _len2; pos++) {
-  roid = asteroids[pos];
-  for (_j = 0, _len3 = asteroids.length; _j < _len3; _j++) {
-    roid2 = asteroids[_j];
-    if (roid !== roid2) {
-      if (roid.overlaps(roid2)) {
-        roid.explode();
-      }
-    }
-  }
+  eat(food);
 }
 

@@ -901,6 +885,15 @@ countdown = (function() { } return _results; }());;alert(countdown);'>run: countdown
+

+ Note how because we are assigning the value of the comprehensions to a + variable in the example above, CoffeeScript is collecting the result of + each iteration into an array. Sometimes functions end with loops that are + intended to run only for their side-effects. Be careful that you're not + accidentally returning the results of the comprehension in these cases, + by adding a meaningful return value, like true, or null, + to the bottom of your function. +

Comprehensions can also be used to iterate over the keys and values in an object. Use of to signal comprehension over the properties of @@ -975,12 +968,12 @@ middle = copy.slice(3, 6 + 1);;alert(middle);'>run: middle
apply(numbers, [3, 4].concat([-3, -4, -5, -6])); +
+[].splice.apply(numbers, [3, 4].concat([-3, -4, -5, -6]));;alert(numbers);'>run: numbers

Note that JavaScript strings are immutable, and can't be spliced.

@@ -1920,7 +1913,7 @@ task('build:parser '); stdio.on('data', function(buffer) { diff --git a/lib/scope.js b/lib/scope.js index 49feb13c..5097955e 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -48,6 +48,9 @@ return false; }; Scope.prototype.parameter = function(name) { + if (this.shared && this.check(name, true)) { + return; + } return this.add(name, 'param'); }; Scope.prototype.check = function(name, immediate) { diff --git a/src/command.coffee b/src/command.coffee index 9a7711d7..6034db64 100644 --- a/src/command.coffee +++ b/src/command.coffee @@ -7,6 +7,7 @@ # External dependencies. fs = require 'fs' path = require 'path' +util = require 'util' helpers = require './helpers' optparse = require './optparse' CoffeeScript = require './coffee-script' @@ -154,7 +155,7 @@ writeJs = (source, js, base) -> js = ' ' if js.length <= 0 fs.writeFile jsPath, js, (err) -> if err then printLine err.message - else if opts.compile and opts.watch then printLine "Compiled #{source}" + else if opts.compile and opts.watch then util.log "compiled #{source}" path.exists dir, (exists) -> if exists then compile() else exec "mkdir -p #{dir}", compile diff --git a/src/nodes.coffee b/src/nodes.coffee index 26a07830..cfb96ac6 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -433,7 +433,11 @@ exports.Call = class Call extends Base # Tag this invocation as creating a new instance. newInstance: -> - @isNew = true + base = @variable.base or @variable + if base instanceof Call + base.newInstance() + else + @isNew = true this # Grab the reference to the superclass's implementation of the current @@ -978,9 +982,10 @@ exports.Code = class Code extends Base # arrow, generates a wrapper that saves the current value of `this` through # a closure. compileNode: (o) -> - sharedScope = del o, 'sharedScope' - o.scope = scope = sharedScope or new Scope o.scope, @body, this - o.indent += TAB + sharedScope = del o, 'sharedScope' + o.scope = sharedScope or new Scope o.scope, @body, this + o.scope.shared = yes if sharedScope + o.indent += TAB delete o.bare delete o.globals vars = [] @@ -1004,7 +1009,7 @@ exports.Code = class Code extends Base wasEmpty = @body.isEmpty() exprs.unshift splats if splats @body.expressions.unshift exprs... if exprs.length - scope.parameter vars[i] = v.compile o for v, i in vars unless splats + o.scope.parameter vars[i] = v.compile o for v, i in vars unless splats @body.makeReturn() unless wasEmpty or @noReturn idt = o.indent code = 'function' @@ -1421,9 +1426,8 @@ exports.For = class For extends Base scope = o.scope name = @name and @name.compile o, LEVEL_LIST index = @index and @index.compile o, LEVEL_LIST - unless hasCode - scope.find(name, immediate: yes) if name and not @pattern - scope.find(index, immediate: yes) if index + scope.find(name, immediate: yes) if name and not @pattern + scope.find(index, immediate: yes) if index rvar = scope.freeVariable 'results' if @returns and not hasPure ivar = (if @range then name else index) or scope.freeVariable 'i' varPart = '' diff --git a/src/repl.coffee b/src/repl.coffee index 78e49f73..a89f2cc6 100644 --- a/src/repl.coffee +++ b/src/repl.coffee @@ -12,6 +12,10 @@ readline = require 'readline' # Start by opening up **stdio**. stdio = process.openStdin() +# Log an error. +error = (err) -> + stdio.write (err.stack or err.toString()) + '\n\n' + # Quick alias for quitting the REPL. helpers.extend global, quit: -> process.exit(0) @@ -23,9 +27,12 @@ run = (buffer) -> val = CoffeeScript.eval buffer.toString(), bare: on, globals: on, fileName: 'repl' console.log val if val isnt undefined catch err - console.error err.stack or err.toString() + error err repl.prompt() +# Make sure that uncaught exceptions don't kill the REPL. +process.on 'uncaughtException', error + # Create the REPL by listening to **stdin**. repl = readline.createInterface stdio repl.setPrompt 'coffee> ' diff --git a/src/scope.coffee b/src/scope.coffee index 238d0a9e..cde01160 100644 --- a/src/scope.coffee +++ b/src/scope.coffee @@ -44,6 +44,7 @@ exports.Scope = class Scope # Reserve a variable name as originating from a function parameter for this # scope. No `var` required for internal references. parameter: (name) -> + return if @shared and @check name, yes @add name, 'param' # Just check to see if a variable has already been declared, without reserving, diff --git a/test/test_comprehensions.coffee b/test/test_comprehensions.coffee index 86dff3ff..54f802e9 100644 --- a/test/test_comprehensions.coffee +++ b/test/test_comprehensions.coffee @@ -227,4 +227,17 @@ foo = -> for j in [0..7] -> i + j -eq foo()[3][4](), 7 \ No newline at end of file +eq foo()[3][4](), 7 + + +# 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' diff --git a/test/test_functions.coffee b/test/test_functions.coffee index 1bb26f21..1e10e217 100644 --- a/test/test_functions.coffee +++ b/test/test_functions.coffee @@ -346,6 +346,7 @@ eq ok, new -> ### Should `return` implicitly ### ### even with trailing comments. ### + #855: execution context for `func arr...` should be `null` (-> global = @ @@ -355,3 +356,14 @@ eq ok, new -> contextTest.apply null, array contextTest array... )() + + +# #894: Splatting against constructor-chained functions. +x = null + +class Foo + bar: (y) -> x = y + +new Foo().bar([101]...) + +eq x, 101 \ No newline at end of file