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
@@ -1,17 +1,19 @@
1
1
  require 'ruby2js'
2
2
 
3
+ # Convert Wunderbar syntax to JSX
4
+
3
5
  module Ruby2JS
4
6
  module Filter
5
- module Wunderbar
7
+ module JSX
6
8
  include SEXP
7
9
 
8
10
  def on_send(node)
9
- target, method, *attrs = node.children
11
+ target, method, *args = node.children
10
12
 
11
13
  if target == s(:const, nil, :Wunderbar)
12
14
  if [:debug, :info, :warn, :error, :fatal].include? method
13
15
  method = :error if method == :fatal
14
- return node.updated(nil, [s(:const, nil, :console), method, *attrs])
16
+ return node.updated(nil, [s(:const, nil, :console), method, *args])
15
17
  end
16
18
  end
17
19
 
@@ -27,7 +29,17 @@ module Ruby2JS
27
29
  end
28
30
 
29
31
  if target == nil and method.to_s.start_with? "_"
30
- S(:xnode, *method.to_s[1..-1], *stack, *process_all(attrs))
32
+ S(:xnode, method.to_s[1..-1], *stack, *process_all(args))
33
+
34
+ elsif method == :createElement and target == s(:const, nil, :React)
35
+ if args.first.type == :str and \
36
+ (args.length == 1 or %i(nil hash).include? args[1].type)
37
+ attrs = (args[1]&.type != :nil && args[1]) || s(:hash)
38
+ S(:xnode, args[0].children.first, attrs, *process_all(args[2..-1]))
39
+ else
40
+ super
41
+ end
42
+
31
43
  else
32
44
  super
33
45
  end
@@ -42,8 +54,18 @@ module Ruby2JS
42
54
 
43
55
  if target == nil and method.to_s.start_with? "_"
44
56
  if args.children.empty?
45
- # append block as a standalone proc
46
- process send.updated(nil, [*send.children, *process_all(block)])
57
+ if method == :_
58
+ # Fragment
59
+ if send.children.length == 2
60
+ process send.updated(:xnode, ['', *process_all(block)])
61
+ else
62
+ process s(:xnode, 'React.Fragment', *send.children[2..-1],
63
+ *process_all(block))
64
+ end
65
+ else
66
+ # append block as a standalone proc
67
+ process send.updated(nil, [*send.children, *process_all(block)])
68
+ end
47
69
  else
48
70
  # iterate over Enumerable arguments if there are args present
49
71
  send = send.children
@@ -58,6 +80,6 @@ module Ruby2JS
58
80
  end
59
81
  end
60
82
 
61
- DEFAULTS.push Wunderbar
83
+ DEFAULTS.push JSX
62
84
  end
63
85
  end
@@ -14,6 +14,10 @@ module Ruby2JS
14
14
 
15
15
  IMPORT_FS = s(:import, ['fs'], s(:attr, nil, :fs))
16
16
 
17
+ IMPORT_OS = s(:import, ['os'], s(:attr, nil, :os))
18
+
19
+ IMPORT_PATH = s(:import, ['path'], s(:attr, nil, :path))
20
+
17
21
  SETUP_ARGV = s(:lvasgn, :ARGV, s(:send, s(:attr,
18
22
  s(:attr, nil, :process), :argv), :slice, s(:int, 2)))
19
23
 
@@ -31,11 +35,11 @@ module Ruby2JS
31
35
  prepend_list << IMPORT_CHILD_PROCESS
32
36
 
33
37
  if args.length == 1
34
- s(:send, s(:attr, nil, :child_process), :execSync,
38
+ S(:send, s(:attr, nil, :child_process), :execSync,
35
39
  process(args.first),
36
40
  s(:hash, s(:pair, s(:sym, :stdio), s(:str, 'inherit'))))
37
41
  else
38
- s(:send, s(:attr, nil, :child_process), :execFileSync,
42
+ S(:send, s(:attr, nil, :child_process), :execFileSync,
39
43
  process(args.first), s(:array, *process_all(args[1..-1])),
40
44
  s(:hash, s(:pair, s(:sym, :stdio), s(:str, 'inherit'))))
41
45
  end
@@ -57,7 +61,7 @@ module Ruby2JS
57
61
  then
58
62
  if method == :read and args.length == 1
59
63
  prepend_list << IMPORT_FS
60
- s(:send, s(:attr, nil, :fs), :readFileSync, *process_all(args),
64
+ S(:send, s(:attr, nil, :fs), :readFileSync, *process_all(args),
61
65
  s(:str, 'utf8'))
62
66
 
63
67
  elsif method == :write and args.length == 2
@@ -111,15 +115,15 @@ module Ruby2JS
111
115
 
112
116
  elsif method == :symlink and args.length == 2
113
117
  prepend_list << IMPORT_FS
114
- s(:send, s(:attr, nil, :fs), :symlinkSync, *process_all(args))
118
+ S(:send, s(:attr, nil, :fs), :symlinkSync, *process_all(args))
115
119
 
116
120
  elsif method == :truncate and args.length == 2
117
121
  prepend_list << IMPORT_FS
118
- s(:send, s(:attr, nil, :fs), :truncateSync, *process_all(args))
122
+ S(:send, s(:attr, nil, :fs), :truncateSync, *process_all(args))
119
123
 
120
124
  elsif [:stat, :lstat].include? method and args.length == 1
121
125
  prepend_list << IMPORT_FS
122
- s(:send, s(:attr, nil, :fs), method.to_s + 'Sync',
126
+ S(:send, s(:attr, nil, :fs), method.to_s + 'Sync',
123
127
  process(args.first))
124
128
 
125
129
  elsif method == :unlink and args.length == 1
@@ -128,6 +132,29 @@ module Ruby2JS
128
132
  S(:send, s(:attr, nil, :fs), :unlinkSync, process(file))
129
133
  })
130
134
 
135
+ elsif target.children.last == :File
136
+ if method == :absolute_path
137
+ prepend_list << IMPORT_PATH
138
+ S(:send, s(:attr, nil, :path), :resolve,
139
+ *process_all(args.reverse))
140
+ elsif method == :absolute_path?
141
+ prepend_list << IMPORT_PATH
142
+ S(:send, s(:attr, nil, :path), :isAbsolute, *process_all(args))
143
+ elsif method == :basename
144
+ prepend_list << IMPORT_PATH
145
+ S(:send, s(:attr, nil, :path), :basename, *process_all(args))
146
+ elsif method == :dirname
147
+ prepend_list << IMPORT_PATH
148
+ S(:send, s(:attr, nil, :path), :dirname, *process_all(args))
149
+ elsif method == :extname
150
+ prepend_list << IMPORT_PATH
151
+ S(:send, s(:attr, nil, :path), :extname, *process_all(args))
152
+ elsif method == :join
153
+ prepend_list << IMPORT_PATH
154
+ S(:send, s(:attr, nil, :path), :join, *process_all(args))
155
+ else
156
+ super
157
+ end
131
158
  else
132
159
  super
133
160
  end
@@ -163,7 +190,7 @@ module Ruby2JS
163
190
  S(:send, s(:attr, nil, :process), :chdir, *process_all(args))
164
191
 
165
192
  elsif method == :pwd and args.length == 0
166
- s(:send, s(:attr, nil, :process), :cwd)
193
+ S(:send!, s(:attr, nil, :process), :cwd)
167
194
 
168
195
  elsif method == :rmdir and args.length == 1
169
196
  prepend_list << IMPORT_FS
@@ -173,11 +200,11 @@ module Ruby2JS
173
200
 
174
201
  elsif method == :ln and args.length == 2
175
202
  prepend_list << IMPORT_FS
176
- s(:send, s(:attr, nil, :fs), :linkSync, *process_all(args))
203
+ S(:send, s(:attr, nil, :fs), :linkSync, *process_all(args))
177
204
 
178
205
  elsif method == :ln_s and args.length == 2
179
206
  prepend_list << IMPORT_FS
180
- s(:send, s(:attr, nil, :fs), :symlinkSync, *process_all(args))
207
+ S(:send, s(:attr, nil, :fs), :symlinkSync, *process_all(args))
181
208
 
182
209
  elsif method == :rm and args.length == 1
183
210
  prepend_list << IMPORT_FS
@@ -224,16 +251,16 @@ module Ruby2JS
224
251
  if method == :chdir and args.length == 1
225
252
  S(:send, s(:attr, nil, :process), :chdir, *process_all(args))
226
253
  elsif method == :pwd and args.length == 0
227
- s(:send, s(:attr, nil, :process), :cwd)
254
+ S(:send!, s(:attr, nil, :process), :cwd)
228
255
  elsif method == :entries
229
256
  prepend_list << IMPORT_FS
230
- s(:send, s(:attr, nil, :fs), :readdirSync, *process_all(args))
257
+ S(:send, s(:attr, nil, :fs), :readdirSync, *process_all(args))
231
258
  elsif method == :mkdir and args.length == 1
232
259
  prepend_list << IMPORT_FS
233
- s(:send, s(:attr, nil, :fs), :mkdirSync, process(args.first))
260
+ S(:send, s(:attr, nil, :fs), :mkdirSync, process(args.first))
234
261
  elsif method == :rmdir and args.length == 1
235
262
  prepend_list << IMPORT_FS
236
- s(:send, s(:attr, nil, :fs), :rmdirSync, process(args.first))
263
+ S(:send, s(:attr, nil, :fs), :rmdirSync, process(args.first))
237
264
  elsif method == :mktmpdir and args.length <=1
238
265
  prepend_list << IMPORT_FS
239
266
  if args.length == 0
@@ -244,7 +271,14 @@ module Ruby2JS
244
271
  prefix = args.first
245
272
  end
246
273
 
247
- s(:send, s(:attr, nil, :fs), :mkdtempSync, process(prefix))
274
+ S(:send, s(:attr, nil, :fs), :mkdtempSync, process(prefix))
275
+ elsif method == :home and args.length == 0
276
+ prepend_list << IMPORT_OS
277
+ S(:send!, s(:attr, nil, :os), :homedir)
278
+ elsif method == :tmpdir and args.length == 0
279
+ prepend_list << IMPORT_OS
280
+ S(:send!, s(:attr, nil, :os), :tmpdir)
281
+
248
282
  else
249
283
  super
250
284
  end
@@ -285,6 +319,16 @@ module Ruby2JS
285
319
  S(:attr, s(:attr, nil, :process), :stdout)
286
320
  elsif node.children == [nil, :STDERR]
287
321
  S(:attr, s(:attr, nil, :process), :stderr)
322
+ elsif node.children.first == s(:const, nil, :File)
323
+ if node.children.last == :SEPARATOR
324
+ prepend_list << IMPORT_PATH
325
+ S(:attr, s(:attr, nil, :path), :sep)
326
+ elsif node.children.last == :PATH_SEPARATOR
327
+ prepend_list << IMPORT_PATH
328
+ S(:attr, s(:attr, nil, :path), :delimiter)
329
+ else
330
+ super
331
+ end
288
332
  else
289
333
  super
290
334
  end
@@ -47,40 +47,40 @@ module Ruby2JS
47
47
  method == :at and
48
48
  args.length == 1 and args.first.type == :str
49
49
  then
50
- S(:send, target, :querySelector, process(args.first))
50
+ S(:send, process(target), :querySelector, process(args.first))
51
51
 
52
52
  elsif \
53
53
  method == :search and
54
54
  args.length == 1 and args.first.type == :str
55
55
  then
56
- S(:send, target, :querySelectorAll, process(args.first))
56
+ S(:send, process(target), :querySelectorAll, process(args.first))
57
57
 
58
58
  elsif method === :parent and args.length == 0
59
- S(:attr, target, :parentNode)
59
+ S(:attr, process(target), :parentNode)
60
60
 
61
61
  elsif method === :name and args.length == 0
62
- S(:attr, target, :nodeName)
62
+ S(:attr, process(target), :nodeName)
63
63
 
64
64
  elsif [:text, :content].include? method and args.length == 0
65
- S(:attr, target, :textContent)
65
+ S(:attr, process(target), :textContent)
66
66
 
67
67
  elsif method == :content= and args.length == 1
68
- S(:send, target, :textContent=, *process_all(args))
68
+ S(:send, process(target), :textContent=, *process_all(args))
69
69
 
70
70
  elsif method === :inner_html and args.length == 0
71
- S(:attr, target, :innerHTML)
71
+ S(:attr, process(target), :innerHTML)
72
72
 
73
73
  elsif method == :inner_html= and args.length == 1
74
- S(:send, target, :innerHTML=, *process_all(args))
74
+ S(:send, process(target), :innerHTML=, *process_all(args))
75
75
 
76
76
  elsif method === :to_html and args.length == 0
77
- S(:attr, target, :outerHTML)
77
+ S(:attr, process(target), :outerHTML)
78
78
 
79
79
  elsif \
80
80
  [:attr, :get_attribute].include? method and
81
81
  args.length == 1 and args.first.type == :str
82
82
  then
83
- S(:send, target, :getAttribute, process(args.first))
83
+ S(:send, process(target), :getAttribute, process(args.first))
84
84
 
85
85
  elsif \
86
86
  [:key?, :has_attribute].include? method and
@@ -154,14 +154,14 @@ module Ruby2JS
154
154
  [:add_next_sibling, :next=, :after].include? method and
155
155
  args.length == 1
156
156
  then
157
- S(:send, s(:attr, target, :parentNode), :insertBefore,
157
+ S(:send, s(:attr, process(target), :parentNode), :insertBefore,
158
158
  process(args.first), s(:attr, target, :nextSibling))
159
159
 
160
160
  elsif \
161
161
  [:add_previous_sibling, :previous=, :before].include? method and
162
162
  args.length == 1
163
163
  then
164
- S(:send, s(:attr, target, :parentNode), :insertBefore,
164
+ S(:send, s(:attr, process(target), :parentNode), :insertBefore,
165
165
  process(args.first), target)
166
166
 
167
167
  elsif method == :prepend_child and args.length == 1
@@ -17,30 +17,39 @@
17
17
  # * ~"x" becomes document.querySelector("x")
18
18
  #
19
19
  require 'ruby2js'
20
+ require 'ruby2js/jsx'
20
21
 
21
22
  module Ruby2JS
22
23
  module Filter
23
24
  module React
24
25
  include SEXP
26
+ extend SEXP
27
+
28
+ REACT_IMPORTS = {
29
+ React: s(:import, ['React'], s(:attr, nil, :React)),
30
+ ReactDOM: s(:import, ['ReactDOM'], s(:attr, nil, :ReactDOM))
31
+ }
25
32
 
26
33
  # the following command can be used to generate ReactAttrs:
27
34
  #
28
35
  # ruby -r ruby2js/filter/react -e "Ruby2JS::Filter::React.genAttrs"
29
36
  #
30
37
  def self.genAttrs
31
- require 'nokogumbo'
32
- page = 'https://facebook.github.io/react/docs/tags-and-attributes.html'
33
- doc = Nokogiri::HTML5.get(page)
34
-
35
- # delete contents of page prior to the list of supported attributes
36
- attrs = doc.at('a[name=supported-attributes]')
37
- attrs = attrs.parent while attrs and not attrs.name.start_with? 'h'
38
- attrs.previous_sibling.remove while attrs and attrs.previous_sibling
39
-
40
- # extract attribute names with uppercase chars from code and format
41
- attrs = doc.search('code').map(&:text).join(' ')
42
- attrs = attrs.split(/\s+/).grep(/[A-Z]/).sort.uniq.join(' ')
43
- puts "ReactAttrs = %w(#{attrs})".gsub(/(.{1,72})(\s+|\Z)/, "\\1\n")
38
+ unless RUBY_ENGINE == 'opal'
39
+ require 'nokogumbo'
40
+ page = 'https://facebook.github.io/react/docs/tags-and-attributes.html'
41
+ doc = Nokogiri::HTML5.get(page)
42
+
43
+ # delete contents of page prior to the list of supported attributes
44
+ attrs = doc.at('a[name=supported-attributes]')
45
+ attrs = attrs.parent while attrs and not attrs.name.start_with? 'h'
46
+ attrs.previous_sibling.remove while attrs and attrs.previous_sibling
47
+
48
+ # extract attribute names with uppercase chars from code and format
49
+ attrs = doc.search('code').map(&:text).join(' ')
50
+ attrs = attrs.split(/\s+/).grep(/[A-Z]/).sort.uniq.join(' ')
51
+ puts "ReactAttrs = %w(#{attrs})".gsub(/(.{1,72})(\s+|\Z)/, "\\1\n")
52
+ end
44
53
  end
45
54
 
46
55
  # list of react attributes that require special processing
@@ -60,8 +69,13 @@ module Ruby2JS
60
69
  xlinkActuate xlinkArcrole xlinkHref xlinkRole xlinkShow xlinkTitle
61
70
  xlinkType xmlBase xmlLang xmlSpace)
62
71
 
72
+ ReactLifecycle = %w(render componentDidMount shouldComponentUpdate
73
+ getShapshotBeforeUpdate componentDidUpdate componentWillUnmount
74
+ componentDidCatch)
75
+
63
76
  ReactAttrMap = Hash[ReactAttrs.map {|name| [name.downcase, name]}]
64
77
  ReactAttrMap['for'] = 'htmlFor'
78
+ ReactFragment = :'_React.Fragment'
65
79
 
66
80
  def initialize(*args)
67
81
  @react = nil
@@ -88,8 +102,8 @@ module Ruby2JS
88
102
  end
89
103
 
90
104
  if \
91
- defined? Ruby2JS::Filter::Wunderbar and
92
- filters.include? Ruby2JS::Filter::Wunderbar
105
+ defined? Ruby2JS::Filter::JSX and
106
+ filters.include? Ruby2JS::Filter::JSX
93
107
  then
94
108
  @jsx = true
95
109
  end
@@ -106,8 +120,11 @@ module Ruby2JS
106
120
  return super unless cname.children.first == nil
107
121
  return super unless inheritance == s(:const, nil, :React) or
108
122
  inheritance == s(:const, nil, :Vue) or
123
+ inheritance == s(:const, s(:const, nil, :React), :Component) or
109
124
  inheritance == s(:send, s(:const, nil, :React), :Component)
110
125
 
126
+ prepend_list << REACT_IMPORTS[:React] if modules_enabled?
127
+
111
128
  # traverse down to actual list of class statements
112
129
  if body.length == 1
113
130
  if not body.first
@@ -156,24 +173,46 @@ module Ruby2JS
156
173
  end
157
174
  end
158
175
 
159
- # collect instance methods (including getters and setters)
176
+ # collect instance methods (including getters and setters)
160
177
  @react_props = []
161
178
  @react_methods = []
162
- body.each do |statement|
163
- if statement.type == :def
164
- method = statement.children.first
165
- unless method == :initialize
166
- if method.to_s.end_with? '='
167
- method = method.to_s[0..-2].to_sym
168
- @react_props << method unless @react_props.include? method
169
- elsif statement.is_method?
170
- @react_methods << method unless @react_methods.include? method
171
- else
172
- @react_props << method unless @react_props.include? method
173
- end
174
- end
175
- end
176
- end
179
+ body.each do |statement|
180
+ if statement.type == :def
181
+ method = statement.children.first
182
+ unless method == :initialize
183
+ if method.to_s.end_with? '='
184
+ method = method.to_s[0..-2].to_sym
185
+ @react_props << method unless @react_props.include? method
186
+ elsif statement.is_method?
187
+ @react_methods << method unless @react_methods.include? method
188
+ else
189
+ @react_props << method unless @react_props.include? method
190
+ end
191
+ end
192
+ end
193
+ end
194
+
195
+ # determine which instance methods need binding
196
+ needs_binding = []
197
+ scan_events = lambda do |list|
198
+ list.each do |node|
199
+ next unless Parser::AST::Node === node
200
+ node = process node if node.type == :xstr
201
+ if node.type == :hash
202
+ node.children.each do |pair|
203
+ value = pair.children.last
204
+ if value.type == :send and \
205
+ @react_methods.include? value.children[1] and \
206
+ [nil, s(:self), s(:send, nil, :this)].include? value.children[0]
207
+
208
+ needs_binding << value.children[1]
209
+ end
210
+ end
211
+ end
212
+ scan_events[node.children]
213
+ end
214
+ end
215
+ scan_events[body] unless es2015
177
216
 
178
217
  # append statics (if any)
179
218
  unless statics.empty?
@@ -181,9 +220,9 @@ module Ruby2JS
181
220
  end
182
221
 
183
222
  # create a default getInitialState method if there is no such method
184
- # and there are references to instance variables.
223
+ # and there are either references to instance variables or there are
224
+ # methods that need to be bound.
185
225
  if \
186
- not es2015 and
187
226
  not body.any? do |child|
188
227
  child.type == :def and
189
228
  [:getInitialState, :initialize].include? child.children.first
@@ -191,9 +230,11 @@ module Ruby2JS
191
230
  then
192
231
  @reactIvars = {pre: [], post: [], asgn: [], ref: [], cond: []}
193
232
  react_walk(node)
194
- unless @reactIvars.values.flatten.empty?
233
+ if not es2015 and not @reactIvars.values.flatten.empty?
195
234
  body = [s(:def, :getInitialState, s(:args),
196
235
  s(:return, s(:hash))), *body]
236
+ elsif not needs_binding.empty? or not @reactIvars.values.flatten.empty?
237
+ body = [s(:def, :initialize, s(:args)), *body]
197
238
  end
198
239
  end
199
240
 
@@ -221,9 +262,22 @@ module Ruby2JS
221
262
  end
222
263
  end
223
264
 
265
+ # add props argument if there is a reference to a prop
266
+ if args.children.length == 0
267
+ has_cvar = lambda {|list|
268
+ list.any? {|node|
269
+ next unless Parser::AST::Node === node
270
+ return true if node.type == :cvar
271
+ has_cvar.call(node.children)
272
+ }
273
+ }
274
+ args = s(:args, s(:arg, 'prop$')) if has_cvar[block]
275
+ end
276
+
224
277
  # peel off the initial set of instance variable assignment stmts
225
278
  assigns = []
226
279
  block = block.dup
280
+ block.shift if block.first == s(:zsuper)
227
281
  while not block.empty? and block.first.type == :ivasgn
228
282
  node = block.shift
229
283
  vars = [node.children.first]
@@ -240,9 +294,15 @@ module Ruby2JS
240
294
  state = s(:hash, *assigns.map {|anode| s(:pair, s(:str,
241
295
  anode.children.first.to_s[1..-1]), anode.children.last)})
242
296
 
297
+ # bind methods as needed
298
+ needs_binding.each do |method|
299
+ block.push(s(:send, s(:self), "#{method}=",
300
+ s(:send, s(:attr, s(:self), method), :bind, s(:self))))
301
+ end
302
+
243
303
  # modify block to build and/or return state
244
304
  if mname == :initialize
245
- block.unshift(s(:send, s(:self), :state=, state))
305
+ block.unshift(s(:zsuper), s(:send, s(:self), :state=, state))
246
306
  elsif block.empty?
247
307
  block = [s(:return, state)]
248
308
  else
@@ -250,10 +310,10 @@ module Ruby2JS
250
310
  block.push(s(:return, s(:attr, s(:self), :state)))
251
311
  end
252
312
 
253
- elsif mname == :render
313
+ elsif mname == :render and not react_wunderbar_free(block, true)
254
314
  if \
255
315
  block.length != 1 or not block.last or
256
- not [:send, :block].include? block.last.type
316
+ not %i[send block xstr].include? block.last.type
257
317
  then
258
318
  if @jsx
259
319
  while block.length == 1 and block.first.type == :begin
@@ -271,9 +331,9 @@ module Ruby2JS
271
331
  block = [*prolog, s(:return,
272
332
  s(:xnode, '', *process_all(block)))]
273
333
  else
274
- # wrap multi-line blocks with a 'span' element
334
+ # wrap multi-line blocks with a React Fragment
275
335
  block = [s(:return,
276
- s(:block, s(:send, nil, :_span), s(:args), *block))]
336
+ s(:block, s(:send, nil, ReactFragment), s(:args), *block))]
277
337
  end
278
338
  end
279
339
 
@@ -300,7 +360,10 @@ module Ruby2JS
300
360
  end
301
361
 
302
362
  if es2015
303
- pairs << s(:def, mname, args, process(s(type, *block)))
363
+ pairs << child.updated(
364
+ ReactLifecycle.include?(mname.to_s) ? :defm : :def,
365
+ [mname, args, process(s(type, *block))]
366
+ )
304
367
  else
305
368
  pairs << s(:pair, s(:sym, mname), child.updated(:block,
306
369
  [s(:send, nil, :proc), args, process(s(type, *block))]))
@@ -369,8 +432,12 @@ module Ruby2JS
369
432
  # enable React filtering within React class method calls or
370
433
  # React component calls
371
434
  if \
372
- node.children.first == s(:const, nil, :React)
435
+ node.children.first == s(:const, nil, :React) or
436
+ node.children.first == s(:const, nil, :ReactDOM)
373
437
  then
438
+ if modules_enabled?
439
+ prepend_list << REACT_IMPORTS[node.children.first.children.last]
440
+ end
374
441
 
375
442
  begin
376
443
  react, @react = @react, true
@@ -429,6 +496,10 @@ module Ruby2JS
429
496
  # :block arguments are inserted by on_block logic below
430
497
  block = child
431
498
 
499
+ elsif child.type == :splat
500
+ # arrays need not be expanded
501
+ text = child.children.first
502
+
432
503
  else
433
504
  # everything else added as text
434
505
  text = child
@@ -586,6 +657,9 @@ module Ruby2JS
586
657
  next true if arg.children[1] == :createElement and
587
658
  arg.children[0] == s(:const, nil, :Vue)
588
659
 
660
+ # JSX
661
+ next true if arg.type == :xstr
662
+
589
663
  # wunderbar style call
590
664
  arg = arg.children.first if arg.type == :block
591
665
  while arg.type == :send and arg.children.first != nil
@@ -598,7 +672,19 @@ module Ruby2JS
598
672
  if simple
599
673
  # in the normal case, process each argument
600
674
  reactApply, @reactApply = @reactApply, false
601
- params += args.map {|arg| process(arg)}
675
+ args.each do |arg|
676
+ arg = process(arg)
677
+ if arg.type == :send and
678
+ arg.children[0] == s(:const, nil, :React) and
679
+ arg.children[1] == :createElement and
680
+ arg.children[2] == s(:const, nil, "React.Fragment") and
681
+ arg.children[3] == s(:nil)
682
+ then
683
+ params += arg.children[4..-1]
684
+ else
685
+ params << arg
686
+ end
687
+ end
602
688
  else
603
689
  reactApply, @reactApply = @reactApply, true
604
690
 
@@ -860,7 +946,15 @@ module Ruby2JS
860
946
  end
861
947
 
862
948
  # wunderbar style calls
863
- if !@jsx and child.children[0] == nil and child.children[1] =~ /^_\w/
949
+ if child.children[0] == nil and child.children[1] == :_ and \
950
+ node.children[1].children.empty? and !@jsx
951
+
952
+ block = s(:block, s(:send, nil, :proc), s(:args),
953
+ *node.children[2..-1])
954
+ return on_send node.children.first.updated(:send,
955
+ [nil, ReactFragment, block])
956
+
957
+ elsif !@jsx and child.children[0] == nil and child.children[1] =~ /^_\w/
864
958
  if node.children[1].children.empty?
865
959
  # append block as a standalone proc
866
960
  block = s(:block, s(:send, nil, :proc), s(:args),
@@ -871,9 +965,18 @@ module Ruby2JS
871
965
  # iterate over Enumerable arguments if there are args present
872
966
  send = node.children.first.children
873
967
  return super if send.length < 3
874
- return process s(:block, s(:send, *send[0..1], *send[3..-1]),
875
- s(:args), s(:block, s(:send, send[2], :forEach),
876
- *node.children[1..-1]))
968
+ if node.children.length == 3 and
969
+ node.children.last.respond_to? :type and
970
+ node.children.last.type == :send
971
+
972
+ return process s(:send, *send[0..1], *send[3..-1],
973
+ s(:splat, s(:block, s(:send, send[2], :map),
974
+ node.children[1], s(:return, node.children[2]))))
975
+ else
976
+ return process s(:block, s(:send, *send[0..1], *send[3..-1]),
977
+ s(:args), s(:block, s(:send, send[2], :forEach),
978
+ *node.children[1..-1]))
979
+ end
877
980
  end
878
981
  end
879
982
 
@@ -885,6 +988,13 @@ module Ruby2JS
885
988
  end
886
989
  end
887
990
 
991
+ def on_lvasgn(node)
992
+ return super unless @reactClass
993
+ return super unless @react_props.include? node.children.first
994
+ node.updated(:send, [s(:self), "#{node.children.first}=",
995
+ node.children.last])
996
+ end
997
+
888
998
  # convert global variables to refs
889
999
  def on_gvar(node)
890
1000
  return super unless @reactClass
@@ -993,7 +1103,7 @@ module Ruby2JS
993
1103
  end
994
1104
 
995
1105
  # is this a "wunderbar" style call or createElement?
996
- def react_element?(node)
1106
+ def react_element?(node, wunderbar_only=false)
997
1107
  return false unless node
998
1108
 
999
1109
  forEach = [:forEach]
@@ -1001,15 +1111,17 @@ module Ruby2JS
1001
1111
 
1002
1112
  return true if node.type == :block and
1003
1113
  forEach.include? node.children.first.children.last and
1004
- react_element?(node.children.last)
1114
+ react_element?(node.children.last, wunderbar_only)
1005
1115
 
1006
- # explicit call to React.createElement
1007
- return true if node.children[1] == :createElement and
1008
- node.children[0] == s(:const, nil, :React)
1116
+ unless wunderbar_only
1117
+ # explicit call to React.createElement
1118
+ return true if node.children[1] == :createElement and
1119
+ node.children[0] == s(:const, nil, :React)
1009
1120
 
1010
- # explicit call to Vue.createElement
1011
- return true if node.children[1] == :createElement and
1012
- node.children[0] == s(:const, nil, :Vue)
1121
+ # explicit call to Vue.createElement
1122
+ return true if node.children[1] == :createElement and
1123
+ node.children[0] == s(:const, nil, :Vue)
1124
+ end
1013
1125
 
1014
1126
  # wunderbar style call
1015
1127
  node = node.children.first if node.type == :block
@@ -1021,13 +1133,14 @@ module Ruby2JS
1021
1133
 
1022
1134
  # ensure that there are no "wunderbar" or "createElement" calls in
1023
1135
  # a set of statements.
1024
- def react_wunderbar_free(nodes)
1136
+ def react_wunderbar_free(nodes, wunderbar_only=false)
1025
1137
  nodes.each do |node|
1026
1138
  if Parser::AST::Node === node
1027
- return false if react_element?(node)
1139
+ return false if node.type == :xstr
1140
+ return false if react_element?(node, wunderbar_only)
1028
1141
 
1029
1142
  # recurse
1030
- return false unless react_wunderbar_free(node.children)
1143
+ return false unless react_wunderbar_free(node.children, wunderbar_only)
1031
1144
  end
1032
1145
  end
1033
1146
 
@@ -1181,6 +1294,18 @@ module Ruby2JS
1181
1294
 
1182
1295
  block
1183
1296
  end
1297
+
1298
+ def on_xstr(node)
1299
+ loc = node.loc
1300
+ return super unless loc
1301
+ source = loc.begin.source_buffer.source
1302
+ source = source[loc.begin.end_pos...loc.end.begin_pos].strip
1303
+ return super unless @reactClass or source.start_with? '<'
1304
+ source = Ruby2JS.jsx2_rb(source)
1305
+ ast = Ruby2JS.parse(source).first
1306
+ ast = s(:block, s(:send, nil, :_), s(:args), ast) if ast.type == :begin
1307
+ process ast
1308
+ end
1184
1309
  end
1185
1310
 
1186
1311
  DEFAULTS.push React