ruby2js 3.5.2 → 4.0.1

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