Skip to content

Commit 1fd2f0e

Browse files
committed
correct es5 support for classes, modules (props and autobind)
1 parent 358b2f5 commit 1fd2f0e

File tree

3 files changed

+38
-20
lines changed

3 files changed

+38
-20
lines changed

lib/ruby2js/converter/class.rb

+6-2
Original file line numberDiff line numberDiff line change
@@ -70,15 +70,18 @@ class Converter
7070
{enumerable: s(:true), configurable: s(:true),
7171
set: s(:defm, nil, *m.children[1..-1])})
7272
else
73-
visible[m.children[0]] = s(:self)
7473

7574
if not m.is_method?
75+
visible[m.children[0]] = s(:self)
76+
7677
# property getter
7778
s(:prop, s(:attr, name, :prototype), m.children.first =>
7879
{enumerable: s(:true), configurable: s(:true),
7980
get: s(:defm, nil, m.children[1],
8081
m.updated(:autoreturn, m.children[2..-1]))})
8182
else
83+
visible[m.children[0]] = s(:autobind, s(:self))
84+
8285
# method: add to prototype
8386
s(:method, s(:attr, name, :prototype),
8487
:"#{m.children[0].to_s.chomp('!')}=",
@@ -222,7 +225,7 @@ class Converter
222225
methods = 0
223226
start = 0
224227
body.each do |node|
225-
if [:method, :prop].include? node.type and
228+
if (node.type == :method or (node.type == :prop and es2015)) and
226229
node.children[0].type == :attr and
227230
node.children[0].children[1] == :prototype
228231
methods += 1
@@ -272,6 +275,7 @@ class Converter
272275
end
273276

274277
if @ast.type == :class_module
278+
start = 0 if methods == 0
275279
if name
276280
body[start...start+methods] =
277281
s(:casgn, *name.children, s(:hash, *pairs.flatten))

spec/comments_spec.rb

+6-2
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,12 @@ def self.classsetter=(n)
7878
"Class.call(this)\n};"
7979
js.must_include "//constructor\nfunction Constructor() {"
8080
js.must_include "//method\nMethod.prototype.method = function() {"
81-
js.must_include "//attribute\n get attribute() {"
82-
js.must_include "//setter\n set setter(n) {"
81+
js.must_include "//attribute\n" +
82+
"Object.defineProperty(\n Attribute.prototype,\n " +
83+
'"attribute"'
84+
js.must_include "//setter\n" +
85+
"Object.defineProperty(\n Setter.prototype,\n " +
86+
'"setter"'
8387
js.must_include "//classmethod\nClassMethod.classmethod = function() {"
8488
js.must_include "//classattribute\nObject.defineProperty(\n " +
8589
"ClassAttribute,\n \"classattribute\""

spec/transliteration_spec.rb

+26-16
Original file line numberDiff line numberDiff line change
@@ -614,7 +614,7 @@ def to_js( string, opts={} )
614614

615615
it "should parse class with attr_accessor" do
616616
to_js('class Person; attr_accessor :a; end').
617-
must_equal 'function Person() {}; Person.prototype = {get a() {return this._a}, set a(a) {this._a = a}}'
617+
must_equal 'function Person() {}; Object.defineProperty(Person.prototype, "a", {enumerable: true, configurable: true, get: function() {return this._a}, set: function(a) {this._a = a}})'
618618
end
619619

620620
it "should parse class with constructor" do
@@ -629,27 +629,27 @@ def to_js( string, opts={} )
629629

630630
it "should parse class with constructor and method" do
631631
to_js('class Person; def initialize(name); @name = name; end; def name; @name; end; end').
632-
must_equal 'function Person(name) {this._name = name}; Person.prototype = {get name() {return this._name}}'
632+
must_equal 'function Person(name) {this._name = name}; Object.defineProperty(Person.prototype, "name", {enumerable: true, configurable: true, get: function() {return this._name}})'
633633
end
634634

635635
it "should parse class with constructor and two methods" do
636636
to_js('class Person; def initialize(name); @name = name; end; def name; @name; end; def reset!; @name = nil; end; end').
637-
must_equal 'function Person(name) {this._name = name}; Person.prototype = {get name() {return this._name}, reset: function() {this._name = null}}'
637+
must_equal 'function Person(name) {this._name = name}; Object.defineProperty(Person.prototype, "name", {enumerable: true, configurable: true, get: function() {return this._name}}); Person.prototype.reset = function() {this._name = null}'
638638
end
639639

640640
it "should parse class with constructor and methods with multiple arguments" do
641641
to_js('class Person; def initialize(name, surname); @name, @surname = name, surname; end; def full_name; @name + @surname; end; end').
642-
must_equal 'function Person(name, surname) {this._name = name; this._surname = surname}; Person.prototype = {get full_name() {return this._name + this._surname}}'
642+
must_equal 'function Person(name, surname) {this._name = name; this._surname = surname}; Object.defineProperty(Person.prototype, "full_name", {enumerable: true, configurable: true, get: function() {return this._name + this._surname}})'
643643
end
644644

645645
it "should collapse multiple methods in a class" do
646646
to_js('class C; def a; end; def b; end; end').
647-
must_equal 'function C() {}; C.prototype = {get a() {}, get b() {}}'
647+
must_equal 'function C() {}; Object.defineProperties(C.prototype, {a: {enumerable: true, configurable: true, get: function() {}}, b: {enumerable: true, configurable: true, get: function() {}}})'
648648
end
649649

650650
it "should collapse getters and setters in a class" do
651651
to_js('class C; def a; end; def a=(a); end; end').
652-
must_equal 'function C() {}; C.prototype = {get a() {}, set a(a) {}}'
652+
must_equal 'function C() {}; Object.defineProperty(C.prototype, "a", {enumerable: true, configurable: true, get: function() {}, set: function(a) {}})'
653653
end
654654

655655
it "should collapse properties" do
@@ -685,21 +685,21 @@ def to_js( string, opts={} )
685685
must_equal 'function Person() {}; Person._count = 0; Person.prototype.offset = function(x) {return Person._count + x}'
686686

687687
to_js('class Person; @@count=0; def count; @@count; end; end').
688-
must_equal 'function Person() {}; Person._count = 0; Person.prototype = {get count() {return Person._count}}'
688+
must_equal 'function Person() {}; Person._count = 0; Object.defineProperty(Person.prototype, "count", {enumerable: true, configurable: true, get: function() {return Person._count}})'
689689

690690
to_js('class Person; @@count=0; def count(); return @@count; end; end').
691691
must_equal 'function Person() {}; Person._count = 0; Person.prototype.count = function() {return Person._count}'
692692

693693
to_js('class Person; def initialize(name); @name = name; end; def name; @name; end; @@count=0; def count; return @@count; end; end').
694-
must_equal 'function Person(name) {this._name = name}; Person.prototype = {get name() {return this._name}}; Person._count = 0; Object.defineProperty(Person.prototype, "count", {enumerable: true, configurable: true, get: function() {return Person._count}})'
694+
must_equal 'function Person(name) {this._name = name}; Object.defineProperty(Person.prototype, "name", {enumerable: true, configurable: true, get: function() {return this._name}}); Person._count = 0; Object.defineProperty(Person.prototype, "count", {enumerable: true, configurable: true, get: function() {return Person._count}})'
695695

696696
to_js('class Person; def initialize(name); @name = name; end; def name; @name; end; @@count=0; def count(); return @@count; end; end').
697-
must_equal 'function Person(name) {this._name = name}; Person.prototype = {get name() {return this._name}}; Person._count = 0; Person.prototype.count = function() {return Person._count}'
697+
must_equal 'function Person(name) {this._name = name}; Object.defineProperty(Person.prototype, "name", {enumerable: true, configurable: true, get: function() {return this._name}}); Person._count = 0; Person.prototype.count = function() {return Person._count}'
698698
end
699699

700700
it "should parse instance methods with class variables" do
701701
to_js('class Person; def count; @@count; end; end').
702-
must_equal 'function Person() {}; Person.prototype = {get count() {return Person._count}}'
702+
must_equal 'function Person() {}; Object.defineProperty(Person.prototype, "count", {enumerable: true, configurable: true, get: function() {return Person._count}})'
703703
end
704704

705705
it "should parse class methods with class variables" do
@@ -762,18 +762,28 @@ def to_js( string, opts={} )
762762

763763
it "should prefix intra-method calls with 'this.'" do
764764
to_js('class C; def m1; end; def m2; m1; end; end').
765-
must_equal 'function C() {}; C.prototype = ' +
766-
'{get m1() {}, get m2() {return this.m1}}'
765+
must_equal 'function C() {}; Object.defineProperties(C.prototype, ' +
766+
'{m1: {enumerable: true, configurable: true, get: function() {}}, ' +
767+
'm2: {enumerable: true, configurable: true, get: function() ' +
768+
'{return this.m1}}})'
769+
end
770+
771+
it "should prefix bind references to methods as properties" do
772+
to_js('class C; def m1(); end; def m2; m1; end; end').
773+
must_equal 'function C() {}; C.prototype.m1 = function() {}; ' +
774+
'Object.defineProperty(C.prototype, ' +
775+
'"m2", {enumerable: true, configurable: true, get: function() ' +
776+
'{return this.m1.bind(this)}})'
767777
end
768778

769779
it "should prefix class constants referenced in methods by class name" do
770780
to_js('class C; X = 1; def m; X; end; end').
771-
must_equal 'function C() {}; C.X = 1; C.prototype = {get m() {return C.X}}'
781+
must_equal 'function C() {}; C.X = 1; Object.defineProperty(C.prototype, "m", {enumerable: true, configurable: true, get: function() {return C.X}})'
772782
end
773783

774784
it "should insert var self = this when needed" do
775785
to_js('class C; def m; list.each do; @ivar; end; end; end').
776-
must_equal 'function C() {}; C.prototype = {get m() {var self = this; return list.each(function() {self._ivar})}}'
786+
must_equal 'function C() {}; Object.defineProperty(C.prototype, "m", {enumerable: true, configurable: true, get: function() {var self = this; return list.each(function() {self._ivar})}})'
777787

778788
to_js('class C; def m(); list.each do; @ivar; @ivar; end; end; end').
779789
must_equal 'function C() {}; C.prototype.m = function() {var self = this; list.each(function() {self._ivar; self._ivar})}'
@@ -841,7 +851,7 @@ def to_js( string, opts={} )
841851
to_js( 'module A; B=1; end' ).
842852
must_equal 'A = function() {var B = 1; return {B: B}}()'
843853
to_js( 'module A; def b; return 1; end; end' ).
844-
must_equal 'var A = {get b() {return 1}}'
854+
must_equal 'var A = {}; Object.defineProperty(A.prototype, "b", {enumerable: true, configurable: true, get: function() {return 1}})'
845855
to_js( 'module A; def b(); return 1; end; end' ).
846856
must_equal 'var A = {b: function() {return 1}}'
847857
to_js( 'module A; class B; def initialize; @c=1; end; end; end' ).
@@ -1107,7 +1117,7 @@ def to_js( string, opts={} )
11071117

11081118
it "should not replace ivars in class definitions" do
11091119
to_js( 'class F; def f; @x; end; end', ivars: {:@x => 1} ).
1110-
must_equal 'function F() {}; F.prototype = {get f() {return this._x}}'
1120+
must_equal 'function F() {}; Object.defineProperty(F.prototype, "f", {enumerable: true, configurable: true, get: function() {return this._x}})'
11111121
end
11121122
end
11131123

0 commit comments

Comments
 (0)