ruby2js 3.1.0 → 3.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +51 -10
  3. data/lib/ruby2js.rb +15 -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 +8 -1
  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.rb +1 -0
  26. data/lib/ruby2js/filter/cjs.rb +2 -2
  27. data/lib/ruby2js/filter/esm.rb +72 -0
  28. data/lib/ruby2js/filter/functions.rb +191 -41
  29. data/lib/ruby2js/filter/matchAll.rb +49 -0
  30. data/lib/ruby2js/filter/node.rb +18 -10
  31. data/lib/ruby2js/filter/nokogiri.rb +13 -13
  32. data/lib/ruby2js/filter/react.rb +190 -30
  33. data/lib/ruby2js/filter/require.rb +1 -4
  34. data/lib/ruby2js/filter/rubyjs.rb +4 -4
  35. data/lib/ruby2js/filter/vue.rb +45 -17
  36. data/lib/ruby2js/filter/wunderbar.rb +63 -0
  37. data/lib/ruby2js/serializer.rb +25 -11
  38. data/lib/ruby2js/version.rb +2 -2
  39. data/ruby2js.gemspec +2 -11
  40. metadata +24 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f0d87e8dda198550df6b79aa5a14e033ff6180f6057998d63252a41011d04735
4
- data.tar.gz: 1869aa7759c6b40137258b3a87c0c250944940753cc5d0814a0e027bd88068dd
3
+ metadata.gz: 01206317a8156704b69b59656c26eb7bc91a832faf85d152fc514b4b9fbee38a
4
+ data.tar.gz: f57331bc9505cd15099c4b4ffb53475e0a04f288fc4949fd4c91961959ee149f
5
5
  SHA512:
6
- metadata.gz: 75b7232fbc65271e6b200f6283f04605de370d6e38b127e6c51a481931a41076e055a3141fe643094c67e9b73988b0e7ccf576d0d6c36e3f44cbdb56face4362
7
- data.tar.gz: c60b7631d17f12cafe859cfc06e7551f3158f1d6f4643f2fd2faf12a4cbd85cb8496c453d8c43d6244e73516726251f438f964a2312fd87deb8c690dc098b208
6
+ metadata.gz: 5d67b911c57d6a76ce93efb80a98e902e0d089125414d0d8f5a25834241f1a336141ef149a8bda48cfd5996e8de032f5b06920cb95c16be10cdf156189714651
7
+ data.tar.gz: e57e5a000416f686d386a7033a082d394d55b42bc0f468bf2b14b74573bbff1157fc5ab1b1de6f8d790e232e8ab71f21974dd81dedb5bb49fab6897b2aa56265
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,9 +245,10 @@ 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)`
239
253
  * `.merge` becomes `Object.assign({}, ...)`
240
254
  * `.merge!` becomes `Object.assign()`
@@ -244,6 +258,7 @@ the script.
244
258
  * `puts` becomes `console.log`
245
259
  * `.replace` becomes `.length = 0; ...push.apply(*)`
246
260
  * `.respond_to?` becomes `right in left`
261
+ * `.rstrip` becomes `.replace(/s+$/, "")`
247
262
  * `.scan` becomes `.match(//g)`
248
263
  * `.start_with?` becomes `.substring(0, arg.length) == arg`
249
264
  * `.upto(lim)` becomes `for (var i=num; i<=lim; i+=1)`
@@ -263,6 +278,7 @@ the script.
263
278
  * `[n...m]` becomes `.slice(n,m)`
264
279
  * `[n..m]` becomes `.slice(n,m+1)`
265
280
  * `[/r/, n]` becomes `.match(/r/)[n]`
281
+ * `[/r/, n]=` becomes `.replace(/r/, ...)`
266
282
  * `(1..2).each {|i| ...}` becomes `for (var i=1 i<=2; i+=1)`
267
283
  * `"string" * length` becomes `new Array(length + 1).join("string")`
268
284
  * `.sub!` and `.gsub!` become equivalent `x = x.replace` statements
@@ -280,6 +296,7 @@ the script.
280
296
  * New classes subclassed off of `Exception` will become subclassed off
281
297
  of `Error` instead; and default constructors will be provided
282
298
  * `loop do...end` will be replaced with `while (true) {...}`
299
+ * `raise Exception.new(...)` will be replaced with `throw new Error(...)`
283
300
 
284
301
  Additionally, there is one mapping that will only be done if explicitly
285
302
  <a href="https://github.com/rubys/ruby2js/blob/master/lib/ruby2js/filter.rb">included</a>:
@@ -515,6 +532,16 @@ the script.
515
532
  * maps `export default async proc` to `module.exports = async`
516
533
  * maps `export default` to `module.exports =`
517
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
+
518
545
  [Wunderbar](https://github.com/rubys/wunderbar) includes additional demos:
519
546
 
520
547
  * [chat](https://github.com/rubys/wunderbar/blob/master/demo/chat.rb),
@@ -536,7 +563,7 @@ conversions are made:
536
563
  * `def f(a, (foo, *bar))` becomes `function f(a, [foo, ...bar])`
537
564
  * `def a(b=1)` becomes `function a(b=1)`
538
565
  * `def a(*b)` becomes `function a(...b)`
539
- * `.each_key` becomes `for (i of ...) {}`
566
+ * `.each_value` becomes `for (i of ...) {}`
540
567
  * `a(*b)` becomes `a(...b)`
541
568
  * `"#{a}"` becomes <code>\`${a}\`</code>
542
569
  * `lambda {|x| x}` becomes `(x) => {return x}`
@@ -572,9 +599,11 @@ ES2017 support
572
599
  ---
573
600
 
574
601
  When option `eslevel: 2017` is provided, the following additional
575
- conversion is made by the `functions` filter:
602
+ conversions are made by the `functions` filter:
576
603
 
577
- * `.each_entry` becomes `Object.entries().forEach`
604
+ * `.values()` becomes `Object.values()`
605
+ * `.entries()` becomes `Object.entries()`
606
+ * `.each_pair {}` becomes `for (let [key, value] of Object.entries()) {}'
578
607
 
579
608
  ES2018 support
580
609
  ---
@@ -590,13 +619,14 @@ optional keyword arguments.
590
619
  ES2019 support
591
620
  ---
592
621
 
593
- When option `eslevel: 2018` is provided, the following additional
622
+ When option `eslevel: 2019` is provided, the following additional
594
623
  conversion is made by the `functions` filter:
595
624
 
596
625
  * `.flatten` becomes `.flat(Infinity)`
597
626
  * `.lstrip` becomes `.trimEnd
598
627
  * `.rstrip` becomes `.trimStart
599
628
  * `a.to_h` becomes `Object.fromEntries(a)`
629
+ * `Hash[a]` becomes `Object.fromEntries(a)`
600
630
 
601
631
  Additionally, `rescue` without a variable will map to `catch` without a
602
632
  variable.
@@ -604,11 +634,22 @@ variable.
604
634
  ES2020 support
605
635
  ---
606
636
 
607
- When option `eslevel: 2018` is provided, the following additional
608
- conversion is made:
637
+ When option `eslevel: 2020` is provided, the following additional
638
+ conversions are made:
609
639
 
610
640
  * `@x` becomes `this.#x`
611
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`
612
653
 
613
654
  Picking a Ruby to JS mapping tool
614
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)
@@ -93,6 +94,10 @@ module Ruby2JS
93
94
  @options[:eslevel] >= 2020
94
95
  end
95
96
 
97
+ def es2021
98
+ @options[:eslevel] >= 2021
99
+ end
100
+
96
101
  def process(node)
97
102
  ast, @ast = @ast, node
98
103
  replacement = super
@@ -126,6 +131,9 @@ module Ruby2JS
126
131
  def on_sendw(node); on_send(node); end
127
132
  def on_undefined?(node); on_defined?(node); end
128
133
  def on_nil(node); end
134
+ def on_xnode(node); end
135
+ def on_export(node); end
136
+ def on_import(node); end
129
137
 
130
138
  # provide a method so filters can call 'super'
131
139
  def on_sym(node); node; end
@@ -155,7 +163,7 @@ module Ruby2JS
155
163
 
156
164
  if Proc === source
157
165
  file,line = source.source_location
158
- source = File.read(file.dup.untaint).untaint
166
+ source = IO.read(file)
159
167
  ast, comments = parse(source)
160
168
  comments = Parser::Source::Comment.associate(ast, comments) if ast
161
169
  ast = find_block( ast, line )
@@ -168,9 +176,12 @@ module Ruby2JS
168
176
  comments = Parser::Source::Comment.associate(ast, comments) if ast
169
177
  end
170
178
 
171
- filters = options[:filters] || Filter::DEFAULTS
179
+ filters = (options[:filters] || Filter::DEFAULTS)
172
180
 
173
181
  unless filters.empty?
182
+ filters.dup.each do |filter|
183
+ filters = filter.reorder(filters) if filter.respond_to? :reorder
184
+ end
174
185
 
175
186
  filter = Filter::Processor
176
187
  filters.reverse.each do |mod|
@@ -188,6 +199,8 @@ module Ruby2JS
188
199
  ruby2js.ivars = options[:ivars]
189
200
  ruby2js.eslevel = options[:eslevel]
190
201
  ruby2js.strict = options[:strict]
202
+ ruby2js.comparison = options[:comparison] || :equality
203
+ ruby2js.or = options[:or] || :logical
191
204
  if ruby2js.binding and not ruby2js.ivars
192
205
  ruby2js.ivars = ruby2js.binding.eval \
193
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