diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index aea906d24d..16b9a58bc0 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript 1.8.0 (function() { - var Access, Arr, Assign, Base, Block, Call, Class, Code, CodeFragment, Comment, Existence, Expansion, Extends, For, HEXNUM, IDENTIFIER, IDENTIFIER_STR, IS_REGEX, IS_STRING, If, In, Index, LEVEL_ACCESS, LEVEL_COND, LEVEL_LIST, LEVEL_OP, LEVEL_PAREN, LEVEL_TOP, Literal, METHOD_DEF, NEGATE, NO, NUMBER, Obj, Op, Param, Parens, RESERVED, Range, Return, SIMPLENUM, STRICT_PROSCRIBED, Scope, Slice, Splat, Switch, TAB, THIS, Throw, Try, UTILITIES, Value, While, YES, addLocationDataFn, compact, del, ends, extend, flatten, fragmentsToText, isLiteralArguments, isLiteralThis, last, locationDataToString, merge, multident, parseNum, some, starts, throwSyntaxError, unfoldSoak, utility, _ref, _ref1, + var Access, Arr, Assign, Base, Block, Call, Class, Code, CodeFragment, Comment, Existence, Expansion, Extends, For, HEXNUM, IDENTIFIER, IS_REGEX, IS_STRING, If, In, Index, LEVEL_ACCESS, LEVEL_COND, LEVEL_LIST, LEVEL_OP, LEVEL_PAREN, LEVEL_TOP, Literal, NEGATE, NO, NUMBER, Obj, Op, Param, Parens, RESERVED, Range, Return, SIMPLENUM, STRICT_PROSCRIBED, Scope, Slice, Splat, Switch, TAB, THIS, Throw, Try, UTILITIES, Value, While, YES, addLocationDataFn, compact, del, ends, extend, flatten, fragmentsToText, isLiteralArguments, isLiteralThis, last, locationDataToString, merge, multident, parseNum, propertyName, some, starts, throwSyntaxError, unfoldSoak, utility, _ref, _ref1, __hasProp = {}.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, @@ -763,8 +763,7 @@ }; 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'; + return this.base.value === className && this.properties.length === 1 && propertyName(this.properties[0]) !== 'prototype'; }; Value.prototype.unwrap = function() { @@ -900,15 +899,28 @@ }; Call.prototype.superReference = function(o) { - var accesses, method; + var accesses, base, bref, klass, method, name, nref, variable; method = o.scope.namedMethod(); if (method != null ? method.klass : void 0) { + klass = method.klass, name = method.name, variable = method.variable; + if (klass.isComplex()) { + bref = new Literal(o.scope.parent.freeVariable('base')); + base = new Value(new Parens(new Assign(bref, klass))); + variable.base = base; + variable.properties.splice(0, klass.properties.length); + } + if (name.isComplex() || (name instanceof Index && name.index.isAssignable())) { + nref = new Literal(o.scope.parent.freeVariable('name')); + name = new Index(new Assign(nref, name.index)); + variable.properties.pop(); + variable.properties.push(name); + } accesses = [new Access(new Literal('__super__'))]; if (method["static"]) { accesses.push(new Access(new Literal('constructor'))); } - accesses.push(new Access(new Literal(method.name))); - return (new Value(new Literal(method.klass), accesses)).compile(o); + accesses.push(nref != null ? new Index(nref) : name); + return (new Value(bref != null ? bref : klass, accesses)).compile(o); } else if (method != null ? method.ctor : void 0) { return method.name + ".__super__.constructor"; } else { @@ -1409,7 +1421,7 @@ if (node instanceof Literal && node.value === 'this') { return node.value = name; } else if (node instanceof Code) { - node.klass = name; + node.klass = new Value(new Literal(name)); if (node.bound) { return node.context = name; } @@ -1594,7 +1606,7 @@ }; Assign.prototype.compileNode = function(o) { - var answer, compiledName, isValue, match, name, val, varBase, _ref2, _ref3, _ref4, _ref5; + var answer, compiledName, isValue, name, properties, prototype, val, varBase, _i, _ref2, _ref3, _ref4, _ref5; if (isValue = this.variable instanceof Value) { if (this.variable.isArray() || this.variable.isObject()) { return this.compilePatternMatch(o); @@ -1609,8 +1621,19 @@ return this.compileSpecialMath(o); } } - compiledName = this.variable.compileToFragments(o, LEVEL_LIST); - name = fragmentsToText(compiledName); + if (this.value instanceof Code) { + if (this.value["static"]) { + this.value.name = this.variable.properties[0]; + this.value.variable = this.variable; + } else if (((_ref4 = this.variable.properties) != null ? _ref4.length : void 0) >= 2) { + _ref5 = this.variable.properties, properties = 3 <= _ref5.length ? __slice.call(_ref5, 0, _i = _ref5.length - 2) : (_i = 0, []), prototype = _ref5[_i++], name = _ref5[_i++]; + if (propertyName(prototype) === 'prototype') { + this.value.klass = new Value(this.variable.base, properties); + this.value.name = name; + this.value.variable = this.variable; + } + } + } if (!this.context) { varBase = this.variable.unwrapAll(); if (!varBase.isAssignable()) { @@ -1618,19 +1641,14 @@ } if (!(typeof varBase.hasProperties === "function" ? varBase.hasProperties() : void 0)) { if (this.param) { - o.scope.add(name, 'var'); + o.scope.add(varBase.value, 'var'); } else { - o.scope.find(name); + o.scope.find(varBase.value); } } } - if (this.value instanceof Code && (match = METHOD_DEF.exec(name))) { - if (match[2]) { - this.value.klass = match[1]; - } - this.value.name = (_ref4 = (_ref5 = match[3]) != null ? _ref5 : match[4]) != null ? _ref4 : match[5]; - } val = this.value.compileToFragments(o, LEVEL_LIST); + compiledName = this.variable.compileToFragments(o, LEVEL_LIST); if (this.context === 'object') { return compiledName.concat(this.makeCode(": "), val); } @@ -3137,9 +3155,7 @@ TAB = ' '; - IDENTIFIER_STR = "[$A-Za-z_\\x7f-\\uffff][$\\w\\x7f-\\uffff]*"; - - IDENTIFIER = RegExp("^" + IDENTIFIER_STR + "$"); + IDENTIFIER = /^(?!\d)[$\w\x7f-\uffff]+$/; SIMPLENUM = /^[+-]?\d+$/; @@ -3147,8 +3163,6 @@ 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+)?)\\])$"); - IS_STRING = /^['"]/; IS_REGEX = /^\//; @@ -3193,4 +3207,13 @@ return ifn; }; + propertyName = function(property) { + switch (false) { + case !(property instanceof Access): + return property.name.value; + case !(property instanceof Index && property.index instanceof Value && property.index.isString()): + return property.index.base.value.slice(1, -1); + } + }; + }).call(this); diff --git a/src/nodes.coffee b/src/nodes.coffee index 4806270972..32a30eb1d8 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -512,8 +512,8 @@ exports.Value = class Value extends Base last(@properties) instanceof Slice looksStatic: (className) -> - @base.value is className and @properties.length and - @properties[0].name?.value isnt 'prototype' + @base.value is className and @properties.length is 1 and + propertyName(@properties[0]) isnt 'prototype' # The value can be unwrapped as its inner node, if there are no attached # properties. @@ -613,10 +613,21 @@ exports.Call = class Call extends Base superReference: (o) -> method = o.scope.namedMethod() if method?.klass - accesses = [new Access(new Literal '__super__')] + {klass, name, variable} = method + if klass.isComplex() + bref = new Literal o.scope.parent.freeVariable 'base' + base = new Value new Parens new Assign bref, klass + variable.base = base + variable.properties.splice 0, klass.properties.length + if name.isComplex() or (name instanceof Index and name.index.isAssignable()) + nref = new Literal o.scope.parent.freeVariable 'name' + name = new Index new Assign nref, name.index + variable.properties.pop() + variable.properties.push name + accesses = [new Access new Literal '__super__'] accesses.push new Access new Literal 'constructor' if method.static - accesses.push new Access new Literal method.name - (new Value (new Literal method.klass), accesses).compile o + accesses.push if nref? then new Index nref else name + (new Value bref ? klass, accesses).compile o else if method?.ctor "#{method.name}.__super__.constructor" else @@ -1009,7 +1020,7 @@ exports.Class = class Class extends Base if node instanceof Literal and node.value is 'this' node.value = name else if node instanceof Code - node.klass = name + node.klass = new Value new Literal name node.context = name if node.bound # Ensure that all functions bound to the instance are proxied in the @@ -1160,21 +1171,27 @@ exports.Assign = class Assign extends Base return @compileSplice o if @variable.isSplice() return @compileConditional o if @context in ['||=', '&&=', '?='] return @compileSpecialMath o if @context in ['**=', '//=', '%%='] - compiledName = @variable.compileToFragments o, LEVEL_LIST - name = fragmentsToText compiledName + if @value instanceof Code + if @value.static + @value.name = @variable.properties[0] + @value.variable = @variable + else if @variable.properties?.length >= 2 + [properties..., prototype, name] = @variable.properties + if propertyName(prototype) is 'prototype' + @value.klass = new Value @variable.base, properties + @value.name = name + @value.variable = @variable unless @context varBase = @variable.unwrapAll() unless varBase.isAssignable() @variable.error "\"#{@variable.compile o}\" cannot be assigned" unless varBase.hasProperties?() if @param - o.scope.add name, 'var' + o.scope.add varBase.value, 'var' else - o.scope.find name - if @value instanceof Code and match = METHOD_DEF.exec name - @value.klass = match[1] if match[2] - @value.name = match[3] ? match[4] ? match[5] + o.scope.find varBase.value val = @value.compileToFragments o, LEVEL_LIST + compiledName = @variable.compileToFragments o, LEVEL_LIST return (compiledName.concat @makeCode(": "), val) if @context is 'object' answer = compiledName.concat @makeCode(" #{ @context or '=' } "), val if o.level <= LEVEL_LIST then answer else @wrapInBraces answer @@ -2237,8 +2254,7 @@ LEVEL_ACCESS = 6 # ...[0] # Tabs are two spaces for pretty printing. TAB = ' ' -IDENTIFIER_STR = "[$A-Za-z_\\x7f-\\uffff][$\\w\\x7f-\\uffff]*" -IDENTIFIER = /// ^ #{IDENTIFIER_STR} $ /// +IDENTIFIER = /// ^ (?!\d) [$\w\x7f-\uffff]+ $ /// SIMPLENUM = /^[+-]?\d+$/ HEXNUM = /^[+-]?0x[\da-f]+/i NUMBER = ///^[+-]?(?: @@ -2246,15 +2262,6 @@ 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+)?)\] - ) -$ /// - # Is a literal value a string/regex? IS_STRING = /^['"]/ IS_REGEX = /^\// @@ -2296,3 +2303,10 @@ unfoldSoak = (o, parent, name) -> parent[name] = ifn.body ifn.body = new Value parent ifn + +# Check if `.prop` or `["prop"]` is equal to `property`. +propertyName = (property) -> switch + when property instanceof Access + property.name.value + when property instanceof Index and property.index instanceof Value and property.index.isString() + property.index.base.value[1...-1] diff --git a/test/classes.coffee b/test/classes.coffee index 143b46e07d..a4ea971e81 100644 --- a/test/classes.coffee +++ b/test/classes.coffee @@ -828,3 +828,100 @@ test "#3232: super in static methods (not object-assigned)", -> ok Bar.baz() ok Bar.qux() + +test "#1392 calling `super` in methods defined on namespaced classes", -> + class Base + m: -> 5 + n: -> 4 + namespace = + A: -> + B: -> + namespace.A extends Base + + namespace.A::m = -> super + eq 5, (new namespace.A).m() + namespace.B::m = namespace.A::m + namespace.A::m = null + eq 5, (new namespace.B).m() + + count = 0 + getNamespace = -> count++; namespace + getNamespace().A::n = -> super + eq 4, (new namespace.A).n() + eq 1, count + + class C + @a: -> + @a extends Base + @a::m = -> super + eq 5, (new C.a).m() + +test "dynamic method names and super", -> + class Base + @m: -> 6 + m: -> 5 + n: -> 4 + A = -> + A extends Base + + m = 'm' + A::[m] = -> super + m = 'n' + eq 5, (new A).m() + + name = -> count++; 'n' + + count = 0 + A::[name()] = -> super + eq 4, (new A).n() + eq 1, count + + m = 'm' + count = 0 + class B extends Base + @[name()] = -> super + @::[m] = -> super + b = new B + m = 'n' + eq 6, B.m() + eq 5, b.m() + eq 1, count + + class C extends B + @m: -> super + eq 5, (new C).m() + +test "different ways to write 'prototype'", -> + class Base + a: -> 1 + b: -> 2 + c: -> 3 + d: -> 4 + e: -> 5 + + A = -> + A extends Base + A::a = -> super + A['prototype'].b = -> super + A["prototype"].c = -> super + A['''prototype'''].d = -> super + A["""prototype"""].e = -> super + a = new A + eq 1, a.a() + eq 2, a.b() + eq 3, a.c() + eq 4, a.d() + eq 5, a.e() + + class B extends Base + @::a = -> super + @['prototype'].b = -> super + @["prototype"].c = -> super + @['''prototype'''].d = -> super + @["""prototype"""].e = -> super + b = new B + eq 1, b.a() + eq 2, b.b() + eq 3, b.c() + eq 4, b.d() + eq 5, b.e()