ruby2js 3.6.1 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -7
  3. data/lib/ruby2js.rb +32 -9
  4. data/lib/ruby2js/converter.rb +8 -2
  5. data/lib/ruby2js/converter/assign.rb +159 -0
  6. data/lib/ruby2js/converter/begin.rb +7 -2
  7. data/lib/ruby2js/converter/case.rb +7 -2
  8. data/lib/ruby2js/converter/class.rb +77 -21
  9. data/lib/ruby2js/converter/class2.rb +39 -11
  10. data/lib/ruby2js/converter/def.rb +6 -2
  11. data/lib/ruby2js/converter/dstr.rb +8 -3
  12. data/lib/ruby2js/converter/hash.rb +9 -5
  13. data/lib/ruby2js/converter/hide.rb +13 -0
  14. data/lib/ruby2js/converter/if.rb +10 -2
  15. data/lib/ruby2js/converter/import.rb +18 -3
  16. data/lib/ruby2js/converter/kwbegin.rb +9 -2
  17. data/lib/ruby2js/converter/literal.rb +2 -2
  18. data/lib/ruby2js/converter/module.rb +37 -5
  19. data/lib/ruby2js/converter/opasgn.rb +8 -0
  20. data/lib/ruby2js/converter/send.rb +41 -2
  21. data/lib/ruby2js/converter/vasgn.rb +5 -0
  22. data/lib/ruby2js/demo.rb +53 -0
  23. data/lib/ruby2js/es2022.rb +5 -0
  24. data/lib/ruby2js/es2022/strict.rb +3 -0
  25. data/lib/ruby2js/filter.rb +9 -1
  26. data/lib/ruby2js/filter/active_functions.rb +1 -0
  27. data/lib/ruby2js/filter/cjs.rb +2 -0
  28. data/lib/ruby2js/filter/esm.rb +44 -10
  29. data/lib/ruby2js/filter/functions.rb +84 -95
  30. data/lib/ruby2js/filter/{wunderbar.rb → jsx.rb} +29 -7
  31. data/lib/ruby2js/filter/react.rb +191 -56
  32. data/lib/ruby2js/filter/require.rb +100 -5
  33. data/lib/ruby2js/filter/return.rb +13 -1
  34. data/lib/ruby2js/filter/stimulus.rb +185 -0
  35. data/lib/ruby2js/jsx.rb +291 -0
  36. data/lib/ruby2js/namespace.rb +75 -0
  37. data/lib/ruby2js/version.rb +3 -3
  38. metadata +12 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 11ab4e0003f0ff4f5821c00d5e282dead1201f97d4a2cc9ffb5c1188687f6079
4
- data.tar.gz: fe572bef6b5ee55d46d72bb71dfcca5793fff8dd29a44243da637a7b91264bf7
3
+ metadata.gz: 064d9dc5d65f6e2456e91d951e2f4311b776221422a77d59a8cfd466ceb5056d
4
+ data.tar.gz: ffc274a55ceb9a1280f776c00da8c296dc10672150174135931547cd056a3520
5
5
  SHA512:
6
- metadata.gz: f865febd41bda90440a03b5ccdb7ac921eb794701c61d120b28a850d03001ee95ae0403bd50123d96c0f85b5e3a0e672066fcde7da68216c28f6f5895b2f9398
7
- data.tar.gz: cf4811376c6daf78e951f688b6b2ee78e1b627135ed365e7197a6a318601b8b6313d2c4123031fd395cf5342707914e56bc29d946e15dc8f1dc7d53d35b053c6
6
+ metadata.gz: 8fe9775803716fce25697adaf59a8afb57f4de499052a66847f18dfdd59c1f73b52d77be473adf762a57693a0896a6c90ed275a2b9152b5b6b548ca454e6d9ae
7
+ data.tar.gz: aa901eab47bba051a69a0ee132b69e7754aa5da49ff2e7e22560303a4c0fd02cdb727d4297c13098f3a6f82cf20788f4657c7ec21d145e66533875933d1a5154
data/README.md CHANGED
@@ -13,7 +13,7 @@ Documentation
13
13
 
14
14
  * Visit **[ruby2js.com](https://www.ruby2js.com)** for detailed setup instructions and API reference.
15
15
 
16
- * [Try Ruby2JS online](https://whimsy.apache.org/ruby2js)
16
+ * [Try Ruby2JS online](https://ruby2js.com/demo)
17
17
 
18
18
 
19
19
  Synopsis
@@ -39,12 +39,6 @@ Host variable substitution:
39
39
  puts Ruby2JS.convert("@name", ivars: {:@name => "Joe"})
40
40
  ```
41
41
 
42
- Host expression evalution -- potentially unsafe, use only if you trust
43
- the source being converted::
44
- ```ruby
45
- i = 7; puts Ruby2JS.convert("i = `i`", binding: binding)
46
- ```
47
-
48
42
  Enable ES2015 support:
49
43
 
50
44
  ```ruby
data/lib/ruby2js.rb CHANGED
@@ -10,9 +10,15 @@ end
10
10
 
11
11
  require 'ruby2js/converter'
12
12
  require 'ruby2js/filter'
13
+ require 'ruby2js/namespace'
13
14
 
14
15
  module Ruby2JS
15
16
  class SyntaxError < RuntimeError
17
+ attr_reader :diagnostic
18
+ def initialize(message, diagnostic=nil)
19
+ super(message)
20
+ @diagnostic = diagnostic
21
+ end
16
22
  end
17
23
 
18
24
  @@eslevel_default = 2009 # ecmascript 5
@@ -61,7 +67,7 @@ module Ruby2JS
61
67
  include Ruby2JS::Filter
62
68
  BINARY_OPERATORS = Converter::OPERATORS[2..-1].flatten
63
69
 
64
- attr_accessor :prepend_list, :disable_autoimports
70
+ attr_accessor :prepend_list, :disable_autoimports, :namespace
65
71
 
66
72
  def initialize(comments)
67
73
  @comments = comments
@@ -79,8 +85,8 @@ module Ruby2JS
79
85
  def options=(options)
80
86
  @options = options
81
87
 
82
- @included = @@included
83
- @excluded = @@excluded
88
+ @included = Filter.included_methods
89
+ @excluded = Filter.excluded_methods
84
90
 
85
91
  include_all if options[:include_all]
86
92
  include_only(options[:include_only]) if options[:include_only]
@@ -116,6 +122,10 @@ module Ruby2JS
116
122
  @options[:eslevel] >= 2021
117
123
  end
118
124
 
125
+ def es2022
126
+ @options[:eslevel] >= 2022
127
+ end
128
+
119
129
  def process(node)
120
130
  ast, @ast = @ast, node
121
131
  replacement = super
@@ -130,6 +140,7 @@ module Ruby2JS
130
140
  end
131
141
 
132
142
  # handle all of the 'invented/synthetic' ast types
143
+ def on_assign(node); end
133
144
  def on_async(node); on_def(node); end
134
145
  def on_asyncs(node); on_defs(node); end
135
146
  def on_attr(node); on_send(node); end
@@ -137,18 +148,23 @@ module Ruby2JS
137
148
  def on_await(node); on_send(node); end
138
149
  def on_call(node); on_send(node); end
139
150
  def on_class_extend(node); on_send(node); end
151
+ def on_class_hash(node); on_class(node); end
140
152
  def on_class_module(node); on_send(node); end
141
153
  def on_constructor(node); on_def(node); end
154
+ def on_deff(node); on_def(node); end
142
155
  def on_defm(node); on_defs(node); end
143
156
  def on_defp(node); on_defs(node); end
144
157
  def on_for_of(node); on_for(node); end
145
158
  def on_in?(node); on_send(node); end
146
159
  def on_method(node); on_send(node); end
160
+ def on_module_hash(node); on_module(node); end
147
161
  def on_prop(node); on_array(node); end
148
162
  def on_prototype(node); on_begin(node); end
149
163
  def on_send!(node); on_send(node); end
150
164
  def on_sendw(node); on_send(node); end
151
165
  def on_undefined?(node); on_defined?(node); end
166
+ def on_defineProps(node); end
167
+ def on_hide(node); on_begin(node); end
152
168
  def on_nil(node); end
153
169
  def on_xnode(node); end
154
170
  def on_export(node); end
@@ -178,6 +194,7 @@ module Ruby2JS
178
194
  end
179
195
 
180
196
  def self.convert(source, options={})
197
+ options = options.dup
181
198
  options[:eslevel] ||= @@eslevel_default
182
199
  options[:strict] = @@strict_default if options[:strict] == nil
183
200
  options[:module] ||= @@module_default || :esm
@@ -197,6 +214,8 @@ module Ruby2JS
197
214
  comments = ast ? Parser::Source::Comment.associate(ast, comments) : {}
198
215
  end
199
216
 
217
+ namespace = Namespace.new
218
+
200
219
  filters = (options[:filters] || Filter::DEFAULTS)
201
220
 
202
221
  unless filters.empty?
@@ -211,6 +230,7 @@ module Ruby2JS
211
230
  filter = filter.new(comments)
212
231
 
213
232
  filter.options = options
233
+ filter.namespace = namespace
214
234
  ast = filter.process(ast)
215
235
 
216
236
  unless filter.prepend_list.empty?
@@ -229,7 +249,10 @@ module Ruby2JS
229
249
  ruby2js.comparison = options[:comparison] || :equality
230
250
  ruby2js.or = options[:or] || :logical
231
251
  ruby2js.module_type = options[:module] || :esm
232
- ruby2js.underscored_private = (options[:eslevel] < 2020) || options[:underscored_private]
252
+ ruby2js.underscored_private = (options[:eslevel] < 2022) || options[:underscored_private]
253
+
254
+ ruby2js.namespace = namespace
255
+
233
256
  if ruby2js.binding and not ruby2js.ivars
234
257
  ruby2js.ivars = ruby2js.binding.eval \
235
258
  'Hash[instance_variables.map {|var| [var, instance_variable_get(var)]}]'
@@ -256,16 +279,16 @@ module Ruby2JS
256
279
  buffer = Parser::Source::Buffer.new(file, line)
257
280
  buffer.source = source.encode('utf-8')
258
281
  parser = Parser::CurrentRuby.new
259
- parser.builder.emit_file_line_as_literals=false
260
- ast, comments = parser.parse_with_comments(buffer)
261
- Parser::CurrentRuby.parse(source.encode('utf-8')) unless ast
262
- [ast, comments]
282
+ parser.diagnostics.all_errors_are_fatal = true
283
+ parser.diagnostics.consumer = lambda {|diagnostic| nil}
284
+ parser.builder.emit_file_line_as_literals = false
285
+ parser.parse_with_comments(buffer)
263
286
  rescue Parser::SyntaxError => e
264
287
  split = source[0..e.diagnostic.location.begin_pos].split("\n")
265
288
  line, col = split.length, split.last.length
266
289
  message = "line #{line}, column #{col}: #{e.diagnostic.message}"
267
290
  message += "\n in file #{file}" if file
268
- raise Ruby2JS::SyntaxError.new(message)
291
+ raise Ruby2JS::SyntaxError.new(message, e.diagnostic)
269
292
  end
270
293
 
271
294
  def self.find_block(ast, line)
@@ -14,7 +14,7 @@ module Ruby2JS
14
14
 
15
15
  class Converter < Serializer
16
16
  attr_accessor :ast
17
-
17
+
18
18
  LOGICAL = :and, :not, :or
19
19
  OPERATORS = [:[], :[]=], [:not, :!], [:**], [:*, :/, :%], [:+, :-],
20
20
  [:>>, :<<], [:&], [:^, :|], [:<=, :<, :>, :>=], [:==, :!=, :===, :"!=="],
@@ -34,7 +34,7 @@ module Ruby2JS
34
34
 
35
35
  VASGN = [:cvasgn, :ivasgn, :gvasgn, :lvasgn]
36
36
 
37
- attr_accessor :binding, :ivars
37
+ attr_accessor :binding, :ivars, :namespace
38
38
 
39
39
  def initialize( ast, comments, vars = {} )
40
40
  super()
@@ -160,6 +160,10 @@ module Ruby2JS
160
160
  @eslevel >= 2021
161
161
  end
162
162
 
163
+ def es2022
164
+ @eslevel >= 2022
165
+ end
166
+
163
167
  @@handlers = []
164
168
  def self.handle(*types, &block)
165
169
  types.each do |type|
@@ -295,6 +299,7 @@ end
295
299
  require 'ruby2js/converter/arg'
296
300
  require 'ruby2js/converter/args'
297
301
  require 'ruby2js/converter/array'
302
+ require 'ruby2js/converter/assign'
298
303
  require 'ruby2js/converter/begin'
299
304
  require 'ruby2js/converter/block'
300
305
  require 'ruby2js/converter/blockpass'
@@ -314,6 +319,7 @@ require 'ruby2js/converter/dstr'
314
319
  require 'ruby2js/converter/fileline'
315
320
  require 'ruby2js/converter/for'
316
321
  require 'ruby2js/converter/hash'
322
+ require 'ruby2js/converter/hide'
317
323
  require 'ruby2js/converter/if'
318
324
  require 'ruby2js/converter/in'
319
325
  require 'ruby2js/converter/import'
@@ -0,0 +1,159 @@
1
+ # Kinda like Object.assign, except it handles properties
2
+ #
3
+ # Note: Object.defineProperties, Object.getOwnPropertyNames, etc. technically
4
+ # were not part of ES5, but were implemented by IE prior to ES6, and are
5
+ # the only way to implement getters and setters.
6
+
7
+ module Ruby2JS
8
+ class Converter
9
+
10
+ # (assign
11
+ # target
12
+ # (hash)
13
+ # ...
14
+
15
+ handle :assign do |target, *args|
16
+ collapsible = false
17
+
18
+ nonprop = proc do |node|
19
+ next false unless node.is_a? Parser::AST::Node
20
+ next false if node.type == :pair and node.children.first.type == :prop and es2015
21
+ next true unless node.type == :def
22
+ next false if node.children.first.to_s.end_with? '='
23
+ node.is_method?
24
+ end
25
+
26
+ collapsible = true if args.length == 1 and args.first.type == :hash and
27
+ args.first.children.length == 1
28
+
29
+ collapsible = true if args.length == 1 and args.first.type == :class_module and
30
+ args.first.children.length == 3 and nonprop[args.first.children.last]
31
+
32
+ if es2015 and not collapsible and args.all? {|arg|
33
+ case arg.type
34
+ when :pair, :hash, :class_module
35
+ arg.children.all? {|child| nonprop[child]}
36
+ when :const
37
+ false
38
+ else
39
+ true
40
+ end
41
+ }
42
+ parse s(:send, s(:const, nil, :Object), :assign, target, *args)
43
+ else
44
+
45
+ if target == s(:hash)
46
+ copy = [s(:gvasgn, :$$, target)]
47
+ target = s(:gvar, :$$)
48
+ shadow = [s(:shadowarg, :$$)]
49
+ elsif collapsible or es2015 or
50
+ (%i(send const).include? target.type and
51
+ target.children.length == 2 and target.children[0] == nil)
52
+ then
53
+ copy = []
54
+ shadow = []
55
+ else
56
+ copy = [s(:gvasgn, :$0, target)]
57
+ target = s(:gvar, :$0)
58
+ shadow = [s(:shadowarg, :$0)]
59
+ end
60
+
61
+ body = [*copy,
62
+ *args.map {|modname|
63
+ if modname.type == :hash and
64
+ modname.children.all? {|pair| pair.children.first.type == :prop}
65
+
66
+ if modname.children.length == 1
67
+ pair = modname.children.first
68
+ s(:send, s(:const, nil, :Object), :defineProperty, target,
69
+ s(:sym, pair.children.first.children.last),
70
+ s(:hash, *pair.children.last.map {|name, value| s(:pair,
71
+ s(:sym, name), value)}))
72
+ else
73
+ pair = modname.children.first
74
+ s(:send, s(:const, nil, :Object), :defineProperties, target,
75
+ s(:hash, *modname.children.map {|pair| s(:pair,
76
+ s(:sym, pair.children.first.children.last),
77
+ s(:hash, *pair.children.last.map {|name, value| s(:pair,
78
+ s(:sym, name), value)})
79
+ )}))
80
+ end
81
+
82
+ elsif modname.type == :hash and
83
+ modname.children.all? {|child| nonprop[child]}
84
+
85
+ s(:begin, *modname.children.map {|pair|
86
+ if pair.children.first.type == :prop
87
+ s(:send, s(:const, nil, :Object), :defineProperty, target,
88
+ s(:sym, pair.children.first.children.last),
89
+ s(:hash, *pair.children.last.map {|name, value| s(:pair,
90
+ s(:sym, name), value)}))
91
+ else
92
+ s(:send, target, :[]=, *pair.children)
93
+ end
94
+ })
95
+
96
+ elsif modname.type == :class_module and
97
+ modname.children[2..-1].all? {|child| nonprop[child]}
98
+
99
+ s(:begin, *modname.children[2..-1].map {|pair|
100
+ s(:send, target, :[]=, s(:sym, pair.children.first),
101
+ pair.updated(:defm, [nil, *pair.children[1..-1]]))
102
+ })
103
+
104
+ elsif modname.type == :lvar and not es2015
105
+ s(:for, s(:lvasgn, :$_), modname,
106
+ s(:send, target, :[]=,
107
+ s(:lvar, :$_), s(:send, modname, :[], s(:lvar, :$_))))
108
+
109
+ else
110
+ if es2017
111
+ s(:send, s(:const, nil, :Object), :defineProperties, target,
112
+ s(:send, s(:const, nil, :Object), :getOwnPropertyDescriptors, modname))
113
+ else
114
+ if modname.type == :lvar or (%i(send const).include? modname.type and
115
+ modname.children.length == 2 and modname.children[0] == nil)
116
+
117
+ object = modname
118
+ else
119
+ shadow += [s(:shadowarg, :$1)]
120
+ object = s(:gvar, :$1)
121
+ end
122
+
123
+ copy = s(:send,
124
+ s(:const, nil, :Object), :defineProperties, target,
125
+ s(:send,
126
+ s(:send, s(:const, nil, :Object), :getOwnPropertyNames, object),
127
+ :reduce,
128
+ s(:block,
129
+ s(:send, nil, :lambda),
130
+ s(:args, s(:arg, :$2), s(:arg, :$3)),
131
+ s(:begin,
132
+ s(:send,
133
+ s(:lvar, :$2), :[]=, s(:lvar, :$3),
134
+ s(:send, s(:const, nil, :Object), :getOwnPropertyDescriptor,
135
+ object, s(:lvar, :$3))),
136
+ s(:return, s(:lvar, :$2)))),
137
+ s(:hash)))
138
+
139
+
140
+ if object.type == :gvar
141
+ s(:begin, s(:gvasgn, object.children.last, modname), copy)
142
+ else
143
+ copy
144
+ end
145
+ end
146
+ end
147
+ }]
148
+
149
+ if @state == :statement and shadow.empty?
150
+ parse s(:begin, *body)
151
+ else
152
+ body.push s(:return, target) if @state == :expression
153
+ parse s(:send, s(:block, s(:send, nil, :lambda), s(:args, *shadow),
154
+ s(:begin, *body)), :[])
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
@@ -9,6 +9,11 @@ module Ruby2JS
9
9
  state = @state
10
10
  props = false
11
11
 
12
+ if state == :expression and statements.empty?
13
+ puts 'null'
14
+ return
15
+ end
16
+
12
17
  statements.map! do |statement|
13
18
  case statement and statement.type
14
19
  when :defs, :defp
@@ -32,9 +37,9 @@ module Ruby2JS
32
37
  end
33
38
 
34
39
  def combine_properties(body)
35
- for i in 0...body.length-1
40
+ (0...body.length-1).each do |i|
36
41
  next unless body[i] and body[i].type == :prop
37
- for j in i+1...body.length
42
+ (i+1...body.length).each do |j|
38
43
  break unless body[j] and body[j].type == :prop
39
44
 
40
45
  if body[i].children[0] == body[j].children[0]
@@ -10,10 +10,15 @@ module Ruby2JS
10
10
 
11
11
  handle :case do |expr, *whens, other|
12
12
  begin
13
+ if @state == :expression
14
+ parse s(:kwbegin, @ast), @state
15
+ return
16
+ end
17
+
13
18
  inner, @inner = @inner, @ast
14
19
 
15
20
  has_range = whens.any? do |node|
16
- node.children.any? {|child| [:irange, :erange].include? child.type}
21
+ node.children.any? {|child| [:irange, :erange].include? child&.type}
17
22
  end
18
23
 
19
24
  if has_range
@@ -47,7 +52,7 @@ module Ruby2JS
47
52
 
48
53
  parse code, :statement
49
54
  last = code
50
- while last.type == :begin
55
+ while last&.type == :begin
51
56
  last = last.children.last
52
57
  end
53
58
 
@@ -11,13 +11,27 @@ module Ruby2JS
11
11
  # NOTE: class_extend is not generated by the parser, but instead produced
12
12
  # when ++class is encountered; it signals that this construct is
13
13
  # meant to extend an already existing JavaScrpt class.
14
+ #
15
+ # class_hash is an anonymous class as a value in a hash; the
16
+ # name has already been output so should be ignored other than
17
+ # in determining the namespace.
18
+ #
19
+ # class_module is a module that to be re-processed by this handler
20
+ # given the similarity between the two structures.
14
21
 
15
- handle :class, :class_extend, :class_module do |name, inheritance, *body|
16
- if @ast.type != :class
22
+ handle :class, :class_hash, :class_extend, :class_module do |name, inheritance, *body|
23
+ extend = @namespace.enter(name) unless @ast.type == :class_module
24
+
25
+ if !%i(class class_hash).include?(@ast.type) or extend
17
26
  init = nil
18
27
  else
19
- if es2015
20
- parse @ast.updated(:class2)
28
+ if es2015 and not extend
29
+ if @ast.type == :class_hash
30
+ parse @ast.updated(:class2, [nil, *@ast.children[1..-1]])
31
+ else
32
+ parse @ast.updated(:class2)
33
+ end
34
+ @namespace.leave unless @ast.type == :class_module
21
35
  return
22
36
  end
23
37
 
@@ -35,7 +49,7 @@ module Ruby2JS
35
49
  end
36
50
 
37
51
  body.compact!
38
- visible = {}
52
+ visible = @namespace.getOwnProps
39
53
  body.map! do |m|
40
54
  if \
41
55
  @ast.type == :class_module and m.type == :defs and
@@ -45,7 +59,7 @@ module Ruby2JS
45
59
  end
46
60
 
47
61
  node = if m.type == :def
48
- if m.children.first == :initialize
62
+ if m.children.first == :initialize and !visible[:initialize]
49
63
  # constructor: remove from body and overwrite init function
50
64
  init = m
51
65
  nil
@@ -54,17 +68,20 @@ module Ruby2JS
54
68
  sym = :"#{m.children.first.to_s[0..-2]}"
55
69
  s(:prop, s(:attr, name, :prototype), sym =>
56
70
  {enumerable: s(:true), configurable: s(:true),
57
- set: s(:block, s(:send, nil, :proc), *m.children[1..-1])})
71
+ set: s(:defm, nil, *m.children[1..-1])})
58
72
  else
59
- visible[m.children[0]] = s(:self)
60
73
 
61
74
  if not m.is_method?
75
+ visible[m.children[0]] = s(:self)
76
+
62
77
  # property getter
63
78
  s(:prop, s(:attr, name, :prototype), m.children.first =>
64
79
  {enumerable: s(:true), configurable: s(:true),
65
- get: s(:block, s(:send, nil, :proc), m.children[1],
80
+ get: s(:defm, nil, m.children[1],
66
81
  m.updated(:autoreturn, m.children[2..-1]))})
67
82
  else
83
+ visible[m.children[0]] = s(:autobind, s(:self))
84
+
68
85
  # method: add to prototype
69
86
  s(:method, s(:attr, name, :prototype),
70
87
  :"#{m.children[0].to_s.chomp('!')}=",
@@ -90,7 +107,7 @@ module Ruby2JS
90
107
  else
91
108
  # class method definition: add to prototype
92
109
  s(:prototype, s(:send, name, "#{m.children[1]}=",
93
- s(:block, s(:send, nil, :proc), *m.children[2..-1])))
110
+ s(:defm, nil, *m.children[2..-1])))
94
111
  end
95
112
 
96
113
  elsif m.type == :send and m.children.first == nil
@@ -125,9 +142,10 @@ module Ruby2JS
125
142
  elsif m.children[1] == :include
126
143
  s(:send, s(:block, s(:send, nil, :lambda), s(:args),
127
144
  s(:begin, *m.children[2..-1].map {|modname|
128
- s(:for, s(:lvasgn, :$_), modname,
129
- s(:send, s(:attr, name, :prototype), :[]=,
130
- s(:lvar, :$_), s(:send, modname, :[], s(:lvar, :$_))))
145
+ @namespace.defineProps @namespace.find(modname)
146
+ s(:for, s(:lvasgn, :$_), modname,
147
+ s(:send, s(:attr, name, :prototype), :[]=,
148
+ s(:lvar, :$_), s(:send, modname, :[], s(:lvar, :$_))))
131
149
  })), :[])
132
150
  elsif [:private, :protected, :public].include? m.children[1]
133
151
  raise Error.new("class #{m.children[1]} is not supported", @ast)
@@ -160,17 +178,23 @@ module Ruby2JS
160
178
  s(:send, s(:attr, name, :prototype),
161
179
  "#{m.children[0].children.first}=",
162
180
  s(:attr, s(:attr, name, :prototype), m.children[1].children.first))
163
- elsif m.type == :class
181
+ elsif m.type == :class or m.type == :module
164
182
  innerclass_name = m.children.first
165
183
  if innerclass_name.children.first
166
184
  innerclass_name = innerclass_name.updated(nil,
167
- [s(:attr, innerclass_name.children[0], name),
185
+ [s(:attr, name, innerclass_name.children[0].children.last),
168
186
  innerclass_name.children[1]])
169
187
  else
170
188
  innerclass_name = innerclass_name.updated(nil,
171
189
  [name, innerclass_name.children[1]])
172
190
  end
173
191
  m.updated(nil, [innerclass_name, *m.children[1..-1]])
192
+ elsif @ast.type == :class_module
193
+ m
194
+ elsif m.type == :defineProps
195
+ @namespace.defineProps m.children.first
196
+ visible.merge! m.children.first
197
+ nil
174
198
  else
175
199
  raise Error.new("class #{ m.type } not supported", @ast)
176
200
  end
@@ -194,7 +218,7 @@ module Ruby2JS
194
218
  # merge property definitions
195
219
  combine_properties(body)
196
220
 
197
- if inheritance
221
+ if inheritance and (@ast.type != :class_extend and !extend)
198
222
  body.unshift s(:send, name, :prototype=,
199
223
  s(:send, s(:const, nil, :Object), :create,
200
224
  s(:attr, inheritance, :prototype))),
@@ -206,10 +230,14 @@ module Ruby2JS
206
230
  methods = 0
207
231
  start = 0
208
232
  body.each do |node|
209
- if [:method, :prop].include? node.type and
233
+ if (node.type == :method or (node.type == :prop and es2015)) and
210
234
  node.children[0].type == :attr and
211
235
  node.children[0].children[1] == :prototype
212
236
  methods += 1
237
+ elsif node.type == :class and @ast.type == :class_module and es2015
238
+ methods += 1 if node.children.first.children.first == name
239
+ elsif node.type == :module and @ast.type == :class_module
240
+ methods += 1 if node.children.first.children.first == name
213
241
  elsif methods == 0
214
242
  start += 1
215
243
  else
@@ -219,14 +247,22 @@ module Ruby2JS
219
247
 
220
248
  # collapse sequence to a single assignment
221
249
  if \
222
- @ast.type != :class_extend and
223
- (methods > 1 or (methods == 1 and body[start].type == :prop))
250
+ @ast.type == :class_module or methods > 1 or
251
+ body[start]&.type == :prop
224
252
  then
225
253
  pairs = body[start...start+methods].map do |node|
226
254
  if node.type == :method
227
255
  replacement = node.updated(:pair, [
228
256
  s(:str, node.children[1].to_s.chomp('=')),
229
257
  node.children[2]])
258
+ elsif node.type == :class and node.children.first.children.first == name
259
+ sym = node.children.first.children.last
260
+ replacement = s(:pair, s(:sym, sym),
261
+ s(:class_hash, s(:const, nil, sym), nil, node.children.last))
262
+ elsif node.type == :module and node.children.first.children.first == name
263
+ sym = node.children.first.children.last
264
+ replacement = s(:pair, s(:sym, sym),
265
+ s(:module_hash, s(:const, nil, sym), node.children.last))
230
266
  else
231
267
  replacement = node.children[1].map do |prop, descriptor|
232
268
  node.updated(:pair, [s(:prop, prop), descriptor])
@@ -244,12 +280,30 @@ module Ruby2JS
244
280
  end
245
281
 
246
282
  if @ast.type == :class_module
283
+ start = 0 if methods == 0
284
+ if name
285
+ body[start...start+methods] =
286
+ s(:casgn, *name.children, s(:hash, *pairs.flatten))
287
+ else
288
+ body[start...start+methods] = s(:hash, *pairs.flatten)
289
+ end
290
+ elsif @ast.type == :class_extend or extend
247
291
  body[start...start+methods] =
248
- s(:casgn, *name.children, s(:hash, *pairs.flatten))
292
+ s(:assign, body[start].children.first, s(:hash, *pairs.flatten))
249
293
  else
250
294
  body[start...start+methods] =
251
295
  s(:send, name, :prototype=, s(:hash, *pairs.flatten))
252
296
  end
297
+
298
+ elsif (@ast.type == :class_extend or extend) and methods > 1
299
+
300
+ pairs = body[start...start+methods].map do |node|
301
+ node.updated(:pair, [
302
+ s(:sym, node.children[1].to_s[0..-2]), node.children[2]])
303
+ end
304
+
305
+ body[start...start+methods] =
306
+ s(:assign, body[start].children.first, s(:hash, *pairs))
253
307
  end
254
308
  end
255
309
 
@@ -258,7 +312,7 @@ module Ruby2JS
258
312
  constructor = init.updated(:constructor, [name, *init.children[1..-1]])
259
313
  @comments[constructor] = @comments[init] unless @comments[init].empty?
260
314
 
261
- if @ast.type == :class_extend
315
+ if @ast.type == :class_extend or extend
262
316
  if es2015
263
317
  constructor = s(:masgn, s(:mlhs,
264
318
  s(:attr, s(:casgn, *name.children, constructor), :prototype)),
@@ -285,6 +339,7 @@ module Ruby2JS
285
339
 
286
340
  # add locally visible interfaces to rbstack. See send.rb, const.rb
287
341
  @rbstack.push visible
342
+ @rbstack.last.merge!(@namespace.find(inheritance)) if inheritance
288
343
 
289
344
  parse s(:begin, *body.compact), :statement
290
345
  ensure
@@ -292,6 +347,7 @@ module Ruby2JS
292
347
  @class_name = class_name
293
348
  @class_parent = class_parent
294
349
  @rbstack.pop
350
+ @namespace.leave unless @ast.type == :class_module
295
351
  end
296
352
  end
297
353