ruby2js 3.5.0 → 3.6.1

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