ruby2js 3.5.3 → 4.0.2

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