ruby2js 3.5.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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -662
  3. data/lib/ruby2js.rb +61 -10
  4. data/lib/ruby2js/converter.rb +10 -4
  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 +102 -31
  10. data/lib/ruby2js/converter/def.rb +7 -3
  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 +35 -4
  16. data/lib/ruby2js/converter/kwbegin.rb +9 -2
  17. data/lib/ruby2js/converter/literal.rb +14 -2
  18. data/lib/ruby2js/converter/module.rb +41 -4
  19. data/lib/ruby2js/converter/opasgn.rb +8 -0
  20. data/lib/ruby2js/converter/send.rb +45 -5
  21. data/lib/ruby2js/converter/vasgn.rb +5 -0
  22. data/lib/ruby2js/converter/xstr.rb +1 -1
  23. data/lib/ruby2js/demo.rb +53 -0
  24. data/lib/ruby2js/es2022.rb +5 -0
  25. data/lib/ruby2js/es2022/strict.rb +3 -0
  26. data/lib/ruby2js/filter.rb +9 -1
  27. data/lib/ruby2js/filter/active_functions.rb +44 -0
  28. data/lib/ruby2js/filter/camelCase.rb +4 -3
  29. data/lib/ruby2js/filter/cjs.rb +2 -0
  30. data/lib/ruby2js/filter/esm.rb +133 -7
  31. data/lib/ruby2js/filter/functions.rb +107 -98
  32. data/lib/ruby2js/filter/{wunderbar.rb → jsx.rb} +29 -7
  33. data/lib/ruby2js/filter/node.rb +95 -74
  34. data/lib/ruby2js/filter/nokogiri.rb +15 -41
  35. data/lib/ruby2js/filter/react.rb +191 -56
  36. data/lib/ruby2js/filter/require.rb +100 -5
  37. data/lib/ruby2js/filter/return.rb +15 -1
  38. data/lib/ruby2js/filter/securerandom.rb +33 -0
  39. data/lib/ruby2js/filter/stimulus.rb +185 -0
  40. data/lib/ruby2js/filter/vue.rb +9 -0
  41. data/lib/ruby2js/jsx.rb +291 -0
  42. data/lib/ruby2js/namespace.rb +75 -0
  43. data/lib/ruby2js/rails.rb +15 -9
  44. data/lib/ruby2js/serializer.rb +3 -1
  45. data/lib/ruby2js/version.rb +3 -3
  46. data/ruby2js.gemspec +1 -1
  47. metadata +14 -5
  48. data/lib/ruby2js/filter/esm_migration.rb +0 -72
  49. data/lib/ruby2js/filter/fast-deep-equal.rb +0 -23
@@ -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
 
@@ -9,11 +9,24 @@ module Ruby2JS
9
9
  # NOTE: this is the es2015 version of class
10
10
 
11
11
  handle :class2 do |name, inheritance, *body|
12
- if name.type == :const and name.children.first == nil
12
+ body.compact!
13
+ while body.length == 1 and body.first.type == :begin
14
+ body = body.first.children
15
+ end
16
+
17
+ proxied = body.find do |node|
18
+ node.type == :def and node.children.first == :method_missing
19
+ end
20
+
21
+ if not name
22
+ put 'class'
23
+ elsif name.type == :const and name.children.first == nil
13
24
  put 'class '
14
25
  parse name
26
+ put '$' if proxied
15
27
  else
16
28
  parse name
29
+ put '$' if proxied
17
30
  put ' = class'
18
31
  end
19
32
 
@@ -24,15 +37,11 @@ module Ruby2JS
24
37
 
25
38
  put " {"
26
39
 
27
- body.compact!
28
- while body.length == 1 and body.first.type == :begin
29
- body = body.first.children
30
- end
31
-
32
40
  begin
33
41
  class_name, @class_name = @class_name, name
34
42
  class_parent, @class_parent = @class_parent, inheritance
35
- @rbstack.push({})
43
+ @rbstack.push(@namespace.getOwnProps)
44
+ @rbstack.last.merge!(@namespace.find(inheritance)) if inheritance
36
45
  constructor = []
37
46
  index = 0
38
47
 
@@ -40,10 +49,17 @@ module Ruby2JS
40
49
  body.each do |m|
41
50
  if m.type == :def
42
51
  prop = m.children.first
43
- if prop == :initialize
52
+ if prop == :initialize and !@rbstack.last[:initialize]
44
53
  constructor = m.children[2..-1]
45
- elsif not prop.to_s.end_with? '='
46
- @rbstack.last[prop] = s(:self)
54
+ elsif prop.to_s.end_with? '='
55
+ @rbstack.last[prop.to_s[0..-2].to_sym] = s(:autobind, s(:self))
56
+ else
57
+ @rbstack.last[prop] = m.is_method? ? s(:autobind, s(:self)) : s(:self)
58
+ end
59
+ elsif m.type == :send and m.children[0..1] == [nil, :async]
60
+ if m.children[2].type == :def
61
+ prop = m.children[2].children.first
62
+ @rbstack.last[prop] = s(:autobind, s(:self))
47
63
  end
48
64
  end
49
65
  end
@@ -64,21 +80,21 @@ module Ruby2JS
64
80
  walk[child] if child.is_a? Parser::AST::Node
65
81
  end
66
82
 
67
- if ast.type == :send and ast.children.first == nil
68
- if ast.children[1] == :attr_accessor
69
- ast.children[2..-1].each_with_index do |child_sym, index2|
70
- ivars << :"@#{child_sym.children.first}"
71
- end
72
- elsif ast.children[1] == :attr_reader
73
- ast.children[2..-1].each_with_index do |child_sym, index2|
74
- ivars << :"@#{child_sym.children.first}"
75
- end
76
- elsif ast.children[1] == :attr_writer
77
- ast.children[2..-1].each_with_index do |child_sym, index2|
78
- ivars << :"@#{child_sym.children.first}"
79
- end
80
- end
81
- end
83
+ if ast.type == :send and ast.children.first == nil
84
+ if ast.children[1] == :attr_accessor
85
+ ast.children[2..-1].each_with_index do |child_sym, index2|
86
+ ivars << :"@#{child_sym.children.first}"
87
+ end
88
+ elsif ast.children[1] == :attr_reader
89
+ ast.children[2..-1].each_with_index do |child_sym, index2|
90
+ ivars << :"@#{child_sym.children.first}"
91
+ end
92
+ elsif ast.children[1] == :attr_writer
93
+ ast.children[2..-1].each_with_index do |child_sym, index2|
94
+ ivars << :"@#{child_sym.children.first}"
95
+ end
96
+ end
97
+ end
82
98
 
83
99
  end
84
100
  walk[@ast]
@@ -140,10 +156,10 @@ module Ruby2JS
140
156
  end
141
157
  end
142
158
 
143
- if m.type == :def || m.type == :async
159
+ if m.type == :def || m.type == :defm || m.type == :async
144
160
  @prop = m.children.first
145
161
 
146
- if @prop == :initialize
162
+ if @prop == :initialize and !@rbstack.last[:initialize]
147
163
  @prop = :constructor
148
164
 
149
165
  if constructor == [] or constructor == [(:super)]
@@ -228,13 +244,27 @@ module Ruby2JS
228
244
  else
229
245
  if m.children[1] == :include
230
246
  m = m.updated(:begin, m.children[2..-1].map {|mname|
231
- s(:send, s(:const, nil, :Object), :assign,
232
- s(:attr, name, :prototype), mname)})
247
+ @namespace.defineProps @namespace.find(mname)
248
+ s(:assign, s(:attr, name, :prototype), mname)
249
+ })
233
250
  end
234
251
 
235
252
  skipped = true
236
253
  end
237
254
 
255
+ elsif es2022 and \
256
+ m.type == :send and m.children.first.type == :self and \
257
+ m.children[1].to_s.end_with? '='
258
+
259
+ put 'static '
260
+ parse m.updated(:lvasgn, [m.children[1].to_s.sub('=', ''),
261
+ m.children[2]])
262
+
263
+ elsif m.type == :defineProps
264
+ skipped = true
265
+ @namespace.defineProps m.children.first
266
+ @rbstack.last.merge! m.children.first
267
+
238
268
  else
239
269
  if m.type == :cvasgn and !underscored_private
240
270
  put 'static #$'; put m.children[0].to_s[2..-1]; put ' = '
@@ -257,7 +287,7 @@ module Ruby2JS
257
287
  end
258
288
 
259
289
  if skipped
260
- post << [m, comments] if skipped
290
+ post << [m, comments] unless m.type == :defineProps
261
291
  else
262
292
  comments.reverse.each {|comment| insert location, comment}
263
293
  end
@@ -294,15 +324,56 @@ module Ruby2JS
294
324
  else
295
325
  parse m.updated(:send, [@class_name, *m.children[1..-1]])
296
326
  end
327
+ elsif m.type == :block and m.children.first.children.first == nil
328
+ # class method calls passing a block
329
+ parse s(:block, s(:send, name, *m.children.first.children[1..-1]),
330
+ *m.children[1..-1])
297
331
  else
298
332
  parse m, :statement
299
333
  end
300
334
  end
301
335
 
336
+ if proxied
337
+ put @sep
338
+
339
+ rename = name.updated(nil, [name.children.first, name.children.last.to_s + '$'])
340
+
341
+ if proxied.children[1].children.length == 1
342
+ # special case: if method_missing only has on argument, call it
343
+ # directly (i.e., don't pass arguments). This enables
344
+ # method_missing to return instance attributes (getters) as well
345
+ # as bound functions (methods).
346
+ forward = s(:send, s(:lvar, :obj), :method_missing, s(:lvar, :prop))
347
+ else
348
+ # normal case: return a function which, when called, will call
349
+ # method_missing with method name and arguments.
350
+ forward = s(:block, s(:send, nil, :proc), s(:args, s(:restarg, :args)),
351
+ s(:send, s(:lvar, :obj), :method_missing, s(:lvar, :prop),
352
+ s(:splat, s(:lvar, :args))))
353
+ end
354
+
355
+ proxy = s(:return, s(:send, s(:const, nil, :Proxy), :new,
356
+ s(:send, rename, :new, s(:splat, s(:lvar, :args))),
357
+ s(:hash, s(:pair, s(:sym, :get), s(:block, s(:send, nil, :proc),
358
+ s(:args, s(:arg, :obj), s(:arg, :prop)),
359
+ s(:if, s(:in?, s(:lvar, :prop), s(:lvar, :obj)),
360
+ s(:return, s(:send, s(:lvar, :obj), :[], s(:lvar, :prop))),
361
+ s(:return, forward))))))
362
+ )
363
+
364
+ if name.children.first == nil
365
+ proxy = s(:def, name.children.last, s(:args, s(:restarg, :args)), proxy)
366
+ else
367
+ proxy = s(:defs, *name.children, s(:args, s(:restarg, :args)), proxy)
368
+ end
369
+
370
+ parse proxy
371
+ end
372
+
302
373
  ensure
303
374
  @class_name = class_name
304
375
  @class_parent = class_parent
305
- @rbstack.pop
376
+ @namespace.defineProps @rbstack.pop
306
377
  end
307
378
  end
308
379
  end
@@ -6,7 +6,7 @@ module Ruby2JS
6
6
  # (arg :x)
7
7
  # (...)
8
8
 
9
- handle :def, :defm, :async do |name, args, body=nil|
9
+ handle :def, :defm, :async, :deff do |name, args, body=nil|
10
10
  body ||= s(:begin)
11
11
 
12
12
  add_implicit_block = false
@@ -106,7 +106,7 @@ module Ruby2JS
106
106
  # es2015 fat arrow support
107
107
  if \
108
108
  not name and es2015 and @state != :method and @ast.type != :defm and
109
- not @prop
109
+ @ast.type != :deff and not @prop
110
110
  then
111
111
  expr = body
112
112
  expr = expr.children.first while expr.type == :autoreturn
@@ -118,6 +118,10 @@ module Ruby2JS
118
118
  if EXPRESSIONS.include? expr.type
119
119
  if expr.type == :send and expr.children[0..1] == [nil, :raise]
120
120
  style = :statement
121
+ elsif expr.type == :send and expr.children.length == 2 and
122
+ expr.children.first == nil and @rbstack.last and
123
+ @rbstack.last[expr.children[1]]&.type == :autobind
124
+ style = :statement
121
125
  else
122
126
  style = :expression
123
127
  end
@@ -131,7 +135,7 @@ module Ruby2JS
131
135
  style = :statement
132
136
  end
133
137
 
134
- if args.children.length == 1 and style == :expression
138
+ if args.children.length == 1 and args.children.first.type != :restarg and style == :expression
135
139
  parse args; put ' => '
136
140
  else
137
141
  put '('; parse args; put ') => '