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
data/lib/ruby2js.rb CHANGED
@@ -10,13 +10,20 @@ 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
19
25
  @@strict_default = false
26
+ @@module_default = nil
20
27
 
21
28
  def self.eslevel_default
22
29
  @@eslevel_default
@@ -34,6 +41,14 @@ module Ruby2JS
34
41
  @@strict_default = level
35
42
  end
36
43
 
44
+ def self.module_default
45
+ @@module_default
46
+ end
47
+
48
+ def self.module_default=(module_type)
49
+ @@module_default = module_type
50
+ end
51
+
37
52
  module Filter
38
53
  DEFAULTS = []
39
54
 
@@ -52,18 +67,26 @@ module Ruby2JS
52
67
  include Ruby2JS::Filter
53
68
  BINARY_OPERATORS = Converter::OPERATORS[2..-1].flatten
54
69
 
70
+ attr_accessor :prepend_list, :disable_autoimports, :namespace
71
+
55
72
  def initialize(comments)
56
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
+
57
80
  @ast = nil
58
81
  @exclude_methods = []
59
- @esm = false
82
+ @prepend_list = Set.new
60
83
  end
61
84
 
62
85
  def options=(options)
63
86
  @options = options
64
87
 
65
- @included = @@included
66
- @excluded = @@excluded
88
+ @included = Filter.included_methods
89
+ @excluded = Filter.excluded_methods
67
90
 
68
91
  include_all if options[:include_all]
69
92
  include_only(options[:include_only]) if options[:include_only]
@@ -99,6 +122,10 @@ module Ruby2JS
99
122
  @options[:eslevel] >= 2021
100
123
  end
101
124
 
125
+ def es2022
126
+ @options[:eslevel] >= 2022
127
+ end
128
+
102
129
  def process(node)
103
130
  ast, @ast = @ast, node
104
131
  replacement = super
@@ -113,6 +140,7 @@ module Ruby2JS
113
140
  end
114
141
 
115
142
  # handle all of the 'invented/synthetic' ast types
143
+ def on_assign(node); end
116
144
  def on_async(node); on_def(node); end
117
145
  def on_asyncs(node); on_defs(node); end
118
146
  def on_attr(node); on_send(node); end
@@ -120,17 +148,23 @@ module Ruby2JS
120
148
  def on_await(node); on_send(node); end
121
149
  def on_call(node); on_send(node); end
122
150
  def on_class_extend(node); on_send(node); end
151
+ def on_class_hash(node); on_class(node); end
123
152
  def on_class_module(node); on_send(node); end
124
153
  def on_constructor(node); on_def(node); end
154
+ def on_deff(node); on_def(node); end
125
155
  def on_defm(node); on_defs(node); end
126
156
  def on_defp(node); on_defs(node); end
127
157
  def on_for_of(node); on_for(node); end
128
158
  def on_in?(node); on_send(node); end
129
159
  def on_method(node); on_send(node); end
160
+ def on_module_hash(node); on_module(node); end
130
161
  def on_prop(node); on_array(node); end
131
162
  def on_prototype(node); on_begin(node); end
163
+ def on_send!(node); on_send(node); end
132
164
  def on_sendw(node); on_send(node); end
133
165
  def on_undefined?(node); on_defined?(node); end
166
+ def on_defineProps(node); end
167
+ def on_hide(node); on_begin(node); end
134
168
  def on_nil(node); end
135
169
  def on_xnode(node); end
136
170
  def on_export(node); end
@@ -160,8 +194,10 @@ module Ruby2JS
160
194
  end
161
195
 
162
196
  def self.convert(source, options={})
197
+ options = options.dup
163
198
  options[:eslevel] ||= @@eslevel_default
164
199
  options[:strict] = @@strict_default if options[:strict] == nil
200
+ options[:module] ||= @@module_default || :esm
165
201
 
166
202
  if Proc === source
167
203
  file,line = source.source_location
@@ -175,9 +211,11 @@ module Ruby2JS
175
211
  source = ast.loc.expression.source_buffer.source
176
212
  else
177
213
  ast, comments = parse( source, options[:file] )
178
- comments = Parser::Source::Comment.associate(ast, comments) if ast
214
+ comments = ast ? Parser::Source::Comment.associate(ast, comments) : {}
179
215
  end
180
216
 
217
+ namespace = Namespace.new
218
+
181
219
  filters = (options[:filters] || Filter::DEFAULTS)
182
220
 
183
221
  unless filters.empty?
@@ -192,7 +230,14 @@ module Ruby2JS
192
230
  filter = filter.new(comments)
193
231
 
194
232
  filter.options = options
233
+ filter.namespace = namespace
195
234
  ast = filter.process(ast)
235
+
236
+ unless filter.prepend_list.empty?
237
+ prepend = filter.prepend_list.sort_by {|ast| ast.type == :import ? 0 : 1}
238
+ prepend.reject! {|ast| ast.type == :import} if filter.disable_autoimports
239
+ ast = Parser::AST::Node.new(:begin, [*prepend, ast])
240
+ end
196
241
  end
197
242
 
198
243
  ruby2js = Ruby2JS::Converter.new(ast, comments)
@@ -203,7 +248,11 @@ module Ruby2JS
203
248
  ruby2js.strict = options[:strict]
204
249
  ruby2js.comparison = options[:comparison] || :equality
205
250
  ruby2js.or = options[:or] || :logical
206
- ruby2js.underscored_private = (options[:eslevel] < 2020) || options[:underscored_private]
251
+ ruby2js.module_type = options[:module] || :esm
252
+ ruby2js.underscored_private = (options[:eslevel] < 2022) || options[:underscored_private]
253
+
254
+ ruby2js.namespace = namespace
255
+
207
256
  if ruby2js.binding and not ruby2js.ivars
208
257
  ruby2js.ivars = ruby2js.binding.eval \
209
258
  'Hash[instance_variables.map {|var| [var, instance_variable_get(var)]}]'
@@ -221,6 +270,8 @@ module Ruby2JS
221
270
 
222
271
  ruby2js.timestamp options[:file]
223
272
 
273
+ ruby2js.file_name = options[:file] || ast&.loc&.expression&.source_buffer&.name || ''
274
+
224
275
  ruby2js
225
276
  end
226
277
 
@@ -228,16 +279,16 @@ module Ruby2JS
228
279
  buffer = Parser::Source::Buffer.new(file, line)
229
280
  buffer.source = source.encode('utf-8')
230
281
  parser = Parser::CurrentRuby.new
231
- parser.builder.emit_file_line_as_literals=false
232
- ast, comments = parser.parse_with_comments(buffer)
233
- Parser::CurrentRuby.parse(source.encode('utf-8')) unless ast
234
- [ast, comments]
282
+ parser.diagnostics.all_errors_are_fatal = true
283
+ parser.diagnostics.consumer = lambda {|diagnostic| nil}
284
+ parser.builder.emit_file_line_as_literals = false
285
+ parser.parse_with_comments(buffer)
235
286
  rescue Parser::SyntaxError => e
236
287
  split = source[0..e.diagnostic.location.begin_pos].split("\n")
237
288
  line, col = split.length, split.last.length
238
289
  message = "line #{line}, column #{col}: #{e.diagnostic.message}"
239
290
  message += "\n in file #{file}" if file
240
- raise Ruby2JS::SyntaxError.new(message)
291
+ raise Ruby2JS::SyntaxError.new(message, e.diagnostic)
241
292
  end
242
293
 
243
294
  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()
@@ -130,7 +130,7 @@ module Ruby2JS
130
130
  Parser::AST::Node.new(type, args)
131
131
  end
132
132
 
133
- attr_accessor :strict, :eslevel, :comparison, :or, :underscored_private
133
+ attr_accessor :strict, :eslevel, :module_type, :comparison, :or, :underscored_private
134
134
 
135
135
  def es2015
136
136
  @eslevel >= 2015
@@ -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'
@@ -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