ruby2js 3.5.1 → 4.0.0

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