ruby2js 3.5.3 → 4.0.2

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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -665
  3. data/lib/ruby2js.rb +60 -15
  4. data/lib/ruby2js/converter.rb +39 -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/for.rb +1 -1
  14. data/lib/ruby2js/converter/hash.rb +28 -6
  15. data/lib/ruby2js/converter/hide.rb +13 -0
  16. data/lib/ruby2js/converter/if.rb +10 -2
  17. data/lib/ruby2js/converter/import.rb +19 -4
  18. data/lib/ruby2js/converter/kwbegin.rb +9 -2
  19. data/lib/ruby2js/converter/literal.rb +14 -2
  20. data/lib/ruby2js/converter/logical.rb +1 -1
  21. data/lib/ruby2js/converter/module.rb +41 -4
  22. data/lib/ruby2js/converter/next.rb +10 -2
  23. data/lib/ruby2js/converter/opasgn.rb +8 -0
  24. data/lib/ruby2js/converter/redo.rb +14 -0
  25. data/lib/ruby2js/converter/return.rb +2 -1
  26. data/lib/ruby2js/converter/send.rb +73 -8
  27. data/lib/ruby2js/converter/vasgn.rb +5 -0
  28. data/lib/ruby2js/converter/while.rb +1 -1
  29. data/lib/ruby2js/converter/whilepost.rb +1 -1
  30. data/lib/ruby2js/converter/xstr.rb +2 -3
  31. data/lib/ruby2js/demo.rb +53 -0
  32. data/lib/ruby2js/es2022.rb +5 -0
  33. data/lib/ruby2js/es2022/strict.rb +3 -0
  34. data/lib/ruby2js/filter.rb +9 -1
  35. data/lib/ruby2js/filter/active_functions.rb +44 -0
  36. data/lib/ruby2js/filter/camelCase.rb +6 -3
  37. data/lib/ruby2js/filter/cjs.rb +2 -0
  38. data/lib/ruby2js/filter/esm.rb +118 -26
  39. data/lib/ruby2js/filter/functions.rb +137 -109
  40. data/lib/ruby2js/filter/{wunderbar.rb → jsx.rb} +29 -7
  41. data/lib/ruby2js/filter/node.rb +58 -14
  42. data/lib/ruby2js/filter/nokogiri.rb +12 -12
  43. data/lib/ruby2js/filter/react.rb +182 -57
  44. data/lib/ruby2js/filter/require.rb +102 -11
  45. data/lib/ruby2js/filter/return.rb +13 -1
  46. data/lib/ruby2js/filter/stimulus.rb +187 -0
  47. data/lib/ruby2js/jsx.rb +309 -0
  48. data/lib/ruby2js/namespace.rb +75 -0
  49. data/lib/ruby2js/serializer.rb +19 -12
  50. data/lib/ruby2js/sprockets.rb +40 -0
  51. data/lib/ruby2js/version.rb +3 -3
  52. data/ruby2js.gemspec +2 -2
  53. metadata +23 -13
  54. data/lib/ruby2js/filter/esm_migration.rb +0 -72
  55. data/lib/ruby2js/rails.rb +0 -63
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,26 +67,42 @@ 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]
82
93
  include(options[:include]) if options[:include]
83
94
  exclude(options[:exclude]) if options[:exclude]
95
+
96
+ filters = options[:filters] || DEFAULTS
97
+ @modules_enabled =
98
+ (defined? Ruby2JS::Filter::ESM and
99
+ filters.include? Ruby2JS::Filter::ESM) or
100
+ (defined? Ruby2JS::Filter::CJS and
101
+ filters.include? Ruby2JS::Filter::CJS)
102
+ end
103
+
104
+ def modules_enabled?
105
+ @modules_enabled
84
106
  end
85
107
 
86
108
  def es2015
@@ -111,6 +133,10 @@ module Ruby2JS
111
133
  @options[:eslevel] >= 2021
112
134
  end
113
135
 
136
+ def es2022
137
+ @options[:eslevel] >= 2022
138
+ end
139
+
114
140
  def process(node)
115
141
  ast, @ast = @ast, node
116
142
  replacement = super
@@ -125,6 +151,7 @@ module Ruby2JS
125
151
  end
126
152
 
127
153
  # handle all of the 'invented/synthetic' ast types
154
+ def on_assign(node); end
128
155
  def on_async(node); on_def(node); end
129
156
  def on_asyncs(node); on_defs(node); end
130
157
  def on_attr(node); on_send(node); end
@@ -132,17 +159,23 @@ module Ruby2JS
132
159
  def on_await(node); on_send(node); end
133
160
  def on_call(node); on_send(node); end
134
161
  def on_class_extend(node); on_send(node); end
162
+ def on_class_hash(node); on_class(node); end
135
163
  def on_class_module(node); on_send(node); end
136
164
  def on_constructor(node); on_def(node); end
165
+ def on_deff(node); on_def(node); end
137
166
  def on_defm(node); on_defs(node); end
138
167
  def on_defp(node); on_defs(node); end
139
168
  def on_for_of(node); on_for(node); end
140
169
  def on_in?(node); on_send(node); end
141
170
  def on_method(node); on_send(node); end
171
+ def on_module_hash(node); on_module(node); end
142
172
  def on_prop(node); on_array(node); end
143
173
  def on_prototype(node); on_begin(node); end
174
+ def on_send!(node); on_send(node); end
144
175
  def on_sendw(node); on_send(node); end
145
176
  def on_undefined?(node); on_defined?(node); end
177
+ def on_defineProps(node); end
178
+ def on_hide(node); on_begin(node); end
146
179
  def on_nil(node); end
147
180
  def on_xnode(node); end
148
181
  def on_export(node); end
@@ -160,10 +193,12 @@ module Ruby2JS
160
193
  return on_block s(:block, s(:send, *node.children[0..-2]),
161
194
  s(:args, s(:arg, :a), s(:arg, :b)), s(:return,
162
195
  process(s(:send, s(:lvar, :a), method, s(:lvar, :b)))))
163
- else
196
+ elsif node.children.last.children.first.type == :sym
164
197
  return on_block s(:block, s(:send, *node.children[0..-2]),
165
198
  s(:args, s(:arg, :item)), s(:return,
166
199
  process(s(:attr, s(:lvar, :item), method))))
200
+ else
201
+ super
167
202
  end
168
203
  end
169
204
  super
@@ -172,6 +207,7 @@ module Ruby2JS
172
207
  end
173
208
 
174
209
  def self.convert(source, options={})
210
+ options = options.dup
175
211
  options[:eslevel] ||= @@eslevel_default
176
212
  options[:strict] = @@strict_default if options[:strict] == nil
177
213
  options[:module] ||= @@module_default || :esm
@@ -188,9 +224,11 @@ module Ruby2JS
188
224
  source = ast.loc.expression.source_buffer.source
189
225
  else
190
226
  ast, comments = parse( source, options[:file] )
191
- comments = Parser::Source::Comment.associate(ast, comments) if ast
227
+ comments = ast ? Parser::Source::Comment.associate(ast, comments) : {}
192
228
  end
193
229
 
230
+ namespace = Namespace.new
231
+
194
232
  filters = (options[:filters] || Filter::DEFAULTS)
195
233
 
196
234
  unless filters.empty?
@@ -205,11 +243,13 @@ module Ruby2JS
205
243
  filter = filter.new(comments)
206
244
 
207
245
  filter.options = options
246
+ filter.namespace = namespace
208
247
  ast = filter.process(ast)
209
248
 
210
- if filter.prepend_list
249
+ unless filter.prepend_list.empty?
211
250
  prepend = filter.prepend_list.sort_by {|ast| ast.type == :import ? 0 : 1}
212
- ast = Parser::AST::Node.new(:begin, [*prepend, ast], location: ast.location)
251
+ prepend.reject! {|ast| ast.type == :import} if filter.disable_autoimports
252
+ ast = Parser::AST::Node.new(:begin, [*prepend, ast])
213
253
  end
214
254
  end
215
255
 
@@ -222,7 +262,10 @@ module Ruby2JS
222
262
  ruby2js.comparison = options[:comparison] || :equality
223
263
  ruby2js.or = options[:or] || :logical
224
264
  ruby2js.module_type = options[:module] || :esm
225
- ruby2js.underscored_private = (options[:eslevel] < 2020) || options[:underscored_private]
265
+ ruby2js.underscored_private = (options[:eslevel] < 2022) || options[:underscored_private]
266
+
267
+ ruby2js.namespace = namespace
268
+
226
269
  if ruby2js.binding and not ruby2js.ivars
227
270
  ruby2js.ivars = ruby2js.binding.eval \
228
271
  'Hash[instance_variables.map {|var| [var, instance_variable_get(var)]}]'
@@ -240,23 +283,25 @@ module Ruby2JS
240
283
 
241
284
  ruby2js.timestamp options[:file]
242
285
 
286
+ ruby2js.file_name = options[:file] || ast&.loc&.expression&.source_buffer&.name || ''
287
+
243
288
  ruby2js
244
289
  end
245
290
 
246
291
  def self.parse(source, file=nil, line=1)
247
292
  buffer = Parser::Source::Buffer.new(file, line)
248
- buffer.source = source.encode('utf-8')
293
+ buffer.source = source.encode('UTF-8')
249
294
  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]
295
+ parser.diagnostics.all_errors_are_fatal = true
296
+ parser.diagnostics.consumer = lambda {|diagnostic| nil}
297
+ parser.builder.emit_file_line_as_literals = false
298
+ parser.parse_with_comments(buffer)
254
299
  rescue Parser::SyntaxError => e
255
300
  split = source[0..e.diagnostic.location.begin_pos].split("\n")
256
301
  line, col = split.length, split.last.length
257
302
  message = "line #{line}, column #{col}: #{e.diagnostic.message}"
258
303
  message += "\n in file #{file}" if file
259
- raise Ruby2JS::SyntaxError.new(message)
304
+ raise Ruby2JS::SyntaxError.new(message, e.diagnostic)
260
305
  end
261
306
 
262
307
  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()
@@ -66,6 +66,7 @@ module Ruby2JS
66
66
  @comparison = :equality
67
67
  @or = :logical
68
68
  @underscored_private = true
69
+ @redoable = false
69
70
  end
70
71
 
71
72
  def width=(width)
@@ -160,6 +161,10 @@ module Ruby2JS
160
161
  @eslevel >= 2021
161
162
  end
162
163
 
164
+ def es2022
165
+ @eslevel >= 2022
166
+ end
167
+
163
168
  @@handlers = []
164
169
  def self.handle(*types, &block)
165
170
  types.each do |type|
@@ -239,6 +244,34 @@ module Ruby2JS
239
244
  end
240
245
  end
241
246
 
247
+ def redoable(block)
248
+ save_redoable = @redoable
249
+
250
+ has_redo = proc do |node|
251
+ node.children.any? do |child|
252
+ next false unless child.is_a? Parser::AST::Node
253
+ next true if child.type == :redo
254
+ next false if %i[for while while_post until until_post].include? child.type
255
+ has_redo[child]
256
+ end
257
+ end
258
+
259
+ @redoable = has_redo[@ast]
260
+
261
+ if @redoable
262
+ put es2015 ? 'let ' : 'var '
263
+ put "redo$#@sep"
264
+ puts 'do {'
265
+ put "redo$ = false#@sep"
266
+ scope block
267
+ put "#@nl} while(redo$)"
268
+ else
269
+ scope block
270
+ end
271
+ ensure
272
+ @redoable = save_redoable
273
+ end
274
+
242
275
  def timestamp(file)
243
276
  super
244
277
 
@@ -248,7 +281,7 @@ module Ruby2JS
248
281
  if ast.loc and ast.loc.expression
249
282
  filename = ast.loc.expression.source_buffer.name
250
283
  if filename and not filename.empty?
251
- @timestamps[filename] ||= File.mtime(filename)
284
+ @timestamps[filename] ||= File.mtime(filename) rescue nil
252
285
  end
253
286
  end
254
287
 
@@ -295,6 +328,7 @@ end
295
328
  require 'ruby2js/converter/arg'
296
329
  require 'ruby2js/converter/args'
297
330
  require 'ruby2js/converter/array'
331
+ require 'ruby2js/converter/assign'
298
332
  require 'ruby2js/converter/begin'
299
333
  require 'ruby2js/converter/block'
300
334
  require 'ruby2js/converter/blockpass'
@@ -314,6 +348,7 @@ require 'ruby2js/converter/dstr'
314
348
  require 'ruby2js/converter/fileline'
315
349
  require 'ruby2js/converter/for'
316
350
  require 'ruby2js/converter/hash'
351
+ require 'ruby2js/converter/hide'
317
352
  require 'ruby2js/converter/if'
318
353
  require 'ruby2js/converter/in'
319
354
  require 'ruby2js/converter/import'
@@ -329,6 +364,7 @@ require 'ruby2js/converter/nil'
329
364
  require 'ruby2js/converter/nthref'
330
365
  require 'ruby2js/converter/opasgn'
331
366
  require 'ruby2js/converter/prototype'
367
+ require 'ruby2js/converter/redo'
332
368
  require 'ruby2js/converter/regexp'
333
369
  require 'ruby2js/converter/return'
334
370
  require 'ruby2js/converter/self'
@@ -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