ruby2js 3.0.15 → 3.3.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 +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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7acab4a28c4ec163da40b2f1847692df089ff593af8d735bf163cfe566ed7f5b
4
- data.tar.gz: f0f33e2c6beba7785d535ece64e4402cb5aac29fca9f9541087857905ba33d8f
3
+ metadata.gz: b2ad87139fac25c7b097f2ee32c59b141936bb8424cf93a4a20c2d9393495528
4
+ data.tar.gz: 4de6a0b69d4d496faae496b37c21c66caee4f3793b1241baf134b5a05c85c7f9
5
5
  SHA512:
6
- metadata.gz: d53db4145f632be93c234cf706b25e6c7c27a0d0c1a864543045e6d9a9672e9e9792281677da6a4b107a3eac0d173ca2cd151a0d0f91acf3171ecc5a84dd06c4
7
- data.tar.gz: 49364f4bb8cf3f05ea9d07664d800a86af81817b096a40c89ec8848a6f45fe62b755cc2e0c995ce49f300bb84a6caa1de4b5fd822b8ae403579e9244dde9e442
6
+ metadata.gz: 8e359e299801b09bc428a93fdf95d4ac1128fbb19266636034fad929d3fa3759f504e59bbd70eb923733b0656770309f845e23aeb0e4e24424ab0161f6fe528a
7
+ data.tar.gz: '0874df3f8606f65be7cbae4114c2d384938f7827391020ee0f85d0d99340e4e905010dbe7216ebab17f0adf72fa7793525c95a6e083273abc08cae3dd076d849'
data/README.md CHANGED
@@ -92,6 +92,18 @@ Enable strict support:
92
92
  puts Ruby2JS.convert('a=1', strict: true)
93
93
  ```
94
94
 
95
+ Emit strict equality comparisons:
96
+
97
+ ```ruby
98
+ puts Ruby2JS.convert('a==1', comparison: :identity)
99
+ ```
100
+
101
+ Emit nullish coalescing operators:
102
+
103
+ ```ruby
104
+ puts Ruby2JS.convert('a || 1', or: :nullish)
105
+ ```
106
+
95
107
  With [ExecJS](https://github.com/sstephenson/execjs):
96
108
  ```ruby
97
109
  require 'ruby2js/execjs'
@@ -220,9 +232,10 @@ the script.
220
232
  * `.clear` becomes `.length = 0`
221
233
  * `.delete` becomes `delete target[arg]`
222
234
  * `.downcase` becomes `.toLowerCase`
223
- * `.each` becomes `for (i in ...) {}`
224
- * `.each_key` becomes `Object.keys().forEach`
225
- * `.each_value` becomes `for (i in ...) {}`
235
+ * `.each` becomes `.forEach`
236
+ * `.each_key` becomes `for (i in ...) {}`
237
+ * `.each_pair` becomes `for (var key in item) {var value = item[key]; ...}`
238
+ * `.each_value` becomes `.forEach`
226
239
  * `.each_with_index` becomes `.forEach`
227
240
  * `.end_with?` becomes `.slice(-arg.length) == arg`
228
241
  * `.empty?` becomes `.length == 0`
@@ -232,10 +245,12 @@ the script.
232
245
  * `.gsub` becomes `replace(//g)`
233
246
  * `.include?` becomes `.indexOf() != -1`
234
247
  * `.inspect` becomes `JSON.stringify()`
235
- * `.keys` becomes `Object.keys()`
248
+ * `.keys()` becomes `Object.keys()`
236
249
  * `.last` becomes `[*.length-1]`
237
250
  * `.last(n)` becomes `.slice(*.length-1, *.length)`
251
+ * `.lstrip` becomes `.replace(/^\s+/, "")`
238
252
  * `.max` becomes `Math.max.apply(Math)`
253
+ * `.merge` becomes `Object.assign({}, ...)`
239
254
  * `.merge!` becomes `Object.assign()`
240
255
  * `.min` becomes `Math.min.apply(Math)`
241
256
  * `.nil?` becomes `== null`
@@ -243,12 +258,16 @@ the script.
243
258
  * `puts` becomes `console.log`
244
259
  * `.replace` becomes `.length = 0; ...push.apply(*)`
245
260
  * `.respond_to?` becomes `right in left`
261
+ * `.rstrip` becomes `.replace(/s+$/, "")`
246
262
  * `.scan` becomes `.match(//g)`
247
263
  * `.start_with?` becomes `.substring(0, arg.length) == arg`
248
264
  * `.upto(lim)` becomes `for (var i=num; i<=lim; i+=1)`
249
265
  * `.downto(lim)` becomes `for (var i=num; i>=lim; i-=1)`
250
266
  * `.step(lim, n).each` becomes `for (var i=num; i<=lim; i+=n)`
251
267
  * `.step(lim, -n).each` becomes `for (var i=num; i>=lim; i-=n)`
268
+ * `(0..a).to_a` becomes `Array.apply(null, {length: a}).map(Function.call, Number)`
269
+ * `(b..a).to_a` becomes `Array.apply(null, {length: (a-b+1)}).map(Function.call, Number).map(function (idx) { return idx+b })`
270
+ * `(b...a).to_a` becomes `Array.apply(null, {length: (a-b)}).map(Function.call, Number).map(function (idx) { return idx+b })`
252
271
  * `.strip` becomes `.trim`
253
272
  * `.sub` becomes `.replace`
254
273
  * `.to_f` becomes `parseFloat`
@@ -259,6 +278,7 @@ the script.
259
278
  * `[n...m]` becomes `.slice(n,m)`
260
279
  * `[n..m]` becomes `.slice(n,m+1)`
261
280
  * `[/r/, n]` becomes `.match(/r/)[n]`
281
+ * `[/r/, n]=` becomes `.replace(/r/, ...)`
262
282
  * `(1..2).each {|i| ...}` becomes `for (var i=1 i<=2; i+=1)`
263
283
  * `"string" * length` becomes `new Array(length + 1).join("string")`
264
284
  * `.sub!` and `.gsub!` become equivalent `x = x.replace` statements
@@ -276,13 +296,14 @@ the script.
276
296
  * New classes subclassed off of `Exception` will become subclassed off
277
297
  of `Error` instead; and default constructors will be provided
278
298
  * `loop do...end` will be replaced with `while (true) {...}`
299
+ * `raise Exception.new(...)` will be replaced with `throw new Error(...)`
279
300
 
280
301
  Additionally, there is one mapping that will only be done if explicitly
281
302
  <a href="https://github.com/rubys/ruby2js/blob/master/lib/ruby2js/filter.rb">included</a>:
282
303
 
283
304
  * `.class` becomes `.constructor`
284
305
 
285
- * <a id="node" * href="https://github.com/rubys/ruby2js/blob/master/spec/node_spec.rb">node</a>
306
+ * <a id="node" href="https://github.com/rubys/ruby2js/blob/master/spec/node_spec.rb">node</a>
286
307
 
287
308
  * `` `command` `` becomes `child_process.execSync("command", {encoding: "utf8"})`
288
309
  * `ARGV` becomes `process.argv.slice(2)`
@@ -511,6 +532,16 @@ the script.
511
532
  * maps `export default async proc` to `module.exports = async`
512
533
  * maps `export default` to `module.exports =`
513
534
 
535
+ * <a id="matchAll" href="https://github.com/rubys/ruby2js/blob/master/spec/matchAll">matchAll</a>
536
+
537
+ For ES level < 2020:
538
+
539
+ * maps `str.matchAll(pattern).forEach {}` to
540
+ `while (match = pattern.exec(str)) {}`
541
+
542
+ Note `pattern` must be a simple variable with a value of a regular
543
+ expression with the `g` flag set at runtime.
544
+
514
545
  [Wunderbar](https://github.com/rubys/wunderbar) includes additional demos:
515
546
 
516
547
  * [chat](https://github.com/rubys/wunderbar/blob/master/demo/chat.rb),
@@ -532,13 +563,16 @@ conversions are made:
532
563
  * `def f(a, (foo, *bar))` becomes `function f(a, [foo, ...bar])`
533
564
  * `def a(b=1)` becomes `function a(b=1)`
534
565
  * `def a(*b)` becomes `function a(...b)`
535
- * `.each_key` becomes `for (i of ...) {}`
566
+ * `.each_value` becomes `for (i of ...) {}`
536
567
  * `a(*b)` becomes `a(...b)`
537
568
  * `"#{a}"` becomes <code>\`${a}\`</code>
538
569
  * `lambda {|x| x}` becomes `(x) => {return x}`
539
570
  * `proc {|x| x}` becomes `(x) => {x}`
540
571
  * `a {|x|}` becomes `a((x) => {})`
541
572
  * `class Person; end` becomes `class Person {}`
573
+ * `(0...a).to_a` becomes `[...Array(a).keys()]`
574
+ * `(0..a).to_a` becomes `[...Array(a+1).keys()]`
575
+ * `(b..a).to_a` becomes `Array.from({length: (a-b+1)}, (_, idx) => idx+b)`
542
576
 
543
577
  ES2015 class support includes constructors, super, methods, class methods,
544
578
  instance methods, instance variables, class variables, getters, setters,
@@ -549,6 +583,9 @@ Additionally, the `functions` filter will provide the following conversion:
549
583
  * `Array(x)` becomes `Array.from(x)`
550
584
  * `.inject(n) {}` becomes `.reduce(() => {}, n)`
551
585
 
586
+ Finally, keyword arguments and optional keyword arguments will be mapped to
587
+ parameter detructuring.
588
+
552
589
  ES2016 support
553
590
  ---
554
591
 
@@ -562,9 +599,57 @@ ES2017 support
562
599
  ---
563
600
 
564
601
  When option `eslevel: 2017` is provided, the following additional
602
+ conversions are made by the `functions` filter:
603
+
604
+ * `.values()` becomes `Object.values()`
605
+ * `.entries()` becomes `Object.entries()`
606
+ * `.each_pair {}` becomes `for (let [key, value] of Object.entries()) {}'
607
+
608
+ ES2018 support
609
+ ---
610
+
611
+ When option `eslevel: 2018` is provided, the following additional
565
612
  conversion is made by the `functions` filter:
566
613
 
567
- * `.each_entry` becomes `Object.entries().forEach`
614
+ * `.merge` becomes `{...a, ...b}`
615
+
616
+ Additionally, rest arguments can now be used with keyword arguments and
617
+ optional keyword arguments.
618
+
619
+ ES2019 support
620
+ ---
621
+
622
+ When option `eslevel: 2019` is provided, the following additional
623
+ conversion is made by the `functions` filter:
624
+
625
+ * `.flatten` becomes `.flat(Infinity)`
626
+ * `.lstrip` becomes `.trimEnd
627
+ * `.rstrip` becomes `.trimStart
628
+ * `a.to_h` becomes `Object.fromEntries(a)`
629
+ * `Hash[a]` becomes `Object.fromEntries(a)`
630
+
631
+ Additionally, `rescue` without a variable will map to `catch` without a
632
+ variable.
633
+
634
+ ES2020 support
635
+ ---
636
+
637
+ When option `eslevel: 2020` is provided, the following additional
638
+ conversions are made:
639
+
640
+ * `@x` becomes `this.#x`
641
+ * `@@x` becomes `ClassName.#x`
642
+ * `a&.b` becomes `a?.b`
643
+ * `.scan` becomes `Array.from(str.matchAll(/.../g), s => s.slice(1))`
644
+
645
+ ES2021 support
646
+ ---
647
+
648
+ When option `eslevel: 2021` is provided, the following additional
649
+ conversions are made:
650
+
651
+ * `x ||= 1` becomes `x ||= 1`
652
+ * `x &&= 1` becomes `x &&= 1`
568
653
 
569
654
  Picking a Ruby to JS mapping tool
570
655
  ---
@@ -55,6 +55,7 @@ module Ruby2JS
55
55
  def initialize(comments)
56
56
  @comments = comments
57
57
  @ast = nil
58
+ @exclude_methods = []
58
59
  end
59
60
 
60
61
  def options=(options)
@@ -81,6 +82,22 @@ module Ruby2JS
81
82
  @options[:eslevel] >= 2017
82
83
  end
83
84
 
85
+ def es2018
86
+ @options[:eslevel] >= 2018
87
+ end
88
+
89
+ def es2019
90
+ @options[:eslevel] >= 2019
91
+ end
92
+
93
+ def es2020
94
+ @options[:eslevel] >= 2020
95
+ end
96
+
97
+ def es2021
98
+ @options[:eslevel] >= 2021
99
+ end
100
+
84
101
  def process(node)
85
102
  ast, @ast = @ast, node
86
103
  replacement = super
@@ -114,6 +131,9 @@ module Ruby2JS
114
131
  def on_sendw(node); on_send(node); end
115
132
  def on_undefined?(node); on_defined?(node); end
116
133
  def on_nil(node); end
134
+ def on_xnode(node); end
135
+ def on_export(node); end
136
+ def on_import(node); end
117
137
 
118
138
  # provide a method so filters can call 'super'
119
139
  def on_sym(node); node; end
@@ -143,7 +163,7 @@ module Ruby2JS
143
163
 
144
164
  if Proc === source
145
165
  file,line = source.source_location
146
- source = File.read(file.dup.untaint).untaint
166
+ source = IO.read(file)
147
167
  ast, comments = parse(source)
148
168
  comments = Parser::Source::Comment.associate(ast, comments) if ast
149
169
  ast = find_block( ast, line )
@@ -156,9 +176,12 @@ module Ruby2JS
156
176
  comments = Parser::Source::Comment.associate(ast, comments) if ast
157
177
  end
158
178
 
159
- filters = options[:filters] || Filter::DEFAULTS
179
+ filters = (options[:filters] || Filter::DEFAULTS)
160
180
 
161
181
  unless filters.empty?
182
+ filters.dup.each do |filter|
183
+ filters = filter.reorder(filters) if filter.respond_to? :reorder
184
+ end
162
185
 
163
186
  filter = Filter::Processor
164
187
  filters.reverse.each do |mod|
@@ -176,6 +199,8 @@ module Ruby2JS
176
199
  ruby2js.ivars = options[:ivars]
177
200
  ruby2js.eslevel = options[:eslevel]
178
201
  ruby2js.strict = options[:strict]
202
+ ruby2js.comparison = options[:comparison] || :equality
203
+ ruby2js.or = options[:or] || :logical
179
204
  if ruby2js.binding and not ruby2js.ivars
180
205
  ruby2js.ivars = ruby2js.binding.eval \
181
206
  'Hash[instance_variables.map {|var| [var, instance_variable_get(var)]}]'
@@ -201,7 +226,9 @@ module Ruby2JS
201
226
  buffer.source = source.encode('utf-8')
202
227
  parser = Parser::CurrentRuby.new
203
228
  parser.builder.emit_file_line_as_literals=false
204
- parser.parse_with_comments(buffer)
229
+ ast, comments = parser.parse_with_comments(buffer)
230
+ Parser::CurrentRuby.parse(source.encode('utf-8')) unless ast
231
+ [ast, comments]
205
232
  rescue Parser::SyntaxError => e
206
233
  split = source[0..e.diagnostic.location.begin_pos].split("\n")
207
234
  line, col = split.length, split.last.length
@@ -28,7 +28,7 @@ module Ruby2JS
28
28
  :=== => :'!=='
29
29
  }
30
30
 
31
- GROUP_OPERATORS = [:begin, :dstr, :dsym, :and, :or, :casgn]
31
+ GROUP_OPERATORS = [:begin, :dstr, :dsym, :and, :or, :casgn, :if]
32
32
 
33
33
  VASGN = [:cvasgn, :ivasgn, :gvasgn, :lvasgn]
34
34
 
@@ -57,9 +57,12 @@ module Ruby2JS
57
57
  @prototype = nil
58
58
  @class_parent = nil
59
59
  @class_name = nil
60
+ @jsx = false
60
61
 
61
62
  @eslevel = :es5
62
63
  @strict = false
64
+ @comparison = :equality
65
+ @or = :logical
63
66
  end
64
67
 
65
68
  def width=(width)
@@ -124,7 +127,7 @@ module Ruby2JS
124
127
  Parser::AST::Node.new(type, args)
125
128
  end
126
129
 
127
- attr_accessor :strict, :eslevel
130
+ attr_accessor :strict, :eslevel, :comparison, :or
128
131
 
129
132
  def es2015
130
133
  @eslevel >= 2015
@@ -138,6 +141,22 @@ module Ruby2JS
138
141
  @eslevel >= 2017
139
142
  end
140
143
 
144
+ def es2018
145
+ @eslevel >= 2018
146
+ end
147
+
148
+ def es2019
149
+ @eslevel >= 2019
150
+ end
151
+
152
+ def es2020
153
+ @eslevel >= 2020
154
+ end
155
+
156
+ def es2021
157
+ @eslevel >= 2021
158
+ end
159
+
141
160
  @@handlers = []
142
161
  def self.handle(*types, &block)
143
162
  types.each do |type|
@@ -226,7 +245,6 @@ module Ruby2JS
226
245
  if ast.loc and ast.loc.expression
227
246
  filename = ast.loc.expression.source_buffer.name
228
247
  if filename and not filename.empty?
229
- filename = filename.dup.untaint
230
248
  @timestamps[filename] ||= File.mtime(filename)
231
249
  end
232
250
  end
@@ -295,6 +313,7 @@ require 'ruby2js/converter/for'
295
313
  require 'ruby2js/converter/hash'
296
314
  require 'ruby2js/converter/if'
297
315
  require 'ruby2js/converter/in'
316
+ require 'ruby2js/converter/import'
298
317
  require 'ruby2js/converter/ivar'
299
318
  require 'ruby2js/converter/ivasgn'
300
319
  require 'ruby2js/converter/kwbegin'
@@ -321,3 +340,4 @@ require 'ruby2js/converter/vasgn'
321
340
  require 'ruby2js/converter/while'
322
341
  require 'ruby2js/converter/whilepost'
323
342
  require 'ruby2js/converter/xstr'
343
+ require 'ruby2js/converter/xnode'
@@ -7,11 +7,41 @@ module Ruby2JS
7
7
  # (blockarg :c))
8
8
 
9
9
  handle :args do |*args|
10
+ kwargs = []
11
+ while args.last and
12
+ [:kwarg, :kwoptarg, :kwrestarg].include? args.last.type
13
+ kwargs.unshift args.pop
14
+ end
15
+
16
+ if kwargs.length == 1 and kwargs.last.type == :kwrestarg
17
+ args.push s(:arg, *kwargs.last.children)
18
+ end
19
+
20
+ unless kwargs.empty? or es2015
21
+ raise NotImplementedError.new('Keyword args require ES2015')
22
+ end
23
+
10
24
  parse_all(*args, join: ', ')
25
+ if not kwargs.empty?
26
+ put ', ' unless args.empty?
27
+ put '{ '
28
+ kwargs.each_with_index do |kw, index|
29
+ put ', ' unless index == 0
30
+ if kw.type == :kwarg
31
+ put kw.children.first
32
+ elsif kw.type == :kwoptarg
33
+ put kw.children.first; put ' = '; parse kw.children.last
34
+ elsif kw.type == :kwrestarg
35
+ raise 'Rest arg requires ES2018' unless es2018
36
+ put '...'; put kw.children.first
37
+ end
38
+ end
39
+ put ' }'
40
+ end
11
41
  end
12
42
 
13
43
  handle :mlhs do |*args|
14
- if es2015
44
+ if es2015 or @jsx
15
45
  put '['
16
46
  parse_all(*args, join: ', ')
17
47
  put ']'
@@ -9,7 +9,7 @@ module Ruby2JS
9
9
 
10
10
  handle :block do |call, args, block|
11
11
 
12
- if
12
+ if \
13
13
  @state == :statement and args.children.length == 1 and
14
14
  call.children.first and call.children.first.type == :begin and
15
15
  call.children[1] == :step and
@@ -42,7 +42,7 @@ module Ruby2JS
42
42
  @vars = vars if es2015
43
43
  end
44
44
 
45
- elsif
45
+ elsif \
46
46
  call.children[0] == nil and call.children[1] == :function and
47
47
  call.children[2..-1].all? do |child|
48
48
  child.type == :lvar or (child.type == :send and
@@ -8,7 +8,9 @@ module Ruby2JS
8
8
  multi_assign_declarations if @state == :statement
9
9
 
10
10
  begin
11
- if @state == :statement
11
+ cbase ||= @rbstack.map {|rb| rb[var]}.compact.last
12
+
13
+ if @state == :statement and not cbase
12
14
  if es2015
13
15
  put "const "
14
16
  else
@@ -16,7 +18,6 @@ module Ruby2JS
16
18
  end
17
19
  end
18
20
 
19
- cbase ||= @rbstack.map {|rb| rb[var]}.compact.last
20
21
  (parse cbase; put '.') if cbase
21
22
 
22
23
  put "#{ var } = "; parse value
@@ -37,7 +37,7 @@ module Ruby2JS
37
37
  body.compact!
38
38
  visible = {}
39
39
  body.map! do |m|
40
- if
40
+ if \
41
41
  @ast.type == :class_module and m.type == :defs and
42
42
  m.children.first == s(:self)
43
43
  then
@@ -217,7 +217,7 @@ module Ruby2JS
217
217
  end
218
218
 
219
219
  # collapse sequence to a single assignment
220
- if
220
+ if \
221
221
  @ast.type != :class_extend and
222
222
  (methods > 1 or (methods == 1 and body[start].type == :prop))
223
223
  then