ruby2js 4.0.5 → 4.1.4

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6728e4201e77b477a9203551b57de3f23b36077f6012061233d4b072d92886b9
4
- data.tar.gz: 7d5e6ef7aa5b9add1477b3cc642cc55714a657f7f962dbb6dd7af6688d45cfb0
3
+ metadata.gz: 7ff40ad496d47d0ec4fa430f6d04b47b1c0d7ae6f152c479da8de49a9f506a00
4
+ data.tar.gz: 84de6a0f644de7716d9e094f2944d896c49cafaca082523d4947a5d8f567eddc
5
5
  SHA512:
6
- metadata.gz: 321dc88308946ff626914ceb87fa93f93bec4f5cde0b660cab75d7df46b52f9ca63da830e7fab4f070b989586ef468b3cdf9d8f682f64b44ee06fef9df5230f7
7
- data.tar.gz: 22f5161f0bc03c294fbfd3f32be407030fea293985e1eff4c7d0fc464bd57aaa6a3cfc70e1bf9ed13e9bbb0dbbac9f394b860e76df52ae236b494accd86c840b
6
+ metadata.gz: 7f34521a4ef3677997f541721dfc5a0eb8e870a567ec2198b9b98e1f0d15270160bc076a070e0e5728b77a6622ba4ed0c133f20c9086dbec1cbb1d736bbe28a2
7
+ data.tar.gz: 9196de55781c332df1183361bcf1428f51ccebb1e38eab2b2ab9f7e3b30b6502b4176eefac0539eb3079906b955e138ee95971822ea59a775a36af5ca27a8aa5
@@ -359,6 +359,7 @@ require 'ruby2js/converter/kwbegin'
359
359
  require 'ruby2js/converter/literal'
360
360
  require 'ruby2js/converter/logical'
361
361
  require 'ruby2js/converter/masgn'
362
+ require 'ruby2js/converter/match'
362
363
  require 'ruby2js/converter/module'
363
364
  require 'ruby2js/converter/next'
364
365
  require 'ruby2js/converter/nil'
@@ -36,7 +36,8 @@ module Ruby2JS
36
36
  end
37
37
 
38
38
  if inheritance
39
- init = s(:def, :initialize, s(:args), s(:super))
39
+ parent = @namespace.find(inheritance)&.[](:constructor)
40
+ init = s(:def, :initialize, parent || s(:args), s(:zsuper))
40
41
  else
41
42
  init = s(:def, :initialize, s(:args), nil)
42
43
  end
@@ -58,7 +59,7 @@ module Ruby2JS
58
59
  m = m.updated(:def, m.children[1..-1])
59
60
  end
60
61
 
61
- node = if m.type == :def
62
+ node = if %i(def defm deff).include? m.type
62
63
  if m.children.first == :initialize and !visible[:initialize]
63
64
  # constructor: remove from body and overwrite init function
64
65
  init = m
@@ -89,7 +90,7 @@ module Ruby2JS
89
90
  end
90
91
  end
91
92
 
92
- elsif m.type == :defs and m.children.first == s(:self)
93
+ elsif %i(defs defp).include? m.type and m.children.first == s(:self)
93
94
  if m.children[1] =~ /=$/
94
95
  # class property setter
95
96
  s(:prop, name, m.children[1].to_s[0..-2] =>
@@ -313,7 +314,7 @@ module Ruby2JS
313
314
  # prepend constructor
314
315
  if init
315
316
  constructor = init.updated(:constructor, [name, *init.children[1..-1]])
316
- @comments[constructor] = @comments[init] unless @comments[init].empty?
317
+ visible[:constructor] = init.children[1]
317
318
 
318
319
  if @ast.type == :class_extend or extend
319
320
  if es2015
@@ -329,6 +330,7 @@ module Ruby2JS
329
330
  end
330
331
  end
331
332
 
333
+ @comments[constructor] = @comments[init] unless @comments[init].empty?
332
334
  body.unshift constructor
333
335
  end
334
336
 
@@ -349,7 +351,7 @@ module Ruby2JS
349
351
  self.ivars = ivars
350
352
  @class_name = class_name
351
353
  @class_parent = class_parent
352
- @rbstack.pop
354
+ @namespace.defineProps @rbstack.pop
353
355
  @namespace.leave unless @ast.type == :class_module
354
356
  end
355
357
  end
@@ -99,7 +99,6 @@ module Ruby2JS
99
99
  end
100
100
  walk[@ast]
101
101
 
102
- # process leading initializers in constructor
103
102
  while constructor.length == 1 and constructor.first.type == :begin
104
103
  constructor = constructor.first.children.dup
105
104
  end
@@ -116,6 +115,7 @@ module Ruby2JS
116
115
  put 'static #$' + cvar.to_s[2..-1]
117
116
  end
118
117
 
118
+ # process leading initializers in constructor
119
119
  while constructor.length > 0 and constructor.first.type == :ivasgn
120
120
  put(index == 0 ? @nl : @sep)
121
121
  index += 1
@@ -0,0 +1,7 @@
1
+ module Ruby2JS
2
+ class Converter
3
+ handle :match_pattern do |value, name|
4
+ parse @ast.updated(:lvasgn, [name.children.first, value]), @state
5
+ end
6
+ end
7
+ end
@@ -323,13 +323,13 @@ module Ruby2JS
323
323
  put 'typeof '; parse args.first
324
324
 
325
325
  elsif ast.children[1] == :is_a? and receiver and args.length == 1
326
- parse receiver; put ' instanceof '; parse args.first
326
+ put '('; parse receiver; put ' instanceof '; parse args.first; put ')'
327
327
 
328
328
  elsif ast.children[1] == :kind_of? and receiver and args.length == 1
329
- parse receiver; put ' instanceof '; parse args.first
329
+ put '('; parse receiver; put ' instanceof '; parse args.first; put ')'
330
330
 
331
331
  elsif ast.children[1] == :instance_of? and receiver and args.length == 1
332
- parse s(:send, s(:attr, receiver, :constructor), :==, args.first)
332
+ put '('; parse s(:send, s(:attr, receiver, :constructor), :==, args.first); put ')'
333
333
 
334
334
  else
335
335
  if method == :bind and receiver&.type == :send
@@ -10,8 +10,12 @@ module Ruby2JS
10
10
  # disable autobinding in tag literals
11
11
  save_autobind, @autobind = @autobind, false
12
12
 
13
- put tag.children.first
14
- parse_all(*children, join: '')
13
+ if es2015
14
+ put tag.children.first
15
+ parse_all(*children, join: '')
16
+ else
17
+ parse @ast.updated(:send, [nil, tag.children.last, *children])
18
+ end
15
19
  ensure
16
20
  @autobind = save_autobind
17
21
  end
@@ -14,6 +14,9 @@ module Ruby2JS
14
14
  attr_reader
15
15
  attr_writer
16
16
  method_missing
17
+ is_a?
18
+ kind_of?
19
+ instance_of?
17
20
  }
18
21
 
19
22
  CAPS_EXCEPTIONS = {
@@ -28,6 +31,8 @@ module Ruby2JS
28
31
  }
29
32
 
30
33
  def camelCase(symbol)
34
+ return symbol if ALLOWLIST.include?(symbol.to_s)
35
+
31
36
  should_symbolize = symbol.is_a?(Symbol)
32
37
  symbol = symbol
33
38
  .to_s
@@ -99,6 +104,10 @@ module Ruby2JS
99
104
  handle_generic_node(super, :arg)
100
105
  end
101
106
 
107
+ def on_kwarg(node)
108
+ handle_generic_node(super, :kwarg)
109
+ end
110
+
102
111
  def on_lvasgn(node)
103
112
  handle_generic_node(super, :lvasgn)
104
113
  end
@@ -111,6 +120,14 @@ module Ruby2JS
111
120
  handle_generic_node(super, :cvasgn)
112
121
  end
113
122
 
123
+ def on_match_pattern(node)
124
+ handle_generic_node(super, :match_pattern)
125
+ end
126
+
127
+ def on_match_var(node)
128
+ handle_generic_node(super, :match_var)
129
+ end
130
+
114
131
  def on_sym(node)
115
132
  handle_generic_node(super, :sym)
116
133
  end
@@ -7,6 +7,59 @@ module Ruby2JS
7
7
  module CJS
8
8
  include SEXP
9
9
 
10
+ def options=(options)
11
+ super
12
+ @cjs_autoexports = !@disable_autoexports && options[:autoexports]
13
+ end
14
+
15
+ def process(node)
16
+ return super unless @cjs_autoexports
17
+
18
+ list = [node]
19
+ while list.length == 1 and list.first.type == :begin
20
+ list = list.first.children.dup
21
+ end
22
+
23
+ replaced = []
24
+ list.map! do |child|
25
+ replacement = child
26
+
27
+ if [:module, :class].include? child.type and
28
+ child.children.first.type == :const and
29
+ child.children.first.children.first == nil \
30
+ then
31
+ replacement = s(:send, nil, :export, child)
32
+ elsif child.type == :casgn and child.children.first == nil
33
+ replacement = s(:send, nil, :export, child)
34
+ elsif child.type == :def
35
+ replacement = s(:send, nil, :export, child)
36
+ end
37
+
38
+ if replacement != child
39
+ replaced << replacement
40
+ @comments[replacement] = @comments[child] if @comments[child]
41
+ end
42
+
43
+ replacement
44
+ end
45
+
46
+ if replaced.length == 1 and @cjs_autoexports == :default
47
+ list.map! do |child|
48
+ if child == replaced.first
49
+ replacement = s(:send, nil, :export, s(:send, nil, :default,
50
+ *child.children[2..-1]))
51
+ @comments[replacement] = @comments[child] if @comments[child]
52
+ replacement
53
+ else
54
+ child
55
+ end
56
+ end
57
+ end
58
+
59
+ @cjs_autoexports = false
60
+ process s(:begin, *list)
61
+ end
62
+
10
63
  def on_send(node)
11
64
  return super unless node.children[1] == :export
12
65
 
@@ -23,9 +76,54 @@ module Ruby2JS
23
76
  node.updated(nil, [
24
77
  s(:attr, nil, :exports),
25
78
  assign.children[0].to_s + '=',
26
- *assign.children[1..-1]
79
+ *process_all(assign.children[1..-1])
27
80
  ])
28
81
 
82
+ elsif node.children[2].type == :casgn
83
+ assign = node.children[2]
84
+ if assign.children[0] == nil
85
+ node.updated(nil, [
86
+ s(:attr, nil, :exports),
87
+ assign.children[1].to_s + '=',
88
+ *process_all(assign.children[2..-1])
89
+ ])
90
+ else
91
+ node
92
+ end
93
+
94
+ elsif node.children[2].type == :class
95
+ assign = node.children[2]
96
+ if assign.children[0].children[0] != nil
97
+ node
98
+ elsif assign.children[1] == nil
99
+ node.updated(nil, [
100
+ s(:attr, nil, :exports),
101
+ assign.children[0].children[1].to_s + '=',
102
+ s(:block, s(:send, s(:const, nil, :Class), :new),
103
+ s(:args), *process_all(assign.children[2..-1]))
104
+ ])
105
+ else
106
+ node.updated(nil, [
107
+ s(:attr, nil, :exports),
108
+ assign.children[0].children[1].to_s + '=',
109
+ s(:block, s(:send, s(:const, nil, :Class), :new,
110
+ assign.children[1]), s(:args),
111
+ *process_all(assign.children[2..-1]))
112
+ ])
113
+ end
114
+
115
+ elsif node.children[2].type == :module
116
+ assign = node.children[2]
117
+ if assign.children[0].children[0] != nil
118
+ node
119
+ else
120
+ node.updated(nil, [
121
+ s(:attr, nil, :exports),
122
+ assign.children[0].children[1].to_s + '=',
123
+ s(:class_module, nil, nil, *process_all(assign.children[1..-1]))
124
+ ])
125
+ end
126
+
29
127
  elsif \
30
128
  node.children[2].type == :send and
31
129
  node.children[2].children[0..1] == [nil, :async] and
@@ -49,7 +147,7 @@ module Ruby2JS
49
147
  node.updated(nil, [
50
148
  s(:attr, nil, :module),
51
149
  :exports=,
52
- node.children[2]
150
+ process(node.children[2])
53
151
  ])
54
152
 
55
153
  else
@@ -227,14 +227,19 @@ module Ruby2JS
227
227
  if before.type == :regexp
228
228
  before = before.updated(:regexp, [*before.children[0...-1],
229
229
  s(:regopt, :g, *before.children.last)])
230
- elsif before.type == :str
230
+ elsif before.type == :str and not es2021
231
231
  before = before.updated(:regexp,
232
232
  [s(:str, Regexp.escape(before.children.first)), s(:regopt, :g)])
233
233
  end
234
234
  if after.type == :str
235
235
  after = s(:str, after.children.first.gsub(/\\(\d)/, "$\\1"))
236
236
  end
237
- process node.updated nil, [target, :replace, before, after]
237
+
238
+ if es2021
239
+ process node.updated nil, [target, :replaceAll, before, after]
240
+ else
241
+ process node.updated nil, [target, :replace, before, after]
242
+ end
238
243
 
239
244
  elsif method == :ord and args.length == 0
240
245
  if target.type == :str
@@ -767,10 +772,10 @@ module Ruby2JS
767
772
  body.any? {|statement| statement.type == :def and
768
773
  statement.children.first == :initialize}
769
774
  then
770
- body.unshift S(:def, :initialize, s(:args, s(:arg, :message)),
771
- s(:begin, S(:send, s(:self), :message=, s(:lvar, :message)),
772
- S(:send, s(:self), :name=, s(:sym, name.children[1])),
773
- S(:send, s(:self), :stack=, s(:send, s(:send, nil, :Error,
775
+ body.unshift s(:def, :initialize, s(:args, s(:arg, :message)),
776
+ s(:begin, s(:send, s(:self), :message=, s(:lvar, :message)),
777
+ s(:send, s(:self), :name=, s(:sym, name.children[1])),
778
+ s(:send, s(:self), :stack=, s(:attr, s(:send, nil, :Error,
774
779
  s(:lvar, :message)), :stack))))
775
780
  end
776
781
 
@@ -22,10 +22,19 @@ module Ruby2JS
22
22
 
23
23
  def on_ivasgn(node)
24
24
  return super unless @le_props&.include?(node.children.first)
25
+ return super unless node.children.length > 1
25
26
  s(:send, s(:self), node.children.first.to_s[1..-1]+'=',
26
27
  process(node.children[1]))
27
28
  end
28
29
 
30
+ def on_op_asgn(node)
31
+ return super unless node.children.first.type == :ivasgn
32
+ var = node.children.first.children.first
33
+ return super unless @le_props&.include?(var)
34
+ super node.updated(nil, [s(:attr, s(:attr, nil, :this),
35
+ var.to_s[1..-1]), *node.children[1..-1]])
36
+ end
37
+
29
38
  def on_class(node)
30
39
  cname, inheritance, *body = node.children
31
40
  return super unless inheritance == s(:const, nil, :LitElement)
@@ -66,9 +75,9 @@ module Ruby2JS
66
75
 
67
76
  # render of a string is converted to a taglit :html
68
77
  render = nodes.find_index {|child|
69
- child.type == :def and child.children.first == :render
78
+ child&.type == :def and child.children.first == :render
70
79
  }
71
- if render and %i[str dstr].include?(nodes[render].children[2].type)
80
+ if render and %i[str dstr].include?(nodes[render].children[2]&.type)
72
81
  nodes[render] = nodes[render].updated(:deff,
73
82
  [*nodes[render].children[0..1],
74
83
  s(:autoreturn, html_wrap(nodes[render].children[2]))])
@@ -76,9 +85,9 @@ module Ruby2JS
76
85
 
77
86
  # self.styles returning string is converted to a taglit :css
78
87
  styles = nodes.find_index {|child|
79
- child.type == :defs and child.children[0..1] == [s(:self), :styles]
88
+ child&.type == :defs and child.children[0..1] == [s(:self), :styles]
80
89
  }
81
- if styles and %i[str dstr].include?(nodes[styles].children[3].type)
90
+ if styles and %i[str dstr].include?(nodes[styles].children[3]&.type)
82
91
  string = nodes[styles].children[3]
83
92
  string = s(:dstr, string) if string.type == :str
84
93
  children = string.children.dup
@@ -100,13 +109,13 @@ module Ruby2JS
100
109
 
101
110
  # insert super calls into initializer
102
111
  initialize = nodes.find_index {|child|
103
- child.type == :def and child.children.first == :initialize
112
+ child&.type == :def and child.children.first == :initialize
104
113
  }
105
114
  if initialize and nodes[initialize].children.length == 3
106
- statements = nodes[initialize].children[2].children
115
+ statements = nodes[initialize].children[2..-1]
107
116
 
108
- if statements.length == 1 and statements.children.first.type == :begin
109
- statements = statements.children
117
+ if statements.length == 1 and statements.first.type == :begin
118
+ statements = statements.first.children
110
119
  end
111
120
 
112
121
  unless statements.any? {|statement| %i[super zuper].include? statement.type}
@@ -174,13 +183,18 @@ module Ruby2JS
174
183
  # analyze ivar usage
175
184
  def le_walk(node)
176
185
  node.children.each do |child|
177
- next unless Parser::AST::Node === child
178
- le_walk(child)
186
+ next unless child.is_a? Parser::AST::Node
179
187
 
180
188
  if child.type == :ivar
181
189
  @le_props[child.children.first] ||= nil
182
- elsif child.type == :ivasgn
183
- @le_props[child.children.first] = case child.children.last.type
190
+ elsif child.type == :ivasgn || child.type == :op_asgn
191
+ prop = child.children.first
192
+ unless prop.is_a? Symbol
193
+ prop = prop.children.first if prop.type == :ivasgn
194
+ next unless prop.is_a? Symbol
195
+ end
196
+
197
+ @le_props[prop] = case child.children.last.type
184
198
  when :str, :dstr
185
199
  :String
186
200
  when :array
@@ -190,8 +204,10 @@ module Ruby2JS
190
204
  when :true, :false
191
205
  :Boolean
192
206
  else
193
- @le_props[child.children.first] || :Object
207
+ @le_props[prop] || :Object
194
208
  end
209
+ else
210
+ le_walk(child)
195
211
  end
196
212
  end
197
213
  end
@@ -27,7 +27,12 @@ module Ruby2JS
27
27
 
28
28
  REACT_IMPORTS = {
29
29
  React: s(:import, ['react'], s(:attr, nil, :React)),
30
- ReactDOM: s(:import, ['react-dom'], s(:attr, nil, :ReactDOM))
30
+ ReactDOM: s(:import, ['react-dom'], s(:attr, nil, :ReactDOM)),
31
+ Preact: s(:import,
32
+ [s(:pair, s(:sym, :as), s(:const, nil, :Preact)),
33
+ s(:pair, s(:sym, :from), s(:str, "preact"))],
34
+ s(:str, '*')),
35
+ PreactHook: s(:import, ["preact/hooks"], [s(:attr, nil, :useState)])
31
36
  }
32
37
 
33
38
  # the following command can be used to generate ReactAttrs:
@@ -71,17 +76,23 @@ module Ruby2JS
71
76
 
72
77
  ReactLifecycle = %w(render componentDidMount shouldComponentUpdate
73
78
  getShapshotBeforeUpdate componentDidUpdate componentWillUnmount
74
- componentDidCatch)
79
+ componentDidCatch componentWillReceiveProps)
75
80
 
76
81
  ReactAttrMap = Hash[ReactAttrs.map {|name| [name.downcase, name]}]
77
82
  ReactAttrMap['for'] = 'htmlFor'
78
- ReactFragment = :'_React.Fragment'
83
+
84
+ PreactAttrMap = {
85
+ htmlFor: 'for',
86
+ onDoubleClick: 'onDblClick',
87
+ tabIndex: 'tabindex'
88
+ }
79
89
 
80
90
  def initialize(*args)
81
91
  @react = nil
82
92
  @reactApply = nil
83
93
  @reactBlock = nil
84
94
  @reactClass = nil
95
+ @reactMethod = nil
85
96
  @react_props = []
86
97
  @react_methods = []
87
98
  @react_filter_functions = false
@@ -118,12 +129,23 @@ module Ruby2JS
118
129
  def on_class(node)
119
130
  cname, inheritance, *body = node.children
120
131
  return super unless cname.children.first == nil
121
- return super unless inheritance == s(:const, nil, :React) or
122
- inheritance == s(:const, nil, :Vue) or
132
+
133
+ if inheritance == s(:const, nil, :React) or
123
134
  inheritance == s(:const, s(:const, nil, :React), :Component) or
124
135
  inheritance == s(:send, s(:const, nil, :React), :Component)
125
136
 
126
- prepend_list << REACT_IMPORTS[:React] if modules_enabled?
137
+ react = :React
138
+ prepend_list << REACT_IMPORTS[:React] if modules_enabled?
139
+
140
+ elsif inheritance == s(:const, nil, :Preact) or
141
+ inheritance == s(:const, s(:const, nil, :Preact), :Component) or
142
+ inheritance == s(:send, s(:const, nil, :Preact), :Component)
143
+
144
+ react = :Preact
145
+ prepend_list << REACT_IMPORTS[:Preact] if modules_enabled?
146
+ else
147
+ return super
148
+ end
127
149
 
128
150
  # traverse down to actual list of class statements
129
151
  if body.length == 1
@@ -141,12 +163,14 @@ module Ruby2JS
141
163
  end
142
164
 
143
165
  begin
144
- react, @react = @react, true
166
+ react, @react = @react, react
145
167
  reactClass, @reactClass = @reactClass, true
146
168
 
147
169
  pairs = []
148
170
 
149
- unless es2015
171
+ createClass = (@react == :React and not es2015)
172
+
173
+ if createClass
150
174
  # automatically capture the displayName for the class
151
175
  pairs << s(:pair, s(:sym, :displayName),
152
176
  s(:str, cname.children.last.to_s))
@@ -156,12 +180,11 @@ module Ruby2JS
156
180
  statics = []
157
181
  body.select {|child| child.type == :defs}.each do |child|
158
182
  _parent, mname, args, *block = child.children
159
- if es2015
160
- block = [s(:autoreturn, *block)] unless child.is_method?
161
- pairs << s(:defs, s(:self), mname, args, *block)
183
+ if not createClass
184
+ pairs << child
162
185
  elsif child.is_method?
163
- statics << s(:pair, s(:sym, mname), process(child.updated(:block,
164
- [s(:send, nil, :proc), args, s(:autoreturn, *block)])))
186
+ statics << s(:pair, s(:sym, mname), child.updated(:block,
187
+ [s(:send, nil, :proc), args, s(:autoreturn, *block)]))
165
188
  elsif \
166
189
  block.length == 1 and
167
190
  Converter::EXPRESSIONS.include? block.first.type
@@ -212,13 +235,53 @@ module Ruby2JS
212
235
  scan_events[node.children]
213
236
  end
214
237
  end
215
- scan_events[body] unless es2015
238
+ scan_events[body] if createClass
216
239
 
217
240
  # append statics (if any)
218
241
  unless statics.empty?
219
242
  pairs << s(:pair, s(:sym, :statics), s(:hash, *statics))
220
243
  end
221
244
 
245
+ # determine if this class can be emitted as a hook
246
+ hook = (es2015 and inheritance.children.first == nil)
247
+ hookinit = nil
248
+ useState = []
249
+ body.each_with_index do |statement, index|
250
+ if statement.type == :def
251
+ method = statement.children.first
252
+ if method == :initialize
253
+ children = statement.children[2..-1]
254
+ children.pop unless children.last
255
+ while children.length == 1 and children.first.type == :begin
256
+ children = children.first.children
257
+ end
258
+ hookinit = index if children.any? {|child| child.type != :ivasgn}
259
+ elsif method == :render
260
+ nil
261
+ elsif ReactLifecycle.include? method.to_s
262
+ hook = false
263
+ elsif not statement.is_method?
264
+ hook = false
265
+ elsif method.to_s.end_with? '='
266
+ hook = false
267
+ end
268
+ elsif statement.type == :defs
269
+ hook = false
270
+ end
271
+ end
272
+
273
+ if hook
274
+ @reactClass = :hook
275
+ @react_props = []
276
+ @react_methods = []
277
+
278
+ if hookinit
279
+ body = body.dup
280
+ hookinit = body.delete_at(hookinit)
281
+ pairs.unshift process hookinit.children[2]
282
+ end
283
+ end
284
+
222
285
  # create a default getInitialState method if there is no such method
223
286
  # and there are either references to instance variables or there are
224
287
  # methods that need to be bound.
@@ -230,7 +293,13 @@ module Ruby2JS
230
293
  then
231
294
  @reactIvars = {pre: [], post: [], asgn: [], ref: [], cond: []}
232
295
  react_walk(node)
233
- if not es2015 and not @reactIvars.values.flatten.empty?
296
+
297
+ if hook
298
+ react_walk(hookinit) if hookinit
299
+ useState = (@reactIvars[:asgn] + @reactIvars[:ref]).uniq
300
+ end
301
+
302
+ if createClass and not @reactIvars.values.flatten.empty?
234
303
  body = [s(:def, :getInitialState, s(:args),
235
304
  s(:return, s(:hash))), *body]
236
305
  elsif not needs_binding.empty? or not @reactIvars.values.flatten.empty?
@@ -242,16 +311,21 @@ module Ruby2JS
242
311
  body.select {|child| child.type == :def}.each do |child|
243
312
  mname, args, *block = child.children
244
313
  @reactMethod = mname
245
- @reactProps = child.updated(:attr, [s(:self), :props])
314
+
315
+ if @reactClass == :hook
316
+ @reactProps = s(:lvar, :"prop$")
317
+ else
318
+ @reactProps = child.updated(:attr, [s(:self), :props])
319
+ end
246
320
 
247
321
  # analyze ivar usage
248
322
  @reactIvars = {pre: [], post: [], asgn: [], ref: [], cond: []}
249
323
  react_walk(child) unless mname == :initialize
250
- @reactIvars[:capture] =
251
- (@reactIvars[:pre] + @reactIvars[:post]).uniq
324
+ @reactIvars[:capture] = (@reactIvars[:pre] + @reactIvars[:post]).uniq
325
+ @reactIvars[:pre] = @reactIvars[:post] = [] if @reactClass == :hook
252
326
 
253
327
  if mname == :initialize
254
- mname = es2015 ? :initialize : :getInitialState
328
+ mname = createClass ? :getInitialState : :initialize
255
329
 
256
330
  # extract real list of statements
257
331
  if block.length == 1
@@ -333,7 +407,7 @@ module Ruby2JS
333
407
  else
334
408
  # wrap multi-line blocks with a React Fragment
335
409
  block = [s(:return,
336
- s(:block, s(:send, nil, ReactFragment), s(:args), *block))]
410
+ s(:block, s(:send, nil, :"_#{@react}.Fragment"), s(:args), *block))]
337
411
  end
338
412
  end
339
413
 
@@ -359,14 +433,14 @@ module Ruby2JS
359
433
  type = :begin if block.first.type == :return
360
434
  end
361
435
 
362
- if es2015
436
+ if createClass
437
+ pairs << s(:pair, s(:sym, mname), child.updated(:block,
438
+ [s(:send, nil, :proc), args, process(s(type, *block))]))
439
+ else
363
440
  pairs << child.updated(
364
441
  ReactLifecycle.include?(mname.to_s) ? :defm : :def,
365
442
  [mname, args, process(s(type, *block))]
366
443
  )
367
- else
368
- pairs << s(:pair, s(:sym, mname), child.updated(:block,
369
- [s(:send, nil, :proc), args, process(s(type, *block))]))
370
444
  end
371
445
 
372
446
  # retain comment
@@ -374,38 +448,70 @@ module Ruby2JS
374
448
  @comments[pairs.last] = @comments[child]
375
449
  end
376
450
  end
451
+
452
+ if createClass
453
+ # emit a createClass statement
454
+ node.updated(:casgn, [nil, cname.children.last,
455
+ s(:send, s(:const, nil, :React), :createClass, s(:hash, *pairs))])
456
+ elsif hook
457
+ initialize = pairs.find_index {|node| node.type == :def and node.children.first == :initialize}
458
+
459
+ hash = {}
460
+ if initialize
461
+ hash = pairs.delete_at(initialize)
462
+ hash = hash.children.last while %i(def begin send).include? hash&.type
463
+ hash = s(:hash) unless hash&.type == :hash
464
+ hash = hash.children.map {|pair|
465
+ [pair.children.first.children.first, pair.children.last]
466
+ }.to_h
467
+ end
468
+
469
+ useState.each do |symbol|
470
+ hash[symbol.to_s[1..-1]] ||= s(:nil)
471
+ end
472
+
473
+ hash.sort.reverse.each do |var, value|
474
+ if @react == :Preact
475
+ hooker = nil
476
+ prepend_list << REACT_IMPORTS[:PreactHook] if modules_enabled?
477
+ else
478
+ hooker = s(:const, nil, :React)
479
+ end
480
+
481
+ setter = 'set' + var[0].upcase + var[1..-1]
482
+ pairs.unshift(s(:masgn, s(:mlhs, s(:lvasgn, var),
483
+ s(:lvasgn, setter)), s(:send, hooker, :useState, value)))
484
+ end
485
+
486
+ render = pairs.find_index {|node| node.type == :defm and node.children.first == :render}
487
+ if render
488
+ render = pairs.delete_at(render)
489
+ pairs.push s(:autoreturn, render.children.last)
490
+ end
491
+
492
+ has_cvar = lambda {|list|
493
+ list.any? {|node|
494
+ next unless Parser::AST::Node === node
495
+ return true if node.type == :cvar
496
+ has_cvar.call(node.children)
497
+ }
498
+ }
499
+ args = has_cvar[node.children] ? s(:args, s(:arg, 'prop$')) : s(:args)
500
+
501
+ node.updated(:def, [cname.children.last, args, s(:begin, *pairs)])
502
+ else
503
+ # emit a class that extends React.Component
504
+ node.updated(:class, [s(:const, nil, cname.children.last),
505
+ s(:attr, s(:const, nil, @react), :Component), *pairs])
506
+ end
377
507
  ensure
378
508
  @react = react
379
509
  @reactClass = reactClass
380
510
  @reactMethod = nil
381
511
  end
382
-
383
- if es2015
384
- # emit a class that extends React.Component
385
- node.updated(:class, [s(:const, nil, cname.children.last),
386
- s(:attr, s(:const, nil, :React), :Component), *pairs])
387
- else
388
- # emit a createClass statement
389
- node.updated(:casgn, [nil, cname.children.last,
390
- s(:send, s(:const, nil, :React), :createClass, s(:hash, *pairs))])
391
- end
392
512
  end
393
513
 
394
514
  def on_send(node)
395
- # convert Vue.utile.defineReactive to class fields or assignments
396
- if node.children.first == s(:send, s(:const, nil, :Vue), :util)
397
- if node.children[1] == :defineReactive
398
- if node.children[2].type == :cvar
399
- return process s(:cvasgn, node.children[2].children.first,
400
- node.children[3])
401
- elsif node.children[2].type == :send
402
- assign = node.children[2]
403
- return assign.updated(nil, [assign.children[0],
404
- assign.children[1].to_s + '=', node.children[3]])
405
- end
406
- end
407
- end
408
-
409
515
  # calls to methods (including getters) defined in this class
410
516
  if node.children[0]==nil and Symbol === node.children[1]
411
517
  if node.is_method?
@@ -423,16 +529,12 @@ module Ruby2JS
423
529
  end
424
530
  end
425
531
 
426
- if node.children.first == s(:const, nil, :Vue)
427
- node = node.updated(nil, [s(:const, nil, :React),
428
- *node.children[1..-1]])
429
- end
430
-
431
532
  if not @react
432
533
  # enable React filtering within React class method calls or
433
534
  # React component calls
434
535
  if \
435
536
  node.children.first == s(:const, nil, :React) or
537
+ node.children.first == s(:const, nil, :Preact) or
436
538
  node.children.first == s(:const, nil, :ReactDOM)
437
539
  then
438
540
  if modules_enabled?
@@ -440,7 +542,8 @@ module Ruby2JS
440
542
  end
441
543
 
442
544
  begin
443
- react, @react = @react, true
545
+ react = @react
546
+ @react = (node.children.first.children.last == :Preact ? :Preact : :React)
444
547
  return on_send(node)
445
548
  ensure
446
549
  @react = react
@@ -461,13 +564,26 @@ module Ruby2JS
461
564
  end
462
565
 
463
566
  elsif \
464
- @reactApply and node.children[1] == :createElement and
465
- node.children[0] == s(:const, nil, :React)
567
+ (@reactApply and node.children[1] == :createElement and
568
+ node.children[0] == s(:const, nil, :React)) or
569
+ (@reactApply and node.children[1] == :h and
570
+ node.children[0] == s(:const, nil, :Preact))
466
571
  then
467
572
  # push results of explicit calls to React.createElement
468
573
  s(:send, s(:gvar, :$_), :push, s(:send, *node.children[0..1],
469
574
  *process_all(node.children[2..-1])))
470
575
 
576
+ elsif \
577
+ @react == :Preact and node.children[1] == :h and node.children[0] == nil
578
+ then
579
+ if @reactApply
580
+ # push results of explicit calls to Preact.h
581
+ s(:send, s(:gvar, :$_), :push, s(:send, s(:const, nil, :Preact), :h,
582
+ *process_all(node.children[2..-1])))
583
+ else
584
+ node.updated(nil, [s(:const, nil, :Preact), :h, *process_all(node.children[2..-1])])
585
+ end
586
+
471
587
  elsif !@jsx and node.children[0] == nil and node.children[1] =~ /^_\w/
472
588
  # map method calls starting with an underscore to React calls
473
589
  # to create an element.
@@ -549,7 +665,12 @@ module Ruby2JS
549
665
  else
550
666
  value = s(:str, values.join(' '))
551
667
  end
552
- pairs.unshift s(:pair, s(:sym, :className), value)
668
+
669
+ if @react == :Preact
670
+ pairs.unshift s(:pair, s(:sym, :class), value)
671
+ else
672
+ pairs.unshift s(:pair, s(:sym, :className), value)
673
+ end
553
674
  end
554
675
 
555
676
  # support controlled form components
@@ -559,13 +680,28 @@ module Ruby2JS
559
680
  ['value', :value].include? pair.children.first.children.first
560
681
  end
561
682
 
562
- # search for the presence of a 'onChange' attribute
683
+ event = (@react == :Preact ? :onInput : :onChange)
684
+
685
+
686
+ # search for the presence of a onInput/onChange attribute
563
687
  onChange = pairs.find_index do |pair|
564
- ['onChange', :onChange].include? pair.children.first.children[0]
688
+ pair.children.first.children[0].to_s == event.to_s
689
+ end
690
+
691
+ if event == :onInput and not onChange
692
+ # search for the presence of a 'onChange' attribute
693
+ onChange = pairs.find_index do |pair|
694
+ pair.children.first.children[0].to_s == 'onChange'
695
+ end
696
+
697
+ if onChange
698
+ pairs[onChange] = s(:pair, s(:sym, event),
699
+ pairs[onChange].children.last)
700
+ end
565
701
  end
566
702
 
567
703
  if value and pairs[value].children.last.type == :ivar and !onChange
568
- pairs << s(:pair, s(:sym, :onChange),
704
+ pairs << s(:pair, s(:sym, event),
569
705
  s(:block, s(:send, nil, :proc), s(:args, s(:arg, :event)),
570
706
  s(:ivasgn, pairs[value].children.last.children.first,
571
707
  s(:attr, s(:attr, s(:lvar, :event), :target), :value))))
@@ -578,7 +714,7 @@ module Ruby2JS
578
714
  end
579
715
 
580
716
  if checked and pairs[checked].children.last.type == :ivar
581
- pairs << s(:pair, s(:sym, :onChange),
717
+ pairs << s(:pair, s(:sym, event),
582
718
  s(:block, s(:send, nil, :proc), s(:args),
583
719
  s(:ivasgn, pairs[checked].children.last.children.first,
584
720
  s(:send, pairs[checked].children.last, :!))))
@@ -586,13 +722,25 @@ module Ruby2JS
586
722
  end
587
723
  end
588
724
 
589
- # replace attribute names with case-sensitive javascript properties
590
- pairs.each_with_index do |pair, index|
591
- next if pair.type == :kwsplat
592
- name = pair.children.first.children.first.downcase
593
- if ReactAttrMap[name] and name.to_s != ReactAttrMap[name]
594
- pairs[index] = pairs[index].updated(nil,
595
- [s(:str, ReactAttrMap[name]), pairs[index].children.last])
725
+ if @react == :Preact
726
+ # replace selected Reactisms with native HTML
727
+ pairs.each_with_index do |pair, index|
728
+ next if pair.type == :kwsplat
729
+ name = pair.children.first.children.first.to_sym
730
+ if PreactAttrMap[name]
731
+ pairs[index] = pairs[index].updated(nil,
732
+ [s(:str, PreactAttrMap[name]), pairs[index].children.last])
733
+ end
734
+ end
735
+ else
736
+ # replace attribute names with case-sensitive javascript properties
737
+ pairs.each_with_index do |pair, index|
738
+ next if pair.type == :kwsplat
739
+ name = pair.children.first.children.first.downcase
740
+ if ReactAttrMap[name] and name.to_s != ReactAttrMap[name]
741
+ pairs[index] = pairs[index].updated(nil,
742
+ [s(:str, ReactAttrMap[name]), pairs[index].children.last])
743
+ end
596
744
  end
597
745
  end
598
746
 
@@ -654,8 +802,14 @@ module Ruby2JS
654
802
  # explicit call to React.createElement
655
803
  next true if arg.children[1] == :createElement and
656
804
  arg.children[0] == s(:const, nil, :React)
657
- next true if arg.children[1] == :createElement and
658
- arg.children[0] == s(:const, nil, :Vue)
805
+
806
+ # explicit call to Preact.h
807
+ next true if arg.children[1] == :h and
808
+ arg.children[0] == s(:const, nil, :Preact)
809
+
810
+ # explicit call to h
811
+ next true if arg.children[1] == :h and
812
+ arg.children[0] == nil
659
813
 
660
814
  # JSX
661
815
  next true if arg.type == :xstr
@@ -717,8 +871,13 @@ module Ruby2JS
717
871
  params.pop if params.last == s(:nil)
718
872
 
719
873
  # construct element using params
720
- element = node.updated(:send, [s(:const, nil, :React),
721
- :createElement, *params])
874
+ if @react == :Preact
875
+ element = node.updated(:send, [s(:const, nil, :Preact),
876
+ :h, *params])
877
+ else
878
+ element = node.updated(:send, [s(:const, nil, :React),
879
+ :createElement, *params])
880
+ end
722
881
 
723
882
  if @reactApply
724
883
  # if apply is set, emit code that pushes result
@@ -846,8 +1005,13 @@ module Ruby2JS
846
1005
  while node != child
847
1006
  if node.children[1] !~ /!$/
848
1007
  # convert method name to hash {className: name} pair
849
- pair = s(:pair, s(:sym, :className),
850
- s(:str, node.children[1].to_s.gsub('_','-')))
1008
+ if @react == :Preact
1009
+ pair = s(:pair, s(:sym, :class),
1010
+ s(:str, node.children[1].to_s.gsub('_','-')))
1011
+ else
1012
+ pair = s(:pair, s(:sym, :className),
1013
+ s(:str, node.children[1].to_s.gsub('_','-')))
1014
+ end
851
1015
  else
852
1016
  # convert method name to hash {id: name} pair
853
1017
  pair = s(:pair, s(:sym, :id),
@@ -917,8 +1081,11 @@ module Ruby2JS
917
1081
  # Base Ruby2JS processing will convert the 'splat' to 'apply'
918
1082
  child = node.children.first
919
1083
  if \
920
- child.children[1] == :createElement and
921
- child.children[0] == s(:const, nil, :React)
1084
+ (child.children[1] == :createElement and
1085
+ child.children[0] == s(:const, nil, :React)) or
1086
+ (child.children[1] == :h and
1087
+ (child.children[0] == s(:const, nil, :Preact) or
1088
+ child.children[0] == nil))
922
1089
  then
923
1090
  begin
924
1091
  reactApply, @reactApply = @reactApply, true
@@ -931,11 +1098,13 @@ module Ruby2JS
931
1098
  @reactApply = reactApply
932
1099
  end
933
1100
 
1101
+ target = child.children[0] || s(:const, nil, :Preact)
1102
+
934
1103
  if reactApply
935
1104
  return child.updated(:send, [s(:gvar, :$_), :push,
936
- s(:send, *child.children[0..1], *params)])
1105
+ s(:send, target, child.children[1], *params)])
937
1106
  else
938
- return child.updated(:send, [*child.children[0..1], *params])
1107
+ return child.updated(:send, [target, child.children[1], *params])
939
1108
  end
940
1109
  end
941
1110
 
@@ -952,7 +1121,7 @@ module Ruby2JS
952
1121
  block = s(:block, s(:send, nil, :proc), s(:args),
953
1122
  *node.children[2..-1])
954
1123
  return on_send node.children.first.updated(:send,
955
- [nil, ReactFragment, block])
1124
+ [nil, :"_#{@react}.Fragment", block])
956
1125
 
957
1126
  elsif !@jsx and child.children[0] == nil and child.children[1] =~ /^_\w/
958
1127
  if node.children[1].children.empty?
@@ -998,13 +1167,17 @@ module Ruby2JS
998
1167
  # convert global variables to refs
999
1168
  def on_gvar(node)
1000
1169
  return super unless @reactClass
1170
+ return super if @reactClass == :hook
1001
1171
  s(:attr, s(:attr, s(:self), :refs), node.children.first.to_s[1..-1])
1002
1172
  end
1003
1173
 
1004
1174
  # convert instance variables to state
1005
1175
  def on_ivar(node)
1006
1176
  return super unless @reactClass
1007
- if @reactMethod and @reactIvars[:capture].include? node.children.first
1177
+
1178
+ if @reactClass == :hook
1179
+ node.updated(:lvar, [node.children.first.to_s[1..-1]])
1180
+ elsif @reactMethod and @reactIvars[:capture].include? node.children.first
1008
1181
  node.updated(:lvar, ["$#{node.children.first[1..-1]}"])
1009
1182
  else
1010
1183
  node.updated(:attr, [s(:attr, s(:self), :state),
@@ -1016,11 +1189,17 @@ module Ruby2JS
1016
1189
  def on_ivasgn(node)
1017
1190
  return super unless @react
1018
1191
 
1192
+ if @reactClass == :hook
1193
+ var = node.children.first.to_s[1..-1]
1194
+ return node.updated(:send, [nil, 'set' + var[0].upcase + var[1..-1],
1195
+ process(node.children.last)])
1196
+ end
1197
+
1019
1198
  if @reactMethod and @reactIvars[:capture].include? node.children.first
1020
1199
  ivar = node.children.first.to_s
1021
1200
  if @reactBlock
1022
1201
  return s(:send, s(:self), :setState, s(:hash, s(:pair,
1023
- s(:lvar, ivar[1..-1]), process(s(:lvasgn, "$#{ivar[1..-1]}",
1202
+ s(:str, ivar[1..-1]), process(s(:lvasgn, "$#{ivar[1..-1]}",
1024
1203
  *node.children[1..-1])))))
1025
1204
  else
1026
1205
  return s(:lvasgn, "$#{ivar[1..-1]}",
@@ -1072,10 +1251,14 @@ module Ruby2JS
1072
1251
  return super unless @react
1073
1252
  return super unless node.children.first.type == :ivasgn
1074
1253
  var = node.children.first.children.first
1075
- if @reactMethod and @reactIvars[:capture].include? var
1254
+ if @reactClass == :hook
1255
+ var = node.children.first.children.first.to_s[1..-1]
1256
+ node.updated(:send, [nil, 'set' + var[0].upcase + var[1..-1],
1257
+ s(:send, s(:lvar, var), *node.children[1..-1])])
1258
+ elsif @reactMethod and @reactIvars[:capture].include? var
1076
1259
  if @reactBlock
1077
1260
  s(:send, s(:self), :setState, s(:hash, s(:pair,
1078
- s(:lvar, var[1..-1]), process(s(node.type,
1261
+ s(:str, var[1..-1]), process(s(node.type,
1079
1262
  s(:lvasgn, "$#{var[1..-1]}"), *node.children[1..-1])))))
1080
1263
  else
1081
1264
  process s(node.type, s(:lvasgn, "$#{var[1..-1]}"),
@@ -1118,9 +1301,13 @@ module Ruby2JS
1118
1301
  return true if node.children[1] == :createElement and
1119
1302
  node.children[0] == s(:const, nil, :React)
1120
1303
 
1121
- # explicit call to Vue.createElement
1122
- return true if node.children[1] == :createElement and
1123
- node.children[0] == s(:const, nil, :Vue)
1304
+ # explicit call to Preact.h
1305
+ return true if node.children[1] == :h and
1306
+ node.children[0] == s(:const, nil, :Preact)
1307
+
1308
+ # explicit call to h
1309
+ return true if node.children[1] == :h and
1310
+ node.children[0] == nil
1124
1311
  end
1125
1312
 
1126
1313
  # wunderbar style call
@@ -1256,6 +1443,7 @@ module Ruby2JS
1256
1443
  @reactIvars = {pre: [], post: [], asgn: [], ref: [], cond: []}
1257
1444
  react_walk(node.children.last)
1258
1445
  @reactIvars[:capture] = (@reactIvars[:pre] + @reactIvars[:post]).uniq
1446
+ @reactIvars[:pre] = @reactIvars[:post] = [] if @reactClass == :hook
1259
1447
  node = super
1260
1448
  block = react_process_ivars([node.children.last.dup])
1261
1449
  node.updated(nil, [*node.children[0..-2], s(:begin, *block)])
@@ -1280,7 +1468,7 @@ module Ruby2JS
1280
1468
  # update ivars that are set and later referenced
1281
1469
  unless @reactIvars[:post].empty?
1282
1470
  updates = @reactIvars[:post].uniq.sort.reverse.map do |ivar|
1283
- s(:pair, s(:lvar, ivar.to_s[1..-1]),
1471
+ s(:pair, s(:str, ivar.to_s[1..-1]),
1284
1472
  s(:lvar, "$#{ivar.to_s[1..-1]}"))
1285
1473
  end
1286
1474
  update = s(:send, s(:self), :setState, s(:hash, *updates))