ruby2js 3.5.1 → 4.0.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 +5 -662
  3. data/lib/ruby2js.rb +61 -10
  4. data/lib/ruby2js/converter.rb +10 -4
  5. data/lib/ruby2js/converter/assign.rb +159 -0
  6. data/lib/ruby2js/converter/begin.rb +7 -2
  7. data/lib/ruby2js/converter/case.rb +7 -2
  8. data/lib/ruby2js/converter/class.rb +77 -21
  9. data/lib/ruby2js/converter/class2.rb +102 -31
  10. data/lib/ruby2js/converter/def.rb +7 -3
  11. data/lib/ruby2js/converter/dstr.rb +8 -3
  12. data/lib/ruby2js/converter/hash.rb +9 -5
  13. data/lib/ruby2js/converter/hide.rb +13 -0
  14. data/lib/ruby2js/converter/if.rb +10 -2
  15. data/lib/ruby2js/converter/import.rb +35 -4
  16. data/lib/ruby2js/converter/kwbegin.rb +9 -2
  17. data/lib/ruby2js/converter/literal.rb +14 -2
  18. data/lib/ruby2js/converter/module.rb +41 -4
  19. data/lib/ruby2js/converter/opasgn.rb +8 -0
  20. data/lib/ruby2js/converter/send.rb +45 -5
  21. data/lib/ruby2js/converter/vasgn.rb +5 -0
  22. data/lib/ruby2js/converter/xstr.rb +1 -1
  23. data/lib/ruby2js/demo.rb +53 -0
  24. data/lib/ruby2js/es2022.rb +5 -0
  25. data/lib/ruby2js/es2022/strict.rb +3 -0
  26. data/lib/ruby2js/filter.rb +9 -1
  27. data/lib/ruby2js/filter/active_functions.rb +44 -0
  28. data/lib/ruby2js/filter/camelCase.rb +4 -3
  29. data/lib/ruby2js/filter/cjs.rb +2 -0
  30. data/lib/ruby2js/filter/esm.rb +133 -7
  31. data/lib/ruby2js/filter/functions.rb +107 -98
  32. data/lib/ruby2js/filter/{wunderbar.rb → jsx.rb} +29 -7
  33. data/lib/ruby2js/filter/node.rb +95 -74
  34. data/lib/ruby2js/filter/nokogiri.rb +15 -41
  35. data/lib/ruby2js/filter/react.rb +191 -56
  36. data/lib/ruby2js/filter/require.rb +100 -5
  37. data/lib/ruby2js/filter/return.rb +15 -1
  38. data/lib/ruby2js/filter/securerandom.rb +33 -0
  39. data/lib/ruby2js/filter/stimulus.rb +185 -0
  40. data/lib/ruby2js/filter/vue.rb +9 -0
  41. data/lib/ruby2js/jsx.rb +291 -0
  42. data/lib/ruby2js/namespace.rb +75 -0
  43. data/lib/ruby2js/rails.rb +15 -9
  44. data/lib/ruby2js/serializer.rb +3 -1
  45. data/lib/ruby2js/version.rb +3 -3
  46. data/ruby2js.gemspec +1 -1
  47. metadata +14 -5
  48. data/lib/ruby2js/filter/esm_migration.rb +0 -72
  49. data/lib/ruby2js/filter/fast-deep-equal.rb +0 -23
@@ -0,0 +1,5 @@
1
+ require 'ruby2js'
2
+
3
+ if Ruby2JS.eslevel_default < 2022
4
+ Ruby2JS.eslevel_default = 2022
5
+ end
@@ -0,0 +1,3 @@
1
+ require 'ruby2js/es2022'
2
+
3
+ Ruby2JS.strict_default = true
@@ -13,6 +13,14 @@ module Ruby2JS
13
13
  @@included = nil
14
14
  @@excluded = []
15
15
 
16
+ def self.included_methods
17
+ @@included&.dup
18
+ end
19
+
20
+ def self.excluded_methods
21
+ @@excluded&.dup
22
+ end
23
+
16
24
  # indicate that the specified methods are not to be processed
17
25
  def self.exclude(*methods)
18
26
  if @@included
@@ -52,7 +60,7 @@ module Ruby2JS
52
60
  not @included.include? method
53
61
  else
54
62
  return true if @exclude_methods.flatten.include? method
55
- @excluded.include? method
63
+ @excluded&.include? method
56
64
  end
57
65
  end
58
66
 
@@ -0,0 +1,44 @@
1
+ require 'ruby2js'
2
+
3
+ module Ruby2JS
4
+ module Filter
5
+ module ActiveFunctions
6
+ include SEXP
7
+
8
+ def on_send(node)
9
+ target, method, *args = node.children
10
+ return super unless args.empty?
11
+
12
+ if es2015 and method == :blank?
13
+ create_or_update_import("blank$")
14
+ process node.updated :send, [nil, "blank$", target]
15
+ elsif es2015 and method == :present?
16
+ create_or_update_import("present$")
17
+ process node.updated :send, [nil, "present$", target]
18
+ elsif es2015 and method == :presence
19
+ create_or_update_import("presence$")
20
+ process node.updated :send, [nil, "presence$", target]
21
+ else
22
+ super
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def create_or_update_import(token)
29
+ af_import = @options[:import_from_skypack] ? "https://cdn.skypack.dev/@ruby2js/active-functions" : "@ruby2js/active-functions"
30
+
31
+ if found_node = prepend_list.find {|ast| ast.type == :import && ast.children.first == af_import}
32
+ unless found_node.children.last.find {|const| const.children.last == token}
33
+ prepend_list.delete found_node
34
+ prepend_list << s(:import, found_node.children.first, found_node.children.last.push(s(:const, nil, token)))
35
+ end
36
+ else
37
+ prepend_list << s(:import, af_import, [s(:const, nil, token)])
38
+ end
39
+ end
40
+ end
41
+
42
+ DEFAULTS.push ActiveFunctions
43
+ end
44
+ end
@@ -9,8 +9,9 @@ module Ruby2JS
9
9
  module CamelCase
10
10
  include SEXP
11
11
 
12
- WHITELIST = %w{
12
+ ALLOWLIST = %w{
13
13
  attr_accessor
14
+ method_missing
14
15
  }
15
16
 
16
17
  CAPS_EXCEPTIONS = {
@@ -37,7 +38,7 @@ module Ruby2JS
37
38
  node = super
38
39
  return node unless [:send, :csend, :attr].include? node.type
39
40
 
40
- if node.children[0] == nil and WHITELIST.include? node.children[1].to_s
41
+ if node.children[0] == nil and ALLOWLIST.include? node.children[1].to_s
41
42
  node
42
43
  elsif node.children[0] && [:ivar, :cvar].include?(node.children[0].type)
43
44
  S(node.type, s(node.children[0].type, camelCase(node.children[0].children[0])),
@@ -61,7 +62,7 @@ module Ruby2JS
61
62
  def handle_generic_node(node, node_type)
62
63
  return node if node.type != node_type
63
64
 
64
- if node.children[0] =~ /_.*\w$/
65
+ if node.children[0] =~ /_.*\w$/ and !ALLOWLIST.include?(node.children[0].to_s)
65
66
  S(node_type , camelCase(node.children[0]), *node.children[1..-1])
66
67
  else
67
68
  node
@@ -1,5 +1,7 @@
1
1
  require 'ruby2js'
2
2
 
3
+ Ruby2JS.module_default ||= :cjs
4
+
3
5
  module Ruby2JS
4
6
  module Filter
5
7
  module CJS
@@ -1,13 +1,83 @@
1
1
  require 'ruby2js'
2
2
 
3
+ Ruby2JS.module_default = :esm
4
+
3
5
  module Ruby2JS
4
6
  module Filter
5
7
  module ESM
6
8
  include SEXP
7
9
 
8
- def initialize(*args)
10
+ def options=(options)
11
+ super
12
+ @esm_autoexports = !@disable_autoexports && options[:autoexports]
13
+ @esm_autoimports = options[:autoimports]
14
+ @esm_defs = options[:defs] || {}
15
+ @esm_explicit_tokens = Set.new
16
+ end
17
+
18
+ def process(node)
19
+ return super unless @esm_autoexports
20
+
21
+ list = [node]
22
+ while list.length == 1 and list.first.type == :begin
23
+ list = list.first.children.dup
24
+ end
25
+
26
+ replaced = []
27
+ list.map! do |child|
28
+ replacement = child
29
+
30
+ if [:module, :class].include? child.type and
31
+ child.children.first.type == :const and
32
+ child.children.first.children.first == nil \
33
+ then
34
+ replacement = s(:export, child)
35
+ elsif child.type == :casgn and child.children.first == nil
36
+ replacement = s(:export, child)
37
+ elsif child.type == :def
38
+ replacement = s(:export, child)
39
+ end
40
+
41
+ if replacement != child
42
+ replaced << replacement
43
+ @comments[replacement] = @comments[child] if @comments[child]
44
+ end
45
+
46
+ replacement
47
+ end
48
+
49
+ if replaced.length == 1 and @esm_autoexports == :default
50
+ list.map! do |child|
51
+ if child == replaced.first
52
+ replacement = s(:export, s(:send, nil, :default, *child.children))
53
+ @comments[replacement] = @comments[child] if @comments[child]
54
+ replacement
55
+ else
56
+ child
57
+ end
58
+ end
59
+ end
60
+
61
+ @esm_autoexports = false
62
+ process s(:begin, *list)
63
+ end
64
+
65
+ def on_class(node)
66
+ @esm_explicit_tokens << node.children.first.children.last
67
+
68
+ super
69
+ end
70
+
71
+ def on_def(node)
72
+ @esm_explicit_tokens << node.children.first
73
+
74
+ super
75
+ end
76
+
77
+ def on_lvasgn(node)
78
+ @esm_explicit_tokens << node.children.first
79
+
9
80
  super
10
- @esm = true # signal for other filters
11
81
  end
12
82
 
13
83
  def on_send(node)
@@ -23,7 +93,7 @@ module Ruby2JS
23
93
  end
24
94
  end
25
95
 
26
- if args[0].type == :str
96
+ if args[0].type == :str and args.length == 1
27
97
  # import "file.css"
28
98
  # => import "file.css"
29
99
  s(:import, args[0].children[0])
@@ -36,6 +106,8 @@ module Ruby2JS
36
106
  args[0].children[2].children[2].type == :str
37
107
  # import name from "file.js"
38
108
  # => import name from "file.js"
109
+ @esm_explicit_tokens << args[0].children[1]
110
+
39
111
  s(:import,
40
112
  [args[0].children[2].children[2].children[0]],
41
113
  process(s(:attr, nil, args[0].children[1])))
@@ -49,17 +121,71 @@ module Ruby2JS
49
121
  # => import Stuff as * from "file.js"
50
122
  # import [ Some, Stuff ], from: "file.js"
51
123
  # => import { Some, Stuff } from "file.js"
52
- imports = (args[0].type == :const || args[0].type == :send) ?
53
- process(args[0]) :
54
- process_all(args[0].children)
55
- s(:import, args[1].children, imports) unless args[1].nil?
124
+ # import Some, [ More, Stuff ], from: "file.js"
125
+ # => import Some, { More, Stuff } from "file.js"
126
+ imports = []
127
+ if %i(const send str).include? args[0].type
128
+ @esm_explicit_tokens << args[0].children.last
129
+ imports << process(args.shift)
130
+ end
131
+
132
+ if args[0].type == :array
133
+ args[0].children.each {|i| @esm_explicit_tokens << i.children.last}
134
+ imports << process_all(args.shift.children)
135
+ end
136
+
137
+ s(:import, args[0].children, *imports) unless args[0].nil?
56
138
  end
57
139
  elsif method == :export
58
140
  s(:export, *process_all(args))
141
+ elsif target.nil? and found_import = find_autoimport(method)
142
+ prepend_list << s(:import, found_import[0], found_import[1])
143
+ super
59
144
  else
60
145
  super
61
146
  end
62
147
  end
148
+
149
+ def on_const(node)
150
+ if node.children.first == nil and found_import = find_autoimport(node.children.last)
151
+ prepend_list << s(:import, found_import[0], found_import[1])
152
+
153
+ values = @esm_defs[node.children.last]
154
+
155
+ if values
156
+ values = values.map {|value|
157
+ if value.to_s.start_with? "@"
158
+ [value.to_s[1..-1].to_sym, s(:self)]
159
+ else
160
+ [value.to_sym, s(:autobind, s(:self))]
161
+ end
162
+ }.to_h
163
+
164
+ @namespace.defineProps values, [node.children.last]
165
+ end
166
+ end
167
+
168
+ super
169
+ end
170
+
171
+ def on_export(node)
172
+ s(:export, *process_all(node.children))
173
+ end
174
+ end
175
+
176
+ private
177
+
178
+ def find_autoimport(token)
179
+ return nil if @esm_autoimports.nil?
180
+ return nil if @esm_explicit_tokens.include?(token)
181
+
182
+ token = camelCase(token) if respond_to?(:camelCase)
183
+
184
+ if @esm_autoimports[token]
185
+ [@esm_autoimports[token], s(:const, nil, token)]
186
+ elsif found_key = @esm_autoimports.keys.find {|key| key.is_a?(Array) && key.include?(token)}
187
+ [@esm_autoimports[found_key], found_key.map {|key| s(:const, nil, key)}]
188
+ end
63
189
  end
64
190
 
65
191
  DEFAULTS.push ESM
@@ -1,5 +1,5 @@
1
1
  require 'ruby2js'
2
- require 'regexp_parser'
2
+ require 'regexp_parser/scanner'
3
3
 
4
4
  module Ruby2JS
5
5
  module Filter
@@ -26,9 +26,15 @@ module Ruby2JS
26
26
  return super if excluded?(method)
27
27
 
28
28
  if [:max, :min].include? method and args.length == 0
29
- return super unless node.is_method?
30
- process S(:send, s(:const, nil, :Math), node.children[1],
31
- s(:splat, target))
29
+ if target.type == :array
30
+ process S(:send, s(:const, nil, :Math), node.children[1],
31
+ *target.children)
32
+ elsif node.is_method?
33
+ process S(:send, s(:const, nil, :Math), node.children[1],
34
+ s(:splat, target))
35
+ else
36
+ return super
37
+ end
32
38
 
33
39
  elsif method == :call and target and target.type == :ivar
34
40
  process S(:send, s(:self), "_#{target.children.first.to_s[1..-1]}",
@@ -43,71 +49,78 @@ module Ruby2JS
43
49
 
44
50
  elsif method == :[]= and args.length == 3 and
45
51
  args[0].type == :regexp and args[1].type == :int
52
+
46
53
  index = args[1].children.first
47
54
 
48
- parts = Regexp::Parser.parse(args[0].children.first.children.first)
49
- split = parts.index do |part|
50
- part.type == :group and part.number == index
55
+ # identify groups
56
+ regex = args[0].children.first.children.first
57
+ tokens = Regexp::Scanner.scan(regex)
58
+ groups = []
59
+ stack = []
60
+ tokens.each do |token|
61
+ next unless token[0] == :group
62
+ if token[1] == :capture
63
+ groups.push token.dup
64
+ return super if groups.length == index and not stack.empty?
65
+ stack.push groups.last
66
+ elsif token[1] == :close
67
+ stack.pop[-1]=token.last
68
+ end
51
69
  end
70
+ group = groups[index-1]
52
71
 
53
- return super unless split
54
-
55
- rewritten = parts[split].to_s
72
+ # rewrite regex
73
+ prepend = nil
74
+ append = nil
56
75
 
57
- dstr = [args.last]
58
- if rewritten == '()'
59
- index -= 1
60
- rewritten = ''
76
+ if group[4] < regex.length
77
+ regex = (regex[0...group[4]] + '(' + regex[group[4]..-1] + ')').
78
+ sub(/\$\)$/, ')$')
79
+ append = 2
61
80
  end
62
81
 
63
- parts = parts.to_a
64
-
65
- pre = ''
66
- if parts.first.type == :anchor
67
- pre = parts.shift.to_s
68
- split -= 1
82
+ if group[4] - group[3] == 2
83
+ regex = regex[0...group[3]] + regex[group[4]..-1]
84
+ append = 1 if append
69
85
  end
70
86
 
71
- if split > 0
72
- if split == 1 and parts.first.type == :group
73
- rewritten = parts.first.to_s + rewritten
74
- else
75
- rewritten = '(' + parts[0 .. split - 1].join + ')' + rewritten
76
- index += 1
77
- end
78
- dstr.unshift s(:send, s(:lvar, :match), :[], s(:int, 0))
87
+ if group[3] > 0
88
+ regex = ('(' + regex[0...group[3]] + ')' + regex[group[3]..-1]).
89
+ sub(/^\(\^/, '^(')
90
+ prepend = 1
91
+ append += 1 if append
79
92
  end
80
93
 
94
+ regex = process s(:regexp, s(:str, regex), args[0].children.last)
81
95
 
82
- post = ''
83
- if parts.last.type == :anchor
84
- post = parts.pop.to_s
85
- end
86
-
87
- if split + 1 < parts.length
88
- if split + 2 == parts.length and parts.last.type == :group
89
- rewritten += parts.last.to_s
90
- else
91
- rewritten += '(' + parts[split + 1 .. -1].join + ')'
96
+ #
97
+ if args.last.type == :str
98
+ str = args.last.children.first.gsub('$', '$$')
99
+ str = "$#{prepend}#{str}" if prepend
100
+ str = "#{str}$#{append}" if append
101
+ expr = s(:send, target, :replace, regex, s(:str, str))
102
+ else
103
+ dstr = args.last.type == :dstr ? args.last.children.dup : [args.last]
104
+ if prepend
105
+ dstr.unshift s(:send, s(:lvar, :match), :[], s(:int, prepend-1))
106
+ end
107
+ if append
108
+ dstr << s(:send, s(:lvar, :match), :[], s(:int, append-1))
92
109
  end
93
- dstr << s(:send, s(:lvar, :match), :[], s(:int, index))
94
- end
95
-
96
- rewritten = pre + rewritten + post
97
110
 
98
- regex = process s(:regexp, s(:str, rewritten), args[0].children.last)
99
- block = s(:block,
100
- s(:send, target, :replace, regex),
101
- s(:args, s(:arg, :match)),
102
- process(s(:dstr, *dstr)))
111
+ expr = s(:block,
112
+ s(:send, target, :replace, regex),
113
+ s(:args, s(:arg, :match)),
114
+ process(s(:dstr, *dstr)))
115
+ end
103
116
 
104
117
  if VAR_TO_ASSIGN.keys.include? target.type
105
- S(VAR_TO_ASSIGN[target.type], target.children.first, block)
118
+ S(VAR_TO_ASSIGN[target.type], target.children.first, expr)
106
119
  elsif target.type == :send
107
120
  if target.children[0] == nil
108
- S(:lvasgn, target.children[1], block)
121
+ S(:lvasgn, target.children[1], expr)
109
122
  else
110
- S(:send, target.children[0], :"#{target.children[1]}=", block)
123
+ S(:send, target.children[0], :"#{target.children[1]}=", expr)
111
124
  end
112
125
  else
113
126
  super
@@ -116,57 +129,14 @@ module Ruby2JS
116
129
  elsif method == :merge
117
130
  args.unshift target
118
131
 
119
- if es2015
120
- if es2018
121
- process S(:hash, *args.map {|arg| s(:kwsplat, arg)})
122
- else
123
- process S(:send, s(:const, nil, :Object), :assign, s(:hash),
124
- *args)
125
- end
132
+ if es2018
133
+ process S(:hash, *args.map {|arg| s(:kwsplat, arg)})
126
134
  else
127
- copy = [s(:gvasgn, :$$, s(:hash))]
128
-
129
- s(:send, s(:block, s(:send, nil, :lambda), s(:args),
130
- s(:begin, *copy, *args.map {|modname|
131
- if modname.type == :hash
132
- s(:begin, *modname.children.map {|pair|
133
- s(:send, s(:gvar, :$$), :[]=, *pair.children)
134
- })
135
- else
136
- s(:for, s(:lvasgn, :$_), modname,
137
- s(:send, s(:gvar, :$$), :[]=,
138
- s(:lvar, :$_), s(:send, modname, :[], s(:lvar, :$_))))
139
- end
140
- }, s(:return, s(:gvar, :$$)))), :[])
135
+ process S(:assign, s(:hash), *args)
141
136
  end
142
137
 
143
138
  elsif method == :merge!
144
- if es2015
145
- process S(:send, s(:const, nil, :Object), :assign, target, *args)
146
- else
147
- copy = []
148
-
149
- unless
150
- target.type == :send and target.children.length == 2 and
151
- target.children[0] == nil
152
- then
153
- copy << s(:gvasgn, :$0, target)
154
- target = s(:gvar, :$0)
155
- end
156
-
157
- s(:send, s(:block, s(:send, nil, :lambda), s(:args),
158
- s(:begin, *copy, *args.map {|modname|
159
- if modname.type == :hash
160
- s(:begin, *modname.children.map {|pair|
161
- s(:send, target, :[]=, *pair.children)
162
- })
163
- else
164
- s(:for, s(:lvasgn, :$_), modname,
165
- s(:send, target, :[]=,
166
- s(:lvar, :$_), s(:send, modname, :[], s(:lvar, :$_))))
167
- end
168
- }, s(:return, target))), :[])
169
- end
139
+ process S(:assign, target, *args)
170
140
 
171
141
  elsif method == :delete and args.length == 1
172
142
  if not target
@@ -531,6 +501,34 @@ module Ruby2JS
531
501
  elsif method == :block_given? and target == nil and args.length == 0
532
502
  process process s(:lvar, "_implicitBlockYield")
533
503
 
504
+ elsif method == :abs and args.length == 0
505
+ process S(:send, s(:const, nil, :Math), :abs, target)
506
+
507
+ elsif method == :ceil and args.length == 0
508
+ process S(:send, s(:const, nil, :Math), :ceil, target)
509
+
510
+ elsif method == :floor and args.length == 0
511
+ process S(:send, s(:const, nil, :Math), :floor, target)
512
+
513
+ elsif method == :sum and args.length == 0
514
+ process S(:send, target, :reduce, s(:block, s(:send, nil, :proc),
515
+ s(:args, s(:arg, :a), s(:arg, :b)),
516
+ s(:send, s(:lvar, :a), :+, s(:lvar, :b))), s(:int, 0))
517
+
518
+ elsif method == :method_defined? and args.length >= 1
519
+ if args[1] == s(:false)
520
+ process S(:send, s(:attr, target, :prototype), :hasOwnProperty, args[0])
521
+ elsif args.length == 1 or args[1] == s(:true)
522
+ process S(:in?, args[0], s(:attr, target, :prototype))
523
+ else
524
+ process S(:if, args[1], s(:in?, args[0], s(:attr, target, :prototype)),
525
+ s(:send, s(:attr, target, :prototype), :hasOwnProperty, args[0]))
526
+ end
527
+
528
+ elsif method == :alias_method and args.length == 2
529
+ process S(:send, s(:attr, target, :prototype), :[]=, args[0],
530
+ s(:attr, s(:attr, target, :prototype), args[1].children[0]))
531
+
534
532
  else
535
533
  super
536
534
  end
@@ -717,6 +715,10 @@ module Ruby2JS
717
715
  s(:return, s(:lvar, node.children[1].children[0].children[0])))),
718
716
  :[], call.children[0]])
719
717
 
718
+ elsif method == :define_method and call.children.length == 3
719
+ process node.updated(:send, [s(:attr, call.children[0], :prototype), :[]=,
720
+ call.children[2], s(:deff, nil, *node.children[1..-1])])
721
+
720
722
  else
721
723
  super
722
724
  end
@@ -726,6 +728,12 @@ module Ruby2JS
726
728
  name, inheritance, *body = node.children
727
729
  body.compact!
728
730
 
731
+ body.each_with_index do |node, i|
732
+ if node.type == :send and node.children[0..1] == [nil, :alias_method]
733
+ body[i] = node.updated(:send, [name, *node.children[1..-1]])
734
+ end
735
+ end
736
+
729
737
  if inheritance == s(:const, nil, :Exception)
730
738
  unless
731
739
  body.any? {|statement| statement.type == :def and
@@ -741,7 +749,8 @@ module Ruby2JS
741
749
  body = [s(:begin, *body)] if body.length > 1
742
750
  S(:class, name, s(:const, nil, :Error), *body)
743
751
  else
744
- super
752
+ body = [s(:begin, *body)] if body.length > 1
753
+ super S(:class, name, inheritance, *body)
745
754
  end
746
755
  end
747
756
  end