ruby2js 3.5.2 → 4.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -665
  3. data/lib/ruby2js.rb +47 -13
  4. data/lib/ruby2js/converter.rb +9 -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/hash.rb +28 -6
  14. data/lib/ruby2js/converter/hide.rb +13 -0
  15. data/lib/ruby2js/converter/if.rb +10 -2
  16. data/lib/ruby2js/converter/import.rb +19 -4
  17. data/lib/ruby2js/converter/kwbegin.rb +9 -2
  18. data/lib/ruby2js/converter/literal.rb +14 -2
  19. data/lib/ruby2js/converter/logical.rb +1 -1
  20. data/lib/ruby2js/converter/module.rb +41 -4
  21. data/lib/ruby2js/converter/opasgn.rb +8 -0
  22. data/lib/ruby2js/converter/return.rb +2 -1
  23. data/lib/ruby2js/converter/send.rb +73 -8
  24. data/lib/ruby2js/converter/vasgn.rb +5 -0
  25. data/lib/ruby2js/converter/xstr.rb +2 -3
  26. data/lib/ruby2js/demo.rb +53 -0
  27. data/lib/ruby2js/es2022.rb +5 -0
  28. data/lib/ruby2js/es2022/strict.rb +3 -0
  29. data/lib/ruby2js/filter.rb +9 -1
  30. data/lib/ruby2js/filter/active_functions.rb +44 -0
  31. data/lib/ruby2js/filter/camelCase.rb +6 -3
  32. data/lib/ruby2js/filter/cjs.rb +2 -0
  33. data/lib/ruby2js/filter/esm.rb +118 -26
  34. data/lib/ruby2js/filter/functions.rb +104 -106
  35. data/lib/ruby2js/filter/{wunderbar.rb → jsx.rb} +29 -7
  36. data/lib/ruby2js/filter/node.rb +58 -14
  37. data/lib/ruby2js/filter/nokogiri.rb +12 -12
  38. data/lib/ruby2js/filter/react.rb +192 -57
  39. data/lib/ruby2js/filter/require.rb +102 -11
  40. data/lib/ruby2js/filter/return.rb +13 -1
  41. data/lib/ruby2js/filter/stimulus.rb +185 -0
  42. data/lib/ruby2js/jsx.rb +309 -0
  43. data/lib/ruby2js/namespace.rb +75 -0
  44. data/lib/ruby2js/rails.rb +15 -9
  45. data/lib/ruby2js/serializer.rb +3 -1
  46. data/lib/ruby2js/version.rb +3 -3
  47. data/ruby2js.gemspec +2 -2
  48. metadata +17 -9
  49. data/lib/ruby2js/filter/esm_migration.rb +0 -72
  50. data/lib/ruby2js/filter/fast-deep-equal.rb +0 -23
@@ -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
@@ -71,6 +85,7 @@ module Ruby2JS
71
85
  @react_props = []
72
86
  @react_methods = []
73
87
  @react_filter_functions = false
88
+ @react_imports = false
74
89
  @jsx = false
75
90
  super
76
91
  end
@@ -88,8 +103,17 @@ module Ruby2JS
88
103
  end
89
104
 
90
105
  if \
91
- defined? Ruby2JS::Filter::Wunderbar and
92
- filters.include? Ruby2JS::Filter::Wunderbar
106
+ (defined? Ruby2JS::Filter::ESM and
107
+ filters.include? Ruby2JS::Filter::ESM) or
108
+ (defined? Ruby2JS::Filter::CJS and
109
+ filters.include? Ruby2JS::Filter::CJS)
110
+ then
111
+ @react_imports = true
112
+ end
113
+
114
+ if \
115
+ defined? Ruby2JS::Filter::JSX and
116
+ filters.include? Ruby2JS::Filter::JSX
93
117
  then
94
118
  @jsx = true
95
119
  end
@@ -106,8 +130,11 @@ module Ruby2JS
106
130
  return super unless cname.children.first == nil
107
131
  return super unless inheritance == s(:const, nil, :React) or
108
132
  inheritance == s(:const, nil, :Vue) or
133
+ inheritance == s(:const, s(:const, nil, :React), :Component) or
109
134
  inheritance == s(:send, s(:const, nil, :React), :Component)
110
135
 
136
+ prepend_list << REACT_IMPORTS[:React] if @react_imports
137
+
111
138
  # traverse down to actual list of class statements
112
139
  if body.length == 1
113
140
  if not body.first
@@ -156,24 +183,46 @@ module Ruby2JS
156
183
  end
157
184
  end
158
185
 
159
- # collect instance methods (including getters and setters)
186
+ # collect instance methods (including getters and setters)
160
187
  @react_props = []
161
188
  @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
189
+ body.each do |statement|
190
+ if statement.type == :def
191
+ method = statement.children.first
192
+ unless method == :initialize
193
+ if method.to_s.end_with? '='
194
+ method = method.to_s[0..-2].to_sym
195
+ @react_props << method unless @react_props.include? method
196
+ elsif statement.is_method?
197
+ @react_methods << method unless @react_methods.include? method
198
+ else
199
+ @react_props << method unless @react_props.include? method
200
+ end
201
+ end
202
+ end
203
+ end
204
+
205
+ # determine which instance methods need binding
206
+ needs_binding = []
207
+ scan_events = lambda do |list|
208
+ list.each do |node|
209
+ next unless Parser::AST::Node === node
210
+ node = process node if node.type == :xstr
211
+ if node.type == :hash
212
+ node.children.each do |pair|
213
+ value = pair.children.last
214
+ if value.type == :send and \
215
+ @react_methods.include? value.children[1] and \
216
+ [nil, s(:self), s(:send, nil, :this)].include? value.children[0]
217
+
218
+ needs_binding << value.children[1]
219
+ end
220
+ end
221
+ end
222
+ scan_events[node.children]
223
+ end
224
+ end
225
+ scan_events[body] unless es2015
177
226
 
178
227
  # append statics (if any)
179
228
  unless statics.empty?
@@ -181,9 +230,9 @@ module Ruby2JS
181
230
  end
182
231
 
183
232
  # create a default getInitialState method if there is no such method
184
- # and there are references to instance variables.
233
+ # and there are either references to instance variables or there are
234
+ # methods that need to be bound.
185
235
  if \
186
- not es2015 and
187
236
  not body.any? do |child|
188
237
  child.type == :def and
189
238
  [:getInitialState, :initialize].include? child.children.first
@@ -191,9 +240,11 @@ module Ruby2JS
191
240
  then
192
241
  @reactIvars = {pre: [], post: [], asgn: [], ref: [], cond: []}
193
242
  react_walk(node)
194
- unless @reactIvars.values.flatten.empty?
243
+ if not es2015 and not @reactIvars.values.flatten.empty?
195
244
  body = [s(:def, :getInitialState, s(:args),
196
245
  s(:return, s(:hash))), *body]
246
+ elsif not needs_binding.empty? or not @reactIvars.values.flatten.empty?
247
+ body = [s(:def, :initialize, s(:args)), *body]
197
248
  end
198
249
  end
199
250
 
@@ -221,9 +272,22 @@ module Ruby2JS
221
272
  end
222
273
  end
223
274
 
275
+ # add props argument if there is a reference to a prop
276
+ if args.children.length == 0
277
+ has_cvar = lambda {|list|
278
+ list.any? {|node|
279
+ next unless Parser::AST::Node === node
280
+ return true if node.type == :cvar
281
+ has_cvar.call(node.children)
282
+ }
283
+ }
284
+ args = s(:args, s(:arg, 'prop$')) if has_cvar[block]
285
+ end
286
+
224
287
  # peel off the initial set of instance variable assignment stmts
225
288
  assigns = []
226
289
  block = block.dup
290
+ block.shift if block.first == s(:zsuper)
227
291
  while not block.empty? and block.first.type == :ivasgn
228
292
  node = block.shift
229
293
  vars = [node.children.first]
@@ -240,9 +304,15 @@ module Ruby2JS
240
304
  state = s(:hash, *assigns.map {|anode| s(:pair, s(:str,
241
305
  anode.children.first.to_s[1..-1]), anode.children.last)})
242
306
 
307
+ # bind methods as needed
308
+ needs_binding.each do |method|
309
+ block.push(s(:send, s(:self), "#{method}=",
310
+ s(:send, s(:attr, s(:self), method), :bind, s(:self))))
311
+ end
312
+
243
313
  # modify block to build and/or return state
244
314
  if mname == :initialize
245
- block.unshift(s(:send, s(:self), :state=, state))
315
+ block.unshift(s(:zsuper), s(:send, s(:self), :state=, state))
246
316
  elsif block.empty?
247
317
  block = [s(:return, state)]
248
318
  else
@@ -250,10 +320,10 @@ module Ruby2JS
250
320
  block.push(s(:return, s(:attr, s(:self), :state)))
251
321
  end
252
322
 
253
- elsif mname == :render
323
+ elsif mname == :render and not react_wunderbar_free(block, true)
254
324
  if \
255
325
  block.length != 1 or not block.last or
256
- not [:send, :block].include? block.last.type
326
+ not %i[send block xstr].include? block.last.type
257
327
  then
258
328
  if @jsx
259
329
  while block.length == 1 and block.first.type == :begin
@@ -271,9 +341,9 @@ module Ruby2JS
271
341
  block = [*prolog, s(:return,
272
342
  s(:xnode, '', *process_all(block)))]
273
343
  else
274
- # wrap multi-line blocks with a 'span' element
344
+ # wrap multi-line blocks with a React Fragment
275
345
  block = [s(:return,
276
- s(:block, s(:send, nil, :_span), s(:args), *block))]
346
+ s(:block, s(:send, nil, ReactFragment), s(:args), *block))]
277
347
  end
278
348
  end
279
349
 
@@ -300,7 +370,10 @@ module Ruby2JS
300
370
  end
301
371
 
302
372
  if es2015
303
- pairs << s(:def, mname, args, process(s(type, *block)))
373
+ pairs << child.updated(
374
+ ReactLifecycle.include?(mname.to_s) ? :defm : :def,
375
+ [mname, args, process(s(type, *block))]
376
+ )
304
377
  else
305
378
  pairs << s(:pair, s(:sym, mname), child.updated(:block,
306
379
  [s(:send, nil, :proc), args, process(s(type, *block))]))
@@ -369,8 +442,12 @@ module Ruby2JS
369
442
  # enable React filtering within React class method calls or
370
443
  # React component calls
371
444
  if \
372
- node.children.first == s(:const, nil, :React)
445
+ node.children.first == s(:const, nil, :React) or
446
+ node.children.first == s(:const, nil, :ReactDOM)
373
447
  then
448
+ if @react_imports
449
+ prepend_list << REACT_IMPORTS[node.children.first.children.last]
450
+ end
374
451
 
375
452
  begin
376
453
  react, @react = @react, true
@@ -429,6 +506,10 @@ module Ruby2JS
429
506
  # :block arguments are inserted by on_block logic below
430
507
  block = child
431
508
 
509
+ elsif child.type == :splat
510
+ # arrays need not be expanded
511
+ text = child.children.first
512
+
432
513
  else
433
514
  # everything else added as text
434
515
  text = child
@@ -586,6 +667,9 @@ module Ruby2JS
586
667
  next true if arg.children[1] == :createElement and
587
668
  arg.children[0] == s(:const, nil, :Vue)
588
669
 
670
+ # JSX
671
+ next true if arg.type == :xstr
672
+
589
673
  # wunderbar style call
590
674
  arg = arg.children.first if arg.type == :block
591
675
  while arg.type == :send and arg.children.first != nil
@@ -598,7 +682,19 @@ module Ruby2JS
598
682
  if simple
599
683
  # in the normal case, process each argument
600
684
  reactApply, @reactApply = @reactApply, false
601
- params += args.map {|arg| process(arg)}
685
+ args.each do |arg|
686
+ arg = process(arg)
687
+ if arg.type == :send and
688
+ arg.children[0] == s(:const, nil, :React) and
689
+ arg.children[1] == :createElement and
690
+ arg.children[2] == s(:const, nil, "React.Fragment") and
691
+ arg.children[3] == s(:nil)
692
+ then
693
+ params += arg.children[4..-1]
694
+ else
695
+ params << arg
696
+ end
697
+ end
602
698
  else
603
699
  reactApply, @reactApply = @reactApply, true
604
700
 
@@ -860,7 +956,15 @@ module Ruby2JS
860
956
  end
861
957
 
862
958
  # wunderbar style calls
863
- if !@jsx and child.children[0] == nil and child.children[1] =~ /^_\w/
959
+ if child.children[0] == nil and child.children[1] == :_ and \
960
+ node.children[1].children.empty? and !@jsx
961
+
962
+ block = s(:block, s(:send, nil, :proc), s(:args),
963
+ *node.children[2..-1])
964
+ return on_send node.children.first.updated(:send,
965
+ [nil, ReactFragment, block])
966
+
967
+ elsif !@jsx and child.children[0] == nil and child.children[1] =~ /^_\w/
864
968
  if node.children[1].children.empty?
865
969
  # append block as a standalone proc
866
970
  block = s(:block, s(:send, nil, :proc), s(:args),
@@ -871,9 +975,18 @@ module Ruby2JS
871
975
  # iterate over Enumerable arguments if there are args present
872
976
  send = node.children.first.children
873
977
  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]))
978
+ if node.children.length == 3 and
979
+ node.children.last.respond_to? :type and
980
+ node.children.last.type == :send
981
+
982
+ return process s(:send, *send[0..1], *send[3..-1],
983
+ s(:splat, s(:block, s(:send, send[2], :map),
984
+ node.children[1], s(:return, node.children[2]))))
985
+ else
986
+ return process s(:block, s(:send, *send[0..1], *send[3..-1]),
987
+ s(:args), s(:block, s(:send, send[2], :forEach),
988
+ *node.children[1..-1]))
989
+ end
877
990
  end
878
991
  end
879
992
 
@@ -885,6 +998,13 @@ module Ruby2JS
885
998
  end
886
999
  end
887
1000
 
1001
+ def on_lvasgn(node)
1002
+ return super unless @reactClass
1003
+ return super unless @react_props.include? node.children.first
1004
+ node.updated(:send, [s(:self), "#{node.children.first}=",
1005
+ node.children.last])
1006
+ end
1007
+
888
1008
  # convert global variables to refs
889
1009
  def on_gvar(node)
890
1010
  return super unless @reactClass
@@ -993,7 +1113,7 @@ module Ruby2JS
993
1113
  end
994
1114
 
995
1115
  # is this a "wunderbar" style call or createElement?
996
- def react_element?(node)
1116
+ def react_element?(node, wunderbar_only=false)
997
1117
  return false unless node
998
1118
 
999
1119
  forEach = [:forEach]
@@ -1001,15 +1121,17 @@ module Ruby2JS
1001
1121
 
1002
1122
  return true if node.type == :block and
1003
1123
  forEach.include? node.children.first.children.last and
1004
- react_element?(node.children.last)
1124
+ react_element?(node.children.last, wunderbar_only)
1005
1125
 
1006
- # explicit call to React.createElement
1007
- return true if node.children[1] == :createElement and
1008
- node.children[0] == s(:const, nil, :React)
1126
+ unless wunderbar_only
1127
+ # explicit call to React.createElement
1128
+ return true if node.children[1] == :createElement and
1129
+ node.children[0] == s(:const, nil, :React)
1009
1130
 
1010
- # explicit call to Vue.createElement
1011
- return true if node.children[1] == :createElement and
1012
- node.children[0] == s(:const, nil, :Vue)
1131
+ # explicit call to Vue.createElement
1132
+ return true if node.children[1] == :createElement and
1133
+ node.children[0] == s(:const, nil, :Vue)
1134
+ end
1013
1135
 
1014
1136
  # wunderbar style call
1015
1137
  node = node.children.first if node.type == :block
@@ -1021,13 +1143,14 @@ module Ruby2JS
1021
1143
 
1022
1144
  # ensure that there are no "wunderbar" or "createElement" calls in
1023
1145
  # a set of statements.
1024
- def react_wunderbar_free(nodes)
1146
+ def react_wunderbar_free(nodes, wunderbar_only=false)
1025
1147
  nodes.each do |node|
1026
1148
  if Parser::AST::Node === node
1027
- return false if react_element?(node)
1149
+ return false if node.type == :xstr
1150
+ return false if react_element?(node, wunderbar_only)
1028
1151
 
1029
1152
  # recurse
1030
- return false unless react_wunderbar_free(node.children)
1153
+ return false unless react_wunderbar_free(node.children, wunderbar_only)
1031
1154
  end
1032
1155
  end
1033
1156
 
@@ -1181,6 +1304,18 @@ module Ruby2JS
1181
1304
 
1182
1305
  block
1183
1306
  end
1307
+
1308
+ def on_xstr(node)
1309
+ loc = node.loc
1310
+ return super unless loc
1311
+ source = loc.begin.source_buffer.source
1312
+ source = source[loc.begin.end_pos...loc.end.begin_pos].strip
1313
+ return super unless @reactClass or source.start_with? '<'
1314
+ source = Ruby2JS.jsx2_rb(source)
1315
+ ast = Ruby2JS.parse(source).first
1316
+ ast = s(:block, s(:send, nil, :_), s(:args), ast) if ast.type == :begin
1317
+ process ast
1318
+ end
1184
1319
  end
1185
1320
 
1186
1321
  DEFAULTS.push React