ruby2js 3.2.0 → 3.3.0

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