ruby2js 3.5.0 → 3.6.1

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.
@@ -17,6 +17,7 @@ module Ruby2JS
17
17
 
18
18
  @@eslevel_default = 2009 # ecmascript 5
19
19
  @@strict_default = false
20
+ @@module_default = nil
20
21
 
21
22
  def self.eslevel_default
22
23
  @@eslevel_default
@@ -34,6 +35,14 @@ module Ruby2JS
34
35
  @@strict_default = level
35
36
  end
36
37
 
38
+ def self.module_default
39
+ @@module_default
40
+ end
41
+
42
+ def self.module_default=(module_type)
43
+ @@module_default = module_type
44
+ end
45
+
37
46
  module Filter
38
47
  DEFAULTS = []
39
48
 
@@ -52,11 +61,19 @@ module Ruby2JS
52
61
  include Ruby2JS::Filter
53
62
  BINARY_OPERATORS = Converter::OPERATORS[2..-1].flatten
54
63
 
64
+ attr_accessor :prepend_list, :disable_autoimports
65
+
55
66
  def initialize(comments)
56
67
  @comments = comments
68
+
69
+ # check if magic comment is present:
70
+ first_comment = @comments.values.first&.map(&:text)&.first
71
+ @disable_autoimports = first_comment&.include?(" autoimports: false")
72
+ @disable_autoexports = first_comment&.include?(" autoexports: false")
73
+
57
74
  @ast = nil
58
75
  @exclude_methods = []
59
- @esm = false
76
+ @prepend_list = Set.new
60
77
  end
61
78
 
62
79
  def options=(options)
@@ -129,6 +146,7 @@ module Ruby2JS
129
146
  def on_method(node); on_send(node); end
130
147
  def on_prop(node); on_array(node); end
131
148
  def on_prototype(node); on_begin(node); end
149
+ def on_send!(node); on_send(node); end
132
150
  def on_sendw(node); on_send(node); end
133
151
  def on_undefined?(node); on_defined?(node); end
134
152
  def on_nil(node); end
@@ -162,6 +180,7 @@ module Ruby2JS
162
180
  def self.convert(source, options={})
163
181
  options[:eslevel] ||= @@eslevel_default
164
182
  options[:strict] = @@strict_default if options[:strict] == nil
183
+ options[:module] ||= @@module_default || :esm
165
184
 
166
185
  if Proc === source
167
186
  file,line = source.source_location
@@ -175,7 +194,7 @@ module Ruby2JS
175
194
  source = ast.loc.expression.source_buffer.source
176
195
  else
177
196
  ast, comments = parse( source, options[:file] )
178
- comments = Parser::Source::Comment.associate(ast, comments) if ast
197
+ comments = ast ? Parser::Source::Comment.associate(ast, comments) : {}
179
198
  end
180
199
 
181
200
  filters = (options[:filters] || Filter::DEFAULTS)
@@ -193,6 +212,12 @@ module Ruby2JS
193
212
 
194
213
  filter.options = options
195
214
  ast = filter.process(ast)
215
+
216
+ unless filter.prepend_list.empty?
217
+ prepend = filter.prepend_list.sort_by {|ast| ast.type == :import ? 0 : 1}
218
+ prepend.reject! {|ast| ast.type == :import} if filter.disable_autoimports
219
+ ast = Parser::AST::Node.new(:begin, [*prepend, ast])
220
+ end
196
221
  end
197
222
 
198
223
  ruby2js = Ruby2JS::Converter.new(ast, comments)
@@ -203,6 +228,7 @@ module Ruby2JS
203
228
  ruby2js.strict = options[:strict]
204
229
  ruby2js.comparison = options[:comparison] || :equality
205
230
  ruby2js.or = options[:or] || :logical
231
+ ruby2js.module_type = options[:module] || :esm
206
232
  ruby2js.underscored_private = (options[:eslevel] < 2020) || options[:underscored_private]
207
233
  if ruby2js.binding and not ruby2js.ivars
208
234
  ruby2js.ivars = ruby2js.binding.eval \
@@ -221,6 +247,8 @@ module Ruby2JS
221
247
 
222
248
  ruby2js.timestamp options[:file]
223
249
 
250
+ ruby2js.file_name = options[:file] || ast&.loc&.expression&.source_buffer&.name || ''
251
+
224
252
  ruby2js
225
253
  end
226
254
 
@@ -3,9 +3,11 @@ require 'ruby2js/serializer'
3
3
  module Ruby2JS
4
4
  class Error < NotImplementedError
5
5
  def initialize(message, ast)
6
- message += ' at ' + ast.loc.expression.source_buffer.name.to_s
7
- message += ':' + ast.loc.expression.line.inspect
8
- message += ':' + ast.loc.expression.column.to_s
6
+ if ast.loc
7
+ message += ' at ' + ast.loc.expression.source_buffer.name.to_s
8
+ message += ':' + ast.loc.expression.line.inspect
9
+ message += ':' + ast.loc.expression.column.to_s
10
+ end
9
11
  super(message)
10
12
  end
11
13
  end
@@ -128,7 +130,7 @@ module Ruby2JS
128
130
  Parser::AST::Node.new(type, args)
129
131
  end
130
132
 
131
- attr_accessor :strict, :eslevel, :comparison, :or, :underscored_private
133
+ attr_accessor :strict, :eslevel, :module_type, :comparison, :or, :underscored_private
132
134
 
133
135
  def es2015
134
136
  @eslevel >= 2015
@@ -246,7 +248,7 @@ module Ruby2JS
246
248
  if ast.loc and ast.loc.expression
247
249
  filename = ast.loc.expression.source_buffer.name
248
250
  if filename and not filename.empty?
249
- @timestamps[filename] ||= File.mtime(filename)
251
+ @timestamps[filename] ||= File.mtime(filename) rescue nil
250
252
  end
251
253
  end
252
254
 
@@ -9,11 +9,22 @@ module Ruby2JS
9
9
  # NOTE: this is the es2015 version of class
10
10
 
11
11
  handle :class2 do |name, inheritance, *body|
12
+ body.compact!
13
+ while body.length == 1 and body.first.type == :begin
14
+ body = body.first.children
15
+ end
16
+
17
+ proxied = body.find do |node|
18
+ node.type == :def and node.children.first == :method_missing
19
+ end
20
+
12
21
  if name.type == :const and name.children.first == nil
13
22
  put 'class '
14
23
  parse name
24
+ put '$' if proxied
15
25
  else
16
26
  parse name
27
+ put '$' if proxied
17
28
  put ' = class'
18
29
  end
19
30
 
@@ -24,11 +35,6 @@ module Ruby2JS
24
35
 
25
36
  put " {"
26
37
 
27
- body.compact!
28
- while body.length == 1 and body.first.type == :begin
29
- body = body.first.children
30
- end
31
-
32
38
  begin
33
39
  class_name, @class_name = @class_name, name
34
40
  class_parent, @class_parent = @class_parent, inheritance
@@ -64,21 +70,21 @@ module Ruby2JS
64
70
  walk[child] if child.is_a? Parser::AST::Node
65
71
  end
66
72
 
67
- if ast.type == :send and ast.children.first == nil
68
- if ast.children[1] == :attr_accessor
69
- ast.children[2..-1].each_with_index do |child_sym, index2|
70
- ivars << :"@#{child_sym.children.first}"
71
- end
72
- elsif ast.children[1] == :attr_reader
73
- ast.children[2..-1].each_with_index do |child_sym, index2|
74
- ivars << :"@#{child_sym.children.first}"
75
- end
76
- elsif ast.children[1] == :attr_writer
77
- ast.children[2..-1].each_with_index do |child_sym, index2|
78
- ivars << :"@#{child_sym.children.first}"
79
- end
80
- end
81
- end
73
+ if ast.type == :send and ast.children.first == nil
74
+ if ast.children[1] == :attr_accessor
75
+ ast.children[2..-1].each_with_index do |child_sym, index2|
76
+ ivars << :"@#{child_sym.children.first}"
77
+ end
78
+ elsif ast.children[1] == :attr_reader
79
+ ast.children[2..-1].each_with_index do |child_sym, index2|
80
+ ivars << :"@#{child_sym.children.first}"
81
+ end
82
+ elsif ast.children[1] == :attr_writer
83
+ ast.children[2..-1].each_with_index do |child_sym, index2|
84
+ ivars << :"@#{child_sym.children.first}"
85
+ end
86
+ end
87
+ end
82
88
 
83
89
  end
84
90
  walk[@ast]
@@ -299,6 +305,43 @@ module Ruby2JS
299
305
  end
300
306
  end
301
307
 
308
+ if proxied
309
+ put @sep
310
+
311
+ rename = name.updated(nil, [name.children.first, name.children.last.to_s + '$'])
312
+
313
+ if proxied.children[1].children.length == 1
314
+ # special case: if method_missing only has on argument, call it
315
+ # directly (i.e., don't pass arguments). This enables
316
+ # method_missing to return instance attributes (getters) as well
317
+ # as bound functions (methods).
318
+ forward = s(:send, s(:lvar, :obj), :method_missing, s(:lvar, :prop))
319
+ else
320
+ # normal case: return a function which, when called, will call
321
+ # method_missing with method name and arguments.
322
+ forward = s(:block, s(:send, nil, :proc), s(:args, s(:restarg, :args)),
323
+ s(:send, s(:lvar, :obj), :method_missing, s(:lvar, :prop),
324
+ s(:splat, s(:lvar, :args))))
325
+ end
326
+
327
+ proxy = s(:return, s(:send, s(:const, nil, :Proxy), :new,
328
+ s(:send, rename, :new, s(:splat, s(:lvar, :args))),
329
+ s(:hash, s(:pair, s(:sym, :get), s(:block, s(:send, nil, :proc),
330
+ s(:args, s(:arg, :obj), s(:arg, :prop)),
331
+ s(:if, s(:in?, s(:lvar, :prop), s(:lvar, :obj)),
332
+ s(:return, s(:send, s(:lvar, :obj), :[], s(:lvar, :prop))),
333
+ s(:return, forward))))))
334
+ )
335
+
336
+ if name.children.first == nil
337
+ proxy = s(:def, name.children.last, s(:args, s(:restarg, :args)), proxy)
338
+ else
339
+ proxy = s(:defs, *name.children, s(:args, s(:restarg, :args)), proxy)
340
+ end
341
+
342
+ parse proxy
343
+ end
344
+
302
345
  ensure
303
346
  @class_name = class_name
304
347
  @class_parent = class_parent
@@ -131,7 +131,7 @@ module Ruby2JS
131
131
  style = :statement
132
132
  end
133
133
 
134
- if args.children.length == 1 and style == :expression
134
+ if args.children.length == 1 and args.children.first.type != :restarg and style == :expression
135
135
  parse args; put ' => '
136
136
  else
137
137
  put '('; parse args; put ') => '
@@ -6,7 +6,23 @@ module Ruby2JS
6
6
  # NOTE: import is a synthetic
7
7
 
8
8
  handle :import do |path, *args|
9
+ if module_type == :cjs
10
+ # only the subset of import syntaxes generated by filters are handled here
11
+ if Parser::AST::Node === args.first and args.first.type == :attr
12
+ return parse s(:casgn, *args.first.children,
13
+ s(:send, nil, :require, s(:str, Array(path).first))), :statement
14
+ elsif Array === args.first and args.first.length == 1
15
+ target = args.first.first
16
+ if Parser::AST::Node === target and target.type == :attr and target.children.first == nil
17
+ return parse s(:casgn, *target.children,
18
+ s(:attr, s(:send, nil, :require, s(:str, Array(path).first)), target.children.last)),
19
+ :statement
20
+ end
21
+ end
22
+ end
23
+
9
24
  put 'import '
25
+
10
26
  if args.length == 0
11
27
  # import "file.css"
12
28
  put path.inspect
@@ -66,7 +82,7 @@ module Ruby2JS
66
82
  elsif node.respond_to?(:type) && node.children[1] == :default
67
83
  put 'default '
68
84
  args[0] = node.children[2]
69
- elsif node.respond_to?(:type) && node.type == :lvasgn
85
+ elsif node.respond_to?(:type) && [:lvasgn, :casgn].include?(node.type)
70
86
  if node.children[0] == :default
71
87
  put 'default '
72
88
  args[0] = node.children[1]
@@ -16,8 +16,16 @@ module Ruby2JS
16
16
  handle :hostvalue do |value|
17
17
  case value
18
18
  when Hash
19
- parse s(:hash, *value.map {|key, hvalue| s(:pair, s(:hostvalue, key),
20
- s(:hostvalue, hvalue))})
19
+ parse s(:hash, *value.map {|key, hvalue|
20
+ case key
21
+ when String
22
+ s(:pair, s(:str, key), s(:hostvalue, hvalue))
23
+ when Symbol
24
+ s(:pair, s(:sym, key), s(:hostvalue, hvalue))
25
+ else
26
+ s(:pair, s(:hostvalue, key), s(:hostvalue, hvalue))
27
+ end
28
+ })
21
29
  when Array
22
30
  parse s(:array, *value.map {|hvalue| s(:hostvalue, hvalue)})
23
31
  when String
@@ -5,12 +5,24 @@ module Ruby2JS
5
5
  # (float 1.1)
6
6
  # (str "1"))
7
7
 
8
- handle :int, :float, :str do |value|
8
+ handle :str do |value|
9
9
  put value.inspect
10
10
  end
11
11
 
12
+ handle :int, :float do |value|
13
+ put number_format(value)
14
+ end
15
+
12
16
  handle :octal do |value|
13
- put '0' + value.to_s(8)
17
+ put '0' + number_format(value.to_s(8))
18
+ end
19
+
20
+ def number_format(number)
21
+ return number.to_s unless es2021
22
+ parts = number.to_s.split('.')
23
+ parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1_")
24
+ parts[1].gsub!(/(\d\d\d)(?=\d)/, "\\1_") if parts[1]
25
+ parts.join('.')
14
26
  end
15
27
  end
16
28
  end
@@ -6,6 +6,11 @@ module Ruby2JS
6
6
  # (...)
7
7
 
8
8
  handle :module do |name, *body|
9
+ if body == [nil]
10
+ parse @ast.updated(:casgn, [*name.children, s(:hash)])
11
+ return
12
+ end
13
+
9
14
  while body.length == 1 and body.first.type == :begin
10
15
  body = body.first.children
11
16
  end
@@ -9,11 +9,12 @@ module Ruby2JS
9
9
  # (sendw nil :puts
10
10
  # (int 1))
11
11
 
12
- # Note: attr, sendw, and await are only generated by filters. Attr forces
12
+ # Note: attr, sendw, send!, and await are only generated by filters. Attr forces
13
13
  # interpretation as an attribute vs a function call with zero parameters.
14
+ # send! forces interpretation as a method call even with zero parameters.
14
15
  # Sendw forces parameters to be placed on separate lines.
15
16
 
16
- handle :send, :sendw, :await, :attr, :call do |receiver, method, *args|
17
+ handle :send, :sendw, :send!, :await, :attr, :call do |receiver, method, *args|
17
18
  ast = @ast
18
19
 
19
20
  if \
@@ -280,7 +281,7 @@ module Ruby2JS
280
281
  else
281
282
  put 'await ' if @ast.type == :await
282
283
 
283
- if not ast.is_method?
284
+ if not ast.is_method? and ast.type != :send!
284
285
  if receiver
285
286
  (group_receiver ? group(receiver) : parse(receiver))
286
287
  put ".#{ method }"
@@ -10,7 +10,7 @@ module Ruby2JS
10
10
  if @binding
11
11
  puts @binding.eval(str).to_s
12
12
  else
13
- puts eval(str).to_s
13
+ raise SecurityError.new('Insecure operation, eval without binding option')
14
14
  end
15
15
  end
16
16
  end
@@ -0,0 +1,43 @@
1
+ require 'ruby2js'
2
+
3
+ module Ruby2JS
4
+ module Filter
5
+ module ActiveFunctions
6
+ include SEXP
7
+
8
+ def on_send(node)
9
+ target, method, *args = node.children
10
+
11
+ if es2015 and method == :blank?
12
+ create_or_update_import("blank$")
13
+ process node.updated :send, [nil, "blank$", target]
14
+ elsif es2015 and method == :present?
15
+ create_or_update_import("present$")
16
+ process node.updated :send, [nil, "present$", target]
17
+ elsif es2015 and method == :presence
18
+ create_or_update_import("presence$")
19
+ process node.updated :send, [nil, "presence$", target]
20
+ else
21
+ super
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def create_or_update_import(token)
28
+ af_import = @options[:import_from_skypack] ? "https://cdn.skypack.dev/@ruby2js/active-functions" : "@ruby2js/active-functions"
29
+
30
+ if found_node = prepend_list.find {|ast| ast.type == :import && ast.children.first == af_import}
31
+ unless found_node.children.last.find {|const| const.children.last == token}
32
+ prepend_list.delete found_node
33
+ prepend_list << s(:import, found_node.children.first, found_node.children.last.push(s(:const, nil, token)))
34
+ end
35
+ else
36
+ prepend_list << s(:import, af_import, [s(:const, nil, token)])
37
+ end
38
+ end
39
+ end
40
+
41
+ DEFAULTS.push ActiveFunctions
42
+ end
43
+ end
@@ -9,8 +9,9 @@ module Ruby2JS
9
9
  module CamelCase
10
10
  include SEXP
11
11
 
12
- WHITELIST = %w{
12
+ ALLOWLIST = %w{
13
13
  attr_accessor
14
+ method_missing
14
15
  }
15
16
 
16
17
  CAPS_EXCEPTIONS = {
@@ -37,7 +38,7 @@ module Ruby2JS
37
38
  node = super
38
39
  return node unless [:send, :csend, :attr].include? node.type
39
40
 
40
- if node.children[0] == nil and WHITELIST.include? node.children[1].to_s
41
+ if node.children[0] == nil and ALLOWLIST.include? node.children[1].to_s
41
42
  node
42
43
  elsif node.children[0] && [:ivar, :cvar].include?(node.children[0].type)
43
44
  S(node.type, s(node.children[0].type, camelCase(node.children[0].children[0])),
@@ -61,7 +62,7 @@ module Ruby2JS
61
62
  def handle_generic_node(node, node_type)
62
63
  return node if node.type != node_type
63
64
 
64
- if node.children[0] =~ /_.*\w$/
65
+ if node.children[0] =~ /_.*\w$/ and !ALLOWLIST.include?(node.children[0].to_s)
65
66
  S(node_type , camelCase(node.children[0]), *node.children[1..-1])
66
67
  else
67
68
  node