diff --git a/lib/nodes.js b/lib/nodes.js index 83be0b3df0..2ce6fdab3f 100644 --- a/lib/nodes.js +++ b/lib/nodes.js @@ -7,7 +7,12 @@ child.prototype = new ctor; child.__super__ = parent.prototype; return child; - }, __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + }, __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, __indexOf = Array.prototype.indexOf || function(item) { + for (var i = 0, l = this.length; i < l; i++) { + if (this[i] === item) return i; + } + return -1; + }; Scope = require('./scope').Scope; _ref = require('./helpers'), compact = _ref.compact, flatten = _ref.flatten, extend = _ref.extend, merge = _ref.merge, del = _ref.del, starts = _ref.starts, ends = _ref.ends, last = _ref.last; exports.extend = extend; @@ -1281,7 +1286,7 @@ Assign.prototype.compileConditional = function(o) { var left, rite, _ref2; _ref2 = this.variable.cacheReference(o), left = _ref2[0], rite = _ref2[1]; - return new Op(this.context.slice(0, -1), left, new Assign(rite, this.value, '=')).compile(o); + return new Op(this.context.slice(0, -1), left, new Assign(rite, this.value, '='), void 0, __indexOf.call(this.context, "?") >= 0).compile(o); }; Assign.prototype.compileSplice = function(o) { var code, exclusive, from, fromDecl, fromRef, name, to, valDef, valRef, _ref2, _ref3, _ref4; @@ -1579,8 +1584,9 @@ exports.Op = Op = (function() { var CONVERSIONS, INVERSIONS; __extends(Op, Base); - function Op(op, first, second, flip) { + function Op(op, first, second, flip, isExistentialEquals) { var call; + this.isExistentialEquals = isExistentialEquals; if (op === 'in') { return new In(first, second); } @@ -1693,9 +1699,15 @@ fst = this.first; ref = fst; } - return new If(new Existence(fst), ref, { - type: 'if' - }).addElse(this.second).compile(o); + if (this.isExistentialEquals) { + return new If(new Existence(fst).invert(), this.second, { + type: 'if' + }).compile(o); + } else { + return new If(new Existence(fst), ref, { + type: 'if' + }).addElse(this.second).compile(o); + } }; Op.prototype.compileUnary = function(o) { var op, parts; @@ -1829,9 +1841,9 @@ Existence.prototype.children = ['expression']; Existence.prototype.invert = NEGATE; Existence.prototype.compileNode = function(o) { - var code, sym; + var cmp, cnj, code, _ref2; code = this.expression.compile(o, LEVEL_OP); - code = IDENTIFIER.test(code) && !o.scope.check(code) ? this.negated ? "typeof " + code + " === \"undefined\" || " + code + " === null" : "typeof " + code + " !== \"undefined\" && " + code + " !== null" : (sym = this.negated ? '==' : '!=', "" + code + " " + sym + " null"); + code = IDENTIFIER.test(code) && !o.scope.check(code) ? ((_ref2 = this.negated ? ['===', '||'] : ['!==', '&&'], cmp = _ref2[0], cnj = _ref2[1], _ref2), "typeof " + code + " " + cmp + " \"undefined\" " + cnj + " " + code + " " + cmp + " null") : "" + code + " " + (this.negated ? '==' : '!=') + " null"; if (o.level <= LEVEL_COND) { return code; } else { diff --git a/src/nodes.coffee b/src/nodes.coffee index bd594e42df..7e6e2dbeea 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1009,7 +1009,7 @@ exports.Assign = class Assign extends Base # more than once. compileConditional: (o) -> [left, rite] = @variable.cacheReference o - new Op(@context.slice(0, -1), left, new Assign(rite, @value, '=')).compile o + new Op(@context.slice(0, -1), left, new Assign(rite, @value, '='), undefined, "?" in @context ).compile o # Compile the assignment from an array splice literal, using JavaScript's # `Array#splice` method. @@ -1218,7 +1218,10 @@ exports.While = class While extends Base # Simple Arithmetic and logical operations. Performs some conversion from # CoffeeScript operations into their JavaScript equivalents. exports.Op = class Op extends Base - constructor: (op, first, second, flip) -> + + + + constructor: (op, first, second, flip, @isExistentialEquals ) -> return new In first, second if op is 'in' if op is 'do' call = new Call first, first.params or [] @@ -1313,7 +1316,10 @@ exports.Op = class Op extends Base else fst = @first ref = fst - new If(new Existence(fst), ref, type: 'if').addElse(@second).compile o + if @isExistentialEquals + new If(new Existence(fst).invert(), @second, type: 'if').compile o + else + new If(new Existence(fst), ref, type: 'if').addElse(@second).compile o # Compile a unary **Op**. compileUnary: (o) -> @@ -1426,13 +1432,11 @@ exports.Existence = class Existence extends Base compileNode: (o) -> code = @expression.compile o, LEVEL_OP code = if IDENTIFIER.test(code) and not o.scope.check code - if @negated - "typeof #{code} === \"undefined\" || #{code} === null" - else - "typeof #{code} !== \"undefined\" && #{code} !== null" + [cmp, cnj] = if @negated then ['===', '||'] else ['!==', '&&'] + "typeof #{code} #{cmp} \"undefined\" #{cnj} #{code} #{cmp} null" else - sym = if @negated then '==' else '!=' - "#{code} #{sym} null" + # do not use strict equality here; it will break existing code + "#{code} #{if @negated then '==' else '!='} null" if o.level <= LEVEL_COND then code else "(#{code})" #### Parens diff --git a/test/assignment.coffee b/test/assignment.coffee index f9fbb5e038..0a4eddc757 100644 --- a/test/assignment.coffee +++ b/test/assignment.coffee @@ -268,3 +268,38 @@ test "existential assignment", -> eq nonce, c d ?= nonce eq nonce, d + +test "#1216 ?= compilation", -> + c = (s) -> CoffeeScript.compile( s, {bare:true} ) + + # ?= with locally scoped var defined + eq c('a = 0; a ?= b'), + '''var a; + a = 0; + if (a == null) { + a = b; + };''' + + # ?= with locally scoped var not defined + eq c('a ?= b'), + '''if (typeof a === "undefined" || a === null) { + a = b; + };''' + + # ? with locally scoped var defined + eq c('a = 0; return unless a?'), + ''' + var a; + a = 0; + if (a == null) { + return; + } + ''' + + # ? with locally scoped var not defined + eq c('return unless a?'), + ''' + if (typeof a === "undefined" || a === null) { + return; + } + '''