ruby2js 3.0.15 → 3.3.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 +92 -7
  3. data/lib/ruby2js.rb +30 -3
  4. data/lib/ruby2js/converter.rb +23 -3
  5. data/lib/ruby2js/converter/args.rb +31 -1
  6. data/lib/ruby2js/converter/block.rb +2 -2
  7. data/lib/ruby2js/converter/casgn.rb +3 -2
  8. data/lib/ruby2js/converter/class.rb +2 -2
  9. data/lib/ruby2js/converter/class2.rb +117 -13
  10. data/lib/ruby2js/converter/cvar.rb +5 -3
  11. data/lib/ruby2js/converter/cvasgn.rb +5 -3
  12. data/lib/ruby2js/converter/def.rb +2 -2
  13. data/lib/ruby2js/converter/for.rb +8 -1
  14. data/lib/ruby2js/converter/hash.rb +22 -6
  15. data/lib/ruby2js/converter/if.rb +13 -2
  16. data/lib/ruby2js/converter/import.rb +38 -0
  17. data/lib/ruby2js/converter/ivar.rb +2 -0
  18. data/lib/ruby2js/converter/ivasgn.rb +1 -1
  19. data/lib/ruby2js/converter/kwbegin.rb +16 -2
  20. data/lib/ruby2js/converter/logical.rb +46 -1
  21. data/lib/ruby2js/converter/opasgn.rb +5 -2
  22. data/lib/ruby2js/converter/regexp.rb +27 -2
  23. data/lib/ruby2js/converter/return.rb +1 -1
  24. data/lib/ruby2js/converter/send.rb +160 -69
  25. data/lib/ruby2js/converter/super.rb +15 -9
  26. data/lib/ruby2js/converter/xnode.rb +89 -0
  27. data/lib/ruby2js/es2018.rb +5 -0
  28. data/lib/ruby2js/es2018/strict.rb +3 -0
  29. data/lib/ruby2js/es2019.rb +5 -0
  30. data/lib/ruby2js/es2019/strict.rb +3 -0
  31. data/lib/ruby2js/es2020.rb +5 -0
  32. data/lib/ruby2js/es2020/strict.rb +3 -0
  33. data/lib/ruby2js/es2021.rb +5 -0
  34. data/lib/ruby2js/es2021/strict.rb +3 -0
  35. data/lib/ruby2js/filter.rb +1 -0
  36. data/lib/ruby2js/filter/cjs.rb +2 -2
  37. data/lib/ruby2js/filter/esm.rb +72 -0
  38. data/lib/ruby2js/filter/functions.rb +218 -34
  39. data/lib/ruby2js/filter/matchAll.rb +49 -0
  40. data/lib/ruby2js/filter/node.rb +18 -10
  41. data/lib/ruby2js/filter/nokogiri.rb +13 -13
  42. data/lib/ruby2js/filter/react.rb +190 -30
  43. data/lib/ruby2js/filter/require.rb +1 -4
  44. data/lib/ruby2js/filter/rubyjs.rb +4 -4
  45. data/lib/ruby2js/filter/vue.rb +45 -17
  46. data/lib/ruby2js/filter/wunderbar.rb +63 -0
  47. data/lib/ruby2js/serializer.rb +25 -11
  48. data/lib/ruby2js/version.rb +2 -2
  49. metadata +16 -4
@@ -12,7 +12,7 @@ module Ruby2JS
12
12
  var = s(:lvar, var.children.first) if var.type == :lvasgn
13
13
  var = s(:cvar, var.children.first) if var.type == :cvasgn
14
14
 
15
- if
15
+ if \
16
16
  [:+, :-].include?(op) and value.type==:int and
17
17
  (value.children==[1] or value.children==[-1])
18
18
  then
@@ -46,7 +46,10 @@ module Ruby2JS
46
46
  vtype = :ivar if asgn.type == :ivasgn
47
47
  vtype = :cvar if asgn.type == :cvasgn
48
48
 
49
- if vtype
49
+ if es2021
50
+ op = type == :and ? '&&' : (@or == :nullish ? '??' : '||')
51
+ parse s(:op_asgn, asgn, op, value);
52
+ elsif vtype
50
53
  parse s(asgn.type, asgn.children.first, s(type,
51
54
  s(vtype, asgn.children.first), value))
52
55
  elsif asgn.type == :send and asgn.children[1] == :[]
@@ -27,10 +27,35 @@ module Ruby2JS
27
27
  end
28
28
  end
29
29
 
30
+ # in Ruby regular expressions, ^ and $ apply to each line
30
31
  if parts.first.type == :str and parts.first.children[0].start_with?('^')
31
- opts = opts + [:m] unless opts.include? :m or opts.include? 'm'
32
+ if opts.include? :m or opts.include? 'm'
33
+ if parts.first.children[0].gsub(/\\./, '').gsub(/\[.*?\]/, '').include? '.'
34
+ opts = opts + [:s] unless opts.include? :s or opts.include? 's'
35
+ end
36
+ else
37
+ opts = opts + [:m]
38
+ end
32
39
  elsif parts.last.type == :str and parts.last.children[0].end_with?('$')
33
- opts = opts + [:m] unless opts.include? :m or opts.include? 'm'
40
+ if opts.include? :m or opts.include? 'm'
41
+ if parts.last.children[0].gsub(/\\./, '').gsub(/\[.*?\]/, '').include? '.'
42
+ opts = opts + [:s] unless opts.include? :s or opts.include? 's'
43
+ end
44
+ else
45
+ opts = opts + [:m]
46
+ end
47
+ end
48
+
49
+ # in Ruby regular expressions, /A is the start of the string
50
+ if parts.first.type == :str and parts.first.children[0].start_with?('\A')
51
+ parts = [s(:str, parts.first.children[0].sub('\A', '^'))] +
52
+ parts[1..-1]
53
+ end
54
+
55
+ # in Ruby regular expressions, /z is the end of the string
56
+ if parts.last.type == :str and parts.last.children[0].end_with?('\z')
57
+ parts = parts[0..-2] +
58
+ [s(:str, parts.first.children[0].sub('\z', '$'))]
34
59
  end
35
60
 
36
61
  # use slash syntax if there are few embedded slashes in the regexp
@@ -14,7 +14,7 @@ module Ruby2JS
14
14
 
15
15
  EXPRESSIONS = [ :array, :float, :hash, :int, :lvar, :nil, :send, :attr,
16
16
  :str, :sym, :dstr, :dsym, :cvar, :ivar, :zsuper, :super, :or, :and,
17
- :block, :const, :true, :false ]
17
+ :block, :const, :true, :false, :xnode ]
18
18
 
19
19
  handle :autoreturn do |*statements|
20
20
  return if statements == [nil]
@@ -16,12 +16,26 @@ module Ruby2JS
16
16
  handle :send, :sendw, :await, :attr, :call do |receiver, method, *args|
17
17
  ast = @ast
18
18
 
19
- if
20
- args.length == 1 and method == :+
21
- then
22
- node = collapse_strings(ast)
23
- return parse node if node != ast
24
- end
19
+ if \
20
+ args.length == 1 and method == :+
21
+ then
22
+ node = collapse_strings(ast)
23
+ return parse node if node != ast
24
+ end
25
+
26
+ # :irange support
27
+ # - currently only .to_a
28
+ if \
29
+ receiver and
30
+ receiver.type == :begin and
31
+ [:irange, :erange].include? receiver.children.first.type
32
+ then
33
+ unless method == :to_a
34
+ raise Error.new("#{receiver.children.first.type} can only be converted to array currently", receiver.children.first)
35
+ else
36
+ return range_to_array(receiver.children.first)
37
+ end
38
+ end
25
39
 
26
40
  # strip '!' and '?' decorations
27
41
  method = method.to_s[0..-2] if method =~ /\w[!?]$/
@@ -29,6 +43,7 @@ module Ruby2JS
29
43
  # three ways to define anonymous functions
30
44
  if method == :new and receiver and receiver.children == [nil, :Proc]
31
45
  return parse args.first, @state
46
+
32
47
  elsif not receiver and [:lambda, :proc].include? method
33
48
  if method == :lambda
34
49
  return parse s(args.first.type, *args.first.children[0..-2],
@@ -39,7 +54,7 @@ module Ruby2JS
39
54
  end
40
55
 
41
56
  # call anonymous function
42
- if [:call, :[]].include? method and receiver and receiver.type == :block
57
+ if [:call, :[]].include? method and receiver and receiver.type == :block
43
58
  t2,m2,*args2 = receiver.children.first.children
44
59
  if not t2 and [:lambda, :proc].include? m2 and args2.length == 0
45
60
  (es2015 || @state == :statement ? group(receiver) : parse(receiver))
@@ -72,7 +87,7 @@ module Ruby2JS
72
87
  # async proc {|x| ... }
73
88
  return parse block.updated(:async, [nil, *block.children[1..-1]])
74
89
 
75
- elsif
90
+ elsif \
76
91
  block.children[0].children[1] == :new and
77
92
  block.children[0].children[0] == s(:const, nil, :Proc)
78
93
  then
@@ -97,7 +112,7 @@ module Ruby2JS
97
112
 
98
113
  op_index = operator_index method
99
114
  if op_index != -1
100
- target = args.first
115
+ target = args.first
101
116
  end
102
117
 
103
118
  # resolve anonymous receivers against rbstack
@@ -109,7 +124,7 @@ module Ruby2JS
109
124
  group_receiver ||= GROUP_OPERATORS.include? receiver.type
110
125
  group_receiver = false if receiver.children[1] == :[]
111
126
  if receiver.type == :int and !OPERATORS.flatten.include?(method)
112
- group_receiver = true
127
+ group_receiver = true
113
128
  end
114
129
  if not receiver.is_method? and receiver.children.last == :new
115
130
  group_receiver = true
@@ -117,7 +132,7 @@ module Ruby2JS
117
132
  end
118
133
 
119
134
  if target
120
- group_target = target.type == :send &&
135
+ group_target = target.type == :send &&
121
136
  op_index < operator_index( target.children[1] )
122
137
  group_target ||= GROUP_OPERATORS.include? target.type
123
138
  end
@@ -127,7 +142,7 @@ module Ruby2JS
127
142
 
128
143
  elsif method == :[]
129
144
  (group_receiver ? group(receiver) : parse(receiver))
130
- if
145
+ if \
131
146
  args.length == 1 and [:str, :sym].include? args.first.type and
132
147
  args.first.children.first.to_s =~ /^[a-zA-Z]\w*$/
133
148
  then
@@ -138,7 +153,7 @@ module Ruby2JS
138
153
 
139
154
  elsif method == :[]=
140
155
  parse receiver
141
- if
156
+ if \
142
157
  args.length == 2 and [:str, :sym].include? args.first.type and
143
158
  args.first.children.first.to_s =~ /^[a-zA-Z]\w*$/
144
159
  then
@@ -156,7 +171,7 @@ module Ruby2JS
156
171
  put ')'
157
172
 
158
173
  elsif [:-@, :+@, :~, '~'].include? method
159
- if
174
+ if \
160
175
  receiver.type == :send and
161
176
  receiver.children[1] == :+@ and
162
177
  Parser::AST::Node === receiver.children[0] and
@@ -182,7 +197,13 @@ module Ruby2JS
182
197
 
183
198
  elsif OPERATORS.flatten.include?(method) and not LOGICAL.include?(method)
184
199
  (group_receiver ? group(receiver) : parse(receiver))
185
- put " #{ method } "
200
+
201
+ if @comparison == :identity and [:==, :!=].include? method
202
+ put " #{ method }= "
203
+ else
204
+ put " #{ method } "
205
+ end
206
+
186
207
  (group_target ? group(target) : parse(target))
187
208
 
188
209
  elsif method =~ /=$/
@@ -235,7 +256,7 @@ module Ruby2JS
235
256
  elsif args.length == 1 and args.first.type == :const
236
257
  # accommodation for JavaScript like new syntax w/o argument list
237
258
  parse s(:attr, args.first, :new), @state
238
- elsif
259
+ elsif \
239
260
  args.length == 2 and [:send, :const].include? args.first.type and
240
261
  args.last.type == :def and args.last.children.first == nil
241
262
  then
@@ -269,7 +290,7 @@ module Ruby2JS
269
290
  parse ast.updated(:lvasgn, [method]), @state
270
291
  end
271
292
  elsif args.any? {|arg| arg.type == :splat} and not es2015
272
- parse s(:send, s(:attr, receiver, method), :apply,
293
+ parse s(:send, s(:attr, receiver, method), :apply,
273
294
  (receiver || s(:nil)), s(:array, *args))
274
295
  else
275
296
  (group_receiver ? group(receiver) : parse(receiver))
@@ -285,27 +306,46 @@ module Ruby2JS
285
306
  end
286
307
 
287
308
  handle :csend do |receiver, method, *args|
288
- node = @ast
309
+ if es2020
289
310
 
290
- # collect up chain of conditional sends
291
- stack = []
292
- while node.children.first.type == :csend
293
- stack << node
294
- node = node.children.first
295
- end
311
+ # optional chaining
312
+ parse receiver
313
+ put "?."
314
+ if method == :[]
315
+ put '['
316
+ args.each {|arg| parse arg}
317
+ put ']'
318
+ else
319
+ put method.to_s
320
+ put '(' if @ast.is_method?
321
+ args.each {|arg| parse arg}
322
+ put ')' if @ast.is_method?
323
+ end
296
324
 
297
- # conditionally evaluate most nested expression
298
- expr = node.updated(:send)
299
- result = s(:and, node.children.first, expr)
325
+ else
300
326
 
301
- # build up chain of conditional evaluations
302
- until stack.empty?
303
- node = stack.pop
304
- expr = node.updated(:send, [expr, *node.children[1..-1]])
305
- result = s(:and, result, expr)
306
- end
327
+ node = @ast
328
+
329
+ # collect up chain of conditional sends
330
+ stack = []
331
+ while node.children.first.type == :csend
332
+ stack << node
333
+ node = node.children.first
334
+ end
307
335
 
308
- parse result
336
+ # conditionally evaluate most nested expression
337
+ expr = node.updated(:send)
338
+ result = s(:and, node.children.first, expr)
339
+
340
+ # build up chain of conditional evaluations
341
+ until stack.empty?
342
+ node = stack.pop
343
+ expr = node.updated(:send, [expr, *node.children[1..-1]])
344
+ result = s(:and, result, expr)
345
+ end
346
+
347
+ parse result
348
+ end
309
349
  end
310
350
 
311
351
  handle :splat do |expr|
@@ -313,47 +353,98 @@ module Ruby2JS
313
353
  parse expr
314
354
  end
315
355
 
316
- # do string concatenation when possible
317
- def collapse_strings(node)
318
- left = node.children[0]
319
- return node unless left
320
- right = node.children[2]
321
-
322
- # recursively evaluate left hand side
323
- if
324
- left.type == :send and left.children.length == 3 and
325
- left.children[1] == :+
326
- then
327
- left = collapse_strings(left)
328
- end
356
+ # do string concatenation when possible
357
+ def collapse_strings(node)
358
+ left = node.children[0]
359
+ return node unless left
360
+ right = node.children[2]
361
+
362
+ # recursively evaluate left hand side
363
+ if \
364
+ left.type == :send and left.children.length == 3 and
365
+ left.children[1] == :+
366
+ then
367
+ left = collapse_strings(left)
368
+ end
329
369
 
330
- # recursively evaluate right hand side
331
- if
332
- right.type == :send and right.children.length == 3 and
333
- right.children[1] == :+
334
- then
335
- right = collapse_strings(right)
336
- end
370
+ # recursively evaluate right hand side
371
+ if \
372
+ right.type == :send and right.children.length == 3 and
373
+ right.children[1] == :+
374
+ then
375
+ right = collapse_strings(right)
376
+ end
337
377
 
338
- # if left and right are both strings, perform concatenation
339
- if [:dstr, :str].include? left.type and [:dstr, :str].include? right.type
340
- if left.type == :str and right.type == :str
341
- return left.updated nil,
342
- [left.children.first + right.children.first]
378
+ # if left and right are both strings, perform concatenation
379
+ if [:dstr, :str].include? left.type and [:dstr, :str].include? right.type
380
+ if left.type == :str and right.type == :str
381
+ return left.updated nil,
382
+ [left.children.first + right.children.first]
383
+ else
384
+ left = s(:dstr, left) if left.type == :str
385
+ right = s(:dstr, right) if right.type == :str
386
+ return left.updated(nil, left.children + right.children)
387
+ end
388
+ end
389
+
390
+ # if left and right are unchanged, return original node; otherwise
391
+ # return node modified to include new left and/or right hand sides.
392
+ if left == node.children[0] and right == node.children[2]
393
+ return node
343
394
  else
344
- left = s(:dstr, left) if left.type == :str
345
- right = s(:dstr, right) if right.type == :str
346
- return left.updated(nil, left.children + right.children)
395
+ return node.updated(nil, [left, :+, right])
347
396
  end
348
397
  end
349
398
 
350
- # if left and right are unchanged, return original node; otherwise
351
- # return node modified to include new left and/or right hand sides.
352
- if left == node.children[0] and right == node.children[2]
353
- return node
354
- else
355
- return node.updated(nil, [left, :+, right])
399
+ def range_to_array(node)
400
+ start, finish = node.children
401
+ if start.type == :int and start.children.first == 0
402
+ # Ranges which start from 0 can be achieved with more simpler code
403
+ if finish.type == :int
404
+ # output cleaner code if we know the value already
405
+ length = finish.children.first + (node.type == :irange ? 1 : 0)
406
+ else
407
+ # If this is variable we need to fix indexing by 1 in js
408
+ length = "#{finish.children.last}" + (node.type == :irange ? "+1" : "")
409
+ end
410
+
411
+ if es2015
412
+ return put "[...Array(#{length}).keys()]"
413
+ else
414
+ return put "Array.apply(null, {length: #{length}}).map(Function.call, Number)"
415
+ end
416
+ else
417
+ # Use .compact because the first argument is nil with variables
418
+ # This way the first value is always set
419
+ start_value = start.children.compact.first
420
+ finish_value = finish.children.compact.first
421
+ if start.type == :int and finish.type == :int
422
+ length = finish_value - start_value + (node.type == :irange ? 1 : 0)
423
+ else
424
+ length = "(#{finish_value}-#{start_value}" + (node.type == :irange ? "+1" : "") + ")"
425
+ end
426
+
427
+ # Avoid of using same variables in the map as used in the irange or elsewhere in this code
428
+ # Ruby2js only allows dollar sign in beginning of variable so i$ is safe
429
+ if @vars.include? :idx or start_value == :idx or finish_value == :idx
430
+ index_var = 'i$'
431
+ else
432
+ index_var = 'idx'
433
+ end
434
+
435
+ if es2015
436
+ # Use _ because it's normal convention in JS for variable which is not used at all
437
+ if @vars.include? :_ or start_value == :_ or finish_value == :_
438
+ blank = '_$'
439
+ else
440
+ blank = '_'
441
+ end
442
+
443
+ return put "Array.from({length: #{length}}, (#{blank}, #{index_var}) => #{index_var}+#{start_value})"
444
+ else
445
+ return put "Array.apply(null, {length: #{length}}).map(Function.call, Number).map(function (#{index_var}) { return #{index_var}+#{start_value} })"
446
+ end
447
+ end
356
448
  end
357
449
  end
358
- end
359
450
  end
@@ -6,27 +6,33 @@ module Ruby2JS
6
6
  # (super ...)
7
7
 
8
8
  handle :super, :zsuper do |*args|
9
- unless @instance_method and @class_parent
9
+ method = @instance_method || @class_method
10
+
11
+ unless method and @class_parent
10
12
  raise Error.new("super outside of a method", @ast)
11
13
  end
12
14
 
13
15
  # what to pass
14
16
  if @ast.type == :zsuper
15
- if @instance_method.type == :method
16
- args = @instance_method.children[2].children[1].children
17
- elsif @instance_method.type == :prop
17
+ if method.type == :method
18
+ args = method.children[2].children[1].children
19
+ elsif method.type == :prop
18
20
  args = nil
19
21
  else
20
- args = @instance_method.children[1].children
22
+ args = method.children[1].children
21
23
  end
22
24
  end
23
25
 
24
26
  if es2015
25
- if @instance_method.children[0] == :constructor
27
+ if @class_method
28
+ parse @class_parent
29
+ put '.'
30
+ put method.children[0]
31
+ elsif method.children[0] == :constructor
26
32
  put 'super'
27
33
  else
28
34
  put 'super.'
29
- put @instance_method.children[0]
35
+ put method.children[0]
30
36
  end
31
37
 
32
38
  put '('
@@ -36,8 +42,8 @@ module Ruby2JS
36
42
  parse @class_parent
37
43
 
38
44
  # what to call
39
- if @instance_method.type != :constructor
40
- puts ".prototype.#{ @instance_method.children[1].to_s.chomp('=') }"
45
+ if method.type != :constructor
46
+ puts ".prototype.#{ method.children[1].to_s.chomp('=') }"
41
47
  end
42
48
 
43
49
  if args