ruby2js 3.5.3 → 4.0.2

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