diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index cea7a3f5a3..be7c0e9a61 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -759,6 +759,11 @@ return last(this.properties) instanceof Slice; }; + Value.prototype.looksStatic = function(className) { + var _ref2; + return this.base.value === className && this.properties.length && ((_ref2 = this.properties[0].name) != null ? _ref2.value : void 0) !== 'prototype'; + }; + Value.prototype.unwrap = function() { if (this.properties.length) { return this; @@ -1446,9 +1451,6 @@ } else { if (assign.variable["this"]) { func["static"] = true; - if (func.bound) { - func.context = name; - } } else { assign.variable = new Value(new Literal(name), [new Access(new Literal('prototype')), new Access(base)]); if (func instanceof Code && func.bound) { @@ -1477,7 +1479,9 @@ _ref2 = exps = child.expressions; for (i = _i = 0, _len = _ref2.length; _i < _len; i = ++_i) { node = _ref2[i]; - if (node instanceof Value && node.isObject(true)) { + 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); } @@ -1587,7 +1591,7 @@ }; Assign.prototype.compileNode = function(o) { - var answer, compiledName, isValue, match, name, val, varBase, _ref2, _ref3, _ref4, _ref5; + var answer, compiledName, isValue, match, name, val, varBase, _ref2, _ref3, _ref4; if (isValue = this.variable instanceof Value) { if (this.variable.isArray() || this.variable.isObject()) { return this.compilePatternMatch(o); @@ -1615,10 +1619,10 @@ } } if (this.value instanceof Code && (match = METHOD_DEF.exec(name))) { - if (match[1]) { + if (match[2]) { this.value.klass = match[1]; } - this.value.name = (_ref3 = (_ref4 = (_ref5 = match[2]) != null ? _ref5 : match[3]) != null ? _ref4 : match[4]) != null ? _ref3 : match[5]; + this.value.name = (_ref3 = (_ref4 = match[3]) != null ? _ref4 : match[4]) != null ? _ref3 : match[5]; } val = this.value.compileToFragments(o, LEVEL_LIST); if (this.context === 'object') { @@ -3025,7 +3029,7 @@ NUMBER = /^[+-]?(?:0x[\da-f]+|\d*\.?\d+(?:e[+-]?\d+)?)$/i; - METHOD_DEF = RegExp("^(?:(" + IDENTIFIER_STR + ")\\.prototype(?:\\.(" + IDENTIFIER_STR + ")|\\[(\"(?:[^\\\\\"\\r\\n]|\\\\.)*\"|'(?:[^\\\\'\\r\\n]|\\\\.)*')\\]|\\[(0x[\\da-fA-F]+|\\d*\\.?\\d+(?:[eE][+-]?\\d+)?)\\]))|(" + IDENTIFIER_STR + ")$"); + METHOD_DEF = RegExp("^(" + IDENTIFIER_STR + ")(\\.prototype)?(?:\\.(" + IDENTIFIER_STR + ")|\\[(\"(?:[^\\\\\"\\r\\n]|\\\\.)*\"|'(?:[^\\\\'\\r\\n]|\\\\.)*')\\]|\\[(0x[\\da-fA-F]+|\\d*\\.?\\d+(?:[eE][+-]?\\d+)?)\\])$"); IS_STRING = /^['"]/; diff --git a/src/nodes.coffee b/src/nodes.coffee index 04b797df70..dbd2dfa04a 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -509,6 +509,10 @@ exports.Value = class Value extends Base isSplice: -> last(@properties) instanceof Slice + looksStatic: (className) -> + @base.value is className and @properties.length and + @properties[0].name?.value isnt 'prototype' + # The value can be unwrapped as its inner node, if there are no attached # properties. unwrap: -> @@ -1036,8 +1040,6 @@ exports.Class = class Class extends Base else if assign.variable.this func.static = yes - if func.bound - func.context = name else assign.variable = new Value(new Literal(name), [(new Access new Literal 'prototype'), new Access base]) if func instanceof Code and func.bound @@ -1046,14 +1048,17 @@ exports.Class = class Class extends Base assign compact exprs - # Walk the body of the class, looking for prototype properties to be converted. + # Walk the body of the class, looking for prototype properties to be converted + # and tagging static assignments. walkBody: (name, o) -> @traverseChildren false, (child) => cont = true return false if child instanceof Class if child instanceof Block for node, i in exps = child.expressions - if node instanceof Value and node.isObject(true) + if node instanceof Assign and node.variable.looksStatic name + node.value.static = yes + else if node instanceof Value and node.isObject(true) cont = false exps[i] = @addProperties node, name, o child.expressions = exps = flatten exps @@ -1164,8 +1169,8 @@ exports.Assign = class Assign extends Base else o.scope.find name if @value instanceof Code and match = METHOD_DEF.exec name - @value.klass = match[1] if match[1] - @value.name = match[2] ? match[3] ? match[4] ? match[5] + @value.klass = match[1] if match[2] + @value.name = match[3] ? match[4] ? match[5] val = @value.compileToFragments o, LEVEL_LIST return (compiledName.concat @makeCode(": "), val) if @context is 'object' answer = compiledName.concat @makeCode(" #{ @context or '=' } "), val @@ -2141,21 +2146,14 @@ NUMBER = ///^[+-]?(?: \d*\.?\d+ (?:e[+-]?\d+)? # decimal )$///i -METHOD_DEF = /// - ^ - (?: - (#{IDENTIFIER_STR}) - \.prototype - (?: - \.(#{IDENTIFIER_STR}) - | \[("(?:[^\\"\r\n]|\\.)*"|'(?:[^\\'\r\n]|\\.)*')\] - | \[(0x[\da-fA-F]+ | \d*\.?\d+ (?:[eE][+-]?\d+)?)\] - ) - ) - | - (#{IDENTIFIER_STR}) - $ -/// +METHOD_DEF = /// ^ + (#{IDENTIFIER_STR}) + (\.prototype)? + (?: \.(#{IDENTIFIER_STR}) + | \[("(?:[^\\"\r\n]|\\.)*"|'(?:[^\\'\r\n]|\\.)*')\] + | \[(0x[\da-fA-F]+ | \d*\.?\d+ (?:[eE][+-]?\d+)?)\] + ) +$ /// # Is a literal value a string/regex? IS_STRING = /^['"]/ diff --git a/test/classes.coffee b/test/classes.coffee index ab8d78e418..143b46e07d 100644 --- a/test/classes.coffee +++ b/test/classes.coffee @@ -807,3 +807,24 @@ test "#3063: Class bodies cannot contain pure statements", -> return if S.f @f: => this """ + +test "#2949: super in static method with reserved name", -> + class Foo + @static: -> 'baz' + + class Bar extends Foo + @static: -> super + + eq Bar.static(), 'baz' + +test "#3232: super in static methods (not object-assigned)", -> + class Foo + @baz = -> true + @qux = -> true + + class Bar extends Foo + @baz = -> super + Bar.qux = -> super + + ok Bar.baz() + ok Bar.qux()