ruby2js 3.5.1 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
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 ') => '