ruby2js 4.0.5 → 4.1.4

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