ruby2js 3.4.0 → 3.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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.find {|child| child == 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
@@ -1,13 +1,68 @@
1
1
  require 'ruby2js'
2
2
 
3
+ Ruby2JS.module_default = :esm
4
+
3
5
  module Ruby2JS
4
6
  module Filter
5
7
  module ESM
6
8
  include SEXP
7
9
 
8
- def initialize(*args)
10
+ def options=(options)
11
+ super
12
+ @esm_autoexports = options[:autoexports] && !@disable_autoexports
13
+ @esm_autoimports = options[:autoimports]
14
+ @esm_explicit_tokens = Set.new
15
+ end
16
+
17
+ def process(node)
18
+ return super unless @esm_autoexports
19
+ @esm_autoexports = false
20
+
21
+ list = [node]
22
+ while list.length == 1 and list.first.type == :begin
23
+ list = list.first.children.dup
24
+ end
25
+
26
+ list.map! do |child|
27
+ replacement = child
28
+
29
+ if [:module, :class].include? child.type and
30
+ child.children.first.type == :const and
31
+ child.children.first.children.first == nil \
32
+ then
33
+ replacement = s(:export, child)
34
+ elsif child.type == :casgn and child.children.first == nil
35
+ replacement = s(:export, child)
36
+ elsif child.type == :def
37
+ replacement = s(:export, child)
38
+ end
39
+
40
+ if replacement != child and @comments[child]
41
+ @comments[replacement] = @comments[child]
42
+ end
43
+
44
+ replacement
45
+ end
46
+
47
+ process s(:begin, *list)
48
+ end
49
+
50
+ def on_class(node)
51
+ @esm_explicit_tokens << node.children.first.children.last
52
+
53
+ super
54
+ end
55
+
56
+ def on_def(node)
57
+ @esm_explicit_tokens << node.children.first
58
+
59
+ super
60
+ end
61
+
62
+ def on_lvasgn(node)
63
+ @esm_explicit_tokens << node.children.first
64
+
9
65
  super
10
- @esm = true # signal for other filters
11
66
  end
12
67
 
13
68
  def on_send(node)
@@ -36,6 +91,8 @@ module Ruby2JS
36
91
  args[0].children[2].children[2].type == :str
37
92
  # import name from "file.js"
38
93
  # => import name from "file.js"
94
+ @esm_explicit_tokens << args[0].children[1]
95
+
39
96
  s(:import,
40
97
  [args[0].children[2].children[2].children[0]],
41
98
  process(s(:attr, nil, args[0].children[1])))
@@ -49,17 +106,52 @@ module Ruby2JS
49
106
  # => import Stuff as * from "file.js"
50
107
  # import [ Some, Stuff ], from: "file.js"
51
108
  # => import { Some, Stuff } from "file.js"
52
- imports = (args[0].type == :const || args[0].type == :send) ?
53
- process(args[0]) :
109
+ imports = if args[0].type == :const || args[0].type == :send
110
+ @esm_explicit_tokens << args[0].children.last
111
+ process(args[0])
112
+ else
113
+ args[0].children.each {|i| @esm_explicit_tokens << i.children.last}
54
114
  process_all(args[0].children)
115
+ end
116
+
55
117
  s(:import, args[1].children, imports) unless args[1].nil?
56
118
  end
57
119
  elsif method == :export
58
120
  s(:export, *process_all(args))
121
+ elsif target.nil? and found_import = find_autoimport(method)
122
+ prepend_list << s(:import, found_import[0], found_import[1])
123
+ super
59
124
  else
60
125
  super
61
126
  end
62
127
  end
128
+
129
+ def on_const(node)
130
+ if node.children.first == nil and found_import = find_autoimport(node.children.last)
131
+ prepend_list << s(:import, found_import[0], found_import[1])
132
+ end
133
+
134
+ super
135
+ end
136
+
137
+ def on_export(node)
138
+ s(:export, *process_all(node.children))
139
+ end
140
+ end
141
+
142
+ private
143
+
144
+ def find_autoimport(token)
145
+ return nil if @esm_autoimports.nil?
146
+ return nil if @esm_explicit_tokens.include?(token)
147
+
148
+ token = camelCase(token) if respond_to?(:camelCase)
149
+
150
+ if @esm_autoimports[token]
151
+ [@esm_autoimports[token], s(:const, nil, token)]
152
+ elsif found_key = @esm_autoimports.keys.find {|key| key.is_a?(Array) && key.include?(token)}
153
+ [@esm_autoimports[found_key], found_key.map {|key| s(:const, nil, key)}]
154
+ end
63
155
  end
64
156
 
65
157
  DEFAULTS.push ESM
@@ -531,6 +531,20 @@ module Ruby2JS
531
531
  elsif method == :block_given? and target == nil and args.length == 0
532
532
  process process s(:lvar, "_implicitBlockYield")
533
533
 
534
+ elsif method == :abs and args.length == 0
535
+ process S(:send, s(:const, nil, :Math), :abs, target)
536
+
537
+ elsif method == :ceil and args.length == 0
538
+ process S(:send, s(:const, nil, :Math), :ceil, target)
539
+
540
+ elsif method == :floor and args.length == 0
541
+ process S(:send, s(:const, nil, :Math), :floor, target)
542
+
543
+ elsif method == :sum and args.length == 0
544
+ process S(:send, target, :reduce, s(:block, s(:send, nil, :proc),
545
+ s(:args, s(:arg, :a), s(:arg, :b)),
546
+ s(:send, s(:lvar, :a), :+, s(:lvar, :b))), s(:int, 0))
547
+
534
548
  else
535
549
  super
536
550
  end