ruby2js 3.2.0 → 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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +44 -4
  3. data/lib/ruby2js.rb +14 -2
  4. data/lib/ruby2js/converter.rb +11 -3
  5. data/lib/ruby2js/converter/args.rb +1 -1
  6. data/lib/ruby2js/converter/block.rb +2 -2
  7. data/lib/ruby2js/converter/class.rb +2 -2
  8. data/lib/ruby2js/converter/class2.rb +38 -9
  9. data/lib/ruby2js/converter/cvar.rb +1 -1
  10. data/lib/ruby2js/converter/cvasgn.rb +1 -1
  11. data/lib/ruby2js/converter/def.rb +2 -2
  12. data/lib/ruby2js/converter/for.rb +7 -0
  13. data/lib/ruby2js/converter/hash.rb +3 -3
  14. data/lib/ruby2js/converter/if.rb +13 -2
  15. data/lib/ruby2js/converter/import.rb +38 -0
  16. data/lib/ruby2js/converter/logical.rb +46 -1
  17. data/lib/ruby2js/converter/opasgn.rb +5 -2
  18. data/lib/ruby2js/converter/regexp.rb +27 -2
  19. data/lib/ruby2js/converter/return.rb +1 -1
  20. data/lib/ruby2js/converter/send.rb +53 -27
  21. data/lib/ruby2js/converter/super.rb +15 -9
  22. data/lib/ruby2js/converter/xnode.rb +89 -0
  23. data/lib/ruby2js/es2021.rb +5 -0
  24. data/lib/ruby2js/es2021/strict.rb +3 -0
  25. data/lib/ruby2js/filter/cjs.rb +2 -2
  26. data/lib/ruby2js/filter/esm.rb +72 -0
  27. data/lib/ruby2js/filter/functions.rb +142 -26
  28. data/lib/ruby2js/filter/matchAll.rb +49 -0
  29. data/lib/ruby2js/filter/node.rb +18 -10
  30. data/lib/ruby2js/filter/nokogiri.rb +13 -13
  31. data/lib/ruby2js/filter/react.rb +190 -30
  32. data/lib/ruby2js/filter/require.rb +1 -4
  33. data/lib/ruby2js/filter/rubyjs.rb +4 -4
  34. data/lib/ruby2js/filter/vue.rb +15 -15
  35. data/lib/ruby2js/filter/wunderbar.rb +63 -0
  36. data/lib/ruby2js/serializer.rb +25 -11
  37. data/lib/ruby2js/version.rb +1 -1
  38. metadata +10 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c2414f75bafbcf153ced6b3834b19513f4589d0745ca508eb2169fdc6c190c2f
4
- data.tar.gz: b8847a24e1b7f96a5602c84034623c2f3847c17458b521d949fcbd5b9cb9c2b8
3
+ metadata.gz: b2ad87139fac25c7b097f2ee32c59b141936bb8424cf93a4a20c2d9393495528
4
+ data.tar.gz: 4de6a0b69d4d496faae496b37c21c66caee4f3793b1241baf134b5a05c85c7f9
5
5
  SHA512:
6
- metadata.gz: 736f8322e18bad8b0e6b70a6c41337f0db73499193ae0b7137e703bd9093ff8ff30a91610d0db93f6a8b3e9f9df017f42025cb28799fc80d72fe10ddd07a2eb1
7
- data.tar.gz: 3faea6f125e9ec36c292be5afda1bf9de5a79c41e1ff8b1c46715a3508d06f977e0eae91cdb543e49899e416ce04d8aa01785244067709b75b50a6fb58da90f1
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'
@@ -233,9 +245,10 @@ the script.
233
245
  * `.gsub` becomes `replace(//g)`
234
246
  * `.include?` becomes `.indexOf() != -1`
235
247
  * `.inspect` becomes `JSON.stringify()`
236
- * `.keys` becomes `Object.keys()`
248
+ * `.keys()` becomes `Object.keys()`
237
249
  * `.last` becomes `[*.length-1]`
238
250
  * `.last(n)` becomes `.slice(*.length-1, *.length)`
251
+ * `.lstrip` becomes `.replace(/^\s+/, "")`
239
252
  * `.max` becomes `Math.max.apply(Math)`
240
253
  * `.merge` becomes `Object.assign({}, ...)`
241
254
  * `.merge!` becomes `Object.assign()`
@@ -245,6 +258,7 @@ the script.
245
258
  * `puts` becomes `console.log`
246
259
  * `.replace` becomes `.length = 0; ...push.apply(*)`
247
260
  * `.respond_to?` becomes `right in left`
261
+ * `.rstrip` becomes `.replace(/s+$/, "")`
248
262
  * `.scan` becomes `.match(//g)`
249
263
  * `.start_with?` becomes `.substring(0, arg.length) == arg`
250
264
  * `.upto(lim)` becomes `for (var i=num; i<=lim; i+=1)`
@@ -264,6 +278,7 @@ the script.
264
278
  * `[n...m]` becomes `.slice(n,m)`
265
279
  * `[n..m]` becomes `.slice(n,m+1)`
266
280
  * `[/r/, n]` becomes `.match(/r/)[n]`
281
+ * `[/r/, n]=` becomes `.replace(/r/, ...)`
267
282
  * `(1..2).each {|i| ...}` becomes `for (var i=1 i<=2; i+=1)`
268
283
  * `"string" * length` becomes `new Array(length + 1).join("string")`
269
284
  * `.sub!` and `.gsub!` become equivalent `x = x.replace` statements
@@ -281,6 +296,7 @@ the script.
281
296
  * New classes subclassed off of `Exception` will become subclassed off
282
297
  of `Error` instead; and default constructors will be provided
283
298
  * `loop do...end` will be replaced with `while (true) {...}`
299
+ * `raise Exception.new(...)` will be replaced with `throw new Error(...)`
284
300
 
285
301
  Additionally, there is one mapping that will only be done if explicitly
286
302
  <a href="https://github.com/rubys/ruby2js/blob/master/lib/ruby2js/filter.rb">included</a>:
@@ -516,6 +532,16 @@ the script.
516
532
  * maps `export default async proc` to `module.exports = async`
517
533
  * maps `export default` to `module.exports =`
518
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
+
519
545
  [Wunderbar](https://github.com/rubys/wunderbar) includes additional demos:
520
546
 
521
547
  * [chat](https://github.com/rubys/wunderbar/blob/master/demo/chat.rb),
@@ -573,9 +599,11 @@ ES2017 support
573
599
  ---
574
600
 
575
601
  When option `eslevel: 2017` is provided, the following additional
576
- conversion is made by the `functions` filter:
602
+ conversions are made by the `functions` filter:
577
603
 
578
- * `.each_pair` becomes `for (let [key, value] of Object.entries()) {}'
604
+ * `.values()` becomes `Object.values()`
605
+ * `.entries()` becomes `Object.entries()`
606
+ * `.each_pair {}` becomes `for (let [key, value] of Object.entries()) {}'
579
607
 
580
608
  ES2018 support
581
609
  ---
@@ -598,6 +626,7 @@ conversion is made by the `functions` filter:
598
626
  * `.lstrip` becomes `.trimEnd
599
627
  * `.rstrip` becomes `.trimStart
600
628
  * `a.to_h` becomes `Object.fromEntries(a)`
629
+ * `Hash[a]` becomes `Object.fromEntries(a)`
601
630
 
602
631
  Additionally, `rescue` without a variable will map to `catch` without a
603
632
  variable.
@@ -606,10 +635,21 @@ ES2020 support
606
635
  ---
607
636
 
608
637
  When option `eslevel: 2020` is provided, the following additional
609
- conversion is made:
638
+ conversions are made:
610
639
 
611
640
  * `@x` becomes `this.#x`
612
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`
613
653
 
614
654
  Picking a Ruby to JS mapping tool
615
655
  ---
@@ -94,6 +94,10 @@ module Ruby2JS
94
94
  @options[:eslevel] >= 2020
95
95
  end
96
96
 
97
+ def es2021
98
+ @options[:eslevel] >= 2021
99
+ end
100
+
97
101
  def process(node)
98
102
  ast, @ast = @ast, node
99
103
  replacement = super
@@ -127,6 +131,9 @@ module Ruby2JS
127
131
  def on_sendw(node); on_send(node); end
128
132
  def on_undefined?(node); on_defined?(node); end
129
133
  def on_nil(node); end
134
+ def on_xnode(node); end
135
+ def on_export(node); end
136
+ def on_import(node); end
130
137
 
131
138
  # provide a method so filters can call 'super'
132
139
  def on_sym(node); node; end
@@ -156,7 +163,7 @@ module Ruby2JS
156
163
 
157
164
  if Proc === source
158
165
  file,line = source.source_location
159
- source = File.read(file.dup.untaint).untaint
166
+ source = IO.read(file)
160
167
  ast, comments = parse(source)
161
168
  comments = Parser::Source::Comment.associate(ast, comments) if ast
162
169
  ast = find_block( ast, line )
@@ -169,9 +176,12 @@ module Ruby2JS
169
176
  comments = Parser::Source::Comment.associate(ast, comments) if ast
170
177
  end
171
178
 
172
- filters = options[:filters] || Filter::DEFAULTS
179
+ filters = (options[:filters] || Filter::DEFAULTS)
173
180
 
174
181
  unless filters.empty?
182
+ filters.dup.each do |filter|
183
+ filters = filter.reorder(filters) if filter.respond_to? :reorder
184
+ end
175
185
 
176
186
  filter = Filter::Processor
177
187
  filters.reverse.each do |mod|
@@ -189,6 +199,8 @@ module Ruby2JS
189
199
  ruby2js.ivars = options[:ivars]
190
200
  ruby2js.eslevel = options[:eslevel]
191
201
  ruby2js.strict = options[:strict]
202
+ ruby2js.comparison = options[:comparison] || :equality
203
+ ruby2js.or = options[:or] || :logical
192
204
  if ruby2js.binding and not ruby2js.ivars
193
205
  ruby2js.ivars = ruby2js.binding.eval \
194
206
  'Hash[instance_variables.map {|var| [var, instance_variable_get(var)]}]'
@@ -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
@@ -150,6 +153,10 @@ module Ruby2JS
150
153
  @eslevel >= 2020
151
154
  end
152
155
 
156
+ def es2021
157
+ @eslevel >= 2021
158
+ end
159
+
153
160
  @@handlers = []
154
161
  def self.handle(*types, &block)
155
162
  types.each do |type|
@@ -238,7 +245,6 @@ module Ruby2JS
238
245
  if ast.loc and ast.loc.expression
239
246
  filename = ast.loc.expression.source_buffer.name
240
247
  if filename and not filename.empty?
241
- filename = filename.dup.untaint
242
248
  @timestamps[filename] ||= File.mtime(filename)
243
249
  end
244
250
  end
@@ -307,6 +313,7 @@ require 'ruby2js/converter/for'
307
313
  require 'ruby2js/converter/hash'
308
314
  require 'ruby2js/converter/if'
309
315
  require 'ruby2js/converter/in'
316
+ require 'ruby2js/converter/import'
310
317
  require 'ruby2js/converter/ivar'
311
318
  require 'ruby2js/converter/ivasgn'
312
319
  require 'ruby2js/converter/kwbegin'
@@ -333,3 +340,4 @@ require 'ruby2js/converter/vasgn'
333
340
  require 'ruby2js/converter/while'
334
341
  require 'ruby2js/converter/whilepost'
335
342
  require 'ruby2js/converter/xstr'
343
+ require 'ruby2js/converter/xnode'
@@ -41,7 +41,7 @@ module Ruby2JS
41
41
  end
42
42
 
43
43
  handle :mlhs do |*args|
44
- if es2015
44
+ if es2015 or @jsx
45
45
  put '['
46
46
  parse_all(*args, join: ', ')
47
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
@@ -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
@@ -56,10 +56,30 @@ module Ruby2JS
56
56
  # find ivars and cvars
57
57
  walk = proc do |ast|
58
58
  ivars << ast.children.first if ast.type === :ivar
59
+ ivars << ast.children.first if ast.type === :ivasgn
59
60
  cvars << ast.children.first if ast.type === :cvar
61
+ cvars << ast.children.first if ast.type === :cvasgn
62
+
60
63
  ast.children.each do |child|
61
64
  walk[child] if child.is_a? Parser::AST::Node
62
65
  end
66
+
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
82
+
63
83
  end
64
84
  walk[@ast]
65
85
 
@@ -77,7 +97,7 @@ module Ruby2JS
77
97
  cvars.to_a.sort.each do |cvar|
78
98
  put(index == 0 ? @nl : @sep)
79
99
  index += 1
80
- put 'static #' + cvar.to_s[2..-1]
100
+ put 'static #$' + cvar.to_s[2..-1]
81
101
  end
82
102
 
83
103
  while constructor.length > 0 and constructor.first.type == :ivasgn
@@ -131,7 +151,7 @@ module Ruby2JS
131
151
  next
132
152
  end
133
153
 
134
- m = m.updated(m.type, [@prop, m.children[1], *constructor])
154
+ m = m.updated(m.type, [@prop, m.children[1], s(:begin, *constructor)])
135
155
  elsif not m.is_method?
136
156
  @prop = "get #{@prop}"
137
157
  m = m.updated(m.type, [*m.children[0..1],
@@ -147,12 +167,13 @@ module Ruby2JS
147
167
 
148
168
  begin
149
169
  @instance_method = m
170
+ @class_method = nil
150
171
  parse m # unless skipped
151
172
  ensure
152
173
  @instance_method = nil
153
174
  end
154
175
 
155
- elsif
176
+ elsif \
156
177
  [:defs, :asyncs].include? m.type and m.children.first.type == :self
157
178
  then
158
179
 
@@ -172,27 +193,35 @@ module Ruby2JS
172
193
  @prop.sub! 'static', 'static async' if m.type == :asyncs
173
194
 
174
195
  m = m.updated(:def, m.children[1..3])
175
- parse m
196
+ begin
197
+ @instance_method = nil
198
+ @class_method = m
199
+ parse m # unless skipped
200
+ ensure
201
+ @instance_method = nil
202
+ end
176
203
 
177
204
  elsif m.type == :send and m.children.first == nil
205
+ p = es2020 ? '#' : '_'
206
+
178
207
  if m.children[1] == :attr_accessor
179
208
  m.children[2..-1].each_with_index do |child_sym, index2|
180
209
  put @sep unless index2 == 0
181
210
  var = child_sym.children.first
182
- put "get #{var}() {#{@nl}return this._#{var}#@nl}#@sep"
183
- put "set #{var}(#{var}) {#{@nl}this._#{var} = #{var}#@nl}"
211
+ put "get #{var}() {#{@nl}return this.#{p}#{var}#@nl}#@sep"
212
+ put "set #{var}(#{var}) {#{@nl}this.#{p}#{var} = #{var}#@nl}"
184
213
  end
185
214
  elsif m.children[1] == :attr_reader
186
215
  m.children[2..-1].each_with_index do |child_sym, index2|
187
216
  put @sep unless index2 == 0
188
217
  var = child_sym.children.first
189
- put "get #{var}() {#{@nl}return this._#{var}#@nl}"
218
+ put "get #{var}() {#{@nl}return this.#{p}#{var}#@nl}"
190
219
  end
191
220
  elsif m.children[1] == :attr_writer
192
221
  m.children[2..-1].each_with_index do |child_sym, index2|
193
222
  put @sep unless index2 == 0
194
223
  var = child_sym.children.first
195
- put "set #{var}(#{var}) {#{@nl}this._#{var} = #{var}#@nl}"
224
+ put "set #{var}(#{var}) {#{@nl}this.#{p}#{var} = #{var}#@nl}"
196
225
  end
197
226
  elsif [:private, :protected, :public].include? m.children[1]
198
227
  raise Error.new("class #{m.children[1]} is not supported", @ast)
@@ -208,7 +237,7 @@ module Ruby2JS
208
237
 
209
238
  else
210
239
  if m.type == :cvasgn and es2020
211
- put 'static #'; put m.children[0].to_s[2..-1]; put ' = '
240
+ put 'static #$'; put m.children[0].to_s[2..-1]; put ' = '
212
241
  parse m.children[1]
213
242
  else
214
243
  skipped = true
@@ -4,7 +4,7 @@ module Ruby2JS
4
4
  # (cvar :@@a)
5
5
 
6
6
  handle :cvar do |var|
7
- prefix = es2020 ? '#' : '_'
7
+ prefix = es2020 ? '#$' : '_'
8
8
 
9
9
  @class_name ||= nil
10
10
  if @class_name
@@ -7,7 +7,7 @@ module Ruby2JS
7
7
  handle :cvasgn do |var, expression=nil|
8
8
  multi_assign_declarations if @state == :statement
9
9
 
10
- prefix = es2020 ? '#' : '_'
10
+ prefix = es2020 ? '#$' : '_'
11
11
 
12
12
  if @class_name
13
13
  parse @class_name
@@ -88,7 +88,7 @@ module Ruby2JS
88
88
  put 'async ' if @ast.type == :async
89
89
 
90
90
  # es2015 fat arrow support
91
- if
91
+ if \
92
92
  not name and es2015 and @state != :method and @ast.type != :defm and
93
93
  not @prop
94
94
  then
@@ -105,7 +105,7 @@ module Ruby2JS
105
105
  else
106
106
  style = :expression
107
107
  end
108
- elsif
108
+ elsif \
109
109
  expr.type == :if and expr.children[1] and expr.children[2] and
110
110
  EXPRESSIONS.include? expr.children[1].type and
111
111
  EXPRESSIONS.include? expr.children[2].type
@@ -10,6 +10,13 @@ module Ruby2JS
10
10
  # (...)
11
11
 
12
12
  handle :for, :for_of do |var, expression, block|
13
+ if @jsx and @ast.type == :for_of
14
+ parse s(:block, s(:send, expression, :map),
15
+ s(:args, s(:arg, var.children[0])),
16
+ s(:autoreturn, block))
17
+ return
18
+ end
19
+
13
20
  begin
14
21
  vars = @vars.dup
15
22
  next_token, @next_token = @next_token, :continue