ruby2js 3.5.2 → 4.0.1

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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -665
  3. data/lib/ruby2js.rb +47 -13
  4. data/lib/ruby2js/converter.rb +9 -3
  5. data/lib/ruby2js/converter/args.rb +6 -1
  6. data/lib/ruby2js/converter/assign.rb +159 -0
  7. data/lib/ruby2js/converter/begin.rb +7 -2
  8. data/lib/ruby2js/converter/case.rb +7 -2
  9. data/lib/ruby2js/converter/class.rb +80 -21
  10. data/lib/ruby2js/converter/class2.rb +107 -33
  11. data/lib/ruby2js/converter/def.rb +7 -3
  12. data/lib/ruby2js/converter/dstr.rb +8 -3
  13. data/lib/ruby2js/converter/hash.rb +28 -6
  14. data/lib/ruby2js/converter/hide.rb +13 -0
  15. data/lib/ruby2js/converter/if.rb +10 -2
  16. data/lib/ruby2js/converter/import.rb +19 -4
  17. data/lib/ruby2js/converter/kwbegin.rb +9 -2
  18. data/lib/ruby2js/converter/literal.rb +14 -2
  19. data/lib/ruby2js/converter/logical.rb +1 -1
  20. data/lib/ruby2js/converter/module.rb +41 -4
  21. data/lib/ruby2js/converter/opasgn.rb +8 -0
  22. data/lib/ruby2js/converter/return.rb +2 -1
  23. data/lib/ruby2js/converter/send.rb +73 -8
  24. data/lib/ruby2js/converter/vasgn.rb +5 -0
  25. data/lib/ruby2js/converter/xstr.rb +2 -3
  26. data/lib/ruby2js/demo.rb +53 -0
  27. data/lib/ruby2js/es2022.rb +5 -0
  28. data/lib/ruby2js/es2022/strict.rb +3 -0
  29. data/lib/ruby2js/filter.rb +9 -1
  30. data/lib/ruby2js/filter/active_functions.rb +44 -0
  31. data/lib/ruby2js/filter/camelCase.rb +6 -3
  32. data/lib/ruby2js/filter/cjs.rb +2 -0
  33. data/lib/ruby2js/filter/esm.rb +118 -26
  34. data/lib/ruby2js/filter/functions.rb +104 -106
  35. data/lib/ruby2js/filter/{wunderbar.rb → jsx.rb} +29 -7
  36. data/lib/ruby2js/filter/node.rb +58 -14
  37. data/lib/ruby2js/filter/nokogiri.rb +12 -12
  38. data/lib/ruby2js/filter/react.rb +192 -57
  39. data/lib/ruby2js/filter/require.rb +102 -11
  40. data/lib/ruby2js/filter/return.rb +13 -1
  41. data/lib/ruby2js/filter/stimulus.rb +185 -0
  42. data/lib/ruby2js/jsx.rb +309 -0
  43. data/lib/ruby2js/namespace.rb +75 -0
  44. data/lib/ruby2js/rails.rb +15 -9
  45. data/lib/ruby2js/serializer.rb +3 -1
  46. data/lib/ruby2js/version.rb +3 -3
  47. data/ruby2js.gemspec +2 -2
  48. metadata +17 -9
  49. data/lib/ruby2js/filter/esm_migration.rb +0 -72
  50. data/lib/ruby2js/filter/fast-deep-equal.rb +0 -23
data/lib/ruby2js.rb CHANGED
@@ -10,9 +10,15 @@ end
10
10
 
11
11
  require 'ruby2js/converter'
12
12
  require 'ruby2js/filter'
13
+ require 'ruby2js/namespace'
13
14
 
14
15
  module Ruby2JS
15
16
  class SyntaxError < RuntimeError
17
+ attr_reader :diagnostic
18
+ def initialize(message, diagnostic=nil)
19
+ super(message)
20
+ @diagnostic = diagnostic
21
+ end
16
22
  end
17
23
 
18
24
  @@eslevel_default = 2009 # ecmascript 5
@@ -61,21 +67,26 @@ module Ruby2JS
61
67
  include Ruby2JS::Filter
62
68
  BINARY_OPERATORS = Converter::OPERATORS[2..-1].flatten
63
69
 
64
- attr_accessor :prepend_list
70
+ attr_accessor :prepend_list, :disable_autoimports, :namespace
65
71
 
66
72
  def initialize(comments)
67
73
  @comments = comments
74
+
75
+ # check if magic comment is present:
76
+ first_comment = @comments.values.first&.map(&:text)&.first
77
+ @disable_autoimports = first_comment&.include?(" autoimports: false")
78
+ @disable_autoexports = first_comment&.include?(" autoexports: false")
79
+
68
80
  @ast = nil
69
81
  @exclude_methods = []
70
- @esm = false
71
82
  @prepend_list = Set.new
72
83
  end
73
84
 
74
85
  def options=(options)
75
86
  @options = options
76
87
 
77
- @included = @@included
78
- @excluded = @@excluded
88
+ @included = Filter.included_methods
89
+ @excluded = Filter.excluded_methods
79
90
 
80
91
  include_all if options[:include_all]
81
92
  include_only(options[:include_only]) if options[:include_only]
@@ -111,6 +122,10 @@ module Ruby2JS
111
122
  @options[:eslevel] >= 2021
112
123
  end
113
124
 
125
+ def es2022
126
+ @options[:eslevel] >= 2022
127
+ end
128
+
114
129
  def process(node)
115
130
  ast, @ast = @ast, node
116
131
  replacement = super
@@ -125,6 +140,7 @@ module Ruby2JS
125
140
  end
126
141
 
127
142
  # handle all of the 'invented/synthetic' ast types
143
+ def on_assign(node); end
128
144
  def on_async(node); on_def(node); end
129
145
  def on_asyncs(node); on_defs(node); end
130
146
  def on_attr(node); on_send(node); end
@@ -132,17 +148,23 @@ module Ruby2JS
132
148
  def on_await(node); on_send(node); end
133
149
  def on_call(node); on_send(node); end
134
150
  def on_class_extend(node); on_send(node); end
151
+ def on_class_hash(node); on_class(node); end
135
152
  def on_class_module(node); on_send(node); end
136
153
  def on_constructor(node); on_def(node); end
154
+ def on_deff(node); on_def(node); end
137
155
  def on_defm(node); on_defs(node); end
138
156
  def on_defp(node); on_defs(node); end
139
157
  def on_for_of(node); on_for(node); end
140
158
  def on_in?(node); on_send(node); end
141
159
  def on_method(node); on_send(node); end
160
+ def on_module_hash(node); on_module(node); end
142
161
  def on_prop(node); on_array(node); end
143
162
  def on_prototype(node); on_begin(node); end
163
+ def on_send!(node); on_send(node); end
144
164
  def on_sendw(node); on_send(node); end
145
165
  def on_undefined?(node); on_defined?(node); end
166
+ def on_defineProps(node); end
167
+ def on_hide(node); on_begin(node); end
146
168
  def on_nil(node); end
147
169
  def on_xnode(node); end
148
170
  def on_export(node); end
@@ -160,10 +182,12 @@ module Ruby2JS
160
182
  return on_block s(:block, s(:send, *node.children[0..-2]),
161
183
  s(:args, s(:arg, :a), s(:arg, :b)), s(:return,
162
184
  process(s(:send, s(:lvar, :a), method, s(:lvar, :b)))))
163
- else
185
+ elsif node.children.last.children.first.type == :sym
164
186
  return on_block s(:block, s(:send, *node.children[0..-2]),
165
187
  s(:args, s(:arg, :item)), s(:return,
166
188
  process(s(:attr, s(:lvar, :item), method))))
189
+ else
190
+ super
167
191
  end
168
192
  end
169
193
  super
@@ -172,6 +196,7 @@ module Ruby2JS
172
196
  end
173
197
 
174
198
  def self.convert(source, options={})
199
+ options = options.dup
175
200
  options[:eslevel] ||= @@eslevel_default
176
201
  options[:strict] = @@strict_default if options[:strict] == nil
177
202
  options[:module] ||= @@module_default || :esm
@@ -188,9 +213,11 @@ module Ruby2JS
188
213
  source = ast.loc.expression.source_buffer.source
189
214
  else
190
215
  ast, comments = parse( source, options[:file] )
191
- comments = Parser::Source::Comment.associate(ast, comments) if ast
216
+ comments = ast ? Parser::Source::Comment.associate(ast, comments) : {}
192
217
  end
193
218
 
219
+ namespace = Namespace.new
220
+
194
221
  filters = (options[:filters] || Filter::DEFAULTS)
195
222
 
196
223
  unless filters.empty?
@@ -205,10 +232,12 @@ module Ruby2JS
205
232
  filter = filter.new(comments)
206
233
 
207
234
  filter.options = options
235
+ filter.namespace = namespace
208
236
  ast = filter.process(ast)
209
237
 
210
- if filter.prepend_list
238
+ unless filter.prepend_list.empty?
211
239
  prepend = filter.prepend_list.sort_by {|ast| ast.type == :import ? 0 : 1}
240
+ prepend.reject! {|ast| ast.type == :import} if filter.disable_autoimports
212
241
  ast = Parser::AST::Node.new(:begin, [*prepend, ast])
213
242
  end
214
243
  end
@@ -222,7 +251,10 @@ module Ruby2JS
222
251
  ruby2js.comparison = options[:comparison] || :equality
223
252
  ruby2js.or = options[:or] || :logical
224
253
  ruby2js.module_type = options[:module] || :esm
225
- ruby2js.underscored_private = (options[:eslevel] < 2020) || options[:underscored_private]
254
+ ruby2js.underscored_private = (options[:eslevel] < 2022) || options[:underscored_private]
255
+
256
+ ruby2js.namespace = namespace
257
+
226
258
  if ruby2js.binding and not ruby2js.ivars
227
259
  ruby2js.ivars = ruby2js.binding.eval \
228
260
  'Hash[instance_variables.map {|var| [var, instance_variable_get(var)]}]'
@@ -240,6 +272,8 @@ module Ruby2JS
240
272
 
241
273
  ruby2js.timestamp options[:file]
242
274
 
275
+ ruby2js.file_name = options[:file] || ast&.loc&.expression&.source_buffer&.name || ''
276
+
243
277
  ruby2js
244
278
  end
245
279
 
@@ -247,16 +281,16 @@ module Ruby2JS
247
281
  buffer = Parser::Source::Buffer.new(file, line)
248
282
  buffer.source = source.encode('utf-8')
249
283
  parser = Parser::CurrentRuby.new
250
- parser.builder.emit_file_line_as_literals=false
251
- ast, comments = parser.parse_with_comments(buffer)
252
- Parser::CurrentRuby.parse(source.encode('utf-8')) unless ast
253
- [ast, comments]
284
+ parser.diagnostics.all_errors_are_fatal = true
285
+ parser.diagnostics.consumer = lambda {|diagnostic| nil}
286
+ parser.builder.emit_file_line_as_literals = false
287
+ parser.parse_with_comments(buffer)
254
288
  rescue Parser::SyntaxError => e
255
289
  split = source[0..e.diagnostic.location.begin_pos].split("\n")
256
290
  line, col = split.length, split.last.length
257
291
  message = "line #{line}, column #{col}: #{e.diagnostic.message}"
258
292
  message += "\n in file #{file}" if file
259
- raise Ruby2JS::SyntaxError.new(message)
293
+ raise Ruby2JS::SyntaxError.new(message, e.diagnostic)
260
294
  end
261
295
 
262
296
  def self.find_block(ast, line)
@@ -14,7 +14,7 @@ module Ruby2JS
14
14
 
15
15
  class Converter < Serializer
16
16
  attr_accessor :ast
17
-
17
+
18
18
  LOGICAL = :and, :not, :or
19
19
  OPERATORS = [:[], :[]=], [:not, :!], [:**], [:*, :/, :%], [:+, :-],
20
20
  [:>>, :<<], [:&], [:^, :|], [:<=, :<, :>, :>=], [:==, :!=, :===, :"!=="],
@@ -34,7 +34,7 @@ module Ruby2JS
34
34
 
35
35
  VASGN = [:cvasgn, :ivasgn, :gvasgn, :lvasgn]
36
36
 
37
- attr_accessor :binding, :ivars
37
+ attr_accessor :binding, :ivars, :namespace
38
38
 
39
39
  def initialize( ast, comments, vars = {} )
40
40
  super()
@@ -160,6 +160,10 @@ module Ruby2JS
160
160
  @eslevel >= 2021
161
161
  end
162
162
 
163
+ def es2022
164
+ @eslevel >= 2022
165
+ end
166
+
163
167
  @@handlers = []
164
168
  def self.handle(*types, &block)
165
169
  types.each do |type|
@@ -248,7 +252,7 @@ module Ruby2JS
248
252
  if ast.loc and ast.loc.expression
249
253
  filename = ast.loc.expression.source_buffer.name
250
254
  if filename and not filename.empty?
251
- @timestamps[filename] ||= File.mtime(filename)
255
+ @timestamps[filename] ||= File.mtime(filename) rescue nil
252
256
  end
253
257
  end
254
258
 
@@ -295,6 +299,7 @@ end
295
299
  require 'ruby2js/converter/arg'
296
300
  require 'ruby2js/converter/args'
297
301
  require 'ruby2js/converter/array'
302
+ require 'ruby2js/converter/assign'
298
303
  require 'ruby2js/converter/begin'
299
304
  require 'ruby2js/converter/block'
300
305
  require 'ruby2js/converter/blockpass'
@@ -314,6 +319,7 @@ require 'ruby2js/converter/dstr'
314
319
  require 'ruby2js/converter/fileline'
315
320
  require 'ruby2js/converter/for'
316
321
  require 'ruby2js/converter/hash'
322
+ require 'ruby2js/converter/hide'
317
323
  require 'ruby2js/converter/if'
318
324
  require 'ruby2js/converter/in'
319
325
  require 'ruby2js/converter/import'
@@ -30,13 +30,18 @@ module Ruby2JS
30
30
  if kw.type == :kwarg
31
31
  put kw.children.first
32
32
  elsif kw.type == :kwoptarg
33
- put kw.children.first; put ' = '; parse kw.children.last
33
+ put kw.children.first
34
+ unless kw.children.last == s(:send, nil, :undefined)
35
+ put ' = '; parse kw.children.last
36
+ end
34
37
  elsif kw.type == :kwrestarg
35
38
  raise 'Rest arg requires ES2018' unless es2018
36
39
  put '...'; put kw.children.first
37
40
  end
38
41
  end
39
42
  put ' }'
43
+
44
+ put ' = {}' unless kwargs.any? {|kw| kw.type == :kwarg}
40
45
  end
41
46
  end
42
47
 
@@ -0,0 +1,159 @@
1
+ # Kinda like Object.assign, except it handles properties
2
+ #
3
+ # Note: Object.defineProperties, Object.getOwnPropertyNames, etc. technically
4
+ # were not part of ES5, but were implemented by IE prior to ES6, and are
5
+ # the only way to implement getters and setters.
6
+
7
+ module Ruby2JS
8
+ class Converter
9
+
10
+ # (assign
11
+ # target
12
+ # (hash)
13
+ # ...
14
+
15
+ handle :assign do |target, *args|
16
+ collapsible = false
17
+
18
+ nonprop = proc do |node|
19
+ next false unless node.is_a? Parser::AST::Node
20
+ next false if node.type == :pair and node.children.first.type == :prop and es2015
21
+ next true unless node.type == :def
22
+ next false if node.children.first.to_s.end_with? '='
23
+ node.is_method?
24
+ end
25
+
26
+ collapsible = true if args.length == 1 and args.first.type == :hash and
27
+ args.first.children.length == 1
28
+
29
+ collapsible = true if args.length == 1 and args.first.type == :class_module and
30
+ args.first.children.length == 3 and nonprop[args.first.children.last]
31
+
32
+ if es2015 and not collapsible and args.all? {|arg|
33
+ case arg.type
34
+ when :pair, :hash, :class_module
35
+ arg.children.all? {|child| nonprop[child]}
36
+ when :const
37
+ false
38
+ else
39
+ true
40
+ end
41
+ }
42
+ parse s(:send, s(:const, nil, :Object), :assign, target, *args)
43
+ else
44
+
45
+ if target == s(:hash)
46
+ copy = [s(:gvasgn, :$$, target)]
47
+ target = s(:gvar, :$$)
48
+ shadow = [s(:shadowarg, :$$)]
49
+ elsif collapsible or es2015 or
50
+ (%i(send const).include? target.type and
51
+ target.children.length == 2 and target.children[0] == nil)
52
+ then
53
+ copy = []
54
+ shadow = []
55
+ else
56
+ copy = [s(:gvasgn, :$0, target)]
57
+ target = s(:gvar, :$0)
58
+ shadow = [s(:shadowarg, :$0)]
59
+ end
60
+
61
+ body = [*copy,
62
+ *args.map {|modname|
63
+ if modname.type == :hash and
64
+ modname.children.all? {|pair| pair.children.first.type == :prop}
65
+
66
+ if modname.children.length == 1
67
+ pair = modname.children.first
68
+ s(:send, s(:const, nil, :Object), :defineProperty, target,
69
+ s(:sym, pair.children.first.children.last),
70
+ s(:hash, *pair.children.last.map {|name, value| s(:pair,
71
+ s(:sym, name), value)}))
72
+ else
73
+ pair = modname.children.first
74
+ s(:send, s(:const, nil, :Object), :defineProperties, target,
75
+ s(:hash, *modname.children.map {|pair| s(:pair,
76
+ s(:sym, pair.children.first.children.last),
77
+ s(:hash, *pair.children.last.map {|name, value| s(:pair,
78
+ s(:sym, name), value)})
79
+ )}))
80
+ end
81
+
82
+ elsif modname.type == :hash and
83
+ modname.children.all? {|child| nonprop[child]}
84
+
85
+ s(:begin, *modname.children.map {|pair|
86
+ if pair.children.first.type == :prop
87
+ s(:send, s(:const, nil, :Object), :defineProperty, target,
88
+ s(:sym, pair.children.first.children.last),
89
+ s(:hash, *pair.children.last.map {|name, value| s(:pair,
90
+ s(:sym, name), value)}))
91
+ else
92
+ s(:send, target, :[]=, *pair.children)
93
+ end
94
+ })
95
+
96
+ elsif modname.type == :class_module and
97
+ modname.children[2..-1].all? {|child| nonprop[child]}
98
+
99
+ s(:begin, *modname.children[2..-1].map {|pair|
100
+ s(:send, target, :[]=, s(:sym, pair.children.first),
101
+ pair.updated(:defm, [nil, *pair.children[1..-1]]))
102
+ })
103
+
104
+ elsif modname.type == :lvar and not es2015
105
+ s(:for, s(:lvasgn, :$_), modname,
106
+ s(:send, target, :[]=,
107
+ s(:lvar, :$_), s(:send, modname, :[], s(:lvar, :$_))))
108
+
109
+ else
110
+ if es2017
111
+ s(:send, s(:const, nil, :Object), :defineProperties, target,
112
+ s(:send, s(:const, nil, :Object), :getOwnPropertyDescriptors, modname))
113
+ else
114
+ if modname.type == :lvar or (%i(send const).include? modname.type and
115
+ modname.children.length == 2 and modname.children[0] == nil)
116
+
117
+ object = modname
118
+ else
119
+ shadow += [s(:shadowarg, :$1)]
120
+ object = s(:gvar, :$1)
121
+ end
122
+
123
+ copy = s(:send,
124
+ s(:const, nil, :Object), :defineProperties, target,
125
+ s(:send,
126
+ s(:send, s(:const, nil, :Object), :getOwnPropertyNames, object),
127
+ :reduce,
128
+ s(:block,
129
+ s(:send, nil, :lambda),
130
+ s(:args, s(:arg, :$2), s(:arg, :$3)),
131
+ s(:begin,
132
+ s(:send,
133
+ s(:lvar, :$2), :[]=, s(:lvar, :$3),
134
+ s(:send, s(:const, nil, :Object), :getOwnPropertyDescriptor,
135
+ object, s(:lvar, :$3))),
136
+ s(:return, s(:lvar, :$2)))),
137
+ s(:hash)))
138
+
139
+
140
+ if object.type == :gvar
141
+ s(:begin, s(:gvasgn, object.children.last, modname), copy)
142
+ else
143
+ copy
144
+ end
145
+ end
146
+ end
147
+ }]
148
+
149
+ if @state == :statement and shadow.empty?
150
+ parse s(:begin, *body)
151
+ else
152
+ body.push s(:return, target) if @state == :expression
153
+ parse s(:send, s(:block, s(:send, nil, :lambda), s(:args, *shadow),
154
+ s(:begin, *body)), :[])
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
@@ -9,6 +9,11 @@ module Ruby2JS
9
9
  state = @state
10
10
  props = false
11
11
 
12
+ if state == :expression and statements.empty?
13
+ puts 'null'
14
+ return
15
+ end
16
+
12
17
  statements.map! do |statement|
13
18
  case statement and statement.type
14
19
  when :defs, :defp
@@ -32,9 +37,9 @@ module Ruby2JS
32
37
  end
33
38
 
34
39
  def combine_properties(body)
35
- for i in 0...body.length-1
40
+ (0...body.length-1).each do |i|
36
41
  next unless body[i] and body[i].type == :prop
37
- for j in i+1...body.length
42
+ (i+1...body.length).each do |j|
38
43
  break unless body[j] and body[j].type == :prop
39
44
 
40
45
  if body[i].children[0] == body[j].children[0]
@@ -10,10 +10,15 @@ module Ruby2JS
10
10
 
11
11
  handle :case do |expr, *whens, other|
12
12
  begin
13
+ if @state == :expression
14
+ parse s(:kwbegin, @ast), @state
15
+ return
16
+ end
17
+
13
18
  inner, @inner = @inner, @ast
14
19
 
15
20
  has_range = whens.any? do |node|
16
- node.children.any? {|child| [:irange, :erange].include? child.type}
21
+ node.children.any? {|child| [:irange, :erange].include? child&.type}
17
22
  end
18
23
 
19
24
  if has_range
@@ -47,7 +52,7 @@ module Ruby2JS
47
52
 
48
53
  parse code, :statement
49
54
  last = code
50
- while last.type == :begin
55
+ while last&.type == :begin
51
56
  last = last.children.last
52
57
  end
53
58