From d66e4b1af9f100596054788875e5e3946256cbba Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Mon, 19 Sep 2016 20:55:08 -0700 Subject: [PATCH 01/34] =?UTF-8?q?Eliminate=20wrapper=20around=20=E2=80=9Cb?= =?UTF-8?q?ound=E2=80=9D=20(arrow)=20functions;=20output=20`=3D>`=20for=20?= =?UTF-8?q?such=20functions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/coffee-script/nodes.js | 35 ++++++++++++++++++----------------- src/nodes.coffee | 25 ++++++++++--------------- 2 files changed, 28 insertions(+), 32 deletions(-) diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index 9a870ff82b..411a5b7709 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -2426,16 +2426,14 @@ }; Code.prototype.compileNode = function(o) { - var answer, boundfunc, code, exprs, i, j, k, l, len1, len2, len3, len4, len5, len6, lit, m, p, param, params, q, r, ref, ref3, ref4, ref5, ref6, ref7, ref8, splats, uniqs, val, wasEmpty, wrapper; - if (this.bound && ((ref3 = o.scope.method) != null ? ref3.bound : void 0)) { - this.context = o.scope.method.context; - } - if (this.bound && !this.context) { - this.context = '_this'; - wrapper = new Code([new Param(new IdentifierLiteral(this.context))], new Block([this])); - boundfunc = new Call(wrapper, [new ThisLiteral]); - boundfunc.updateLocationDataIfMissing(this.locationData); - return boundfunc.compileNode(o); + var answer, code, exprs, i, j, k, l, len1, len2, len3, len4, len5, len6, lit, m, p, param, params, q, r, ref, ref3, ref4, ref5, ref6, ref7, ref8, splats, uniqs, val, wasEmpty; + if (this.bound) { + if ((ref3 = o.scope.method) != null ? ref3.bound : void 0) { + this.context = o.scope.method.context; + } + if (!this.context) { + this.context = 'this'; + } } o.scope = del(o, 'classScope') || this.makeScope(o.scope); o.scope.shared = del(o, 'sharedScope'); @@ -2521,12 +2519,15 @@ if (!(wasEmpty || this.noReturn)) { this.body.makeReturn(); } - code = 'function'; - if (this.isGenerator) { - code += '*'; - } - if (this.ctor) { - code += ' ' + this.name; + code = ''; + if (!this.bound) { + code += 'function'; + if (this.isGenerator) { + code += '*'; + } + if (this.ctor) { + code += ' ' + this.name; + } } code += '('; answer = [this.makeCode(code)]; @@ -2537,7 +2538,7 @@ } answer.push.apply(answer, p); } - answer.push(this.makeCode(') {')); + answer.push(this.makeCode(!this.bound ? ') {' : ') => {')); if (!this.body.isEmpty()) { answer = answer.concat(this.makeCode("\n"), this.body.compileWithDeclarations(o), this.makeCode("\n" + this.tab)); } diff --git a/src/nodes.coffee b/src/nodes.coffee index 818bfea30c..66d589dc50 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1617,17 +1617,9 @@ exports.Code = class Code extends Base # arrow, generates a wrapper that saves the current value of `this` through # a closure. compileNode: (o) -> - - if @bound and o.scope.method?.bound - @context = o.scope.method.context - - # Handle bound functions early. - if @bound and not @context - @context = '_this' - wrapper = new Code [new Param new IdentifierLiteral @context], new Block [this] - boundfunc = new Call(wrapper, [new ThisLiteral]) - boundfunc.updateLocationDataIfMissing @locationData - return boundfunc.compileNode(o) + if @bound + @context = o.scope.method.context if o.scope.method?.bound + @context = 'this' unless @context o.scope = del(o, 'classScope') or @makeScope o.scope o.scope.shared = del(o, 'sharedScope') @@ -1667,15 +1659,18 @@ exports.Code = class Code extends Base node.error "multiple parameters named #{name}" if name in uniqs uniqs.push name @body.makeReturn() unless wasEmpty or @noReturn - code = 'function' - code += '*' if @isGenerator - code += ' ' + @name if @ctor + + code = '' + unless @bound + code += 'function' + code += '*' if @isGenerator # Arrow functions can’t be generators + code += ' ' + @name if @ctor code += '(' answer = [@makeCode(code)] for p, i in params if i then answer.push @makeCode ", " answer.push p... - answer.push @makeCode ') {' + answer.push @makeCode unless @bound then ') {' else ') => {' answer = answer.concat(@makeCode("\n"), @body.compileWithDeclarations(o), @makeCode("\n#{@tab}")) unless @body.isEmpty() answer.push @makeCode '}' From b9cc2642efb58bf2fa6287aa6c9a7f167469b109 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Tue, 20 Sep 2016 20:14:07 -0700 Subject: [PATCH 02/34] Remove irrelevant (and breaking) tests --- lib/coffee-script/lexer.js | 52 ++++++++++------------ lib/coffee-script/nodes.js | 90 ++++++++++++++++++-------------------- test/classes.coffee | 7 --- test/functions.coffee | 9 ---- 4 files changed, 67 insertions(+), 91 deletions(-) diff --git a/lib/coffee-script/lexer.js b/lib/coffee-script/lexer.js index f1b095c0df..15026d1add 100644 --- a/lib/coffee-script/lexer.js +++ b/lib/coffee-script/lexer.js @@ -260,37 +260,33 @@ } this.mergeInterpolationTokens(tokens, { delimiter: delimiter - }, (function(_this) { - return function(value, i) { - value = _this.formatString(value); - if (i === 0) { - value = value.replace(LEADING_BLANK_LINE, ''); - } - if (i === $) { - value = value.replace(TRAILING_BLANK_LINE, ''); - } - if (indentRegex) { - value = value.replace(indentRegex, ''); - } - return value; - }; - })(this)); + }, (value, i) => { + value = this.formatString(value); + if (i === 0) { + value = value.replace(LEADING_BLANK_LINE, ''); + } + if (i === $) { + value = value.replace(TRAILING_BLANK_LINE, ''); + } + if (indentRegex) { + value = value.replace(indentRegex, ''); + } + return value; + }); } else { this.mergeInterpolationTokens(tokens, { delimiter: delimiter - }, (function(_this) { - return function(value, i) { - value = _this.formatString(value); - value = value.replace(SIMPLE_STRING_OMIT, function(match, offset) { - if ((i === 0 && offset === 0) || (i === $ && offset + match.length === value.length)) { - return ''; - } else { - return ' '; - } - }); - return value; - }; - })(this)); + }, (value, i) => { + value = this.formatString(value); + value = value.replace(SIMPLE_STRING_OMIT, function(match, offset) { + if ((i === 0 && offset === 0) || (i === $ && offset + match.length === value.length)) { + return ''; + } else { + return ' '; + } + }); + return value; + }); } return end; }; diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index 411a5b7709..3b6cc7058e 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -950,34 +950,32 @@ }; Value.prototype.unfoldSoak = function(o) { - return this.unfoldedSoak != null ? this.unfoldedSoak : this.unfoldedSoak = (function(_this) { - return function() { - var fst, i, ifn, j, len1, prop, ref, ref3, ref4, snd; - if (ifn = _this.base.unfoldSoak(o)) { - (ref3 = ifn.body.properties).push.apply(ref3, _this.properties); - return ifn; + return this.unfoldedSoak != null ? this.unfoldedSoak : this.unfoldedSoak = (() => { + var fst, i, ifn, j, len1, prop, ref, ref3, ref4, snd; + if (ifn = this.base.unfoldSoak(o)) { + (ref3 = ifn.body.properties).push.apply(ref3, this.properties); + return ifn; + } + ref4 = this.properties; + for (i = j = 0, len1 = ref4.length; j < len1; i = ++j) { + prop = ref4[i]; + if (!prop.soak) { + continue; } - ref4 = _this.properties; - for (i = j = 0, len1 = ref4.length; j < len1; i = ++j) { - prop = ref4[i]; - if (!prop.soak) { - continue; - } - prop.soak = false; - fst = new Value(_this.base, _this.properties.slice(0, i)); - snd = new Value(_this.base, _this.properties.slice(i)); - if (fst.isComplex()) { - ref = new IdentifierLiteral(o.scope.freeVariable('ref')); - fst = new Parens(new Assign(ref, fst)); - snd.base = ref; - } - return new If(new Existence(fst), snd, { - soak: true - }); + prop.soak = false; + fst = new Value(this.base, this.properties.slice(0, i)); + snd = new Value(this.base, this.properties.slice(i)); + if (fst.isComplex()) { + ref = new IdentifierLiteral(o.scope.freeVariable('ref')); + fst = new Parens(new Assign(ref, fst)); + snd.base = ref; } - return false; - }; - })(this)(); + return new If(new Existence(fst), snd, { + soak: true + }); + } + return false; + })(); }; return Value; @@ -1704,29 +1702,27 @@ }; Class.prototype.walkBody = function(name, o) { - return this.traverseChildren(false, (function(_this) { - return function(child) { - var cont, exps, i, j, len1, node, ref3; - cont = true; - if (child instanceof Class) { - return false; - } - if (child instanceof Block) { - ref3 = exps = child.expressions; - for (i = j = 0, len1 = ref3.length; j < len1; i = ++j) { - node = ref3[i]; - if (node instanceof Assign && node.variable.looksStatic(name)) { - node.value["static"] = true; - } else if (node instanceof Value && node.isObject(true)) { - cont = false; - exps[i] = _this.addProperties(node, name, o); - } + return this.traverseChildren(false, (child) => { + var cont, exps, i, j, len1, node, ref3; + cont = true; + if (child instanceof Class) { + return false; + } + if (child instanceof Block) { + ref3 = exps = child.expressions; + for (i = j = 0, len1 = ref3.length; j < len1; i = ++j) { + node = ref3[i]; + if (node instanceof Assign && node.variable.looksStatic(name)) { + node.value["static"] = true; + } else if (node instanceof Value && node.isObject(true)) { + cont = false; + exps[i] = this.addProperties(node, name, o); } - child.expressions = exps = flatten(exps); } - return cont && !(child instanceof Class); - }; - })(this)); + child.expressions = exps = flatten(exps); + } + return cont && !(child instanceof Class); + }); }; Class.prototype.hoistDirectivePrologue = function() { diff --git a/test/classes.coffee b/test/classes.coffee index 90867a123e..95d406d22c 100644 --- a/test/classes.coffee +++ b/test/classes.coffee @@ -471,13 +471,6 @@ 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 diff --git a/test/functions.coffee b/test/functions.coffee index 005700cda2..d7a7fabaa4 100644 --- a/test/functions.coffee +++ b/test/functions.coffee @@ -86,15 +86,6 @@ test "self-referencing functions", -> 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 From c29bd90315fc0b6af49df1739d503c405d6771f2 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Sat, 24 Sep 2016 23:23:24 -0700 Subject: [PATCH 03/34] Minor cleanup --- src/grammar.coffee | 5 ++--- src/nodes.coffee | 6 +++--- test/error_messages.coffee | 4 ++-- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/grammar.coffee b/src/grammar.coffee index bf236a4ee1..a9f09abfb8 100644 --- a/src/grammar.coffee +++ b/src/grammar.coffee @@ -225,11 +225,10 @@ grammar = ] # The **Code** node is the function literal. It's defined by an indented block - # of **Block** preceded by a function arrow, with an optional parameter - # list. + # of **Block** preceded by a function arrow, with an optional parameter list. Code: [ o 'PARAM_START ParamList PARAM_END FuncGlyph Block', -> new Code $2, $5, $4 - o 'FuncGlyph Block', -> new Code [], $2, $1 + o 'FuncGlyph Block', -> new Code [], $2, $1 ] # CoffeeScript has two different symbols for functions. `->` is for ordinary diff --git a/src/nodes.coffee b/src/nodes.coffee index 66d589dc50..e4c50d01e4 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1655,8 +1655,8 @@ exports.Code = class Code extends Base params[i] = p.compileToFragments o o.scope.parameter fragmentsToText params[i] uniqs = [] - @eachParamName (name, node) -> - node.error "multiple parameters named #{name}" if name in uniqs + @eachParamName (name, node) => + node.error "multiple parameters named '#{name}'" if name in uniqs uniqs.push name @body.makeReturn() unless wasEmpty or @noReturn @@ -1726,7 +1726,7 @@ exports.Param = class Param extends Base # The `iterator` function will be called as `iterator(name, node)` where # `name` is the name of the parameter and `node` is the AST node corresponding # to that name. - eachName: (iterator, name = @name)-> + eachName: (iterator, name = @name) -> atParam = (obj) -> iterator "@#{obj.properties[0].name.value}", obj # * simple literals `foo` return iterator name.value, name if name instanceof Literal diff --git a/test/error_messages.coffee b/test/error_messages.coffee index e0a04e4ed7..812041b224 100644 --- a/test/error_messages.coffee +++ b/test/error_messages.coffee @@ -642,14 +642,14 @@ test "duplicate function arguments", -> assertErrorFormat ''' (foo, bar, foo) -> ''', ''' - [stdin]:1:12: error: multiple parameters named foo + [stdin]:1:12: error: multiple parameters named 'foo' (foo, bar, foo) -> ^^^ ''' assertErrorFormat ''' (@foo, bar, @foo) -> ''', ''' - [stdin]:1:13: error: multiple parameters named @foo + [stdin]:1:13: error: multiple parameters named '@foo' (@foo, bar, @foo) -> ^^^^ ''' From 5bc46ec74b2ce425983c08289b97d2319a123bc3 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Sun, 25 Sep 2016 00:01:44 -0700 Subject: [PATCH 04/34] When a function parameter is a splat (i.e., it uses the ES2015 rest parameter syntax) output that parameter as ES2015 --- lib/coffee-script/nodes.js | 16 ++++++++-------- src/nodes.coffee | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index f942adb1ba..8a70709a98 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -2441,14 +2441,14 @@ ref4 = this.params; for (j = 0, len1 = ref4.length; j < len1; j++) { param = ref4[j]; - if (!(param instanceof Expansion)) { + if (param.splat || !(param instanceof Expansion)) { o.scope.parameter(param.asReference(o)); } } ref5 = this.params; for (k = 0, len2 = ref5.length; k < len2; k++) { param = ref5[k]; - if (!(param.splat || param instanceof Expansion)) { + if (!(param instanceof Expansion)) { continue; } ref6 = this.params; @@ -2506,9 +2506,9 @@ o.scope.parameter(fragmentsToText(params[i])); } uniqs = []; - this.eachParamName(function(name, node) { + this.eachParamName((name, node) => { if (indexOf.call(uniqs, name) >= 0) { - node.error("multiple parameters named " + name); + node.error("multiple parameters named '" + name + "'"); } return uniqs.push(name); }); @@ -2530,7 +2530,10 @@ for (i = r = 0, len6 = params.length; r < len6; i = ++r) { p = params[i]; if (i) { - answer.push(this.makeCode(", ")); + answer.push(this.makeCode(', ')); + } + if (this.params[i].splat) { + answer.push(this.makeCode('...')); } answer.push.apply(answer, p); } @@ -2610,9 +2613,6 @@ node = new IdentifierLiteral(o.scope.freeVariable('arg')); } node = new Value(node); - if (this.splat) { - node = new Splat(node); - } node.updateLocationDataIfMissing(this.locationData); return this.reference = node; }; diff --git a/src/nodes.coffee b/src/nodes.coffee index e4c50d01e4..ede80c605d 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1628,9 +1628,9 @@ exports.Code = class Code extends Base delete o.isExistentialEquals params = [] exprs = [] - for param in @params when param not instanceof Expansion + for param in @params when param.splat or param not instanceof Expansion o.scope.parameter param.asReference o - for param in @params when param.splat or param instanceof Expansion + for param in @params when param instanceof Expansion for p in @params when p not instanceof Expansion and p.name.value o.scope.add p.name.value, 'var', yes splats = new Assign new Value(new Arr(p.asReference o for p in @params)), @@ -1668,7 +1668,8 @@ exports.Code = class Code extends Base code += '(' answer = [@makeCode(code)] for p, i in params - if i then answer.push @makeCode ", " + answer.push @makeCode ', ' if i + answer.push @makeCode '...' if @params[i].splat answer.push p... answer.push @makeCode unless @bound then ') {' else ') => {' answer = answer.concat(@makeCode("\n"), @body.compileWithDeclarations(o), @makeCode("\n#{@tab}")) unless @body.isEmpty() @@ -1713,7 +1714,6 @@ exports.Param = class Param extends Base else if node.isComplex() node = new IdentifierLiteral o.scope.freeVariable 'arg' node = new Value node - node = new Splat node if @splat node.updateLocationDataIfMissing @locationData @reference = node From a5d63dc4d94ad0c303483d06d0f41462ed15ed8a Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Tue, 27 Sep 2016 22:30:37 -0700 Subject: [PATCH 05/34] =?UTF-8?q?Rearrange=20function=20parameters=20when?= =?UTF-8?q?=20one=20of=20the=20parameters=20is=20a=20splat=20and=20isn?= =?UTF-8?q?=E2=80=99t=20the=20last=20parameter=20(very=20WIP)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/nodes.coffee | 58 +++++++++++++++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/src/nodes.coffee b/src/nodes.coffee index 9c714d5f27..8e34abb900 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1626,28 +1626,32 @@ exports.Code = class Code extends Base delete o.isExistentialEquals params = [] exprs = [] - for param in @params when param.splat or param not instanceof Expansion + + # If `...` was used with a parameter, which parameter was it used with? + for param, i in @params when param.splat or param instanceof Expansion + splatParam = param + splatIndex = i + splatIsExpansion = param instanceof Expansion + splatNotLast = i isnt @params.length - 1 + break # Only one splat/expansion parameter is permitted + + for param in @params when not param.splat and param not instanceof Expansion + # Add named parameters to the scope o.scope.parameter param.asReference o - for param in @params when param instanceof Expansion - for p in @params when p not instanceof Expansion and p.name.value - o.scope.add p.name.value, 'var', yes - splats = new Assign new Value(new Arr(p.asReference o for p in @params)), - new Value new IdentifierLiteral 'arguments' - break - for param in @params - if param.isComplex() - val = ref = param.asReference o - val = new Op '?', ref, param.value if param.value - exprs.push new Assign new Value(param.name), val, '=', param: yes + # Prepare the parameters for output + if param.isComplex() # Parameter is destructured + # TODO: Anything else to do here? + params.push param else - ref = param - if param.value - lit = new Literal ref.name.value + ' == null' - val = new Assign new Value(param.name), param.value, '=' - exprs.push new If lit, val - params.push ref unless splats + if param.value? # Parameter has a default value + params.push new Assign new Value(param.name), param.value, '=' + else + params.push param + + if splatNotLast and not splatIsExpansion + params.push splatParam + wasEmpty = @body.isEmpty() - exprs.unshift splats if splats @body.expressions.unshift exprs... if exprs.length for p, i in params params[i] = p.compileToFragments o @@ -1667,9 +1671,23 @@ exports.Code = class Code extends Base answer = [@makeCode(code)] for p, i in params answer.push @makeCode ', ' if i - answer.push @makeCode '...' if @params[i].splat + answer.push @makeCode '...' if splatIndex? and i is params.length - 1 # Rest syntax is always on the last parameter answer.push p... answer.push @makeCode unless @bound then ') {' else ') => {' + + # ES2015 allows `...` to be used only in the final parameter. CoffeeScript allows it in any parameter, so we need + # to apply some gymnastics to move the `...` to the final parameter and reassign the parameter values accordingly. + if splatNotLast + answer.push @makeCode "\n#{o.indent}[" + for param, i in @params when i > splatIndex + answer.push @makeCode ', ' if i isnt splatIndex + 1 + answer = answer.concat param.compileToFragments o + answer.push @makeCode ", ...#{splatParam.name.value}] = #{splatParam.name.value}.slice(-#{@params.length - 1 - splatIndex}).concat([" + for param, i in @params when i > splatIndex + answer.push @makeCode ', ' if i isnt splatIndex + 1 + answer.push @makeCode param.name.value + answer.push @makeCode "], #{splatParam.name.value}.slice(0, -#{@params.length - 1 - splatIndex}));\n" + answer = answer.concat(@makeCode("\n"), @body.compileWithDeclarations(o), @makeCode("\n#{@tab}")) unless @body.isEmpty() answer.push @makeCode '}' From 528f00787af00f684a0c4cea0cb7eb15d34b17e0 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Sat, 1 Oct 2016 10:13:35 -0700 Subject: [PATCH 06/34] Handle params like `@param`, adding assignment expressions for them when they appear; ensure splat parameter is last --- src/nodes.coffee | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/nodes.coffee b/src/nodes.coffee index 8e34abb900..dfb30cd686 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1635,21 +1635,28 @@ exports.Code = class Code extends Base splatNotLast = i isnt @params.length - 1 break # Only one splat/expansion parameter is permitted - for param in @params when not param.splat and param not instanceof Expansion + for param in @params when param not instanceof Expansion # Add named parameters to the scope o.scope.parameter param.asReference o + # Prepare the parameters for output - if param.isComplex() # Parameter is destructured - # TODO: Anything else to do here? - params.push param + if param.isComplex() # Parameter is destructured or attached to `this` + val = ref = param.asReference o + val = new Op '?', ref, param.value if param.value + exprs.push new Assign new Value(param.name), val, '=', param: yes else if param.value? # Parameter has a default value - params.push new Assign new Value(param.name), param.value, '=' + ref = new Assign new Value(param.name), param.value, '=' else - params.push param + ref = param + + # Add to the list of parameters, except if the param is a splat which must go last + unless param.splat + params.push ref + else + splatParamReference = ref - if splatNotLast and not splatIsExpansion - params.push splatParam + params.push splatParamReference if splatParamReference? wasEmpty = @body.isEmpty() @body.expressions.unshift exprs... if exprs.length From 0433ec05ce02af5022acdb87f99496a3f3247178 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Sat, 1 Oct 2016 11:16:28 -0700 Subject: [PATCH 07/34] =?UTF-8?q?Add=20parameter=20names=20(not=20a=20text?= =?UTF-8?q?=20like=20`'\nValue=20IdentifierLiteral:=20a'`)=20to=20the=20sc?= =?UTF-8?q?ope,=20so=20that=20parameters=20can=E2=80=99t=20be=20deleted;?= =?UTF-8?q?=20move=20body-related=20lines=20together;=20more=20explanation?= =?UTF-8?q?=20of=20what=E2=80=99s=20going=20on?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/nodes.coffee | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/nodes.coffee b/src/nodes.coffee index dfb30cd686..29456365dd 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1635,9 +1635,10 @@ exports.Code = class Code extends Base splatNotLast = i isnt @params.length - 1 break # Only one splat/expansion parameter is permitted + # Parse the parameters for param in @params when param not instanceof Expansion # Add named parameters to the scope - o.scope.parameter param.asReference o + o.scope.parameter fragmentsToText param.compileToFragments o # Prepare the parameters for output if param.isComplex() # Parameter is destructured or attached to `this` @@ -1655,20 +1656,21 @@ exports.Code = class Code extends Base params.push ref else splatParamReference = ref - + # If we have a splat parameter, add it to the end params.push splatParamReference if splatParamReference? - wasEmpty = @body.isEmpty() - @body.expressions.unshift exprs... if exprs.length - for p, i in params - params[i] = p.compileToFragments o - o.scope.parameter fragmentsToText params[i] + # Check for duplicate parameters uniqs = [] @eachParamName (name, node) => node.error "multiple parameters named '#{name}'" if name in uniqs uniqs.push name + + # Add new expressions to the function body + wasEmpty = @body.isEmpty() + @body.expressions.unshift exprs... if exprs.length @body.makeReturn() unless wasEmpty or @noReturn + # Assemble the output code = '' unless @bound code += 'function' @@ -1676,10 +1678,10 @@ exports.Code = class Code extends Base code += ' ' + @name if @ctor code += '(' answer = [@makeCode(code)] - for p, i in params + for param, i in params answer.push @makeCode ', ' if i answer.push @makeCode '...' if splatIndex? and i is params.length - 1 # Rest syntax is always on the last parameter - answer.push p... + answer.push param.compileToFragments(o)... answer.push @makeCode unless @bound then ') {' else ') => {' # ES2015 allows `...` to be used only in the final parameter. CoffeeScript allows it in any parameter, so we need From 387c5bd7c030012406afc047aa32f6bf205a429e Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Sat, 1 Oct 2016 16:53:05 -0700 Subject: [PATCH 08/34] For parameters with a default value, correctly add the parameter name to the function scope --- lib/coffee-script/lexer.js | 77 ++++++++++++++----------- lib/coffee-script/nodes.js | 105 ++++++++++++++++++---------------- lib/coffee-script/rewriter.js | 5 +- src/nodes.coffee | 8 +-- 4 files changed, 106 insertions(+), 89 deletions(-) diff --git a/lib/coffee-script/lexer.js b/lib/coffee-script/lexer.js index ee2d4b6c40..fb9fb3739b 100644 --- a/lib/coffee-script/lexer.js +++ b/lib/coffee-script/lexer.js @@ -22,6 +22,7 @@ this.indebt = 0; this.outdebt = 0; this.indents = []; + this.indentLiteral = ''; this.ends = []; this.tokens = []; this.seenFor = false; @@ -174,7 +175,7 @@ }; Lexer.prototype.numberToken = function() { - var base, lexedLength, match, number, numberValue, ref2, tag; + var base, lexedLength, match, number, numberValue, tag; if (!(match = NUMBER.exec(this.chunk))) { return 0; } @@ -214,9 +215,6 @@ } })(); numberValue = base != null ? parseInt(number.slice(2), base) : parseFloat(number); - if ((ref2 = number.charAt(1)) === 'b' || ref2 === 'o') { - number = "0x" + (numberValue.toString(16)); - } tag = numberValue === 2e308 ? 'INFINITY' : 'NUMBER'; this.token(tag, number, 0, lexedLength); return lexedLength; @@ -271,37 +269,33 @@ } this.mergeInterpolationTokens(tokens, { delimiter: delimiter - }, (function(_this) { - return function(value, i) { - value = _this.formatString(value); - if (indentRegex) { - value = value.replace(indentRegex, '\n'); - } - if (i === 0) { - value = value.replace(LEADING_BLANK_LINE, ''); - } - if (i === $) { - value = value.replace(TRAILING_BLANK_LINE, ''); - } - return value; - }; - })(this)); + }, (value, i) => { + value = this.formatString(value); + if (indentRegex) { + value = value.replace(indentRegex, '\n'); + } + if (i === 0) { + value = value.replace(LEADING_BLANK_LINE, ''); + } + if (i === $) { + value = value.replace(TRAILING_BLANK_LINE, ''); + } + return value; + }); } else { this.mergeInterpolationTokens(tokens, { delimiter: delimiter - }, (function(_this) { - return function(value, i) { - value = _this.formatString(value); - value = value.replace(SIMPLE_STRING_OMIT, function(match, offset) { - if ((i === 0 && offset === 0) || (i === $ && offset + match.length === value.length)) { - return ''; - } else { - return ' '; - } - }); - return value; - }; - })(this)); + }, (value, i) => { + value = this.formatString(value); + value = value.replace(SIMPLE_STRING_OMIT, function(match, offset) { + if ((i === 0 && offset === 0) || (i === $ && offset + match.length === value.length)) { + return ''; + } else { + return ' '; + } + }); + return value; + }); } return end; }; @@ -408,7 +402,7 @@ }; Lexer.prototype.lineToken = function() { - var diff, indent, match, noNewlines, size; + var diff, indent, match, minLiteralLength, newIndentLiteral, noNewlines, size; if (!(match = MULTI_DENT.exec(this.chunk))) { return 0; } @@ -416,6 +410,20 @@ this.seenFor = false; size = indent.length - 1 - indent.lastIndexOf('\n'); noNewlines = this.unfinished(); + newIndentLiteral = size > 0 ? indent.slice(-size) : ''; + if (!/^(.?)\1*$/.exec(newIndentLiteral)) { + this.error('mixed indentation', { + offset: indent.length + }); + return indent.length; + } + minLiteralLength = Math.min(newIndentLiteral.length, this.indentLiteral.length); + if (newIndentLiteral.slice(0, minLiteralLength) !== this.indentLiteral.slice(0, minLiteralLength)) { + this.error('indentation mismatch', { + offset: indent.length + }); + return indent.length; + } if (size - this.indebt === this.indent) { if (noNewlines) { this.suppressNewlines(); @@ -432,6 +440,7 @@ } if (!this.tokens.length) { this.baseIndent = this.indent = size; + this.indentLiteral = newIndentLiteral; return indent.length; } diff = size - this.indent + this.outdebt; @@ -442,6 +451,7 @@ }); this.outdebt = this.indebt = 0; this.indent = size; + this.indentLiteral = newIndentLiteral; } else if (size < this.baseIndent) { this.error('missing indentation', { offset: indent.length @@ -488,6 +498,7 @@ this.token('TERMINATOR', '\n', outdentLength, 0); } this.indent = decreasedIndent; + this.indentLiteral = this.indentLiteral.slice(0, decreasedIndent); return this; }; diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index d5b3724769..cfa6a421c2 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -2423,7 +2423,7 @@ }; Code.prototype.compileNode = function(o) { - var answer, code, exprs, i, j, k, l, len1, len2, len3, len4, len5, len6, lit, m, p, param, params, q, r, ref, ref3, ref4, ref5, ref6, ref7, ref8, splats, uniqs, val, wasEmpty; + var answer, code, exprs, i, j, k, l, len1, len2, len3, len4, len5, m, p, param, params, ref, ref3, ref4, ref5, ref6, ref7, ref8, splatIndex, splatIsExpansion, splatNotLast, splatParam, splatParamReference, uniqs, val, wasEmpty; if (this.bound) { if ((ref3 = o.scope.method) != null ? ref3.bound : void 0) { this.context = o.scope.method.context; @@ -2440,40 +2440,23 @@ params = []; exprs = []; ref4 = this.params; - for (j = 0, len1 = ref4.length; j < len1; j++) { - param = ref4[j]; - if (param.splat || !(param instanceof Expansion)) { - o.scope.parameter(param.asReference(o)); + for (i = j = 0, len1 = ref4.length; j < len1; i = ++j) { + param = ref4[i]; + if (!(param.splat || param instanceof Expansion)) { + continue; } + splatParam = param; + splatIndex = i; + splatIsExpansion = param instanceof Expansion; + splatNotLast = i !== this.params.length - 1; + break; } ref5 = this.params; for (k = 0, len2 = ref5.length; k < len2; k++) { param = ref5[k]; - if (!(param instanceof Expansion)) { + if (!(!(param instanceof Expansion))) { continue; } - ref6 = this.params; - for (l = 0, len3 = ref6.length; l < len3; l++) { - p = ref6[l]; - if (!(p instanceof Expansion) && p.name.value) { - o.scope.add(p.name.value, 'var', true); - } - } - splats = new Assign(new Value(new Arr((function() { - var len4, m, ref7, results; - ref7 = this.params; - results = []; - for (m = 0, len4 = ref7.length; m < len4; m++) { - p = ref7[m]; - results.push(p.asReference(o)); - } - return results; - }).call(this))), new Value(new IdentifierLiteral('arguments'))); - break; - } - ref7 = this.params; - for (m = 0, len4 = ref7.length; m < len4; m++) { - param = ref7[m]; if (param.isComplex()) { val = ref = param.asReference(o); if (param.value) { @@ -2483,28 +2466,21 @@ param: true })); } else { - ref = param; - if (param.value) { - lit = new Literal(ref.name.value + ' == null'); - val = new Assign(new Value(param.name), param.value, '='); - exprs.push(new If(lit, val)); + if (param.value != null) { + ref = new Assign(new Value(param.name), param.value, '='); + } else { + ref = param; } } - if (!splats) { + o.scope.parameter(fragmentsToText((param.value != null ? param : ref).compileToFragments(o))); + if (!param.splat) { params.push(ref); + } else { + splatParamReference = ref; } } - wasEmpty = this.body.isEmpty(); - if (splats) { - exprs.unshift(splats); - } - if (exprs.length) { - (ref8 = this.body.expressions).unshift.apply(ref8, exprs); - } - for (i = q = 0, len5 = params.length; q < len5; i = ++q) { - p = params[i]; - params[i] = p.compileToFragments(o); - o.scope.parameter(fragmentsToText(params[i])); + if (splatParamReference != null) { + params.push(splatParamReference); } uniqs = []; this.eachParamName((name, node) => { @@ -2513,6 +2489,10 @@ } return uniqs.push(name); }); + wasEmpty = this.body.isEmpty(); + if (exprs.length) { + (ref6 = this.body.expressions).unshift.apply(ref6, exprs); + } if (!(wasEmpty || this.noReturn)) { this.body.makeReturn(); } @@ -2528,17 +2508,44 @@ } code += '('; answer = [this.makeCode(code)]; - for (i = r = 0, len6 = params.length; r < len6; i = ++r) { - p = params[i]; + for (i = l = 0, len3 = params.length; l < len3; i = ++l) { + param = params[i]; if (i) { answer.push(this.makeCode(', ')); } - if (this.params[i].splat) { + if ((splatIndex != null) && i === params.length - 1) { answer.push(this.makeCode('...')); } - answer.push.apply(answer, p); + answer.push.apply(answer, param.compileToFragments(o)); } answer.push(this.makeCode(!this.bound ? ') {' : ') => {')); + if (splatNotLast) { + answer.push(this.makeCode("\n" + o.indent + "[")); + ref7 = this.params; + for (i = m = 0, len4 = ref7.length; m < len4; i = ++m) { + param = ref7[i]; + if (!(i > splatIndex)) { + continue; + } + if (i !== splatIndex + 1) { + answer.push(this.makeCode(', ')); + } + answer = answer.concat(param.compileToFragments(o)); + } + answer.push(this.makeCode(", ..." + splatParam.name.value + "] = " + splatParam.name.value + ".slice(-" + (this.params.length - 1 - splatIndex) + ").concat([")); + ref8 = this.params; + for (i = p = 0, len5 = ref8.length; p < len5; i = ++p) { + param = ref8[i]; + if (!(i > splatIndex)) { + continue; + } + if (i !== splatIndex + 1) { + answer.push(this.makeCode(', ')); + } + answer.push(this.makeCode(param.name.value)); + } + answer.push(this.makeCode("], " + splatParam.name.value + ".slice(0, -" + (this.params.length - 1 - splatIndex) + "));\n")); + } if (!this.body.isEmpty()) { answer = answer.concat(this.makeCode("\n"), this.body.compileWithDeclarations(o), this.makeCode("\n" + this.tab)); } diff --git a/lib/coffee-script/rewriter.js b/lib/coffee-script/rewriter.js index 664f1f0b64..1243c81a39 100644 --- a/lib/coffee-script/rewriter.js +++ b/lib/coffee-script/rewriter.js @@ -108,9 +108,8 @@ }); }; - Rewriter.prototype.indexOfTag = function() { - var fuzz, i, j, k, pattern, ref, ref1; - i = arguments[0], pattern = 2 <= arguments.length ? slice.call(arguments, 1) : []; + Rewriter.prototype.indexOfTag = function(i, ...pattern) { + var fuzz, j, k, ref, ref1; fuzz = 0; for (j = k = 0, ref = pattern.length; 0 <= ref ? k < ref : k > ref; j = 0 <= ref ? ++k : --k) { while (this.tag(i + j + fuzz) === 'HERECOMMENT') { diff --git a/src/nodes.coffee b/src/nodes.coffee index a218562773..bc45c93de8 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1638,9 +1638,6 @@ exports.Code = class Code extends Base # Parse the parameters for param in @params when param not instanceof Expansion - # Add named parameters to the scope - o.scope.parameter fragmentsToText param.compileToFragments o - # Prepare the parameters for output if param.isComplex() # Parameter is destructured or attached to `this` val = ref = param.asReference o @@ -1652,7 +1649,10 @@ exports.Code = class Code extends Base else ref = param - # Add to the list of parameters, except if the param is a splat which must go last + # Add this parameter’s reference to the function scope + o.scope.parameter fragmentsToText (if param.value? then param else ref).compileToFragments o + + # Add this parameter to the list of parameters, except if the param is a splat which must go last unless param.splat params.push ref else From 1c08aebe8794d21ade110466d523bde974166348 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Sat, 1 Oct 2016 23:19:12 -0700 Subject: [PATCH 09/34] Handle expansions in function parameters: when an expansion is found, set the parameters to only be the original parameters left of the expansion, then an `...args` parameter; and in the function body define variables for the parameters to the right of the expansion, including setting default values --- lib/coffee-script/browser.js | 23 ++-------- lib/coffee-script/coffee-script.js | 23 ++-------- lib/coffee-script/command.js | 17 ++----- lib/coffee-script/helpers.js | 8 +--- lib/coffee-script/lexer.js | 33 +++----------- lib/coffee-script/nodes.js | 71 ++++++++++++++---------------- lib/coffee-script/optparse.js | 5 +-- lib/coffee-script/repl.js | 5 +-- lib/coffee-script/rewriter.js | 5 +-- lib/coffee-script/scope.js | 10 +---- lib/coffee-script/sourcemap.js | 18 ++------ src/nodes.coffee | 36 ++++++++++++--- 12 files changed, 88 insertions(+), 166 deletions(-) diff --git a/lib/coffee-script/browser.js b/lib/coffee-script/browser.js index 3e0225f5d8..fd37dd0c05 100644 --- a/lib/coffee-script/browser.js +++ b/lib/coffee-script/browser.js @@ -9,20 +9,14 @@ compile = CoffeeScript.compile; - CoffeeScript["eval"] = function(code, options) { - if (options == null) { - options = {}; - } + CoffeeScript["eval"] = function(code, options = {}) { if (options.bare == null) { options.bare = true; } return eval(compile(code, options)); }; - CoffeeScript.run = function(code, options) { - if (options == null) { - options = {}; - } + CoffeeScript.run = function(code, options = {}) { options.bare = true; options.shiftLine = true; return Function(compile(code, options))(); @@ -33,23 +27,14 @@ } if ((typeof btoa !== "undefined" && btoa !== null) && (typeof JSON !== "undefined" && JSON !== null)) { - compile = function(code, options) { - if (options == null) { - options = {}; - } + compile = function(code, options = {}) { options.inlineMap = true; return CoffeeScript.compile(code, options); }; } - CoffeeScript.load = function(url, callback, options, hold) { + CoffeeScript.load = function(url, callback, options = {}, hold = false) { var xhr; - if (options == null) { - options = {}; - } - if (hold == null) { - hold = false; - } options.sourceFiles = [url]; xhr = window.ActiveXObject ? new window.ActiveXObject('Microsoft.XMLHTTP') : new window.XMLHttpRequest(); xhr.open('GET', url, true); diff --git a/lib/coffee-script/coffee-script.js b/lib/coffee-script/coffee-script.js index a868f1c099..e627e08ba4 100644 --- a/lib/coffee-script/coffee-script.js +++ b/lib/coffee-script/coffee-script.js @@ -35,11 +35,8 @@ }; withPrettyErrors = function(fn) { - return function(code, options) { + return function(code, options = {}) { var err; - if (options == null) { - options = {}; - } try { return fn.call(this, code, options); } catch (error) { @@ -145,11 +142,8 @@ } }); - exports.run = function(code, options) { + exports.run = function(code, options = {}) { var answer, dir, mainModule, ref; - if (options == null) { - options = {}; - } mainModule = require.main; mainModule.filename = process.argv[1] = options.filename ? fs.realpathSync(options.filename) : '.'; mainModule.moduleCache && (mainModule.moduleCache = {}); @@ -162,11 +156,8 @@ return mainModule._compile(code, mainModule.filename); }; - exports["eval"] = function(code, options) { + exports["eval"] = function(code, options = {}) { var Module, _module, _require, createContext, i, isContext, js, k, len, o, r, ref, ref1, ref2, ref3, sandbox, v; - if (options == null) { - options = {}; - } if (!(code = code.trim())) { return; } @@ -246,14 +237,8 @@ } } - exports._compileFile = function(filename, sourceMap, inlineMap) { + exports._compileFile = function(filename, sourceMap = false, inlineMap = false) { var answer, err, raw, stripped; - if (sourceMap == null) { - sourceMap = false; - } - if (inlineMap == null) { - inlineMap = false; - } raw = fs.readFileSync(filename, 'utf8'); stripped = raw.charCodeAt(0) === 0xFEFF ? raw.substring(1) : raw; try { diff --git a/lib/coffee-script/command.js b/lib/coffee-script/command.js index f43116d21d..d71d71f9d3 100644 --- a/lib/coffee-script/command.js +++ b/lib/coffee-script/command.js @@ -198,11 +198,8 @@ return process.exit(1); }; - compileScript = function(file, input, base) { + compileScript = function(file, input, base = null) { var compiled, err, message, o, options, t, task; - if (base == null) { - base = null; - } o = opts; options = compileOptions(file, base); try { @@ -439,11 +436,8 @@ } }; - outputPath = function(source, base, extension) { + outputPath = function(source, base, extension = ".js") { var basename, dir, srcDir; - if (extension == null) { - extension = ".js"; - } basename = helpers.baseFileName(source, true, useWinPathSep); srcDir = path.dirname(source); if (!opts.output) { @@ -458,7 +452,7 @@ mkdirp = function(dir, fn) { var mkdirs, mode; - mode = 0x1ff & ~process.umask(); + mode = 0o777 & ~process.umask(); return (mkdirs = function(p, fn) { return fs.exists(p, function(exists) { if (exists) { @@ -477,11 +471,8 @@ })(dir, fn); }; - writeJs = function(base, sourcePath, js, jsPath, generatedSourceMap) { + writeJs = function(base, sourcePath, js, jsPath, generatedSourceMap = null) { var compile, jsDir, sourceMapPath; - if (generatedSourceMap == null) { - generatedSourceMap = null; - } sourceMapPath = outputPath(sourcePath, base, ".js.map"); jsDir = path.dirname(jsPath); compile = function() { diff --git a/lib/coffee-script/helpers.js b/lib/coffee-script/helpers.js index 1450ca9222..e3215e53da 100644 --- a/lib/coffee-script/helpers.js +++ b/lib/coffee-script/helpers.js @@ -153,14 +153,8 @@ } }; - exports.baseFileName = function(file, stripExt, useWinPathSep) { + exports.baseFileName = function(file, stripExt = false, useWinPathSep = false) { var parts, pathSep; - if (stripExt == null) { - stripExt = false; - } - if (useWinPathSep == null) { - useWinPathSep = false; - } pathSep = useWinPathSep ? /\\|\// : /\//; parts = file.split(pathSep); file = parts[parts.length - 1]; diff --git a/lib/coffee-script/lexer.js b/lib/coffee-script/lexer.js index fb9fb3739b..4e65fe113e 100644 --- a/lib/coffee-script/lexer.js +++ b/lib/coffee-script/lexer.js @@ -11,11 +11,8 @@ exports.Lexer = Lexer = (function() { function Lexer() {} - Lexer.prototype.tokenize = function(code, opts) { + Lexer.prototype.tokenize = function(code, opts = {}) { var consumed, end, i, ref2; - if (opts == null) { - opts = {}; - } this.literate = opts.literate; this.indent = 0; this.baseIndent = 0; @@ -806,14 +803,8 @@ return [this.chunkLine + lineCount, column]; }; - Lexer.prototype.makeToken = function(tag, value, offsetInChunk, length) { + Lexer.prototype.makeToken = function(tag, value, offsetInChunk = 0, length = value.length) { var lastCharacter, locationData, ref2, ref3, token; - if (offsetInChunk == null) { - offsetInChunk = 0; - } - if (length == null) { - length = value.length; - } locationData = {}; ref2 = this.getLineAndColumnFromChunk(offsetInChunk), locationData.first_line = ref2[0], locationData.first_column = ref2[1]; lastCharacter = length > 0 ? length - 1 : 0; @@ -857,11 +848,8 @@ return str.replace(HEREGEX_OMIT, '$1$2'); }; - Lexer.prototype.validateEscapes = function(str, options) { + Lexer.prototype.validateEscapes = function(str, options = {}) { var before, hex, invalidEscape, match, message, octal, ref2, unicode; - if (options == null) { - options = {}; - } match = INVALID_ESCAPE.exec(str); if (!match) { return; @@ -878,11 +866,8 @@ }); }; - Lexer.prototype.makeDelimitedLiteral = function(body, options) { + Lexer.prototype.makeDelimitedLiteral = function(body, options = {}) { var regex; - if (options == null) { - options = {}; - } if (body === '' && options.delimiter === '/') { body = '(?:)'; } @@ -918,11 +903,8 @@ return "" + options.delimiter + body + options.delimiter; }; - Lexer.prototype.error = function(message, options) { + Lexer.prototype.error = function(message, options = {}) { var first_column, first_line, location, ref2, ref3, ref4; - if (options == null) { - options = {}; - } location = 'first_line' in options ? options : ((ref3 = this.getLineAndColumnFromChunk((ref2 = options.offset) != null ? ref2 : 0), first_line = ref3[0], first_column = ref3[1], ref3), { first_line: first_line, first_column: first_column, @@ -935,10 +917,7 @@ })(); - isUnassignable = function(name, displayName) { - if (displayName == null) { - displayName = name; - } + isUnassignable = function(name, displayName = name) { switch (false) { case indexOf.call(slice.call(JS_KEYWORDS).concat(slice.call(COFFEE_KEYWORDS)), name) < 0: return "keyword '" + displayName + "' can't be assigned"; diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index cfa6a421c2..75e3d62d97 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -166,14 +166,8 @@ return null; }; - Base.prototype.toString = function(idt, name) { + Base.prototype.toString = function(idt = '', name = this.constructor.name) { var tree; - if (idt == null) { - idt = ''; - } - if (name == null) { - name = this.constructor.name; - } tree = '\n' + idt + name; if (this.soak) { tree += '?'; @@ -361,10 +355,7 @@ return this; }; - Block.prototype.compileToFragments = function(o, level) { - if (o == null) { - o = {}; - } + Block.prototype.compileToFragments = function(o = {}, level) { if (o.scope) { return Block.__super__.compileToFragments.call(this, o, level); } else { @@ -1011,6 +1002,7 @@ extend1(Call, superClass1); function Call(variable1, args1, soak) { + var args1; this.variable = variable1; this.args = args1 != null ? args1 : []; this.soak = soak; @@ -1205,10 +1197,7 @@ exports.RegexWithInterpolations = RegexWithInterpolations = (function(superClass1) { extend1(RegexWithInterpolations, superClass1); - function RegexWithInterpolations(args) { - if (args == null) { - args = []; - } + function RegexWithInterpolations(args = []) { RegexWithInterpolations.__super__.constructor.call(this, new Value(new IdentifierLiteral('RegExp')), args, false); } @@ -1419,6 +1408,7 @@ extend1(Obj, superClass1); function Obj(props, generated) { + var generated; this.generated = generated != null ? generated : false; this.objects = this.properties = props || []; } @@ -1598,6 +1588,7 @@ extend1(Class, superClass1); function Class(variable1, parent1, body1) { + var body1; this.variable = variable1; this.parent = parent1; this.body = body1 != null ? body1 : new Block; @@ -2103,13 +2094,10 @@ exports.Assign = Assign = (function(superClass1) { extend1(Assign, superClass1); - function Assign(variable1, value1, context, options) { + function Assign(variable1, value1, context, options = {}) { this.variable = variable1; this.value = value1; this.context = context; - if (options == null) { - options = {}; - } this.param = options.param, this.subpattern = options.subpattern, this.operatorToken = options.operatorToken, this.moduleDeclaration = options.moduleDeclaration; } @@ -2423,7 +2411,7 @@ }; Code.prototype.compileNode = function(o) { - var answer, code, exprs, i, j, k, l, len1, len2, len3, len4, len5, m, p, param, params, ref, ref3, ref4, ref5, ref6, ref7, ref8, splatIndex, splatIsExpansion, splatNotLast, splatParam, splatParamReference, uniqs, val, wasEmpty; + var answer, code, expansionParamName, exprs, i, j, k, l, len1, len2, len3, len4, len5, lit, m, p, param, params, ref, ref3, ref4, ref5, ref6, ref7, ref8, splatIndex, splatNotLast, splatParam, splatParamReference, uniqs, val, wasEmpty; if (this.bound) { if ((ref3 = o.scope.method) != null ? ref3.bound : void 0) { this.context = o.scope.method.context; @@ -2446,14 +2434,16 @@ continue; } splatParam = param; + if (param instanceof Expansion) { + expansionParamName = o.scope.freeVariable('args'); + } splatIndex = i; - splatIsExpansion = param instanceof Expansion; splatNotLast = i !== this.params.length - 1; break; } ref5 = this.params; - for (k = 0, len2 = ref5.length; k < len2; k++) { - param = ref5[k]; + for (i = k = 0, len2 = ref5.length; k < len2; i = ++k) { + param = ref5[i]; if (!(!(param instanceof Expansion))) { continue; } @@ -2474,13 +2464,25 @@ } o.scope.parameter(fragmentsToText((param.value != null ? param : ref).compileToFragments(o))); if (!param.splat) { - params.push(ref); + if (splatParam instanceof Expansion && i > splatIndex) { + exprs.push(new Assign(param.name, new Value(new IdentifierLiteral(expansionParamName), [new Index(new Op('-', new Value(new IdentifierLiteral(expansionParamName), [new Access(new PropertyName('length'))]), new NumberLiteral(this.params.length - i)))]))); + if (param.value) { + lit = new Literal(param.name.value + ' == null'); + exprs.push(new If(lit, ref)); + } + o.scope.add(param.name.value, 'var', true); + } else { + params.push(ref); + } } else { splatParamReference = ref; } } if (splatParamReference != null) { params.push(splatParamReference); + } else if (splatParam != null) { + o.scope.parameter(expansionParamName); + params.push(new Value(new IdentifierLiteral(expansionParamName))); } uniqs = []; this.eachParamName((name, node) => { @@ -2519,7 +2521,7 @@ answer.push.apply(answer, param.compileToFragments(o)); } answer.push(this.makeCode(!this.bound ? ') {' : ') => {')); - if (splatNotLast) { + if (splatNotLast && !(splatParam instanceof Expansion)) { answer.push(this.makeCode("\n" + o.indent + "[")); ref7 = this.params; for (i = m = 0, len4 = ref7.length; m < len4; i = ++m) { @@ -2629,11 +2631,8 @@ return this.name.isComplex(); }; - Param.prototype.eachName = function(iterator, name) { + Param.prototype.eachName = function(iterator, name = this.name) { var atParam, j, len1, node, obj, ref3, ref4; - if (name == null) { - name = this.name; - } atParam = function(obj) { return iterator("@" + obj.properties[0].name.value, obj); }; @@ -3531,13 +3530,10 @@ Switch.prototype.isStatement = YES; - Switch.prototype.jumps = function(o) { + Switch.prototype.jumps = function(o = { + block: true + }) { var block, conds, j, jumpNode, len1, ref3, ref4, ref5; - if (o == null) { - o = { - block: true - }; - } ref3 = this.cases; for (j = 0, len1 = ref3.length; j < len1; j++) { ref4 = ref3[j], conds = ref4[0], block = ref4[1]; @@ -3606,11 +3602,8 @@ exports.If = If = (function(superClass1) { extend1(If, superClass1); - function If(condition, body1, options) { + function If(condition, body1, options = {}) { this.body = body1; - if (options == null) { - options = {}; - } this.condition = options.type === 'unless' ? condition.invert() : condition; this.elseBody = null; this.isChain = false; diff --git a/lib/coffee-script/optparse.js b/lib/coffee-script/optparse.js index 43c5eabf62..0895d29120 100644 --- a/lib/coffee-script/optparse.js +++ b/lib/coffee-script/optparse.js @@ -100,11 +100,8 @@ return results; }; - buildRule = function(shortFlag, longFlag, description, options) { + buildRule = function(shortFlag, longFlag, description, options = {}) { var match; - if (options == null) { - options = {}; - } match = longFlag.match(OPTIONAL); longFlag = longFlag.match(LONG_FLAG)[1]; return { diff --git a/lib/coffee-script/repl.js b/lib/coffee-script/repl.js index b947d0613d..87dc8525ce 100644 --- a/lib/coffee-script/repl.js +++ b/lib/coffee-script/repl.js @@ -166,11 +166,8 @@ }; module.exports = { - start: function(opts) { + start: function(opts = {}) { var build, major, minor, ref1, repl; - if (opts == null) { - opts = {}; - } ref1 = process.versions.node.split('.').map(function(n) { return parseInt(n); }), major = ref1[0], minor = ref1[1], build = ref1[2]; diff --git a/lib/coffee-script/rewriter.js b/lib/coffee-script/rewriter.js index 1243c81a39..6669df5807 100644 --- a/lib/coffee-script/rewriter.js +++ b/lib/coffee-script/rewriter.js @@ -214,11 +214,8 @@ tokens.splice(i, 0, generate('CALL_END', ')', ['', 'end of input', token[2]])); return i += 1; }; - startImplicitObject = function(j, startsLine) { + startImplicitObject = function(j, startsLine = true) { var idx, val; - if (startsLine == null) { - startsLine = true; - } idx = j != null ? j : i; stack.push([ '{', idx, { diff --git a/lib/coffee-script/scope.js b/lib/coffee-script/scope.js index 320c945406..993310530e 100644 --- a/lib/coffee-script/scope.js +++ b/lib/coffee-script/scope.js @@ -65,11 +65,8 @@ return !!(this.type(name) || ((ref = this.parent) != null ? ref.check(name) : void 0)); }; - Scope.prototype.temporary = function(name, index, single) { + Scope.prototype.temporary = function(name, index, single = false) { var diff, endCode, letter, newCode, num, startCode; - if (single == null) { - single = false; - } if (single) { startCode = name.charCodeAt(0); endCode = 'z'.charCodeAt(0); @@ -95,11 +92,8 @@ return null; }; - Scope.prototype.freeVariable = function(name, options) { + Scope.prototype.freeVariable = function(name, options = {}) { var index, ref, temp; - if (options == null) { - options = {}; - } index = 0; while (true) { temp = this.temporary(name, index, options.single); diff --git a/lib/coffee-script/sourcemap.js b/lib/coffee-script/sourcemap.js index d82531642c..d14b583034 100644 --- a/lib/coffee-script/sourcemap.js +++ b/lib/coffee-script/sourcemap.js @@ -8,12 +8,9 @@ this.columns = []; } - LineMap.prototype.add = function(column, arg, options) { + LineMap.prototype.add = function(column, arg, options = {}) { var sourceColumn, sourceLine; sourceLine = arg[0], sourceColumn = arg[1]; - if (options == null) { - options = {}; - } if (this.columns[column] && options.noReplace) { return; } @@ -44,11 +41,8 @@ this.lines = []; } - SourceMap.prototype.add = function(sourceLocation, generatedLocation, options) { + SourceMap.prototype.add = function(sourceLocation, generatedLocation, options = {}) { var base, column, line, lineMap; - if (options == null) { - options = {}; - } line = generatedLocation[0], column = generatedLocation[1]; lineMap = ((base = this.lines)[line] || (base[line] = new LineMap(line))); return lineMap.add(column, sourceLocation, options); @@ -63,14 +57,8 @@ return lineMap && lineMap.sourceLocation(column); }; - SourceMap.prototype.generate = function(options, code) { + SourceMap.prototype.generate = function(options = {}, code = null) { var buffer, i, j, lastColumn, lastSourceColumn, lastSourceLine, len, len1, lineMap, lineNumber, mapping, needComma, ref, ref1, v3, writingline; - if (options == null) { - options = {}; - } - if (code == null) { - code = null; - } writingline = 0; lastColumn = 0; lastSourceLine = 0; diff --git a/src/nodes.coffee b/src/nodes.coffee index bc45c93de8..2c13e44911 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1631,13 +1631,13 @@ exports.Code = class Code extends Base # If `...` was used with a parameter, which parameter was it used with? for param, i in @params when param.splat or param instanceof Expansion splatParam = param + expansionParamName = o.scope.freeVariable 'args' if param instanceof Expansion splatIndex = i - splatIsExpansion = param instanceof Expansion splatNotLast = i isnt @params.length - 1 break # Only one splat/expansion parameter is permitted # Parse the parameters - for param in @params when param not instanceof Expansion + for param, i in @params when param not instanceof Expansion # Prepare the parameters for output if param.isComplex() # Parameter is destructured or attached to `this` val = ref = param.asReference o @@ -1652,13 +1652,35 @@ exports.Code = class Code extends Base # Add this parameter’s reference to the function scope o.scope.parameter fragmentsToText (if param.value? then param else ref).compileToFragments o - # Add this parameter to the list of parameters, except if the param is a splat which must go last + # Add this parameter to the list of parameters, except if the param is a splat which must go last; + # or if one of the parameters is an expansion, all post-expansion parameters are declared in the body. unless param.splat - params.push ref + if splatParam instanceof Expansion and i > splatIndex + # This parameter was after an expansion, so it won’t be in the function parameter list. + # Declare it based on the index of the expansion object `args` that will become the last parameter. + exprs.push new Assign param.name, new Value new IdentifierLiteral(expansionParamName), [ + new Index new Op '-', + new Value(new IdentifierLiteral(expansionParamName), [new Access new PropertyName 'length']), + new NumberLiteral @params.length - i + ] + # If this parameter had a default value, since it’s no longer in the function parameter list + # we need to assign its default value (if necessary) as an expression in the body. + if param.value + lit = new Literal param.name.value + ' == null' + exprs.push new If lit, ref + # Add this parameter to the scope, since it wouldn’t have been added yet since it was skipped earlier. + o.scope.add param.name.value, 'var', yes + else + params.push ref else splatParamReference = ref - # If we have a splat parameter, add it to the end - params.push splatParamReference if splatParamReference? + + # If we have a splat parameter, or one of the parameters was an expansion, add it to the end + if splatParamReference? + params.push splatParamReference + else if splatParam? # If we’ve reached this point, `splatParam` is an Expansion + o.scope.parameter expansionParamName + params.push new Value new IdentifierLiteral expansionParamName # Check for duplicate parameters uniqs = [] @@ -1687,7 +1709,7 @@ exports.Code = class Code extends Base # ES2015 allows `...` to be used only in the final parameter. CoffeeScript allows it in any parameter, so we need # to apply some gymnastics to move the `...` to the final parameter and reassign the parameter values accordingly. - if splatNotLast + if splatNotLast and splatParam not instanceof Expansion answer.push @makeCode "\n#{o.indent}[" for param, i in @params when i > splatIndex answer.push @makeCode ', ' if i isnt splatIndex + 1 From 2821935d038c6d6fbce2dab3fc1e641325f9b907 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Sun, 2 Oct 2016 22:15:46 -0700 Subject: [PATCH 10/34] Handle splat parameters the same way we handle expansions: if a splat parameter is found, it becomes the last parameter in the function definition, and all following parameters get declared in the function body. Fix the splat/rest parameter values after the post-splat parameters have been extracted from it. Clean up `Code.compileNode` so that we loop through the parameters only once, and we create all expressions using calls like `new IdentifierLiteral` rather than `@makeCode`. --- lib/coffee-script/nodes.js | 116 ++++++++++++-------------------- src/nodes.coffee | 132 +++++++++++++++++++------------------ 2 files changed, 110 insertions(+), 138 deletions(-) diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index 75e3d62d97..492274faca 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -2411,7 +2411,7 @@ }; Code.prototype.compileNode = function(o) { - var answer, code, expansionParamName, exprs, i, j, k, l, len1, len2, len3, len4, len5, lit, m, p, param, params, ref, ref3, ref4, ref5, ref6, ref7, ref8, splatIndex, splatNotLast, splatParam, splatParamReference, uniqs, val, wasEmpty; + var answer, code, exprs, haveSplatParam, i, j, k, len1, len2, lit, param, params, paramsAfterSplat, ref, ref3, ref4, ref5, splatParamName, uniqs, val, wasEmpty; if (this.bound) { if ((ref3 = o.scope.method) != null ? ref3.bound : void 0) { this.context = o.scope.method.context; @@ -2427,62 +2427,57 @@ delete o.isExistentialEquals; params = []; exprs = []; + haveSplatParam = false; + paramsAfterSplat = 0; ref4 = this.params; for (i = j = 0, len1 = ref4.length; j < len1; i = ++j) { param = ref4[i]; - if (!(param.splat || param instanceof Expansion)) { - continue; - } - splatParam = param; - if (param instanceof Expansion) { - expansionParamName = o.scope.freeVariable('args'); - } - splatIndex = i; - splatNotLast = i !== this.params.length - 1; - break; - } - ref5 = this.params; - for (i = k = 0, len2 = ref5.length; k < len2; i = ++k) { - param = ref5[i]; - if (!(!(param instanceof Expansion))) { - continue; - } - if (param.isComplex()) { - val = ref = param.asReference(o); - if (param.value) { - val = new Op('?', ref, param.value); + if (param.splat || param instanceof Expansion) { + haveSplatParam = true; + if (param.name.value != null) { + splatParamName = param.name.value; + params.push(param); + } else { + splatParamName = o.scope.freeVariable('args'); + params.push(new Value(new IdentifierLiteral(splatParamName))); } - exprs.push(new Assign(new Value(param.name), val, '=', { - param: true - })); + o.scope.parameter(splatParamName); } else { - if (param.value != null) { - ref = new Assign(new Value(param.name), param.value, '='); + if (!haveSplatParam) { + if (param.isComplex()) { + val = ref = param.asReference(o); + if (param.value) { + val = new Op('?', ref, param.value); + } + exprs.push(new Assign(new Value(param.name), val, '=', { + param: true + })); + } else { + if (param.value != null) { + ref = new Assign(new Value(param.name), param.value, '='); + } else { + ref = param; + } + } + o.scope.parameter(fragmentsToText((param.value != null ? param : ref).compileToFragments(o))); + params.push(ref); } else { - ref = param; - } - } - o.scope.parameter(fragmentsToText((param.value != null ? param : ref).compileToFragments(o))); - if (!param.splat) { - if (splatParam instanceof Expansion && i > splatIndex) { - exprs.push(new Assign(param.name, new Value(new IdentifierLiteral(expansionParamName), [new Index(new Op('-', new Value(new IdentifierLiteral(expansionParamName), [new Access(new PropertyName('length'))]), new NumberLiteral(this.params.length - i)))]))); + paramsAfterSplat = paramsAfterSplat + 1; + exprs.push(new Assign(param.name, new Value(new IdentifierLiteral(splatParamName), [new Index(new Op('-', new Value(new IdentifierLiteral(splatParamName), [new Access(new PropertyName('length'))]), new NumberLiteral(this.params.length - i)))]))); if (param.value) { lit = new Literal(param.name.value + ' == null'); exprs.push(new If(lit, ref)); } - o.scope.add(param.name.value, 'var', true); - } else { - params.push(ref); + if (!param.isComplex()) { + o.scope.add(param.name.value, 'var', true); + } } - } else { - splatParamReference = ref; } } - if (splatParamReference != null) { - params.push(splatParamReference); - } else if (splatParam != null) { - o.scope.parameter(expansionParamName); - params.push(new Value(new IdentifierLiteral(expansionParamName))); + if (paramsAfterSplat !== 0) { + ref = new IdentifierLiteral(splatParamName); + val = new Call(new Value(ref, [new Access(new PropertyName('slice'))]), [new NumberLiteral(0), new Op('-', new Value(new IdentifierLiteral(splatParamName), [new Access(new PropertyName('length'))]), new NumberLiteral(paramsAfterSplat))]); + exprs.push(new Assign(ref, val, '=')); } uniqs = []; this.eachParamName((name, node) => { @@ -2493,7 +2488,7 @@ }); wasEmpty = this.body.isEmpty(); if (exprs.length) { - (ref6 = this.body.expressions).unshift.apply(ref6, exprs); + (ref5 = this.body.expressions).unshift.apply(ref5, exprs); } if (!(wasEmpty || this.noReturn)) { this.body.makeReturn(); @@ -2510,44 +2505,17 @@ } code += '('; answer = [this.makeCode(code)]; - for (i = l = 0, len3 = params.length; l < len3; i = ++l) { + for (i = k = 0, len2 = params.length; k < len2; i = ++k) { param = params[i]; if (i) { answer.push(this.makeCode(', ')); } - if ((splatIndex != null) && i === params.length - 1) { + if (haveSplatParam && i === params.length - 1) { answer.push(this.makeCode('...')); } answer.push.apply(answer, param.compileToFragments(o)); } answer.push(this.makeCode(!this.bound ? ') {' : ') => {')); - if (splatNotLast && !(splatParam instanceof Expansion)) { - answer.push(this.makeCode("\n" + o.indent + "[")); - ref7 = this.params; - for (i = m = 0, len4 = ref7.length; m < len4; i = ++m) { - param = ref7[i]; - if (!(i > splatIndex)) { - continue; - } - if (i !== splatIndex + 1) { - answer.push(this.makeCode(', ')); - } - answer = answer.concat(param.compileToFragments(o)); - } - answer.push(this.makeCode(", ..." + splatParam.name.value + "] = " + splatParam.name.value + ".slice(-" + (this.params.length - 1 - splatIndex) + ").concat([")); - ref8 = this.params; - for (i = p = 0, len5 = ref8.length; p < len5; i = ++p) { - param = ref8[i]; - if (!(i > splatIndex)) { - continue; - } - if (i !== splatIndex + 1) { - answer.push(this.makeCode(', ')); - } - answer.push(this.makeCode(param.name.value)); - } - answer.push(this.makeCode("], " + splatParam.name.value + ".slice(0, -" + (this.params.length - 1 - splatIndex) + "));\n")); - } if (!this.body.isEmpty()) { answer = answer.concat(this.makeCode("\n"), this.body.compileWithDeclarations(o), this.makeCode("\n" + this.tab)); } diff --git a/src/nodes.coffee b/src/nodes.coffee index 2c13e44911..1a8a441ff6 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1611,10 +1611,11 @@ exports.Code = class Code extends Base makeScope: (parentScope) -> new Scope parentScope, @body, this # Compilation creates a new scope unless explicitly asked to share with the - # outer scope. Handles splat parameters in the parameter list by peeking at - # the JavaScript `arguments` object. If the function is bound with the `=>` - # arrow, generates a wrapper that saves the current value of `this` through - # a closure. + # outer scope. Handles splat parameters in the parameter list by setting + # such parameters to be the final parameter in the function definition, as + # required per the ES2015 spec. If the CoffeeScript function definition had + # parameters after the splat, they are declared via expressions in the + # function body. compileNode: (o) -> if @bound @context = o.scope.method.context if o.scope.method?.bound @@ -1625,42 +1626,57 @@ exports.Code = class Code extends Base o.indent += TAB delete o.bare delete o.isExistentialEquals - params = [] - exprs = [] - - # If `...` was used with a parameter, which parameter was it used with? - for param, i in @params when param.splat or param instanceof Expansion - splatParam = param - expansionParamName = o.scope.freeVariable 'args' if param instanceof Expansion - splatIndex = i - splatNotLast = i isnt @params.length - 1 - break # Only one splat/expansion parameter is permitted - - # Parse the parameters - for param, i in @params when param not instanceof Expansion - # Prepare the parameters for output - if param.isComplex() # Parameter is destructured or attached to `this` - val = ref = param.asReference o - val = new Op '?', ref, param.value if param.value - exprs.push new Assign new Value(param.name), val, '=', param: yes + params = [] + exprs = [] + haveSplatParam = no + paramsAfterSplat = 0 + + # Parse the parameters, adding them to the list of parameters to put in the + # function definition; and dealing with splats or expansions, including + # adding expressions to the function body to declare all parameter + # variables that would have been after the splat/expansion parameter. + for param, i in @params + # Was `...` used with this parameter? (Only one such parameter is allowed per function.) + # Splat/expansion parameters cannot have default values, so we need not worry about that. + if param.splat or param instanceof Expansion + haveSplatParam = yes + if param.name.value? + splatParamName = param.name.value + params.push param + else + splatParamName = o.scope.freeVariable 'args' + params.push new Value new IdentifierLiteral splatParamName + o.scope.parameter splatParamName + + # Parse all other parameters; if a splat paramater has not yet been + # encountered, add these other parameters to the list to be output in + # the function definition. else - if param.value? # Parameter has a default value - ref = new Assign new Value(param.name), param.value, '=' + unless haveSplatParam + # This parameter comes before the splat or expansion, so it will go + # in the function definition parameter list. + if param.isComplex() # Parameter is destructured or attached to `this` + val = ref = param.asReference o + val = new Op '?', ref, param.value if param.value + exprs.push new Assign new Value(param.name), val, '=', param: yes + else + if param.value? # Parameter has a default value + ref = new Assign new Value(param.name), param.value, '=' + else + ref = param + + # Add this parameter’s reference to the function scope + o.scope.parameter fragmentsToText (if param.value? then param else ref).compileToFragments o + + params.push ref else - ref = param - - # Add this parameter’s reference to the function scope - o.scope.parameter fragmentsToText (if param.value? then param else ref).compileToFragments o - - # Add this parameter to the list of parameters, except if the param is a splat which must go last; - # or if one of the parameters is an expansion, all post-expansion parameters are declared in the body. - unless param.splat - if splatParam instanceof Expansion and i > splatIndex - # This parameter was after an expansion, so it won’t be in the function parameter list. - # Declare it based on the index of the expansion object `args` that will become the last parameter. - exprs.push new Assign param.name, new Value new IdentifierLiteral(expansionParamName), [ + paramsAfterSplat = paramsAfterSplat + 1 + # This parameter was after a splat or expansion, so it won’t be in + # the function parameter list. Declare it based on the index of the + # expansion object `args` that will become the last parameter. + exprs.push new Assign param.name, new Value new IdentifierLiteral(splatParamName), [ new Index new Op '-', - new Value(new IdentifierLiteral(expansionParamName), [new Access new PropertyName 'length']), + new Value(new IdentifierLiteral(splatParamName), [new Access new PropertyName 'length']), new NumberLiteral @params.length - i ] # If this parameter had a default value, since it’s no longer in the function parameter list @@ -1669,18 +1685,20 @@ exports.Code = class Code extends Base lit = new Literal param.name.value + ' == null' exprs.push new If lit, ref # Add this parameter to the scope, since it wouldn’t have been added yet since it was skipped earlier. - o.scope.add param.name.value, 'var', yes - else - params.push ref - else - splatParamReference = ref - - # If we have a splat parameter, or one of the parameters was an expansion, add it to the end - if splatParamReference? - params.push splatParamReference - else if splatParam? # If we’ve reached this point, `splatParam` is an Expansion - o.scope.parameter expansionParamName - params.push new Value new IdentifierLiteral expansionParamName + o.scope.add param.name.value, 'var', yes unless param.isComplex() # Don’t add a `this.` param to the scope. + + # If there were parameters after the splat or expansion parameter, those + # parameters need to be sliced off the end of the splat parameter’s + # array of arguments passed into the function. + if paramsAfterSplat isnt 0 + ref = new IdentifierLiteral splatParamName + val = new Call (new Value ref, [new Access new PropertyName 'slice']), [ + new NumberLiteral 0 + (new Op '-', + new Value(new IdentifierLiteral(splatParamName), [new Access new PropertyName 'length']), + new NumberLiteral paramsAfterSplat) + ] + exprs.push new Assign ref, val, '=' # Check for duplicate parameters uniqs = [] @@ -1703,23 +1721,9 @@ exports.Code = class Code extends Base answer = [@makeCode(code)] for param, i in params answer.push @makeCode ', ' if i - answer.push @makeCode '...' if splatIndex? and i is params.length - 1 # Rest syntax is always on the last parameter + answer.push @makeCode '...' if haveSplatParam and i is params.length - 1 # Rest syntax is always on the last parameter answer.push param.compileToFragments(o)... answer.push @makeCode unless @bound then ') {' else ') => {' - - # ES2015 allows `...` to be used only in the final parameter. CoffeeScript allows it in any parameter, so we need - # to apply some gymnastics to move the `...` to the final parameter and reassign the parameter values accordingly. - if splatNotLast and splatParam not instanceof Expansion - answer.push @makeCode "\n#{o.indent}[" - for param, i in @params when i > splatIndex - answer.push @makeCode ', ' if i isnt splatIndex + 1 - answer = answer.concat param.compileToFragments o - answer.push @makeCode ", ...#{splatParam.name.value}] = #{splatParam.name.value}.slice(-#{@params.length - 1 - splatIndex}).concat([" - for param, i in @params when i > splatIndex - answer.push @makeCode ', ' if i isnt splatIndex + 1 - answer.push @makeCode param.name.value - answer.push @makeCode "], #{splatParam.name.value}.slice(0, -#{@params.length - 1 - splatIndex}));\n" - answer = answer.concat(@makeCode("\n"), @body.compileWithDeclarations(o), @makeCode("\n#{@tab}")) unless @body.isEmpty() answer.push @makeCode '}' From e4c0f22620b563ddd999eb10cfa1277e6c4aab5d Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Sun, 2 Oct 2016 22:58:31 -0700 Subject: [PATCH 11/34] Fix parameter name when a parameter is a splat attached to `this` (e.g. `@param...`) --- lib/coffee-script/nodes.js | 6 ++++++ src/nodes.coffee | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index 492274faca..9bf28d3a4e 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -2437,6 +2437,12 @@ if (param.name.value != null) { splatParamName = param.name.value; params.push(param); + } else if (param.isComplex()) { + splatParamName = param.name.properties[0].name; + exprs.push(new Assign(new Value(param.name), splatParamName, '=', { + param: true + })); + params.push(splatParamName); } else { splatParamName = o.scope.freeVariable('args'); params.push(new Value(new IdentifierLiteral(splatParamName))); diff --git a/src/nodes.coffee b/src/nodes.coffee index 1a8a441ff6..110aeefd66 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1643,6 +1643,10 @@ exports.Code = class Code extends Base if param.name.value? splatParamName = param.name.value params.push param + else if param.isComplex() # Parameter is destructured or attached to `this` + splatParamName = param.name.properties[0].name + exprs.push new Assign new Value(param.name), splatParamName, '=', param: yes + params.push splatParamName else splatParamName = o.scope.freeVariable 'args' params.push new Value new IdentifierLiteral splatParamName From 2006b2d44db4d65e640fc944a7fe01442e2d6800 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Sun, 2 Oct 2016 23:40:43 -0700 Subject: [PATCH 12/34] =?UTF-8?q?Rather=20than=20assigning=20post-splat=20?= =?UTF-8?q?parameters=20based=20on=20index,=20use=20slice;=20passes=20test?= =?UTF-8?q?=20=E2=80=9CFunctions=20with=20splats=20being=20called=20with?= =?UTF-8?q?=20too=20few=20arguments=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/coffee-script/nodes.js | 6 ++---- src/nodes.coffee | 34 ++++++++++++++++++++-------------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index 9bf28d3a4e..1bc571f332 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -2469,7 +2469,7 @@ params.push(ref); } else { paramsAfterSplat = paramsAfterSplat + 1; - exprs.push(new Assign(param.name, new Value(new IdentifierLiteral(splatParamName), [new Index(new Op('-', new Value(new IdentifierLiteral(splatParamName), [new Access(new PropertyName('length'))]), new NumberLiteral(this.params.length - i)))]))); + exprs.push(new Assign(param.name, new Value(new Call(new Value(new IdentifierLiteral(splatParamName), [new Access(new PropertyName('slice'))]), [new NumberLiteral(i - this.params.length)]), [new Index(new NumberLiteral(0))]))); if (param.value) { lit = new Literal(param.name.value + ' == null'); exprs.push(new If(lit, ref)); @@ -2481,9 +2481,7 @@ } } if (paramsAfterSplat !== 0) { - ref = new IdentifierLiteral(splatParamName); - val = new Call(new Value(ref, [new Access(new PropertyName('slice'))]), [new NumberLiteral(0), new Op('-', new Value(new IdentifierLiteral(splatParamName), [new Access(new PropertyName('length'))]), new NumberLiteral(paramsAfterSplat))]); - exprs.push(new Assign(ref, val, '=')); + exprs.push(new Assign(new IdentifierLiteral(splatParamName), new Call(new Value(new IdentifierLiteral(splatParamName), [new Access(new PropertyName('slice'))]), [new NumberLiteral(0), new Op('-', new Value(new IdentifierLiteral(splatParamName), [new Access(new PropertyName('length'))]), new NumberLiteral(paramsAfterSplat))]))); } uniqs = []; this.eachParamName((name, node) => { diff --git a/src/nodes.coffee b/src/nodes.coffee index 110aeefd66..0a83a6415f 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1677,12 +1677,14 @@ exports.Code = class Code extends Base paramsAfterSplat = paramsAfterSplat + 1 # This parameter was after a splat or expansion, so it won’t be in # the function parameter list. Declare it based on the index of the - # expansion object `args` that will become the last parameter. - exprs.push new Assign param.name, new Value new IdentifierLiteral(splatParamName), [ - new Index new Op '-', - new Value(new IdentifierLiteral(splatParamName), [new Access new PropertyName 'length']), - new NumberLiteral @params.length - i - ] + # splat/expansion argument that will become the last parameter. + exprs.push new Assign param.name, new Value ( + new Call ( + new Value new IdentifierLiteral(splatParamName), + [new Access new PropertyName 'slice']), + [new NumberLiteral (i - @params.length)] + ), [new Index new NumberLiteral 0] + # If this parameter had a default value, since it’s no longer in the function parameter list # we need to assign its default value (if necessary) as an expression in the body. if param.value @@ -1695,14 +1697,18 @@ exports.Code = class Code extends Base # parameters need to be sliced off the end of the splat parameter’s # array of arguments passed into the function. if paramsAfterSplat isnt 0 - ref = new IdentifierLiteral splatParamName - val = new Call (new Value ref, [new Access new PropertyName 'slice']), [ - new NumberLiteral 0 - (new Op '-', - new Value(new IdentifierLiteral(splatParamName), [new Access new PropertyName 'length']), - new NumberLiteral paramsAfterSplat) - ] - exprs.push new Assign ref, val, '=' + exprs.push new Assign new IdentifierLiteral(splatParamName), new Call ( + new Value new IdentifierLiteral(splatParamName), + [new Access new PropertyName 'slice']), + [ + new NumberLiteral 0 + (new Op '-', + new Value( + new IdentifierLiteral(splatParamName), + [new Access new PropertyName 'length'] + ), + new NumberLiteral paramsAfterSplat) + ] # Check for duplicate parameters uniqs = [] From 1423055a336d687a5fb21d4236e37d769a7104f6 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Mon, 3 Oct 2016 19:50:53 -0700 Subject: [PATCH 13/34] Dial back our w00t indentation --- src/nodes.coffee | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/src/nodes.coffee b/src/nodes.coffee index 0a83a6415f..43fc801e05 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1678,12 +1678,13 @@ exports.Code = class Code extends Base # This parameter was after a splat or expansion, so it won’t be in # the function parameter list. Declare it based on the index of the # splat/expansion argument that will become the last parameter. - exprs.push new Assign param.name, new Value ( - new Call ( - new Value new IdentifierLiteral(splatParamName), - [new Access new PropertyName 'slice']), - [new NumberLiteral (i - @params.length)] - ), [new Index new NumberLiteral 0] + exprs.push new Assign param.name, + new Value ( + new Call ( + new Value new IdentifierLiteral(splatParamName), + [new Access new PropertyName 'slice']), + [new NumberLiteral (i - @params.length)] + ), [new Index new NumberLiteral 0] # If this parameter had a default value, since it’s no longer in the function parameter list # we need to assign its default value (if necessary) as an expression in the body. @@ -1697,18 +1698,19 @@ exports.Code = class Code extends Base # parameters need to be sliced off the end of the splat parameter’s # array of arguments passed into the function. if paramsAfterSplat isnt 0 - exprs.push new Assign new IdentifierLiteral(splatParamName), new Call ( - new Value new IdentifierLiteral(splatParamName), - [new Access new PropertyName 'slice']), - [ - new NumberLiteral 0 - (new Op '-', - new Value( - new IdentifierLiteral(splatParamName), - [new Access new PropertyName 'length'] - ), - new NumberLiteral paramsAfterSplat) - ] + exprs.push new Assign new IdentifierLiteral(splatParamName), + new Call ( + new Value new IdentifierLiteral(splatParamName), + [new Access new PropertyName 'slice']), + [ + new NumberLiteral 0 + (new Op '-', + new Value( + new IdentifierLiteral(splatParamName), + [new Access new PropertyName 'length'] + ), + new NumberLiteral paramsAfterSplat) + ] # Check for duplicate parameters uniqs = [] From 6288ac81227b9cf92992839194f2dad683ce614f Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Mon, 3 Oct 2016 21:20:29 -0700 Subject: [PATCH 14/34] Better parsing of parameter names (WIP) --- src/nodes.coffee | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/nodes.coffee b/src/nodes.coffee index 43fc801e05..c76ec79274 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1631,6 +1631,13 @@ exports.Code = class Code extends Base haveSplatParam = no paramsAfterSplat = 0 + # Check for duplicate parameters, and save the names so that we can pull + # the name of the splat parameter if we have one. + paramNames = [] + @eachParamName (name, node) => + node.error "multiple parameters named '#{name}'" if name in paramNames + paramNames.push name + # Parse the parameters, adding them to the list of parameters to put in the # function definition; and dealing with splats or expansions, including # adding expressions to the function body to declare all parameter @@ -1640,13 +1647,16 @@ exports.Code = class Code extends Base # Splat/expansion parameters cannot have default values, so we need not worry about that. if param.splat or param instanceof Expansion haveSplatParam = yes - if param.name.value? - splatParamName = param.name.value + splatParamName = paramNames[i].replace /@|this\.(.*)/, '$1' + if param.name?.value? params.push param else if param.isComplex() # Parameter is destructured or attached to `this` - splatParamName = param.name.properties[0].name - exprs.push new Assign new Value(param.name), splatParamName, '=', param: yes - params.push splatParamName + if param.name.properties?[0]?.name? + params.push param.name.properties[0].name + else + params.push new Value new IdentifierLiteral splatParamName + exprs.push new Assign new Value(param.name), + new Value(new IdentifierLiteral(splatParamName)), '=', param: yes else splatParamName = o.scope.freeVariable 'args' params.push new Value new IdentifierLiteral splatParamName @@ -1712,12 +1722,6 @@ exports.Code = class Code extends Base new NumberLiteral paramsAfterSplat) ] - # Check for duplicate parameters - uniqs = [] - @eachParamName (name, node) => - node.error "multiple parameters named '#{name}'" if name in uniqs - uniqs.push name - # Add new expressions to the function body wasEmpty = @body.isEmpty() @body.expressions.unshift exprs... if exprs.length From 0750ff7bd33024dc807261edbf5d7927e6150001 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Mon, 3 Oct 2016 21:33:06 -0700 Subject: [PATCH 15/34] Refactor processing of splat/expansion parameters --- lib/coffee-script/nodes.js | 38 ++++++++++++++++++++------------------ src/nodes.coffee | 19 +++++++++---------- 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index 1bc571f332..2bce20fe1d 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -2411,7 +2411,7 @@ }; Code.prototype.compileNode = function(o) { - var answer, code, exprs, haveSplatParam, i, j, k, len1, len2, lit, param, params, paramsAfterSplat, ref, ref3, ref4, ref5, splatParamName, uniqs, val, wasEmpty; + var answer, code, exprs, haveSplatParam, i, j, k, len1, len2, lit, param, paramNames, params, paramsAfterSplat, ref, ref3, ref4, ref5, ref6, splatParamName, val, wasEmpty; if (this.bound) { if ((ref3 = o.scope.method) != null ? ref3.bound : void 0) { this.context = o.scope.method.context; @@ -2429,20 +2429,29 @@ exprs = []; haveSplatParam = false; paramsAfterSplat = 0; + paramNames = []; + this.eachParamName((name, node) => { + if (indexOf.call(paramNames, name) >= 0) { + node.error("multiple parameters named '" + name + "'"); + } + return paramNames.push(name); + }); ref4 = this.params; for (i = j = 0, len1 = ref4.length; j < len1; i = ++j) { param = ref4[i]; if (param.splat || param instanceof Expansion) { haveSplatParam = true; - if (param.name.value != null) { - splatParamName = param.name.value; - params.push(param); - } else if (param.isComplex()) { - splatParamName = param.name.properties[0].name; - exprs.push(new Assign(new Value(param.name), splatParamName, '=', { - param: true - })); - params.push(splatParamName); + if (param.splat) { + if (((ref5 = param.name) != null ? ref5["this"] : void 0) != null) { + splatParamName = paramNames[i].replace(/@|this\.(.*)/, '$1'); + params.push(new Value(new IdentifierLiteral(splatParamName))); + exprs.push(new Assign(new Value(param.name), new Value(new IdentifierLiteral(splatParamName)), '=', { + param: true + })); + } else { + splatParamName = paramNames[i]; + params.push(param); + } } else { splatParamName = o.scope.freeVariable('args'); params.push(new Value(new IdentifierLiteral(splatParamName))); @@ -2483,16 +2492,9 @@ if (paramsAfterSplat !== 0) { exprs.push(new Assign(new IdentifierLiteral(splatParamName), new Call(new Value(new IdentifierLiteral(splatParamName), [new Access(new PropertyName('slice'))]), [new NumberLiteral(0), new Op('-', new Value(new IdentifierLiteral(splatParamName), [new Access(new PropertyName('length'))]), new NumberLiteral(paramsAfterSplat))]))); } - uniqs = []; - this.eachParamName((name, node) => { - if (indexOf.call(uniqs, name) >= 0) { - node.error("multiple parameters named '" + name + "'"); - } - return uniqs.push(name); - }); wasEmpty = this.body.isEmpty(); if (exprs.length) { - (ref5 = this.body.expressions).unshift.apply(ref5, exprs); + (ref6 = this.body.expressions).unshift.apply(ref6, exprs); } if (!(wasEmpty || this.noReturn)) { this.body.makeReturn(); diff --git a/src/nodes.coffee b/src/nodes.coffee index c76ec79274..457ac2ec86 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1647,17 +1647,16 @@ exports.Code = class Code extends Base # Splat/expansion parameters cannot have default values, so we need not worry about that. if param.splat or param instanceof Expansion haveSplatParam = yes - splatParamName = paramNames[i].replace /@|this\.(.*)/, '$1' - if param.name?.value? - params.push param - else if param.isComplex() # Parameter is destructured or attached to `this` - if param.name.properties?[0]?.name? - params.push param.name.properties[0].name - else + if param.splat + if param.name?.this? # Parameter is attached to `this` + splatParamName = paramNames[i].replace /@|this\.(.*)/, '$1' params.push new Value new IdentifierLiteral splatParamName - exprs.push new Assign new Value(param.name), - new Value(new IdentifierLiteral(splatParamName)), '=', param: yes - else + exprs.push new Assign new Value(param.name), + new Value(new IdentifierLiteral(splatParamName)), '=', param: yes + else + splatParamName = paramNames[i] + params.push param + else # `param` is an Expansion splatParamName = o.scope.freeVariable 'args' params.push new Value new IdentifierLiteral splatParamName o.scope.parameter splatParamName From 3545819f894c4846be1147c8034fe01488f09113 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Mon, 3 Oct 2016 21:55:51 -0700 Subject: [PATCH 16/34] Fix assignment of default parameters for parameters that come after a splat --- lib/coffee-script/nodes.js | 3 ++- src/nodes.coffee | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index 2bce20fe1d..3b817c2afc 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -2479,8 +2479,9 @@ } else { paramsAfterSplat = paramsAfterSplat + 1; exprs.push(new Assign(param.name, new Value(new Call(new Value(new IdentifierLiteral(splatParamName), [new Access(new PropertyName('slice'))]), [new NumberLiteral(i - this.params.length)]), [new Index(new NumberLiteral(0))]))); - if (param.value) { + if (param.value != null) { lit = new Literal(param.name.value + ' == null'); + ref = new Assign(new Value(param.name), param.value, '='); exprs.push(new If(lit, ref)); } if (!param.isComplex()) { diff --git a/src/nodes.coffee b/src/nodes.coffee index 457ac2ec86..6f6c4d5e09 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1697,8 +1697,9 @@ exports.Code = class Code extends Base # If this parameter had a default value, since it’s no longer in the function parameter list # we need to assign its default value (if necessary) as an expression in the body. - if param.value + if param.value? lit = new Literal param.name.value + ' == null' + ref = new Assign new Value(param.name), param.value, '=' exprs.push new If lit, ref # Add this parameter to the scope, since it wouldn’t have been added yet since it was skipped earlier. o.scope.add param.name.value, 'var', yes unless param.isComplex() # Don’t add a `this.` param to the scope. From 8c24f760d346b0ded4223a6c5042d4994cfb3250 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Mon, 3 Oct 2016 23:24:03 -0700 Subject: [PATCH 17/34] Better check for whether a param is attached to `this` --- lib/coffee-script/nodes.js | 2 +- src/nodes.coffee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index 3b817c2afc..e61cb468a8 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -2484,7 +2484,7 @@ ref = new Assign(new Value(param.name), param.value, '='); exprs.push(new If(lit, ref)); } - if (!param.isComplex()) { + if (((ref6 = param.name) != null ? ref6["this"] : void 0) == null) { o.scope.add(param.name.value, 'var', true); } } diff --git a/src/nodes.coffee b/src/nodes.coffee index 6f6c4d5e09..99d583756c 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1702,7 +1702,7 @@ exports.Code = class Code extends Base ref = new Assign new Value(param.name), param.value, '=' exprs.push new If lit, ref # Add this parameter to the scope, since it wouldn’t have been added yet since it was skipped earlier. - o.scope.add param.name.value, 'var', yes unless param.isComplex() # Don’t add a `this.` param to the scope. + o.scope.add param.name.value, 'var', yes unless param.name?.this? # If there were parameters after the splat or expansion parameter, those # parameters need to be sliced off the end of the splat parameter’s From 3e889f62c66391cb031c3005c2e72d1467fd2bfd Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Mon, 3 Oct 2016 23:55:35 -0700 Subject: [PATCH 18/34] More understandable variable names --- src/nodes.coffee | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/nodes.coffee b/src/nodes.coffee index 99d583756c..3dd3d8d506 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1698,9 +1698,9 @@ exports.Code = class Code extends Base # If this parameter had a default value, since it’s no longer in the function parameter list # we need to assign its default value (if necessary) as an expression in the body. if param.value? - lit = new Literal param.name.value + ' == null' - ref = new Assign new Value(param.name), param.value, '=' - exprs.push new If lit, ref + condition = new Literal param.name.value + ' == null' + ifTrue = new Assign new Value(param.name), param.value, '=' + exprs.push new If condition, ifTrue # Add this parameter to the scope, since it wouldn’t have been added yet since it was skipped earlier. o.scope.add param.name.value, 'var', yes unless param.name?.this? From cb48a0ce7b8ed9ea76edb279a79da16183f4b808 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Sat, 8 Oct 2016 17:47:08 -0700 Subject: [PATCH 19/34] =?UTF-8?q?For=20parameters=20after=20a=20splat=20or?= =?UTF-8?q?=20expansion,=20assign=20them=20similar=20to=20the=201.x=20dest?= =?UTF-8?q?ructuring=20method=20of=20using=20`arguments`,=20except=20only?= =?UTF-8?q?=20concern=20ourselves=20with=20the=20post-splat=20parameters?= =?UTF-8?q?=20instead=20of=20all=20parameters;=20and=20use=20the=20splat/e?= =?UTF-8?q?xpansion=20parameter=20name,=20since=20`arguments`=20in=20ES=20?= =?UTF-8?q?fat=20arrow=20functions=20refers=20to=20the=20parent=20function?= =?UTF-8?q?=E2=80=99s=20`arguments`=20rather=20than=20the=20fat=20arrow=20?= =?UTF-8?q?function=E2=80=99s=20arguments/parameters?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/coffee-script/nodes.js | 27 +++++++++++++++++---------- src/nodes.coffee | 37 ++++++++----------------------------- 2 files changed, 25 insertions(+), 39 deletions(-) diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index e61cb468a8..af8f02c240 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -2411,7 +2411,7 @@ }; Code.prototype.compileNode = function(o) { - var answer, code, exprs, haveSplatParam, i, j, k, len1, len2, lit, param, paramNames, params, paramsAfterSplat, ref, ref3, ref4, ref5, ref6, splatParamName, val, wasEmpty; + var answer, code, condition, exprs, haveSplatParam, i, ifTrue, j, k, len1, len2, param, paramNames, params, paramsAfterSplat, ref, ref3, ref4, ref5, ref6, ref7, splatParamName, val, wasEmpty; if (this.bound) { if ((ref3 = o.scope.method) != null ? ref3.bound : void 0) { this.context = o.scope.method.context; @@ -2427,8 +2427,8 @@ delete o.isExistentialEquals; params = []; exprs = []; + paramsAfterSplat = []; haveSplatParam = false; - paramsAfterSplat = 0; paramNames = []; this.eachParamName((name, node) => { if (indexOf.call(paramNames, name) >= 0) { @@ -2477,12 +2477,11 @@ o.scope.parameter(fragmentsToText((param.value != null ? param : ref).compileToFragments(o))); params.push(ref); } else { - paramsAfterSplat = paramsAfterSplat + 1; - exprs.push(new Assign(param.name, new Value(new Call(new Value(new IdentifierLiteral(splatParamName), [new Access(new PropertyName('slice'))]), [new NumberLiteral(i - this.params.length)]), [new Index(new NumberLiteral(0))]))); + paramsAfterSplat.push(param); if (param.value != null) { - lit = new Literal(param.name.value + ' == null'); - ref = new Assign(new Value(param.name), param.value, '='); - exprs.push(new If(lit, ref)); + condition = new Literal(param.name.value + ' == null'); + ifTrue = new Assign(new Value(param.name), param.value, '='); + exprs.push(new If(condition, ifTrue)); } if (((ref6 = param.name) != null ? ref6["this"] : void 0) == null) { o.scope.add(param.name.value, 'var', true); @@ -2490,12 +2489,20 @@ } } } - if (paramsAfterSplat !== 0) { - exprs.push(new Assign(new IdentifierLiteral(splatParamName), new Call(new Value(new IdentifierLiteral(splatParamName), [new Access(new PropertyName('slice'))]), [new NumberLiteral(0), new Op('-', new Value(new IdentifierLiteral(splatParamName), [new Access(new PropertyName('length'))]), new NumberLiteral(paramsAfterSplat))]))); + if (paramsAfterSplat.length !== 0) { + exprs.unshift(new Assign(new Value(new Arr([new Splat(new IdentifierLiteral(splatParamName))].concat(slice.call((function() { + var k, len2, results; + results = []; + for (k = 0, len2 = paramsAfterSplat.length; k < len2; k++) { + param = paramsAfterSplat[k]; + results.push(param.asReference(o)); + } + return results; + })())))), new Value(new IdentifierLiteral(splatParamName)))); } wasEmpty = this.body.isEmpty(); if (exprs.length) { - (ref6 = this.body.expressions).unshift.apply(ref6, exprs); + (ref7 = this.body.expressions).unshift.apply(ref7, exprs); } if (!(wasEmpty || this.noReturn)) { this.body.makeReturn(); diff --git a/src/nodes.coffee b/src/nodes.coffee index 3dd3d8d506..72d7c15fec 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1628,8 +1628,8 @@ exports.Code = class Code extends Base delete o.isExistentialEquals params = [] exprs = [] + paramsAfterSplat = [] haveSplatParam = no - paramsAfterSplat = 0 # Check for duplicate parameters, and save the names so that we can pull # the name of the splat parameter if we have one. @@ -1683,18 +1683,7 @@ exports.Code = class Code extends Base params.push ref else - paramsAfterSplat = paramsAfterSplat + 1 - # This parameter was after a splat or expansion, so it won’t be in - # the function parameter list. Declare it based on the index of the - # splat/expansion argument that will become the last parameter. - exprs.push new Assign param.name, - new Value ( - new Call ( - new Value new IdentifierLiteral(splatParamName), - [new Access new PropertyName 'slice']), - [new NumberLiteral (i - @params.length)] - ), [new Index new NumberLiteral 0] - + paramsAfterSplat.push param # If this parameter had a default value, since it’s no longer in the function parameter list # we need to assign its default value (if necessary) as an expression in the body. if param.value? @@ -1705,22 +1694,12 @@ exports.Code = class Code extends Base o.scope.add param.name.value, 'var', yes unless param.name?.this? # If there were parameters after the splat or expansion parameter, those - # parameters need to be sliced off the end of the splat parameter’s - # array of arguments passed into the function. - if paramsAfterSplat isnt 0 - exprs.push new Assign new IdentifierLiteral(splatParamName), - new Call ( - new Value new IdentifierLiteral(splatParamName), - [new Access new PropertyName 'slice']), - [ - new NumberLiteral 0 - (new Op '-', - new Value( - new IdentifierLiteral(splatParamName), - [new Access new PropertyName 'length'] - ), - new NumberLiteral paramsAfterSplat) - ] + # parameters need to be assigned in the body of the function. + if paramsAfterSplat.length isnt 0 + # Create a destructured assignment, e.g. `[a, b, c] = [args..., b, c]` + exprs.unshift new Assign new Value( + new Arr [new Splat(new IdentifierLiteral(splatParamName)), (param.asReference o for param in paramsAfterSplat)...] + ), new Value new IdentifierLiteral splatParamName # Add new expressions to the function body wasEmpty = @body.isEmpty() From c0c659fcdd07bfbc1f30c8bbca467f6d4505e477 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Sat, 8 Oct 2016 18:14:08 -0700 Subject: [PATCH 20/34] =?UTF-8?q?Don=E2=80=99t=20add=20unnamed=20parameter?= =?UTF-8?q?s=20(like=20`[]`=20as=20a=20parameter)=20to=20the=20function=20?= =?UTF-8?q?scope?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/coffee-script/nodes.js | 2 +- src/nodes.coffee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index af8f02c240..235238f9da 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -2483,7 +2483,7 @@ ifTrue = new Assign(new Value(param.name), param.value, '='); exprs.push(new If(condition, ifTrue)); } - if (((ref6 = param.name) != null ? ref6["this"] : void 0) == null) { + if ((((ref6 = param.name) != null ? ref6.value : void 0) != null) && (param.name["this"] == null)) { o.scope.add(param.name.value, 'var', true); } } diff --git a/src/nodes.coffee b/src/nodes.coffee index 72d7c15fec..1e7e68a74c 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1691,7 +1691,7 @@ exports.Code = class Code extends Base ifTrue = new Assign new Value(param.name), param.value, '=' exprs.push new If condition, ifTrue # Add this parameter to the scope, since it wouldn’t have been added yet since it was skipped earlier. - o.scope.add param.name.value, 'var', yes unless param.name?.this? + o.scope.add param.name.value, 'var', yes if param.name?.value? and not param.name.this? # If there were parameters after the splat or expansion parameter, those # parameters need to be assigned in the body of the function. From 61493072a33b5e051c22b93de7ff0793ffcba562 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Sat, 8 Oct 2016 18:36:33 -0700 Subject: [PATCH 21/34] Disallow multiple splat/expansion parameters in function definitions; disallow lone expansion parameters --- lib/coffee-script/nodes.js | 5 +++++ src/nodes.coffee | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index 235238f9da..13246c35d6 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -2440,6 +2440,11 @@ for (i = j = 0, len1 = ref4.length; j < len1; i = ++j) { param = ref4[i]; if (param.splat || param instanceof Expansion) { + if (haveSplatParam) { + param.error('only one splat or expansion parameter is allowed per function definition'); + } else if (param instanceof Expansion && this.params.length === 1) { + param.error('an expansion parameter cannot be the only parameter in a function definition'); + } haveSplatParam = true; if (param.splat) { if (((ref5 = param.name) != null ? ref5["this"] : void 0) != null) { diff --git a/src/nodes.coffee b/src/nodes.coffee index 1e7e68a74c..6368560785 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1646,6 +1646,11 @@ exports.Code = class Code extends Base # Was `...` used with this parameter? (Only one such parameter is allowed per function.) # Splat/expansion parameters cannot have default values, so we need not worry about that. if param.splat or param instanceof Expansion + if haveSplatParam + param.error 'only one splat or expansion parameter is allowed per function definition' + else if param instanceof Expansion and @params.length is 1 + param.error 'an expansion parameter cannot be the only parameter in a function definition' + haveSplatParam = yes if param.splat if param.name?.this? # Parameter is attached to `this` From 073b3cd5ac09f222d8926e11ae3ee947afd1ed21 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Sun, 9 Oct 2016 00:48:02 -0700 Subject: [PATCH 22/34] Fix `this` params not getting assigned if the parameter is after a splat parameter --- lib/coffee-script/nodes.js | 23 ++++++++++++----------- src/nodes.coffee | 27 +++++++++++++++++---------- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index 13246c35d6..8b2fe0f117 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -2463,16 +2463,17 @@ } o.scope.parameter(splatParamName); } else { + if (param.isComplex()) { + val = ref = param.asReference(o); + if (param.value) { + val = new Op('?', ref, param.value); + } + exprs.push(new Assign(new Value(param.name), val, '=', { + param: true + })); + } if (!haveSplatParam) { - if (param.isComplex()) { - val = ref = param.asReference(o); - if (param.value) { - val = new Op('?', ref, param.value); - } - exprs.push(new Assign(new Value(param.name), val, '=', { - param: true - })); - } else { + if (!param.isComplex()) { if (param.value != null) { ref = new Assign(new Value(param.name), param.value, '='); } else { @@ -2483,12 +2484,12 @@ params.push(ref); } else { paramsAfterSplat.push(param); - if (param.value != null) { + if ((param.value != null) && !param.isComplex()) { condition = new Literal(param.name.value + ' == null'); ifTrue = new Assign(new Value(param.name), param.value, '='); exprs.push(new If(condition, ifTrue)); } - if ((((ref6 = param.name) != null ? ref6.value : void 0) != null) && (param.name["this"] == null)) { + if (((ref6 = param.name) != null ? ref6.value : void 0) != null) { o.scope.add(param.name.value, 'var', true); } } diff --git a/src/nodes.coffee b/src/nodes.coffee index 6368560785..2d9e103732 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1670,15 +1670,22 @@ exports.Code = class Code extends Base # encountered, add these other parameters to the list to be output in # the function definition. else + # If the parameter is attached to `this`, add a statement to the body + # that assigns it as such. + if param.isComplex() + val = ref = param.asReference o + val = new Op '?', ref, param.value if param.value + exprs.push new Assign new Value(param.name), val, '=', param: yes + + # If this parameter comes before the splat or expansion, it will go + # in the function definition parameter list. unless haveSplatParam - # This parameter comes before the splat or expansion, so it will go - # in the function definition parameter list. - if param.isComplex() # Parameter is destructured or attached to `this` - val = ref = param.asReference o - val = new Op '?', ref, param.value if param.value - exprs.push new Assign new Value(param.name), val, '=', param: yes - else - if param.value? # Parameter has a default value + # If this parameter has a default value, and it hasn’t already been + # set by the `isComplex()` block above, define it as a statement in + # the function body. This parameter comes after the splat parameter, + # so we can’t define its default value in the parameter list. + unless param.isComplex() + if param.value? ref = new Assign new Value(param.name), param.value, '=' else ref = param @@ -1691,12 +1698,12 @@ exports.Code = class Code extends Base paramsAfterSplat.push param # If this parameter had a default value, since it’s no longer in the function parameter list # we need to assign its default value (if necessary) as an expression in the body. - if param.value? + if param.value? and not param.isComplex() condition = new Literal param.name.value + ' == null' ifTrue = new Assign new Value(param.name), param.value, '=' exprs.push new If condition, ifTrue # Add this parameter to the scope, since it wouldn’t have been added yet since it was skipped earlier. - o.scope.add param.name.value, 'var', yes if param.name?.value? and not param.name.this? + o.scope.add param.name.value, 'var', yes if param.name?.value? # If there were parameters after the splat or expansion parameter, those # parameters need to be assigned in the body of the function. From a509f539ed5e67599db28d31fc5ad4bdbb2560f5 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Sun, 9 Oct 2016 14:21:07 -0700 Subject: [PATCH 23/34] Allow names of function parameters attached to `this` to be reserved words --- lib/coffee-script/nodes.js | 8 ++++---- src/nodes.coffee | 9 ++++----- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index 8b2fe0f117..77e608f15f 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -2448,14 +2448,14 @@ haveSplatParam = true; if (param.splat) { if (((ref5 = param.name) != null ? ref5["this"] : void 0) != null) { - splatParamName = paramNames[i].replace(/@|this\.(.*)/, '$1'); - params.push(new Value(new IdentifierLiteral(splatParamName))); - exprs.push(new Assign(new Value(param.name), new Value(new IdentifierLiteral(splatParamName)), '=', { + params.push(param.asReference(o)); + splatParamName = fragmentsToText(param.reference.compileNode(o)); + exprs.push(new Assign(new Value(param.name), param.reference, '=', { param: true })); } else { - splatParamName = paramNames[i]; params.push(param); + splatParamName = fragmentsToText(param.asReference(o).compileNode(o)); } } else { splatParamName = o.scope.freeVariable('args'); diff --git a/src/nodes.coffee b/src/nodes.coffee index 2d9e103732..e82f6fa1cb 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1654,13 +1654,12 @@ exports.Code = class Code extends Base haveSplatParam = yes if param.splat if param.name?.this? # Parameter is attached to `this` - splatParamName = paramNames[i].replace /@|this\.(.*)/, '$1' - params.push new Value new IdentifierLiteral splatParamName - exprs.push new Assign new Value(param.name), - new Value(new IdentifierLiteral(splatParamName)), '=', param: yes + params.push param.asReference o + splatParamName = fragmentsToText param.reference.compileNode o + exprs.push new Assign new Value(param.name), param.reference, '=', param: yes else - splatParamName = paramNames[i] params.push param + splatParamName = fragmentsToText param.asReference(o).compileNode o else # `param` is an Expansion splatParamName = o.scope.freeVariable 'args' params.push new Value new IdentifierLiteral splatParamName From 4f4e9512562b55369d8f009336589f930bdff450 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Sun, 9 Oct 2016 14:36:14 -0700 Subject: [PATCH 24/34] =?UTF-8?q?Always=20add=20a=20statement=20to=20the?= =?UTF-8?q?=20function=20body=20defining=20a=20variable=20with=20its=20def?= =?UTF-8?q?ault=20value,=20if=20it=20has=20one,=20if=20the=20variable=20`?= =?UTF-8?q?=3D=3D=20null`;=20this=20covers=20the=20case=20when=20ES=20does?= =?UTF-8?q?n=E2=80=99t=20apply=20the=20default=20value=20when=20`null`=20i?= =?UTF-8?q?s=20passed=20in=20as=20a=20value,=20but=20CoffeeScript=20expect?= =?UTF-8?q?s=20`null`=20and=20`undefined`=20to=20act=20interchangeably?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/coffee-script/nodes.js | 21 +++++++------------ src/nodes.coffee | 43 ++++++++++++++++++++------------------ 2 files changed, 31 insertions(+), 33 deletions(-) diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index 77e608f15f..11cc1a1572 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -2471,24 +2471,19 @@ exprs.push(new Assign(new Value(param.name), val, '=', { param: true })); + } else if (param.value != null) { + ref = new Assign(new Value(param.name), param.value, '='); + condition = new Literal(param.name.value + ' == null'); + ifTrue = new Assign(new Value(param.name), param.value, '='); + exprs.push(new If(condition, ifTrue)); + } else { + ref = param; } if (!haveSplatParam) { - if (!param.isComplex()) { - if (param.value != null) { - ref = new Assign(new Value(param.name), param.value, '='); - } else { - ref = param; - } - } - o.scope.parameter(fragmentsToText((param.value != null ? param : ref).compileToFragments(o))); params.push(ref); + o.scope.parameter(fragmentsToText((param.value != null ? param : ref).compileToFragments(o))); } else { paramsAfterSplat.push(param); - if ((param.value != null) && !param.isComplex()) { - condition = new Literal(param.name.value + ' == null'); - ifTrue = new Assign(new Value(param.name), param.value, '='); - exprs.push(new If(condition, ifTrue)); - } if (((ref6 = param.name) != null ? ref6.value : void 0) != null) { o.scope.add(param.name.value, 'var', true); } diff --git a/src/nodes.coffee b/src/nodes.coffee index e82f6fa1cb..e84ba97dba 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1669,38 +1669,41 @@ exports.Code = class Code extends Base # encountered, add these other parameters to the list to be output in # the function definition. else - # If the parameter is attached to `this`, add a statement to the body - # that assigns it as such. if param.isComplex() + # This parameter is attached to `this`, which ES doesn’t allow; + # so add a statement to the function body assigning it, e.g. + # `(a) => { this.a = a; }` or with a default value if it has one. val = ref = param.asReference o val = new Op '?', ref, param.value if param.value exprs.push new Assign new Value(param.name), val, '=', param: yes + else if param.value? + # This parameter isn’t attached to `this` but it has a default value. + # ES supports default values in function parameter lists, but treats + # `null` differently than CoffeeScript does: in CS, passing `null` + # as a function argument for a parameter that has a default value + # will cause the default value to be used, whereas in ES the + # parameter will get the value of `null`. ES applies default values + # only when a parameter is `undefined`. Because of this, even though + # we’re defining the default value in the parameter list, we also + # need to add a statement to the function body that sets the value + # in the event that a parameter is `null`. + ref = new Assign new Value(param.name), param.value, '=' + + condition = new Literal param.name.value + ' == null' + ifTrue = new Assign new Value(param.name), param.value, '=' + exprs.push new If condition, ifTrue + else + ref = param + # If this parameter comes before the splat or expansion, it will go # in the function definition parameter list. unless haveSplatParam - # If this parameter has a default value, and it hasn’t already been - # set by the `isComplex()` block above, define it as a statement in - # the function body. This parameter comes after the splat parameter, - # so we can’t define its default value in the parameter list. - unless param.isComplex() - if param.value? - ref = new Assign new Value(param.name), param.value, '=' - else - ref = param - + params.push ref # Add this parameter’s reference to the function scope o.scope.parameter fragmentsToText (if param.value? then param else ref).compileToFragments o - - params.push ref else paramsAfterSplat.push param - # If this parameter had a default value, since it’s no longer in the function parameter list - # we need to assign its default value (if necessary) as an expression in the body. - if param.value? and not param.isComplex() - condition = new Literal param.name.value + ' == null' - ifTrue = new Assign new Value(param.name), param.value, '=' - exprs.push new If condition, ifTrue # Add this parameter to the scope, since it wouldn’t have been added yet since it was skipped earlier. o.scope.add param.name.value, 'var', yes if param.name?.value? From ccb7d842e85b6074114495fe211e8bf595b22500 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Sun, 9 Oct 2016 14:51:05 -0700 Subject: [PATCH 25/34] Aftermath of having both `undefined` and `null` trigger the use of default values for parameters with default values --- lib/coffee-script/browser.js | 15 +++++++++++++++ lib/coffee-script/coffee-script.js | 15 +++++++++++++++ lib/coffee-script/command.js | 9 +++++++++ lib/coffee-script/helpers.js | 6 ++++++ lib/coffee-script/lexer.js | 21 +++++++++++++++++++++ lib/coffee-script/nodes.js | 26 ++++++++++++++++++++++++++ lib/coffee-script/optparse.js | 3 +++ lib/coffee-script/repl.js | 3 +++ lib/coffee-script/rewriter.js | 3 +++ lib/coffee-script/scope.js | 6 ++++++ lib/coffee-script/sourcemap.js | 12 ++++++++++++ 11 files changed, 119 insertions(+) diff --git a/lib/coffee-script/browser.js b/lib/coffee-script/browser.js index fd37dd0c05..f054cce821 100644 --- a/lib/coffee-script/browser.js +++ b/lib/coffee-script/browser.js @@ -10,6 +10,9 @@ compile = CoffeeScript.compile; CoffeeScript["eval"] = function(code, options = {}) { + if (options == null) { + options = {}; + } if (options.bare == null) { options.bare = true; } @@ -17,6 +20,9 @@ }; CoffeeScript.run = function(code, options = {}) { + if (options == null) { + options = {}; + } options.bare = true; options.shiftLine = true; return Function(compile(code, options))(); @@ -28,6 +34,9 @@ if ((typeof btoa !== "undefined" && btoa !== null) && (typeof JSON !== "undefined" && JSON !== null)) { compile = function(code, options = {}) { + if (options == null) { + options = {}; + } options.inlineMap = true; return CoffeeScript.compile(code, options); }; @@ -35,6 +44,12 @@ CoffeeScript.load = function(url, callback, options = {}, hold = false) { var xhr; + if (options == null) { + options = {}; + } + if (hold == null) { + hold = false; + } options.sourceFiles = [url]; xhr = window.ActiveXObject ? new window.ActiveXObject('Microsoft.XMLHTTP') : new window.XMLHttpRequest(); xhr.open('GET', url, true); diff --git a/lib/coffee-script/coffee-script.js b/lib/coffee-script/coffee-script.js index e627e08ba4..a63c295b97 100644 --- a/lib/coffee-script/coffee-script.js +++ b/lib/coffee-script/coffee-script.js @@ -37,6 +37,9 @@ withPrettyErrors = function(fn) { return function(code, options = {}) { var err; + if (options == null) { + options = {}; + } try { return fn.call(this, code, options); } catch (error) { @@ -144,6 +147,9 @@ exports.run = function(code, options = {}) { var answer, dir, mainModule, ref; + if (options == null) { + options = {}; + } mainModule = require.main; mainModule.filename = process.argv[1] = options.filename ? fs.realpathSync(options.filename) : '.'; mainModule.moduleCache && (mainModule.moduleCache = {}); @@ -158,6 +164,9 @@ exports["eval"] = function(code, options = {}) { var Module, _module, _require, createContext, i, isContext, js, k, len, o, r, ref, ref1, ref2, ref3, sandbox, v; + if (options == null) { + options = {}; + } if (!(code = code.trim())) { return; } @@ -239,6 +248,12 @@ exports._compileFile = function(filename, sourceMap = false, inlineMap = false) { var answer, err, raw, stripped; + if (sourceMap == null) { + sourceMap = false; + } + if (inlineMap == null) { + inlineMap = false; + } raw = fs.readFileSync(filename, 'utf8'); stripped = raw.charCodeAt(0) === 0xFEFF ? raw.substring(1) : raw; try { diff --git a/lib/coffee-script/command.js b/lib/coffee-script/command.js index d71d71f9d3..3d7b1d859a 100644 --- a/lib/coffee-script/command.js +++ b/lib/coffee-script/command.js @@ -200,6 +200,9 @@ compileScript = function(file, input, base = null) { var compiled, err, message, o, options, t, task; + if (base == null) { + base = null; + } o = opts; options = compileOptions(file, base); try { @@ -438,6 +441,9 @@ outputPath = function(source, base, extension = ".js") { var basename, dir, srcDir; + if (extension == null) { + extension = ".js"; + } basename = helpers.baseFileName(source, true, useWinPathSep); srcDir = path.dirname(source); if (!opts.output) { @@ -473,6 +479,9 @@ writeJs = function(base, sourcePath, js, jsPath, generatedSourceMap = null) { var compile, jsDir, sourceMapPath; + if (generatedSourceMap == null) { + generatedSourceMap = null; + } sourceMapPath = outputPath(sourcePath, base, ".js.map"); jsDir = path.dirname(jsPath); compile = function() { diff --git a/lib/coffee-script/helpers.js b/lib/coffee-script/helpers.js index e3215e53da..42cfa18253 100644 --- a/lib/coffee-script/helpers.js +++ b/lib/coffee-script/helpers.js @@ -155,6 +155,12 @@ exports.baseFileName = function(file, stripExt = false, useWinPathSep = false) { var parts, pathSep; + if (stripExt == null) { + stripExt = false; + } + if (useWinPathSep == null) { + useWinPathSep = false; + } pathSep = useWinPathSep ? /\\|\// : /\//; parts = file.split(pathSep); file = parts[parts.length - 1]; diff --git a/lib/coffee-script/lexer.js b/lib/coffee-script/lexer.js index 4e65fe113e..3262a29c7d 100644 --- a/lib/coffee-script/lexer.js +++ b/lib/coffee-script/lexer.js @@ -13,6 +13,9 @@ Lexer.prototype.tokenize = function(code, opts = {}) { var consumed, end, i, ref2; + if (opts == null) { + opts = {}; + } this.literate = opts.literate; this.indent = 0; this.baseIndent = 0; @@ -805,6 +808,12 @@ Lexer.prototype.makeToken = function(tag, value, offsetInChunk = 0, length = value.length) { var lastCharacter, locationData, ref2, ref3, token; + if (offsetInChunk == null) { + offsetInChunk = 0; + } + if (length == null) { + length = value.length; + } locationData = {}; ref2 = this.getLineAndColumnFromChunk(offsetInChunk), locationData.first_line = ref2[0], locationData.first_column = ref2[1]; lastCharacter = length > 0 ? length - 1 : 0; @@ -850,6 +859,9 @@ Lexer.prototype.validateEscapes = function(str, options = {}) { var before, hex, invalidEscape, match, message, octal, ref2, unicode; + if (options == null) { + options = {}; + } match = INVALID_ESCAPE.exec(str); if (!match) { return; @@ -868,6 +880,9 @@ Lexer.prototype.makeDelimitedLiteral = function(body, options = {}) { var regex; + if (options == null) { + options = {}; + } if (body === '' && options.delimiter === '/') { body = '(?:)'; } @@ -905,6 +920,9 @@ Lexer.prototype.error = function(message, options = {}) { var first_column, first_line, location, ref2, ref3, ref4; + if (options == null) { + options = {}; + } location = 'first_line' in options ? options : ((ref3 = this.getLineAndColumnFromChunk((ref2 = options.offset) != null ? ref2 : 0), first_line = ref3[0], first_column = ref3[1], ref3), { first_line: first_line, first_column: first_column, @@ -918,6 +936,9 @@ })(); isUnassignable = function(name, displayName = name) { + if (displayName == null) { + displayName = name; + } switch (false) { case indexOf.call(slice.call(JS_KEYWORDS).concat(slice.call(COFFEE_KEYWORDS)), name) < 0: return "keyword '" + displayName + "' can't be assigned"; diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index 11cc1a1572..6de7600516 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -168,6 +168,12 @@ Base.prototype.toString = function(idt = '', name = this.constructor.name) { var tree; + if (idt == null) { + idt = ''; + } + if (name == null) { + name = this.constructor.name; + } tree = '\n' + idt + name; if (this.soak) { tree += '?'; @@ -356,6 +362,9 @@ }; Block.prototype.compileToFragments = function(o = {}, level) { + if (o == null) { + o = {}; + } if (o.scope) { return Block.__super__.compileToFragments.call(this, o, level); } else { @@ -1198,6 +1207,9 @@ extend1(RegexWithInterpolations, superClass1); function RegexWithInterpolations(args = []) { + if (args == null) { + args = []; + } RegexWithInterpolations.__super__.constructor.call(this, new Value(new IdentifierLiteral('RegExp')), args, false); } @@ -2098,6 +2110,9 @@ this.variable = variable1; this.value = value1; this.context = context; + if (options == null) { + options = {}; + } this.param = options.param, this.subpattern = options.subpattern, this.operatorToken = options.operatorToken, this.moduleDeclaration = options.moduleDeclaration; } @@ -2616,6 +2631,9 @@ Param.prototype.eachName = function(iterator, name = this.name) { var atParam, j, len1, node, obj, ref3, ref4; + if (name == null) { + name = this.name; + } atParam = function(obj) { return iterator("@" + obj.properties[0].name.value, obj); }; @@ -3517,6 +3535,11 @@ block: true }) { var block, conds, j, jumpNode, len1, ref3, ref4, ref5; + if (o == null) { + o = { + block: true + }; + } ref3 = this.cases; for (j = 0, len1 = ref3.length; j < len1; j++) { ref4 = ref3[j], conds = ref4[0], block = ref4[1]; @@ -3587,6 +3610,9 @@ function If(condition, body1, options = {}) { this.body = body1; + if (options == null) { + options = {}; + } this.condition = options.type === 'unless' ? condition.invert() : condition; this.elseBody = null; this.isChain = false; diff --git a/lib/coffee-script/optparse.js b/lib/coffee-script/optparse.js index 0895d29120..2e704f47e7 100644 --- a/lib/coffee-script/optparse.js +++ b/lib/coffee-script/optparse.js @@ -102,6 +102,9 @@ buildRule = function(shortFlag, longFlag, description, options = {}) { var match; + if (options == null) { + options = {}; + } match = longFlag.match(OPTIONAL); longFlag = longFlag.match(LONG_FLAG)[1]; return { diff --git a/lib/coffee-script/repl.js b/lib/coffee-script/repl.js index 87dc8525ce..f4d879a038 100644 --- a/lib/coffee-script/repl.js +++ b/lib/coffee-script/repl.js @@ -168,6 +168,9 @@ module.exports = { start: function(opts = {}) { var build, major, minor, ref1, repl; + if (opts == null) { + opts = {}; + } ref1 = process.versions.node.split('.').map(function(n) { return parseInt(n); }), major = ref1[0], minor = ref1[1], build = ref1[2]; diff --git a/lib/coffee-script/rewriter.js b/lib/coffee-script/rewriter.js index 6669df5807..e8c883e8a9 100644 --- a/lib/coffee-script/rewriter.js +++ b/lib/coffee-script/rewriter.js @@ -216,6 +216,9 @@ }; startImplicitObject = function(j, startsLine = true) { var idx, val; + if (startsLine == null) { + startsLine = true; + } idx = j != null ? j : i; stack.push([ '{', idx, { diff --git a/lib/coffee-script/scope.js b/lib/coffee-script/scope.js index 993310530e..f9f0541639 100644 --- a/lib/coffee-script/scope.js +++ b/lib/coffee-script/scope.js @@ -67,6 +67,9 @@ Scope.prototype.temporary = function(name, index, single = false) { var diff, endCode, letter, newCode, num, startCode; + if (single == null) { + single = false; + } if (single) { startCode = name.charCodeAt(0); endCode = 'z'.charCodeAt(0); @@ -94,6 +97,9 @@ Scope.prototype.freeVariable = function(name, options = {}) { var index, ref, temp; + if (options == null) { + options = {}; + } index = 0; while (true) { temp = this.temporary(name, index, options.single); diff --git a/lib/coffee-script/sourcemap.js b/lib/coffee-script/sourcemap.js index d14b583034..25fc6810fc 100644 --- a/lib/coffee-script/sourcemap.js +++ b/lib/coffee-script/sourcemap.js @@ -11,6 +11,9 @@ LineMap.prototype.add = function(column, arg, options = {}) { var sourceColumn, sourceLine; sourceLine = arg[0], sourceColumn = arg[1]; + if (options == null) { + options = {}; + } if (this.columns[column] && options.noReplace) { return; } @@ -43,6 +46,9 @@ SourceMap.prototype.add = function(sourceLocation, generatedLocation, options = {}) { var base, column, line, lineMap; + if (options == null) { + options = {}; + } line = generatedLocation[0], column = generatedLocation[1]; lineMap = ((base = this.lines)[line] || (base[line] = new LineMap(line))); return lineMap.add(column, sourceLocation, options); @@ -59,6 +65,12 @@ SourceMap.prototype.generate = function(options = {}, code = null) { var buffer, i, j, lastColumn, lastSourceColumn, lastSourceLine, len, len1, lineMap, lineNumber, mapping, needComma, ref, ref1, v3, writingline; + if (options == null) { + options = {}; + } + if (code == null) { + code = null; + } writingline = 0; lastColumn = 0; lastSourceLine = 0; From 8e847929b80623374f95cc0e876e0c0179032fa4 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Sun, 9 Oct 2016 22:29:38 -0700 Subject: [PATCH 26/34] More careful parsing of destructured parameters --- lib/coffee-script/nodes.js | 21 +++++++++++++++------ src/nodes.coffee | 25 +++++++++++++++++++------ 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index 6de7600516..d7d085b2d2 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -2462,15 +2462,24 @@ } haveSplatParam = true; if (param.splat) { + ref = param.asReference(o); + splatParamName = fragmentsToText(ref.compileNode(o)); + if (param.name instanceof Arr || param.name instanceof Obj) { + if (i === this.params.length - 1) { + params.push(param); + } else { + params.push(ref); + exprs.push(new Assign(new Value(param.name), ref, '=', { + param: true + })); + } + } else { + params.push(ref); + } if (((ref5 = param.name) != null ? ref5["this"] : void 0) != null) { - params.push(param.asReference(o)); - splatParamName = fragmentsToText(param.reference.compileNode(o)); - exprs.push(new Assign(new Value(param.name), param.reference, '=', { + exprs.push(new Assign(new Value(param.name), ref, '=', { param: true })); - } else { - params.push(param); - splatParamName = fragmentsToText(param.asReference(o).compileNode(o)); } } else { splatParamName = o.scope.freeVariable('args'); diff --git a/src/nodes.coffee b/src/nodes.coffee index e84ba97dba..5e8d73e73f 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1653,16 +1653,29 @@ exports.Code = class Code extends Base haveSplatParam = yes if param.splat - if param.name?.this? # Parameter is attached to `this` - params.push param.asReference o - splatParamName = fragmentsToText param.reference.compileNode o - exprs.push new Assign new Value(param.name), param.reference, '=', param: yes + ref = param.asReference o + splatParamName = fragmentsToText ref.compileNode o + + if param.name instanceof Arr or param.name instanceof Obj + # ES supports destructured parameters, so pass such parameters + # as is; unless they aren’t the last parameter, in which case + # we need a usable splatParamName below to pull post-splat + # parameters from. + if i is @params.length - 1 + params.push param + else + params.push ref + exprs.push new Assign new Value(param.name), ref, '=', param: yes else - params.push param - splatParamName = fragmentsToText param.asReference(o).compileNode o + params.push ref + + if param.name?.this? # Parameter is attached to `this` + exprs.push new Assign new Value(param.name), ref, '=', param: yes + else # `param` is an Expansion splatParamName = o.scope.freeVariable 'args' params.push new Value new IdentifierLiteral splatParamName + o.scope.parameter splatParamName # Parse all other parameters; if a splat paramater has not yet been From 91396cb67e9c4fe2f72fc9b74f7bc16df2c5a0d0 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Sun, 9 Oct 2016 23:51:37 -0700 Subject: [PATCH 27/34] Fall back to processing destructured parameters in the function body, to account for `this` or default values within destructured objects --- lib/coffee-script/nodes.js | 22 +++++----------------- src/nodes.coffee | 27 +++++++++------------------ 2 files changed, 14 insertions(+), 35 deletions(-) diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index d7d085b2d2..653958d1c3 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -2426,7 +2426,7 @@ }; Code.prototype.compileNode = function(o) { - var answer, code, condition, exprs, haveSplatParam, i, ifTrue, j, k, len1, len2, param, paramNames, params, paramsAfterSplat, ref, ref3, ref4, ref5, ref6, ref7, splatParamName, val, wasEmpty; + var answer, code, condition, exprs, haveSplatParam, i, ifTrue, j, k, len1, len2, param, paramNames, params, paramsAfterSplat, ref, ref3, ref4, ref5, ref6, splatParamName, val, wasEmpty; if (this.bound) { if ((ref3 = o.scope.method) != null ? ref3.bound : void 0) { this.context = o.scope.method.context; @@ -2462,21 +2462,9 @@ } haveSplatParam = true; if (param.splat) { - ref = param.asReference(o); + params.push(ref = param.asReference(o)); splatParamName = fragmentsToText(ref.compileNode(o)); - if (param.name instanceof Arr || param.name instanceof Obj) { - if (i === this.params.length - 1) { - params.push(param); - } else { - params.push(ref); - exprs.push(new Assign(new Value(param.name), ref, '=', { - param: true - })); - } - } else { - params.push(ref); - } - if (((ref5 = param.name) != null ? ref5["this"] : void 0) != null) { + if (param.isComplex()) { exprs.push(new Assign(new Value(param.name), ref, '=', { param: true })); @@ -2508,7 +2496,7 @@ o.scope.parameter(fragmentsToText((param.value != null ? param : ref).compileToFragments(o))); } else { paramsAfterSplat.push(param); - if (((ref6 = param.name) != null ? ref6.value : void 0) != null) { + if (((ref5 = param.name) != null ? ref5.value : void 0) != null) { o.scope.add(param.name.value, 'var', true); } } @@ -2527,7 +2515,7 @@ } wasEmpty = this.body.isEmpty(); if (exprs.length) { - (ref7 = this.body.expressions).unshift.apply(ref7, exprs); + (ref6 = this.body.expressions).unshift.apply(ref6, exprs); } if (!(wasEmpty || this.noReturn)) { this.body.makeReturn(); diff --git a/src/nodes.coffee b/src/nodes.coffee index 5e8d73e73f..7ceaac776a 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1653,24 +1653,14 @@ exports.Code = class Code extends Base haveSplatParam = yes if param.splat - ref = param.asReference o + params.push ref = param.asReference o splatParamName = fragmentsToText ref.compileNode o - - if param.name instanceof Arr or param.name instanceof Obj - # ES supports destructured parameters, so pass such parameters - # as is; unless they aren’t the last parameter, in which case - # we need a usable splatParamName below to pull post-splat - # parameters from. - if i is @params.length - 1 - params.push param - else - params.push ref - exprs.push new Assign new Value(param.name), ref, '=', param: yes - else - params.push ref - - if param.name?.this? # Parameter is attached to `this` + if param.isComplex() # Parameter is destructured or attached to `this` exprs.push new Assign new Value(param.name), ref, '=', param: yes + # TODO: output destrucutred parameters as is, *unless* they contain + # `this` parameters; and fix destructuring of objects with default + # values to work in this context (see Obj.compileNode + # `if prop.context isnt 'object'`) else # `param` is an Expansion splatParamName = o.scope.freeVariable 'args' @@ -1684,8 +1674,9 @@ exports.Code = class Code extends Base else if param.isComplex() # This parameter is attached to `this`, which ES doesn’t allow; - # so add a statement to the function body assigning it, e.g. - # `(a) => { this.a = a; }` or with a default value if it has one. + # or it’s destructured. So add a statement to the function body + # assigning it, e.g. `(a) => { this.a = a; }` or with a default + # value if it has one. val = ref = param.asReference o val = new Op '?', ref, param.value if param.value exprs.push new Assign new Value(param.name), val, '=', param: yes From d75879ad0081f78cf59f106a86aa8ef9b0c1f558 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Mon, 10 Oct 2016 00:11:24 -0700 Subject: [PATCH 28/34] Clean up comments --- src/nodes.coffee | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/nodes.coffee b/src/nodes.coffee index 7ceaac776a..fa1c939f06 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1631,8 +1631,7 @@ exports.Code = class Code extends Base paramsAfterSplat = [] haveSplatParam = no - # Check for duplicate parameters, and save the names so that we can pull - # the name of the splat parameter if we have one. + # Check for duplicate parameters. paramNames = [] @eachParamName (name, node) => node.error "multiple parameters named '#{name}'" if name in paramNames @@ -1643,8 +1642,9 @@ exports.Code = class Code extends Base # adding expressions to the function body to declare all parameter # variables that would have been after the splat/expansion parameter. for param, i in @params - # Was `...` used with this parameter? (Only one such parameter is allowed per function.) - # Splat/expansion parameters cannot have default values, so we need not worry about that. + # Was `...` used with this parameter? (Only one such parameter is allowed + # per function.) Splat/expansion parameters cannot have default values, + # so we need not worry about that. if param.splat or param instanceof Expansion if haveSplatParam param.error 'only one splat or expansion parameter is allowed per function definition' @@ -1708,7 +1708,8 @@ exports.Code = class Code extends Base o.scope.parameter fragmentsToText (if param.value? then param else ref).compileToFragments o else paramsAfterSplat.push param - # Add this parameter to the scope, since it wouldn’t have been added yet since it was skipped earlier. + # Add this parameter to the scope, since it wouldn’t have been + # added yet since it was skipped earlier. o.scope.add param.name.value, 'var', yes if param.name?.value? # If there were parameters after the splat or expansion parameter, those @@ -1746,8 +1747,8 @@ exports.Code = class Code extends Base eachParamName: (iterator) -> param.eachName iterator for param in @params - # Short-circuit `traverseChildren` method to prevent it from crossing scope boundaries - # unless `crossScope` is `true`. + # Short-circuit `traverseChildren` method to prevent it from crossing scope + # boundaries unless `crossScope` is `true`. traverseChildren: (crossScope, func) -> super(crossScope, func) if crossScope From 51736d4af7bdca31b3db630fc63afab7a17014fe Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Sat, 15 Oct 2016 22:41:46 -0700 Subject: [PATCH 29/34] Restore new bare function test, minus the arrow function part of it --- test/classes.coffee | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/classes.coffee b/test/classes.coffee index 5b9205907e..1a92f5186d 100644 --- a/test/classes.coffee +++ b/test/classes.coffee @@ -471,6 +471,12 @@ test "`new` shouldn't add extra parens", -> ok new Date().constructor is Date +test "`new` works against bare function", -> + + eq Date, new -> + Date + + test "#1182: a subclass should be able to set its constructor to an external function", -> ctor = -> @val = 1 From 755fb5ff094fa598e27d06a7f6131d9f76a1a547 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Tue, 18 Oct 2016 23:42:40 -0700 Subject: [PATCH 30/34] =?UTF-8?q?Test=20that=20bound/arrow=20functions=20a?= =?UTF-8?q?ren=E2=80=99t=20overwriting=20the=20`arguments`=20object,=20whi?= =?UTF-8?q?ch=20should=20refer=20to=20the=20parent=20scope=E2=80=99s=20`ar?= =?UTF-8?q?guments`=20(like=20`this`)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/functions.coffee | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/functions.coffee b/test/functions.coffee index d7a7fabaa4..db086e8087 100644 --- a/test/functions.coffee +++ b/test/functions.coffee @@ -79,6 +79,21 @@ test "even more fancy bound functions", -> eq obj.one(), 3 +test "arguments in bound functions inherit from parent function", -> + # The `arguments` object in an ES arrow function refers to the `arguments` + # of the parent scope, just like `this`. In the CoffeeScript 1.x + # implementation of `=>`, the `arguments` object referred to the arguments + # of the arrow function; but per the ES2015 spec, `arguments` should refer + # to the parent. + arrayEq ((a...) -> a)([1, 2, 3]), ((a...) => a)([1, 2, 3]) + + parent = (a, b, c) -> + (bound = => + [arguments[0], arguments[1], arguments[2]] + )() + arrayEq [1, 2, 3], parent(1, 2, 3) + + test "self-referencing functions", -> changeMe = -> changeMe = 2 From d5b3270e58c73df16325946fd9be0fd329b2c297 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Thu, 20 Oct 2016 22:13:46 -0700 Subject: [PATCH 31/34] Follow ES2015 spec for parameter default values: `null` gets assigned as as `null`, not the default value --- lib/coffee-script/browser.js | 15 ----------- lib/coffee-script/coffee-script.js | 15 ----------- lib/coffee-script/command.js | 9 ------- lib/coffee-script/helpers.js | 6 ----- lib/coffee-script/lexer.js | 21 --------------- lib/coffee-script/nodes.js | 43 +++++++----------------------- lib/coffee-script/optparse.js | 3 --- lib/coffee-script/repl.js | 3 --- lib/coffee-script/rewriter.js | 3 --- lib/coffee-script/scope.js | 6 ----- lib/coffee-script/sourcemap.js | 12 --------- src/nodes.coffee | 37 +++++++++++-------------- test/functions.coffee | 6 ++--- 13 files changed, 27 insertions(+), 152 deletions(-) diff --git a/lib/coffee-script/browser.js b/lib/coffee-script/browser.js index e3bd148625..bd67541312 100644 --- a/lib/coffee-script/browser.js +++ b/lib/coffee-script/browser.js @@ -10,9 +10,6 @@ compile = CoffeeScript.compile; CoffeeScript["eval"] = function(code, options = {}) { - if (options == null) { - options = {}; - } if (options.bare == null) { options.bare = true; } @@ -20,9 +17,6 @@ }; CoffeeScript.run = function(code, options = {}) { - if (options == null) { - options = {}; - } options.bare = true; options.shiftLine = true; return Function(compile(code, options))(); @@ -34,9 +28,6 @@ if ((typeof btoa !== "undefined" && btoa !== null) && (typeof JSON !== "undefined" && JSON !== null)) { compile = function(code, options = {}) { - if (options == null) { - options = {}; - } options.inlineMap = true; return CoffeeScript.compile(code, options); }; @@ -44,12 +35,6 @@ CoffeeScript.load = function(url, callback, options = {}, hold = false) { var xhr; - if (options == null) { - options = {}; - } - if (hold == null) { - hold = false; - } options.sourceFiles = [url]; xhr = window.ActiveXObject ? new window.ActiveXObject('Microsoft.XMLHTTP') : new window.XMLHttpRequest(); xhr.open('GET', url, true); diff --git a/lib/coffee-script/coffee-script.js b/lib/coffee-script/coffee-script.js index 24b104ebe6..0398ff6da9 100644 --- a/lib/coffee-script/coffee-script.js +++ b/lib/coffee-script/coffee-script.js @@ -37,9 +37,6 @@ withPrettyErrors = function(fn) { return function(code, options = {}) { var err; - if (options == null) { - options = {}; - } try { return fn.call(this, code, options); } catch (error) { @@ -147,9 +144,6 @@ exports.run = function(code, options = {}) { var answer, dir, mainModule, ref; - if (options == null) { - options = {}; - } mainModule = require.main; mainModule.filename = process.argv[1] = options.filename ? fs.realpathSync(options.filename) : '.'; mainModule.moduleCache && (mainModule.moduleCache = {}); @@ -164,9 +158,6 @@ exports["eval"] = function(code, options = {}) { var Module, _module, _require, createContext, i, isContext, js, k, len, o, r, ref, ref1, ref2, ref3, sandbox, v; - if (options == null) { - options = {}; - } if (!(code = code.trim())) { return; } @@ -248,12 +239,6 @@ exports._compileFile = function(filename, sourceMap = false, inlineMap = false) { var answer, err, raw, stripped; - if (sourceMap == null) { - sourceMap = false; - } - if (inlineMap == null) { - inlineMap = false; - } raw = fs.readFileSync(filename, 'utf8'); stripped = raw.charCodeAt(0) === 0xFEFF ? raw.substring(1) : raw; try { diff --git a/lib/coffee-script/command.js b/lib/coffee-script/command.js index 68915bbc10..ad992cb5d6 100644 --- a/lib/coffee-script/command.js +++ b/lib/coffee-script/command.js @@ -200,9 +200,6 @@ compileScript = function(file, input, base = null) { var compiled, err, message, o, options, t, task; - if (base == null) { - base = null; - } o = opts; options = compileOptions(file, base); try { @@ -441,9 +438,6 @@ outputPath = function(source, base, extension = ".js") { var basename, dir, srcDir; - if (extension == null) { - extension = ".js"; - } basename = helpers.baseFileName(source, true, useWinPathSep); srcDir = path.dirname(source); if (!opts.output) { @@ -479,9 +473,6 @@ writeJs = function(base, sourcePath, js, jsPath, generatedSourceMap = null) { var compile, jsDir, sourceMapPath; - if (generatedSourceMap == null) { - generatedSourceMap = null; - } sourceMapPath = outputPath(sourcePath, base, ".js.map"); jsDir = path.dirname(jsPath); compile = function() { diff --git a/lib/coffee-script/helpers.js b/lib/coffee-script/helpers.js index 3795e4c1ae..0ed06d1930 100644 --- a/lib/coffee-script/helpers.js +++ b/lib/coffee-script/helpers.js @@ -155,12 +155,6 @@ exports.baseFileName = function(file, stripExt = false, useWinPathSep = false) { var parts, pathSep; - if (stripExt == null) { - stripExt = false; - } - if (useWinPathSep == null) { - useWinPathSep = false; - } pathSep = useWinPathSep ? /\\|\// : /\//; parts = file.split(pathSep); file = parts[parts.length - 1]; diff --git a/lib/coffee-script/lexer.js b/lib/coffee-script/lexer.js index 3fada2ac9f..92efcbb360 100644 --- a/lib/coffee-script/lexer.js +++ b/lib/coffee-script/lexer.js @@ -13,9 +13,6 @@ Lexer.prototype.tokenize = function(code, opts = {}) { var consumed, end, i, ref2; - if (opts == null) { - opts = {}; - } this.literate = opts.literate; this.indent = 0; this.baseIndent = 0; @@ -808,12 +805,6 @@ Lexer.prototype.makeToken = function(tag, value, offsetInChunk = 0, length = value.length) { var lastCharacter, locationData, ref2, ref3, token; - if (offsetInChunk == null) { - offsetInChunk = 0; - } - if (length == null) { - length = value.length; - } locationData = {}; ref2 = this.getLineAndColumnFromChunk(offsetInChunk), locationData.first_line = ref2[0], locationData.first_column = ref2[1]; lastCharacter = length > 0 ? length - 1 : 0; @@ -859,9 +850,6 @@ Lexer.prototype.validateEscapes = function(str, options = {}) { var before, hex, invalidEscape, match, message, octal, ref2, unicode; - if (options == null) { - options = {}; - } match = INVALID_ESCAPE.exec(str); if (!match) { return; @@ -880,9 +868,6 @@ Lexer.prototype.makeDelimitedLiteral = function(body, options = {}) { var regex; - if (options == null) { - options = {}; - } if (body === '' && options.delimiter === '/') { body = '(?:)'; } @@ -920,9 +905,6 @@ Lexer.prototype.error = function(message, options = {}) { var first_column, first_line, location, ref2, ref3, ref4; - if (options == null) { - options = {}; - } location = 'first_line' in options ? options : ((ref3 = this.getLineAndColumnFromChunk((ref2 = options.offset) != null ? ref2 : 0), first_line = ref3[0], first_column = ref3[1], ref3), { first_line: first_line, first_column: first_column, @@ -936,9 +918,6 @@ })(); isUnassignable = function(name, displayName = name) { - if (displayName == null) { - displayName = name; - } switch (false) { case indexOf.call(slice.call(JS_KEYWORDS).concat(slice.call(COFFEE_KEYWORDS)), name) < 0: return "keyword '" + displayName + "' can't be assigned"; diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index 520b02ca83..240d3cc5fb 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -168,12 +168,6 @@ Base.prototype.toString = function(idt = '', name = this.constructor.name) { var tree; - if (idt == null) { - idt = ''; - } - if (name == null) { - name = this.constructor.name; - } tree = '\n' + idt + name; if (this.soak) { tree += '?'; @@ -362,9 +356,6 @@ }; Block.prototype.compileToFragments = function(o = {}, level) { - if (o == null) { - o = {}; - } if (o.scope) { return Block.__super__.compileToFragments.call(this, o, level); } else { @@ -1207,9 +1198,6 @@ extend1(RegexWithInterpolations, superClass1); function RegexWithInterpolations(args = []) { - if (args == null) { - args = []; - } RegexWithInterpolations.__super__.constructor.call(this, new Value(new IdentifierLiteral('RegExp')), args, false); } @@ -2113,9 +2101,6 @@ this.variable = variable1; this.value = value1; this.context = context; - if (options == null) { - options = {}; - } this.param = options.param, this.subpattern = options.subpattern, this.operatorToken = options.operatorToken, this.moduleDeclaration = options.moduleDeclaration; } @@ -2486,19 +2471,20 @@ exprs.push(new Assign(new Value(param.name), val, '=', { param: true })); - } else if (param.value != null) { - ref = new Assign(new Value(param.name), param.value, '='); - condition = new Literal(param.name.value + ' == null'); - ifTrue = new Assign(new Value(param.name), param.value, '='); - exprs.push(new If(condition, ifTrue)); - } else { - ref = param; } if (!haveSplatParam) { - params.push(ref); + if (!param.isComplex()) { + ref = param.value != null ? new Assign(new Value(param.name), param.value, '=') : param; + } o.scope.parameter(fragmentsToText((param.value != null ? param : ref).compileToFragments(o))); + params.push(ref); } else { paramsAfterSplat.push(param); + if ((param.value != null) && !param.isComplex()) { + condition = new Literal(param.name.value + ' == undefined'); + ifTrue = new Assign(new Value(param.name), param.value, '='); + exprs.push(new If(condition, ifTrue)); + } if (((ref5 = param.name) != null ? ref5.value : void 0) != null) { o.scope.add(param.name.value, 'var', true); } @@ -2631,9 +2617,6 @@ Param.prototype.eachName = function(iterator, name = this.name) { var atParam, j, len1, node, obj, ref3, ref4; - if (name == null) { - name = this.name; - } atParam = function(obj) { return iterator("@" + obj.properties[0].name.value, obj); }; @@ -3535,11 +3518,6 @@ block: true }) { var block, conds, j, jumpNode, len1, ref3, ref4, ref5; - if (o == null) { - o = { - block: true - }; - } ref3 = this.cases; for (j = 0, len1 = ref3.length; j < len1; j++) { ref4 = ref3[j], conds = ref4[0], block = ref4[1]; @@ -3610,9 +3588,6 @@ function If(condition, body1, options = {}) { this.body = body1; - if (options == null) { - options = {}; - } this.condition = options.type === 'unless' ? condition.invert() : condition; this.elseBody = null; this.isChain = false; diff --git a/lib/coffee-script/optparse.js b/lib/coffee-script/optparse.js index aedd0757c0..05a17bb9f0 100644 --- a/lib/coffee-script/optparse.js +++ b/lib/coffee-script/optparse.js @@ -102,9 +102,6 @@ buildRule = function(shortFlag, longFlag, description, options = {}) { var match; - if (options == null) { - options = {}; - } match = longFlag.match(OPTIONAL); longFlag = longFlag.match(LONG_FLAG)[1]; return { diff --git a/lib/coffee-script/repl.js b/lib/coffee-script/repl.js index f594761b2a..b09d139b70 100644 --- a/lib/coffee-script/repl.js +++ b/lib/coffee-script/repl.js @@ -168,9 +168,6 @@ module.exports = { start: function(opts = {}) { var build, major, minor, ref1, repl; - if (opts == null) { - opts = {}; - } ref1 = process.versions.node.split('.').map(function(n) { return parseInt(n); }), major = ref1[0], minor = ref1[1], build = ref1[2]; diff --git a/lib/coffee-script/rewriter.js b/lib/coffee-script/rewriter.js index 2495a3874a..899ddd08cb 100644 --- a/lib/coffee-script/rewriter.js +++ b/lib/coffee-script/rewriter.js @@ -217,9 +217,6 @@ }; startImplicitObject = function(j, startsLine = true) { var idx, val; - if (startsLine == null) { - startsLine = true; - } idx = j != null ? j : i; stack.push([ '{', idx, { diff --git a/lib/coffee-script/scope.js b/lib/coffee-script/scope.js index 64605c3075..dd277ac2b1 100644 --- a/lib/coffee-script/scope.js +++ b/lib/coffee-script/scope.js @@ -67,9 +67,6 @@ Scope.prototype.temporary = function(name, index, single = false) { var diff, endCode, letter, newCode, num, startCode; - if (single == null) { - single = false; - } if (single) { startCode = name.charCodeAt(0); endCode = 'z'.charCodeAt(0); @@ -97,9 +94,6 @@ Scope.prototype.freeVariable = function(name, options = {}) { var index, ref, temp; - if (options == null) { - options = {}; - } index = 0; while (true) { temp = this.temporary(name, index, options.single); diff --git a/lib/coffee-script/sourcemap.js b/lib/coffee-script/sourcemap.js index 6296ee42f2..4cc780e6dd 100644 --- a/lib/coffee-script/sourcemap.js +++ b/lib/coffee-script/sourcemap.js @@ -11,9 +11,6 @@ LineMap.prototype.add = function(column, arg, options = {}) { var sourceColumn, sourceLine; sourceLine = arg[0], sourceColumn = arg[1]; - if (options == null) { - options = {}; - } if (this.columns[column] && options.noReplace) { return; } @@ -46,9 +43,6 @@ SourceMap.prototype.add = function(sourceLocation, generatedLocation, options = {}) { var base, column, line, lineMap; - if (options == null) { - options = {}; - } line = generatedLocation[0], column = generatedLocation[1]; lineMap = ((base = this.lines)[line] || (base[line] = new LineMap(line))); return lineMap.add(column, sourceLocation, options); @@ -65,12 +59,6 @@ SourceMap.prototype.generate = function(options = {}, code = null) { var buffer, i, j, lastColumn, lastSourceColumn, lastSourceLine, len, len1, lineMap, lineNumber, mapping, needComma, ref, ref1, v3, writingline; - if (options == null) { - options = {}; - } - if (code == null) { - code = null; - } writingline = 0; lastColumn = 0; lastSourceLine = 0; diff --git a/src/nodes.coffee b/src/nodes.coffee index eb791a036c..3fe8dee545 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1685,35 +1685,28 @@ exports.Code = class Code extends Base val = new Op '?', ref, param.value if param.value exprs.push new Assign new Value(param.name), val, '=', param: yes - else if param.value? - # This parameter isn’t attached to `this` but it has a default value. - # ES supports default values in function parameter lists, but treats - # `null` differently than CoffeeScript does: in CS, passing `null` - # as a function argument for a parameter that has a default value - # will cause the default value to be used, whereas in ES the - # parameter will get the value of `null`. ES applies default values - # only when a parameter is `undefined`. Because of this, even though - # we’re defining the default value in the parameter list, we also - # need to add a statement to the function body that sets the value - # in the event that a parameter is `null`. - ref = new Assign new Value(param.name), param.value, '=' - - condition = new Literal param.name.value + ' == null' - ifTrue = new Assign new Value(param.name), param.value, '=' - exprs.push new If condition, ifTrue - else - ref = param - # If this parameter comes before the splat or expansion, it will go # in the function definition parameter list. unless haveSplatParam - params.push ref + # If this parameter has a default value, and it hasn’t already been + # set by the `isComplex()` block above, define it as a statement in + # the function body. This parameter comes after the splat parameter, + # so we can’t define its default value in the parameter list. + unless param.isComplex() + ref = if param.value? then new Assign new Value(param.name), param.value, '=' else param # Add this parameter’s reference to the function scope o.scope.parameter fragmentsToText (if param.value? then param else ref).compileToFragments o + params.push ref else paramsAfterSplat.push param - # Add this parameter to the scope, since it wouldn’t have been - # added yet since it was skipped earlier. + # If this parameter had a default value, since it’s no longer in the + # function parameter list we need to assign its default value + # (if necessary) as an expression in the body. + if param.value? and not param.isComplex() + condition = new Literal param.name.value + ' == undefined' + ifTrue = new Assign new Value(param.name), param.value, '=' + exprs.push new If condition, ifTrue + # Add this parameter to the scope, since it wouldn’t have been added yet since it was skipped earlier. o.scope.add param.name.value, 'var', yes if param.name?.value? # If there were parameters after the splat or expansion parameter, those diff --git a/test/functions.coffee b/test/functions.coffee index db086e8087..9d6e356cb9 100644 --- a/test/functions.coffee +++ b/test/functions.coffee @@ -195,7 +195,7 @@ test "default values", -> eq nonceA, a(0) eq nonceB, a(0,0,nonceB) eq nonceA, a(0,0,undefined) - eq nonceA, a(0,0,null) + eq null, a(0,0,null) # Per ES2015, `null` doesn’t trigger a parameter default value eq false , a(0,0,false) eq nonceB, a(undefined,undefined,nonceB,undefined) b = (_,arg=nonceA,_1,_2) -> arg @@ -203,7 +203,7 @@ test "default values", -> eq nonceA, b(0) eq nonceB, b(0,nonceB) eq nonceA, b(0,undefined) - eq nonceA, b(0,null) + eq null, b(0,null) eq false , b(0,false) eq nonceB, b(undefined,nonceB,undefined) c = (arg=nonceA,_,_1) -> arg @@ -211,7 +211,7 @@ test "default values", -> eq 0, c(0) eq nonceB, c(nonceB) eq nonceA, c(undefined) - eq nonceA, c(null) + eq null, c(null) eq false , c(false) eq nonceB, c(nonceB,undefined,undefined) From 375b9a482e8402c3f8e495ed02fd3ad03a3d8140 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Fri, 21 Oct 2016 11:00:49 -0700 Subject: [PATCH 32/34] Mimic ES default parameters behavior for parameters after a splat or expansion parameter --- lib/coffee-script/nodes.js | 2 +- src/nodes.coffee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index 240d3cc5fb..56cd26fe10 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -2481,7 +2481,7 @@ } else { paramsAfterSplat.push(param); if ((param.value != null) && !param.isComplex()) { - condition = new Literal(param.name.value + ' == undefined'); + condition = new Literal(param.name.value + ' === undefined'); ifTrue = new Assign(new Value(param.name), param.value, '='); exprs.push(new If(condition, ifTrue)); } diff --git a/src/nodes.coffee b/src/nodes.coffee index 3fe8dee545..ee84197da8 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1703,7 +1703,7 @@ exports.Code = class Code extends Base # function parameter list we need to assign its default value # (if necessary) as an expression in the body. if param.value? and not param.isComplex() - condition = new Literal param.name.value + ' == undefined' + condition = new Literal param.name.value + ' === undefined' ifTrue = new Assign new Value(param.name), param.value, '=' exprs.push new If condition, ifTrue # Add this parameter to the scope, since it wouldn’t have been added yet since it was skipped earlier. From 59233ee601f44fb46e28f94711a90aedcf6e653e Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Sun, 23 Oct 2016 17:45:24 -0700 Subject: [PATCH 33/34] Bound functions cannot be generators: remove no-longer-relevant test, add check to throw error if `yield` appears inside a bound (arrow) function --- lib/coffee-script/nodes.js | 3 +++ src/nodes.coffee | 2 ++ test/error_messages.coffee | 7 +++++++ test/generators.coffee | 18 ------------------ 4 files changed, 12 insertions(+), 18 deletions(-) diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index 8bff3e6a81..40813acf8c 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -2388,6 +2388,9 @@ Code.prototype.compileNode = function(o) { var answer, code, condition, exprs, haveSplatParam, i, ifTrue, j, k, len1, len2, param, paramNames, params, paramsAfterSplat, ref, ref3, ref4, ref5, ref6, splatParamName, val, wasEmpty; if (this.bound) { + if (this.isGenerator) { + this.error("bound (fat arrow) functions cannot contain 'yield'"); + } if ((ref3 = o.scope.method) != null ? ref3.bound : void 0) { this.context = o.scope.method.context; } diff --git a/src/nodes.coffee b/src/nodes.coffee index a4bc691405..92ac69c8e6 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1608,6 +1608,8 @@ exports.Code = class Code extends Base # function body. compileNode: (o) -> if @bound + if @isGenerator + @error "bound (fat arrow) functions cannot contain 'yield'" @context = o.scope.method.context if o.scope.method?.bound @context = 'this' unless @context diff --git a/test/error_messages.coffee b/test/error_messages.coffee index 390fca9e35..5e06f19e87 100644 --- a/test/error_messages.coffee +++ b/test/error_messages.coffee @@ -1142,3 +1142,10 @@ test "imported members cannot be reassigned", -> export foo = 'bar' ^^^ ''' + +test "bound functions cannot be generators", -> + assertErrorFormat 'f = => yield this', ''' + [stdin]:1:5: error: bound (fat arrow) functions cannot contain 'yield' + f = => yield this + ^^^^^^^^^^^^^ + ''' diff --git a/test/generators.coffee b/test/generators.coffee index e01af7620b..91ae2bafe7 100644 --- a/test/generators.coffee +++ b/test/generators.coffee @@ -52,24 +52,6 @@ test "yield return can be used anywhere in the function body", -> 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 -> From 37bdef40be2df1317cad886303ef63db8173eec6 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Sun, 23 Oct 2016 23:04:11 -0700 Subject: [PATCH 34/34] Error for bound generator functions should underline the `yield` --- lib/coffee-script/nodes.js | 10 +++++----- src/nodes.coffee | 4 ++-- test/error_messages.coffee | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index 40813acf8c..832076fb0e 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -2388,9 +2388,6 @@ Code.prototype.compileNode = function(o) { var answer, code, condition, exprs, haveSplatParam, i, ifTrue, j, k, len1, len2, param, paramNames, params, paramsAfterSplat, ref, ref3, ref4, ref5, ref6, splatParamName, val, wasEmpty; if (this.bound) { - if (this.isGenerator) { - this.error("bound (fat arrow) functions cannot contain 'yield'"); - } if ((ref3 = o.scope.method) != null ? ref3.bound : void 0) { this.context = o.scope.method.context; } @@ -3022,12 +3019,15 @@ }; Op.prototype.compileYield = function(o) { - var op, parts, ref3; + var op, parts, ref3, ref4; parts = []; op = this.operator; if (o.scope.parent == null) { this.error('yield can only occur inside functions'); } + if (((ref3 = o.scope.method) != null ? ref3.bound : void 0) && o.scope.method.isGenerator) { + this.error('yield cannot occur inside bound (fat arrow) functions'); + } if (indexOf.call(Object.keys(this.first), 'expression') >= 0 && !(this.first instanceof Throw)) { if (this.first.expression != null) { parts.push(this.first.expression.compileToFragments(o, LEVEL_OP)); @@ -3037,7 +3037,7 @@ parts.push([this.makeCode("(")]); } parts.push([this.makeCode(op)]); - if (((ref3 = this.first.base) != null ? ref3.value : void 0) !== '') { + if (((ref4 = this.first.base) != null ? ref4.value : void 0) !== '') { parts.push([this.makeCode(" ")]); } parts.push(this.first.compileToFragments(o, LEVEL_OP)); diff --git a/src/nodes.coffee b/src/nodes.coffee index 92ac69c8e6..e3e2d97638 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1608,8 +1608,6 @@ exports.Code = class Code extends Base # function body. compileNode: (o) -> if @bound - if @isGenerator - @error "bound (fat arrow) functions cannot contain 'yield'" @context = o.scope.method.context if o.scope.method?.bound @context = 'this' unless @context @@ -2096,6 +2094,8 @@ exports.Op = class Op extends Base op = @operator unless o.scope.parent? @error 'yield can only occur inside functions' + if o.scope.method?.bound and o.scope.method.isGenerator + @error 'yield cannot occur inside bound (fat arrow) functions' if 'expression' in Object.keys(@first) and not (@first instanceof Throw) parts.push @first.expression.compileToFragments o, LEVEL_OP if @first.expression? else diff --git a/test/error_messages.coffee b/test/error_messages.coffee index 5e06f19e87..b7f65e4d09 100644 --- a/test/error_messages.coffee +++ b/test/error_messages.coffee @@ -1145,7 +1145,7 @@ test "imported members cannot be reassigned", -> test "bound functions cannot be generators", -> assertErrorFormat 'f = => yield this', ''' - [stdin]:1:5: error: bound (fat arrow) functions cannot contain 'yield' + [stdin]:1:8: error: yield cannot occur inside bound (fat arrow) functions f = => yield this - ^^^^^^^^^^^^^ + ^^^^^^^^^^ '''