ruby2js 3.5.1 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -662
  3. data/lib/ruby2js.rb +61 -10
  4. data/lib/ruby2js/converter.rb +10 -4
  5. data/lib/ruby2js/converter/assign.rb +159 -0
  6. data/lib/ruby2js/converter/begin.rb +7 -2
  7. data/lib/ruby2js/converter/case.rb +7 -2
  8. data/lib/ruby2js/converter/class.rb +77 -21
  9. data/lib/ruby2js/converter/class2.rb +102 -31
  10. data/lib/ruby2js/converter/def.rb +7 -3
  11. data/lib/ruby2js/converter/dstr.rb +8 -3
  12. data/lib/ruby2js/converter/hash.rb +9 -5
  13. data/lib/ruby2js/converter/hide.rb +13 -0
  14. data/lib/ruby2js/converter/if.rb +10 -2
  15. data/lib/ruby2js/converter/import.rb +35 -4
  16. data/lib/ruby2js/converter/kwbegin.rb +9 -2
  17. data/lib/ruby2js/converter/literal.rb +14 -2
  18. data/lib/ruby2js/converter/module.rb +41 -4
  19. data/lib/ruby2js/converter/opasgn.rb +8 -0
  20. data/lib/ruby2js/converter/send.rb +45 -5
  21. data/lib/ruby2js/converter/vasgn.rb +5 -0
  22. data/lib/ruby2js/converter/xstr.rb +1 -1
  23. data/lib/ruby2js/demo.rb +53 -0
  24. data/lib/ruby2js/es2022.rb +5 -0
  25. data/lib/ruby2js/es2022/strict.rb +3 -0
  26. data/lib/ruby2js/filter.rb +9 -1
  27. data/lib/ruby2js/filter/active_functions.rb +44 -0
  28. data/lib/ruby2js/filter/camelCase.rb +4 -3
  29. data/lib/ruby2js/filter/cjs.rb +2 -0
  30. data/lib/ruby2js/filter/esm.rb +133 -7
  31. data/lib/ruby2js/filter/functions.rb +107 -98
  32. data/lib/ruby2js/filter/{wunderbar.rb → jsx.rb} +29 -7
  33. data/lib/ruby2js/filter/node.rb +95 -74
  34. data/lib/ruby2js/filter/nokogiri.rb +15 -41
  35. data/lib/ruby2js/filter/react.rb +191 -56
  36. data/lib/ruby2js/filter/require.rb +100 -5
  37. data/lib/ruby2js/filter/return.rb +15 -1
  38. data/lib/ruby2js/filter/securerandom.rb +33 -0
  39. data/lib/ruby2js/filter/stimulus.rb +185 -0
  40. data/lib/ruby2js/filter/vue.rb +9 -0
  41. data/lib/ruby2js/jsx.rb +291 -0
  42. data/lib/ruby2js/namespace.rb +75 -0
  43. data/lib/ruby2js/rails.rb +15 -9
  44. data/lib/ruby2js/serializer.rb +3 -1
  45. data/lib/ruby2js/version.rb +3 -3
  46. data/ruby2js.gemspec +1 -1
  47. metadata +14 -5
  48. data/lib/ruby2js/filter/esm_migration.rb +0 -72
  49. data/lib/ruby2js/filter/fast-deep-equal.rb +0 -23
@@ -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]
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,7 +320,7 @@ 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
326
  not [:send, :block].include? block.last.type
@@ -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
@@ -1,4 +1,5 @@
1
1
  require 'ruby2js'
2
+ require 'pathname'
2
3
 
3
4
  module Ruby2JS
4
5
  module Filter
@@ -13,9 +14,17 @@ module Ruby2JS
13
14
 
14
15
  def initialize(*args)
15
16
  @require_expr = nil
17
+ @require_seen = {}
18
+ @require_relative = '.'
16
19
  super
17
20
  end
18
21
 
22
+ def options=(options)
23
+ super
24
+ @require_autoexports = !@disable_autoexports && options[:autoexports]
25
+ @require_recursive = options[:require_recursive]
26
+ end
27
+
19
28
  def on_send(node)
20
29
  if \
21
30
  not @require_expr and # only statements
@@ -44,11 +53,97 @@ module Ruby2JS
44
53
  filename += '.js.rb'
45
54
  end
46
55
 
47
- @options[:file2] = filename
48
- ast, comments = Ruby2JS.parse(File.read(filename), filename)
49
- @comments.merge! Parser::Source::Comment.associate(ast, comments)
50
- @comments[node] += @comments[ast]
51
- process ast
56
+ realpath = File.realpath(filename)
57
+ if @require_seen[realpath]
58
+ ast = s(:hide)
59
+ else
60
+ @require_seen[realpath] = []
61
+
62
+ @options[:file2] = filename
63
+ ast, comments = Ruby2JS.parse(File.read(filename), filename)
64
+ @comments.merge! Parser::Source::Comment.associate(ast, comments)
65
+ @comments[node] += @comments[ast]
66
+ end
67
+
68
+ children = ast.type == :begin ? ast.children : [ast]
69
+
70
+ named_exports = []
71
+ auto_exports = []
72
+ default_exports = []
73
+ children.each do |child|
74
+ if child&.type == :send and child.children[0..1] == [nil, :export]
75
+ child = child.children[2]
76
+ if child&.type == :send and child.children[0..1] == [nil, :default]
77
+ child = child.children[2]
78
+ target = default_exports
79
+ else
80
+ target = named_exports
81
+ end
82
+ elsif @require_autoexports
83
+ target = auto_exports
84
+ else
85
+ next
86
+ end
87
+
88
+ if %i[class module].include? child.type and child.children[0].children[0] == nil
89
+ target << child.children[0].children[1]
90
+ elsif child.type == :casgn and child.children[0] == nil
91
+ target << child.children[1]
92
+ elsif child.type == :def
93
+ target << child.children[0]
94
+ end
95
+ end
96
+
97
+ if @require_autoexports == :default and auto_exports.length == 1
98
+ default_exports += auto_exports
99
+ else
100
+ named_exports += auto_exports
101
+ end
102
+
103
+ imports = @require_seen[realpath]
104
+ imports << s(:const, nil, default_exports.first) unless default_exports.empty?
105
+ imports << named_exports.map {|id| s(:const, nil, id)} unless named_exports.empty?
106
+
107
+ if imports.empty?
108
+ process ast
109
+ else
110
+ @require_seen[realpath] = imports
111
+
112
+ importname = Pathname.new(filename).relative_path_from(Pathname.new(dirname)).to_s
113
+ importname = Pathname.new(@require_relative).join(importname).to_s
114
+
115
+ prepend_list << s(:import, importname, *imports)
116
+
117
+ save_prepend_list = prepend_list.dup
118
+
119
+ begin
120
+ require_relative = @require_relative
121
+ @require_relative = Pathname.new(@require_relative).join(basename).parent.to_s
122
+ node = process s(:hide, ast)
123
+ ensure
124
+ @require_relative = require_relative
125
+ end
126
+
127
+ if @require_recursive
128
+ block = node.children
129
+ while block.length == 1 and block.first.type == :begin
130
+ block = block.first.children
131
+ end
132
+
133
+ block.each do |child|
134
+ if child&.type == :import
135
+ puts ['rr', basename, child.inspect]
136
+ prepend_list << child
137
+ end
138
+ end
139
+ else
140
+ prepend_list.keep_if do |import|
141
+ save_prepend_list.include? import
142
+ end
143
+ end
144
+
145
+ node
146
+ end
52
147
  ensure
53
148
  if file2
54
149
  @options[:file2] = file2