ruby2js 3.5.3 → 4.0.2

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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -665
  3. data/lib/ruby2js.rb +60 -15
  4. data/lib/ruby2js/converter.rb +39 -3
  5. data/lib/ruby2js/converter/args.rb +6 -1
  6. data/lib/ruby2js/converter/assign.rb +159 -0
  7. data/lib/ruby2js/converter/begin.rb +7 -2
  8. data/lib/ruby2js/converter/case.rb +7 -2
  9. data/lib/ruby2js/converter/class.rb +80 -21
  10. data/lib/ruby2js/converter/class2.rb +107 -33
  11. data/lib/ruby2js/converter/def.rb +7 -3
  12. data/lib/ruby2js/converter/dstr.rb +8 -3
  13. data/lib/ruby2js/converter/for.rb +1 -1
  14. data/lib/ruby2js/converter/hash.rb +28 -6
  15. data/lib/ruby2js/converter/hide.rb +13 -0
  16. data/lib/ruby2js/converter/if.rb +10 -2
  17. data/lib/ruby2js/converter/import.rb +19 -4
  18. data/lib/ruby2js/converter/kwbegin.rb +9 -2
  19. data/lib/ruby2js/converter/literal.rb +14 -2
  20. data/lib/ruby2js/converter/logical.rb +1 -1
  21. data/lib/ruby2js/converter/module.rb +41 -4
  22. data/lib/ruby2js/converter/next.rb +10 -2
  23. data/lib/ruby2js/converter/opasgn.rb +8 -0
  24. data/lib/ruby2js/converter/redo.rb +14 -0
  25. data/lib/ruby2js/converter/return.rb +2 -1
  26. data/lib/ruby2js/converter/send.rb +73 -8
  27. data/lib/ruby2js/converter/vasgn.rb +5 -0
  28. data/lib/ruby2js/converter/while.rb +1 -1
  29. data/lib/ruby2js/converter/whilepost.rb +1 -1
  30. data/lib/ruby2js/converter/xstr.rb +2 -3
  31. data/lib/ruby2js/demo.rb +53 -0
  32. data/lib/ruby2js/es2022.rb +5 -0
  33. data/lib/ruby2js/es2022/strict.rb +3 -0
  34. data/lib/ruby2js/filter.rb +9 -1
  35. data/lib/ruby2js/filter/active_functions.rb +44 -0
  36. data/lib/ruby2js/filter/camelCase.rb +6 -3
  37. data/lib/ruby2js/filter/cjs.rb +2 -0
  38. data/lib/ruby2js/filter/esm.rb +118 -26
  39. data/lib/ruby2js/filter/functions.rb +137 -109
  40. data/lib/ruby2js/filter/{wunderbar.rb → jsx.rb} +29 -7
  41. data/lib/ruby2js/filter/node.rb +58 -14
  42. data/lib/ruby2js/filter/nokogiri.rb +12 -12
  43. data/lib/ruby2js/filter/react.rb +182 -57
  44. data/lib/ruby2js/filter/require.rb +102 -11
  45. data/lib/ruby2js/filter/return.rb +13 -1
  46. data/lib/ruby2js/filter/stimulus.rb +187 -0
  47. data/lib/ruby2js/jsx.rb +309 -0
  48. data/lib/ruby2js/namespace.rb +75 -0
  49. data/lib/ruby2js/serializer.rb +19 -12
  50. data/lib/ruby2js/sprockets.rb +40 -0
  51. data/lib/ruby2js/version.rb +3 -3
  52. data/ruby2js.gemspec +2 -2
  53. metadata +23 -13
  54. data/lib/ruby2js/filter/esm_migration.rb +0 -72
  55. data/lib/ruby2js/rails.rb +0 -63
@@ -8,6 +8,11 @@ module Ruby2JS
8
8
  # (int 1))
9
9
 
10
10
  handle :lvasgn, :gvasgn do |name, value=nil|
11
+ if @ast.type == :lvasgn and value
12
+ receiver = @rbstack.map {|rb| rb[name]}.compact.last
13
+ return parse s(:attr, receiver, "#{name}=", value) if receiver
14
+ end
15
+
11
16
  state = @state
12
17
  begin
13
18
  if value and value.type == :lvasgn and @state == :statement
@@ -22,7 +22,7 @@ module Ruby2JS
22
22
  end
23
23
  end
24
24
 
25
- put 'while ('; parse condition; puts ') {'; scope block; sput '}'
25
+ put 'while ('; parse condition; puts ') {'; redoable block; sput '}'
26
26
  ensure
27
27
  @next_token = next_token
28
28
  end
@@ -11,7 +11,7 @@ module Ruby2JS
11
11
  begin
12
12
  next_token, @next_token = @next_token, :continue
13
13
 
14
- puts 'do {'; scope block; sput '} while ('; parse condition; put ')'
14
+ puts 'do {'; redoable block; sput '} while ('; parse condition; put ')'
15
15
  ensure
16
16
  @next_token = next_token
17
17
  end
@@ -5,12 +5,11 @@ module Ruby2JS
5
5
  # (str 'a'))
6
6
 
7
7
  handle :xstr do |*children|
8
- str = eval capture { parse_all(*children) }
9
-
10
8
  if @binding
9
+ str = eval capture { parse_all(*children) }
11
10
  puts @binding.eval(str).to_s
12
11
  else
13
- puts eval(str).to_s
12
+ raise SecurityError.new('Insecure operation, eval without binding option')
14
13
  end
15
14
  end
16
15
  end
@@ -0,0 +1,53 @@
1
+ # helper methods shared between MRI CGI/server and Opal implementations
2
+ require 'ruby2js'
3
+
4
+ module Ruby2JS
5
+ module Demo
6
+ def self.parse_autoimports(mappings)
7
+ autoimports = {}
8
+
9
+ mappings = mappings.gsub(/\s+|"|'/, '')
10
+
11
+ while mappings and not mappings.empty?
12
+ if mappings =~ /^(\w+):([^,]+)(,(.*))?$/
13
+ # symbol: module
14
+ autoimports[$1.to_sym] = $2
15
+ mappings = $4
16
+ elsif mappings =~ /^\[([\w,]+)\]:([^,]+)(,(.*))?$/
17
+ # [symbol, symbol]: module
18
+ mname, mappings = $2, $4
19
+ autoimports[$1.split(/,/).map(&:to_sym)] = mname
20
+ elsif mappings =~ /^(\w+)(,(.*))?$/
21
+ # symbol
22
+ autoimports[$1.to_sym] = $1
23
+ mappings = $3
24
+ elsif not mappings.empty?
25
+ $load_error = "unsupported autoimports mapping: #{mappings}"
26
+ mappings = ''
27
+ end
28
+ end
29
+
30
+ # if nothing is listed, provide a mapping for everything
31
+ autoimports = proc {|name| name.to_s} if autoimports.empty?
32
+
33
+ autoimports
34
+ end
35
+
36
+ def self.parse_defs(mappings)
37
+ defs = {}
38
+
39
+ mappings = mappings.gsub(/\s+|"|'/, '')
40
+
41
+ while mappings =~ /^(\w+):\[(:?@?\w+(,:?@?\w+)*)\](,(.*))?$/
42
+ mappings = $5
43
+ defs[$1.to_sym] = $2.gsub(':', '').split(',').map(&:to_sym)
44
+ end
45
+
46
+ if mappings and not mappings.empty?
47
+ $load_error = "unsupported defs: #{mappings}"
48
+ end
49
+
50
+ defs
51
+ end
52
+ end
53
+ end
@@ -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,11 @@ module Ruby2JS
9
9
  module CamelCase
10
10
  include SEXP
11
11
 
12
- WHITELIST = %w{
12
+ ALLOWLIST = %w{
13
13
  attr_accessor
14
+ attr_reader
15
+ attr_writer
16
+ method_missing
14
17
  }
15
18
 
16
19
  CAPS_EXCEPTIONS = {
@@ -37,7 +40,7 @@ module Ruby2JS
37
40
  node = super
38
41
  return node unless [:send, :csend, :attr].include? node.type
39
42
 
40
- if node.children[0] == nil and WHITELIST.include? node.children[1].to_s
43
+ if node.children[0] == nil and ALLOWLIST.include? node.children[1].to_s
41
44
  node
42
45
  elsif node.children[0] && [:ivar, :cvar].include?(node.children[0].type)
43
46
  S(node.type, s(node.children[0].type, camelCase(node.children[0].children[0])),
@@ -61,7 +64,7 @@ module Ruby2JS
61
64
  def handle_generic_node(node, node_type)
62
65
  return node if node.type != node_type
63
66
 
64
- if node.children[0] =~ /_.*\w$/
67
+ if node.children[0] =~ /_.*\w$/ and !ALLOWLIST.include?(node.children[0].to_s)
65
68
  S(node_type , camelCase(node.children[0]), *node.children[1..-1])
66
69
  else
67
70
  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
@@ -7,30 +7,77 @@ module Ruby2JS
7
7
  module ESM
8
8
  include SEXP
9
9
 
10
- def initialize(*args)
11
- super
12
- @esm = true # signal for other filters
13
- @esm_imports = nil
14
- end
15
-
16
10
  def options=(options)
17
11
  super
12
+ @esm_autoexports = !@disable_autoexports && options[:autoexports]
18
13
  @esm_autoimports = options[:autoimports]
19
- return unless @esm_autoimports
14
+ @esm_defs = options[:defs] || {}
15
+ @esm_explicit_tokens = Set.new
20
16
  end
21
17
 
22
18
  def process(node)
23
- return super if @esm_imports or not @esm_autoimports
24
- @esm_imports = Set.new
25
- result = super
19
+ return super unless @esm_autoexports
26
20
 
27
- if @esm_imports.empty?
28
- result
29
- else
30
- s(:begin, *@esm_imports.to_a.map {|token|
31
- s(:import, @esm_autoimports[token], s(:const, nil, token))
32
- }, result)
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
33
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
+
80
+ super
34
81
  end
35
82
 
36
83
  def on_send(node)
@@ -46,7 +93,7 @@ module Ruby2JS
46
93
  end
47
94
  end
48
95
 
49
- if args[0].type == :str
96
+ if args[0].type == :str and args.length == 1
50
97
  # import "file.css"
51
98
  # => import "file.css"
52
99
  s(:import, args[0].children[0])
@@ -59,6 +106,8 @@ module Ruby2JS
59
106
  args[0].children[2].children[2].type == :str
60
107
  # import name from "file.js"
61
108
  # => import name from "file.js"
109
+ @esm_explicit_tokens << args[0].children[1]
110
+
62
111
  s(:import,
63
112
  [args[0].children[2].children[2].children[0]],
64
113
  process(s(:attr, nil, args[0].children[1])))
@@ -72,15 +121,25 @@ module Ruby2JS
72
121
  # => import Stuff as * from "file.js"
73
122
  # import [ Some, Stuff ], from: "file.js"
74
123
  # => import { Some, Stuff } from "file.js"
75
- imports = (args[0].type == :const || args[0].type == :send) ?
76
- process(args[0]) :
77
- process_all(args[0].children)
78
- 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?
79
138
  end
80
139
  elsif method == :export
81
140
  s(:export, *process_all(args))
82
- elsif @esm_imports and args.length == 0 and @esm_autoimports[method]
83
- @esm_imports.add(method)
141
+ elsif target.nil? and found_import = find_autoimport(method)
142
+ prepend_list << s(:import, found_import[0], found_import[1])
84
143
  super
85
144
  else
86
145
  super
@@ -88,12 +147,45 @@ module Ruby2JS
88
147
  end
89
148
 
90
149
  def on_const(node)
91
- return super unless @esm_autoimports
92
- if node.children.first == nil and @esm_autoimports[node.children.last]
93
- @esm_imports.add(node.children.last)
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
94
166
  end
167
+
95
168
  super
96
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
97
189
  end
98
190
 
99
191
  DEFAULTS.push ESM
@@ -1,5 +1,6 @@
1
1
  require 'ruby2js'
2
- require 'regexp_parser'
2
+
3
+ require 'regexp_parser/scanner'
3
4
 
4
5
  module Ruby2JS
5
6
  module Filter
@@ -7,7 +8,7 @@ module Ruby2JS
7
8
  include SEXP
8
9
 
9
10
  # require explicit opt-in to to class => constructor mapping
10
- Filter.exclude :class
11
+ Filter.exclude :class, :call
11
12
 
12
13
  VAR_TO_ASSIGN = {
13
14
  lvar: :lvasgn,
@@ -23,91 +24,101 @@ module Ruby2JS
23
24
 
24
25
  def on_send(node)
25
26
  target, method, *args = node.children
26
- return super if excluded?(method)
27
+ return super if excluded?(method) and method != :call
27
28
 
28
29
  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))
30
+ if target.type == :array
31
+ process S(:send, s(:const, nil, :Math), node.children[1],
32
+ *target.children)
33
+ elsif node.is_method?
34
+ process S(:send, s(:const, nil, :Math), node.children[1],
35
+ s(:splat, target))
36
+ else
37
+ return super
38
+ end
32
39
 
33
- elsif method == :call and target and target.type == :ivar
34
- process S(:send, s(:self), "_#{target.children.first.to_s[1..-1]}",
35
- *args)
40
+ elsif method == :call and target and
41
+ (%i[ivar cvar].include?(target.type) or not excluded?(:call))
36
42
 
37
- elsif method == :call and target and target.type == :cvar
38
- process S(:send, s(:attr, s(:self), :constructor),
39
- "_#{target.children.first.to_s[2..-1]}", *args)
43
+ S(:call, process(target), nil, *process_all(args))
40
44
 
41
45
  elsif method == :keys and args.length == 0 and node.is_method?
42
46
  process S(:send, s(:const, nil, :Object), :keys, target)
43
47
 
44
48
  elsif method == :[]= and args.length == 3 and
45
49
  args[0].type == :regexp and args[1].type == :int
50
+
46
51
  index = args[1].children.first
47
52
 
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
53
+ # identify groups
54
+ regex = args[0].children.first.children.first
55
+ tokens = Regexp::Scanner.scan(regex)
56
+ groups = []
57
+ stack = []
58
+ tokens.each do |token|
59
+ next unless token[0] == :group
60
+ if token[1] == :capture
61
+ groups.push token.dup
62
+ return super if groups.length == index and not stack.empty?
63
+ stack.push groups.last
64
+ elsif token[1] == :close
65
+ stack.pop[-1]=token.last
66
+ end
51
67
  end
68
+ group = groups[index-1]
52
69
 
53
- return super unless split
70
+ # rewrite regex
71
+ prepend = nil
72
+ append = nil
54
73
 
55
- rewritten = parts[split].to_s
56
-
57
- dstr = [args.last]
58
- if rewritten == '()'
59
- index -= 1
60
- rewritten = ''
74
+ if group[4] < regex.length
75
+ regex = (regex[0...group[4]] + '(' + regex[group[4]..-1] + ')').
76
+ sub(/\$\)$/, ')$')
77
+ append = 2
61
78
  end
62
79
 
63
- parts = parts.to_a
64
-
65
- pre = ''
66
- if parts.first.type == :anchor
67
- pre = parts.shift.to_s
68
- split -= 1
80
+ if group[4] - group[3] == 2
81
+ regex = regex[0...group[3]] + regex[group[4]..-1]
82
+ append = 1 if append
69
83
  end
70
84
 
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))
85
+ if group[3] > 0
86
+ regex = ('(' + regex[0...group[3]] + ')' + regex[group[3]..-1]).
87
+ sub(/^\(\^/, '^(')
88
+ prepend = 1
89
+ append += 1 if append
79
90
  end
80
91
 
92
+ regex = process s(:regexp, s(:str, regex), args[0].children.last)
81
93
 
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 + ')'
94
+ #
95
+ if args.last.type == :str
96
+ str = args.last.children.first.gsub('$', '$$')
97
+ str = "$#{prepend}#{str}" if prepend
98
+ str = "#{str}$#{append}" if append
99
+ expr = s(:send, target, :replace, regex, s(:str, str))
100
+ else
101
+ dstr = args.last.type == :dstr ? args.last.children.dup : [args.last]
102
+ if prepend
103
+ dstr.unshift s(:send, s(:lvar, :match), :[], s(:int, prepend-1))
104
+ end
105
+ if append
106
+ dstr << s(:send, s(:lvar, :match), :[], s(:int, append-1))
92
107
  end
93
- dstr << s(:send, s(:lvar, :match), :[], s(:int, index))
94
- end
95
-
96
- rewritten = pre + rewritten + post
97
108
 
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)))
109
+ expr = s(:block,
110
+ s(:send, target, :replace, regex),
111
+ s(:args, s(:arg, :match)),
112
+ process(s(:dstr, *dstr)))
113
+ end
103
114
 
104
115
  if VAR_TO_ASSIGN.keys.include? target.type
105
- S(VAR_TO_ASSIGN[target.type], target.children.first, block)
116
+ S(VAR_TO_ASSIGN[target.type], target.children.first, expr)
106
117
  elsif target.type == :send
107
118
  if target.children[0] == nil
108
- S(:lvasgn, target.children[1], block)
119
+ S(:lvasgn, target.children[1], expr)
109
120
  else
110
- S(:send, target.children[0], :"#{target.children[1]}=", block)
121
+ S(:send, target.children[0], :"#{target.children[1]}=", expr)
111
122
  end
112
123
  else
113
124
  super
@@ -116,57 +127,14 @@ module Ruby2JS
116
127
  elsif method == :merge
117
128
  args.unshift target
118
129
 
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
130
+ if es2018
131
+ process S(:hash, *args.map {|arg| s(:kwsplat, arg)})
126
132
  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, :$$)))), :[])
133
+ process S(:assign, s(:hash), *args)
141
134
  end
142
135
 
143
136
  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
137
+ process S(:assign, target, *args)
170
138
 
171
139
  elsif method == :delete and args.length == 1
172
140
  if not target
@@ -242,10 +210,10 @@ module Ruby2JS
242
210
  s(:block, s(:send, nil, :proc), s(:args, s(:arg, :s)),
243
211
  s(:send, s(:lvar, :s), :slice, s(:int, 1))))
244
212
  else
245
- # str.match(/.../g).map(s => s.match(/.../).slice(1))
213
+ # (str.match(/.../g) || []).map(s => s.match(/.../).slice(1))
246
214
  s(:block, s(:send,
247
- s(:send, process(target), :match, gpattern), :map),
248
- s(:args, s(:arg, :s)),
215
+ s(:or, s(:send, process(target), :match, gpattern), s(:array)),
216
+ :map), s(:args, s(:arg, :s)),
249
217
  s(:return, s(:send, s(:send, s(:lvar, :s), :match, arg),
250
218
  :slice, s(:int, 1))))
251
219
  end
@@ -540,6 +508,55 @@ module Ruby2JS
540
508
  elsif method == :floor and args.length == 0
541
509
  process S(:send, s(:const, nil, :Math), :floor, target)
542
510
 
511
+ elsif method == :rand and target == nil
512
+ if args.length == 0
513
+ process S(:send!, s(:const, nil, :Math), :random)
514
+ elsif %i[irange erange].include? args.first.type
515
+ range = args.first
516
+ multiplier = s(:send, range.children.last, :-, range.children.first)
517
+ if range.children.all? {|child| child.type == :int}
518
+ multiplier = s(:int, range.children.last.children.last - range.children.first.children.last)
519
+ multiplier = s(:int, multiplier.children.first + 1) if range.type == :irange
520
+ elsif range.type == :irange
521
+ if multiplier.children.last.type == :int
522
+ diff = multiplier.children.last.children.last - 1
523
+ multiplier = s(:send, *multiplier.children[0..1], s(:int, diff))
524
+ multiplier = multiplier.children.first if diff == 0
525
+ multiplier = s(:send, multiplier.children[0], :+, s(:int, -diff)) if diff < 0
526
+ else
527
+ multiplier = s(:send, multiplier, :+, s(:int, 1))
528
+ end
529
+ end
530
+ raw = s(:send, s(:send, s(:const, nil, :Math), :random), :*, multiplier)
531
+ if range.children.first != s(:int, 0)
532
+ raw = s(:send, raw, :+, range.children.first)
533
+ end
534
+ process S(:send, nil, :parseInt, raw)
535
+ else
536
+ process S(:send, nil, :parseInt,
537
+ s(:send, s(:send, s(:const, nil, :Math), :random),
538
+ :*, args.first))
539
+ end
540
+
541
+ elsif method == :sum and args.length == 0
542
+ process S(:send, target, :reduce, s(:block, s(:send, nil, :proc),
543
+ s(:args, s(:arg, :a), s(:arg, :b)),
544
+ s(:send, s(:lvar, :a), :+, s(:lvar, :b))), s(:int, 0))
545
+
546
+ elsif method == :method_defined? and args.length >= 1
547
+ if args[1] == s(:false)
548
+ process S(:send, s(:attr, target, :prototype), :hasOwnProperty, args[0])
549
+ elsif args.length == 1 or args[1] == s(:true)
550
+ process S(:in?, args[0], s(:attr, target, :prototype))
551
+ else
552
+ process S(:if, args[1], s(:in?, args[0], s(:attr, target, :prototype)),
553
+ s(:send, s(:attr, target, :prototype), :hasOwnProperty, args[0]))
554
+ end
555
+
556
+ elsif method == :alias_method and args.length == 2
557
+ process S(:send, s(:attr, target, :prototype), :[]=, args[0],
558
+ s(:attr, s(:attr, target, :prototype), args[1].children[0]))
559
+
543
560
  else
544
561
  super
545
562
  end
@@ -726,6 +743,10 @@ module Ruby2JS
726
743
  s(:return, s(:lvar, node.children[1].children[0].children[0])))),
727
744
  :[], call.children[0]])
728
745
 
746
+ elsif method == :define_method and call.children.length == 3
747
+ process node.updated(:send, [s(:attr, call.children[0], :prototype), :[]=,
748
+ call.children[2], s(:deff, nil, *node.children[1..-1])])
749
+
729
750
  else
730
751
  super
731
752
  end
@@ -735,6 +756,12 @@ module Ruby2JS
735
756
  name, inheritance, *body = node.children
736
757
  body.compact!
737
758
 
759
+ body.each_with_index do |node, i|
760
+ if node.type == :send and node.children[0..1] == [nil, :alias_method]
761
+ body[i] = node.updated(:send, [name, *node.children[1..-1]])
762
+ end
763
+ end
764
+
738
765
  if inheritance == s(:const, nil, :Exception)
739
766
  unless
740
767
  body.any? {|statement| statement.type == :def and
@@ -750,7 +777,8 @@ module Ruby2JS
750
777
  body = [s(:begin, *body)] if body.length > 1
751
778
  S(:class, name, s(:const, nil, :Error), *body)
752
779
  else
753
- super
780
+ body = [s(:begin, *body)] if body.length > 1
781
+ super S(:class, name, inheritance, *body)
754
782
  end
755
783
  end
756
784
  end