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
@@ -10,6 +10,11 @@ module Ruby2JS
10
10
  # (...))
11
11
 
12
12
  handle :dstr, :dsym do |*children|
13
+ if @state == :expression and children.empty?
14
+ puts '""'
15
+ return
16
+ end
17
+
13
18
  if es2015
14
19
  # gather length of string parts; if long enough, newlines will
15
20
  # not be escaped (poor man's HEREDOC)
@@ -26,7 +31,7 @@ module Ruby2JS
26
31
  else
27
32
  put str
28
33
  end
29
- else
34
+ elsif child != s(:begin)
30
35
  put '${'
31
36
  parse child
32
37
  put '}'
@@ -40,8 +45,8 @@ module Ruby2JS
40
45
  children.each_with_index do |child, index|
41
46
  put ' + ' unless index == 0
42
47
 
43
- if child.type == :begin and child.children.length == 1
44
- child = child.children.first
48
+ if child.type == :begin and child.children.length <= 1
49
+ child = child.children.first || s(:str, '')
45
50
  end
46
51
 
47
52
  if child.type == :send
@@ -73,7 +73,7 @@ module Ruby2JS
73
73
  if right.type == :hash
74
74
  right.children.each do |pair|
75
75
  next unless Parser::AST::Node === pair.children.last
76
- if [:block, :def, :async].include? pair.children.last.type
76
+ if %i[block def defm async].include? pair.children.last.type
77
77
  if @comments[pair.children.last]
78
78
  (puts ''; singleton = false) if singleton
79
79
  comments(pair.children.last).each do |comment|
@@ -120,22 +120,26 @@ module Ruby2JS
120
120
 
121
121
  if \
122
122
  anonfn and
123
- left.children.first.to_s =~ /\A[a-zA-Z_$][a-zA-Z_$0-9]*\Z/
123
+ left.children.first.to_s =~ /\A[a-zA-Z_$][a-zA-Z_$0-9]*\z/
124
124
  then
125
125
  @prop = left.children.first
126
126
  parse right, :method
127
127
  elsif \
128
- es2015 and left.type == :sym and right.type == :lvar and
129
- left.children == right.children
128
+ es2015 and left.type == :sym and (right.type == :lvar or
129
+ (right.type == :send and right.children.first == nil)) and
130
+ left.children.last == right.children.last
130
131
  then
131
132
  parse right
133
+ elsif right.type == :defm and %i[sym str].include? left.type and es2015
134
+ @prop = left.children.first.to_s
135
+ parse right
132
136
  else
133
137
  if not [:str, :sym].include? left.type and es2015
134
138
  put '['
135
139
  parse left
136
140
  put ']'
137
141
  elsif \
138
- left.children.first.to_s =~ /\A[a-zA-Z_$][a-zA-Z_$0-9]*\Z/
142
+ left.children.first.to_s =~ /\A[a-zA-Z_$][a-zA-Z_$0-9]*\z/
139
143
  then
140
144
  put left.children.first
141
145
  else
@@ -0,0 +1,13 @@
1
+ module Ruby2JS
2
+ class Converter
3
+
4
+ # (hide, ...)
5
+
6
+ handle :hide do |*nodes|
7
+ capture {parse_all(*nodes)}
8
+
9
+ @lines.pop if @state == :statement and @lines.last == []
10
+ @lines.last.pop if @lines.last.last == @sep
11
+ end
12
+ end
13
+ end
@@ -64,10 +64,18 @@ module Ruby2JS
64
64
  if else_block.type == :begin
65
65
  else_block = s(:xnode, '', *else_block.children)
66
66
  end
67
+ else
68
+ if then_block.type == :begin
69
+ then_block = s(:kwbegin, then_block)
70
+ end
71
+
72
+ if else_block.type == :begin
73
+ else_block = s(:kwbegin, else_block)
74
+ end
67
75
  end
68
76
 
69
- parse condition; put ' ? '; parse then_block
70
- put ' : '; parse else_block
77
+ parse condition; put ' ? '; parse then_block, @state
78
+ put ' : '; parse else_block, @state
71
79
  end
72
80
  end
73
81
  end
@@ -6,20 +6,51 @@ 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
13
29
  else
14
30
  # import (x) from "file.js"
15
- default_import = !args.first.is_a?(Array) && [:const, :send, :attr].include?(args.first.type)
31
+ default_import = !args.first.is_a?(Array) && %i[const send attr str].include?(args.first.type)
32
+
33
+ if default_import and args.length > 1
34
+ parse args.shift
35
+ put ', '
36
+ default_import = false
37
+ end
38
+
16
39
  args = args.first if args.first.is_a?(Array)
17
40
 
41
+ if args.first.type == :array
42
+ args = args.first.children
43
+ end
44
+
18
45
  # handle the default name or { ConstA, Const B } portion
19
46
  put "{ " unless default_import
20
47
  args.each_with_index do |arg, index|
21
48
  put ', ' unless index == 0
22
- parse arg
49
+ if arg.type == :str
50
+ put arg.children.first # useful for '*'
51
+ else
52
+ parse arg
53
+ end
23
54
  end
24
55
  put " }" unless default_import
25
56
 
@@ -27,7 +58,7 @@ module Ruby2JS
27
58
 
28
59
  # should there be an as clause? e.g., import React as *
29
60
  if path.is_a?(Array) && !path[0].is_a?(String) && path[0].type == :pair && path[0].children[0].children[0] == :as
30
- put " as #{path[0].children[1].children[0]}"
61
+ put " as #{path[0].children[1].children.last}"
31
62
 
32
63
  # advance to the next kwarg, aka from
33
64
  from_kwarg_position = 1
@@ -66,7 +97,7 @@ module Ruby2JS
66
97
  elsif node.respond_to?(:type) && node.children[1] == :default
67
98
  put 'default '
68
99
  args[0] = node.children[2]
69
- elsif node.respond_to?(:type) && node.type == :lvasgn
100
+ elsif node.respond_to?(:type) && [:lvasgn, :casgn].include?(node.type)
70
101
  if node.children[0] == :default
71
102
  put 'default '
72
103
  args[0] = node.children[1]
@@ -6,7 +6,7 @@ module Ruby2JS
6
6
  # (resbody nil nil
7
7
  # (send nil :b)) nil)
8
8
  handle :rescue do |*statements|
9
- parse_all s(:kwbegin, s(:rescue, *statements))
9
+ parse s(:kwbegin, s(:rescue, *statements)), @state
10
10
  end
11
11
 
12
12
  # (kwbegin
@@ -19,7 +19,14 @@ module Ruby2JS
19
19
 
20
20
  handle :kwbegin do |*children|
21
21
  block = children.first
22
- if block.type == :ensure
22
+
23
+ if @state == :expression
24
+ parse s(:send, s(:block, s(:send, nil, :proc), s(:args),
25
+ s(:begin, s(:autoreturn, *children))), :[])
26
+ return
27
+ end
28
+
29
+ if block&.type == :ensure
23
30
  block, finally = block.children
24
31
  else
25
32
  finally = nil
@@ -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] = parts[0].gsub(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1_")
24
+ parts[1] = parts[1].gsub(/(\d\d\d)(?=\d)/, "\\1_") if parts[1]
25
+ parts.join('.')
14
26
  end
15
27
  end
16
28
  end
@@ -4,14 +4,43 @@ module Ruby2JS
4
4
  # (module
5
5
  # (const nil :A)
6
6
  # (...)
7
+ #
8
+ # Note: modules_hash is an anonymous modules as a value in a hash; the
9
+ # name has already been output so should be ignored other than
10
+ # in determining the namespace.
11
+
12
+ handle :module, :module_hash do |name, *body|
13
+ extend = @namespace.enter(name)
14
+
15
+ if body == [nil]
16
+ if @ast.type == :module and not extend
17
+ parse @ast.updated(:casgn, [*name.children, s(:hash)])
18
+ else
19
+ parse @ast.updated(:hash, [])
20
+ end
21
+
22
+ @namespace.leave
23
+ return
24
+ end
7
25
 
8
- handle :module do |name, *body|
9
26
  while body.length == 1 and body.first.type == :begin
10
27
  body = body.first.children
11
28
  end
12
29
 
13
- if body.length > 0 and body.all? {|child| child.type == :def}
14
- parse @ast.updated(:class_module, [name, nil, *body])
30
+ if body.length > 0 and body.all? {|child|
31
+ %i[def module].include? child.type or
32
+ (es2015 and child.type == :class and child.children[1] == nil)}
33
+
34
+ if extend
35
+ parse s(:assign, name, @ast.updated(:class_module,
36
+ [nil, nil, *body])), :statement
37
+ elsif @ast.type == :module_hash
38
+ parse @ast.updated(:class_module, [nil, nil, *body])
39
+ else
40
+ parse @ast.updated(:class_module, [name, nil, *body])
41
+ end
42
+
43
+ @namespace.leave
15
44
  return
16
45
  end
17
46
 
@@ -42,6 +71,8 @@ module Ruby2JS
42
71
  symbols << node.children.first
43
72
  elsif node.type == :class and node.children.first.children.first == nil
44
73
  symbols << node.children.first.children.last
74
+ elsif node.type == :module
75
+ symbols << node.children.first.children.last
45
76
  end
46
77
  end
47
78
 
@@ -50,11 +81,17 @@ module Ruby2JS
50
81
 
51
82
  body = s(:send, s(:block, s(:send, nil, :proc), s(:args),
52
83
  s(:begin, *body)), :[])
53
- if name.children.first == nil
84
+ if not name
85
+ parse body
86
+ elsif extend
87
+ parse s(:assign, name, body)
88
+ elsif name.children.first == nil
54
89
  parse s(:lvasgn, name.children.last, body)
55
90
  else
56
91
  parse s(:send, name.children.first, "#{name.children.last}=", body)
57
92
  end
93
+
94
+ @namespace.leave
58
95
  end
59
96
  end
60
97
  end
@@ -12,6 +12,14 @@ module Ruby2JS
12
12
  var = s(:lvar, var.children.first) if var.type == :lvasgn
13
13
  var = s(:cvar, var.children.first) if var.type == :cvasgn
14
14
 
15
+ if var.type == :lvar
16
+ name = var.children.first
17
+ receiver = @rbstack.map {|rb| rb[name]}.compact.last
18
+ if receiver
19
+ var = s(:attr, nil, name)
20
+ end
21
+ end
22
+
15
23
  if \
16
24
  [:+, :-].include?(op) and value.type==:int and
17
25
  (value.children==[1] or value.children==[-1])
@@ -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 \
@@ -40,6 +41,20 @@ module Ruby2JS
40
41
  # strip '!' and '?' decorations
41
42
  method = method.to_s[0..-2] if method =~ /\w[!?]$/
42
43
 
44
+ # anonymous class
45
+ if method == :new and receiver and receiver.children == [nil, :Class] and
46
+ args.last.type == :def and args.last.children.first == nil
47
+
48
+ parent = (args.length > 1) ? args.first : nil
49
+
50
+ if es2015
51
+ return parse s(:class2, nil, parent, *args.last.children[2..-1])
52
+ else
53
+ return parse s(:kwbegin, s(:class, s(:const, nil, :$$), parent,
54
+ *args.last.children[2..-1]), s(:const, nil, :$$))
55
+ end
56
+ end
57
+
43
58
  # three ways to define anonymous functions
44
59
  if method == :new and receiver and receiver.children == [nil, :Proc]
45
60
  return parse args.first, @state
@@ -117,8 +132,13 @@ module Ruby2JS
117
132
 
118
133
  # resolve anonymous receivers against rbstack
119
134
  receiver ||= @rbstack.map {|rb| rb[method]}.compact.last
135
+ autobind = nil
120
136
 
121
137
  if receiver
138
+ if receiver.type == :autobind
139
+ autobind = receiver = receiver.children.first
140
+ end
141
+
122
142
  group_receiver = receiver.type == :send &&
123
143
  op_index < operator_index( receiver.children[1] ) if receiver
124
144
  group_receiver ||= GROUP_OPERATORS.include? receiver.type
@@ -175,9 +195,15 @@ module Ruby2JS
175
195
  receiver.type == :send and
176
196
  receiver.children[1] == :+@ and
177
197
  Parser::AST::Node === receiver.children[0] and
178
- receiver.children[0].type == :class
198
+ %i(class module).include? receiver.children[0].type
179
199
  then
180
- parse receiver.children[0].updated(:class_extend)
200
+ if receiver.children[0].type == :class
201
+ parse receiver.children[0].updated(:class_extend)
202
+ else
203
+ mod = receiver.children[0]
204
+ parse s(:assign, mod.children[0],
205
+ mod.updated(nil, [nil, *mod.children[1..-1]]))
206
+ end
181
207
  else
182
208
  put method.to_s[0]; parse receiver
183
209
  end
@@ -280,7 +306,13 @@ module Ruby2JS
280
306
  else
281
307
  put 'await ' if @ast.type == :await
282
308
 
283
- if not ast.is_method?
309
+ if method == :bind and receiver&.type == :send
310
+ if receiver.children.length == 2 and receiver.children.first == nil
311
+ receiver = receiver.updated(:attr) # prevent autobind
312
+ end
313
+ end
314
+
315
+ if not ast.is_method? and ast.type != :send!
284
316
  if receiver
285
317
  (group_receiver ? group(receiver) : parse(receiver))
286
318
  put ".#{ method }"
@@ -302,6 +334,14 @@ module Ruby2JS
302
334
  compact { puts "("; parse_all(*args, join: ",#@ws"); sput ')' }
303
335
  end
304
336
  end
337
+
338
+ if autobind and not ast.is_method? and ast.type != :attr
339
+ if @state == :statement
340
+ put '()'
341
+ else
342
+ put '.bind('; parse(autobind); put ')'
343
+ end
344
+ end
305
345
  end
306
346
  end
307
347
 
@@ -8,6 +8,11 @@ module Ruby2JS
8
8
  # (int 1))
9
9
 
10
10
  handle :lvasgn, :gvasgn do |name, value=nil|
11
+ if @ast.type == :lvasgn and value
12
+ receiver = @rbstack.map {|rb| rb[name]}.compact.last
13
+ return parse s(:attr, receiver, "#{name}=", value) if receiver
14
+ end
15
+
11
16
  state = @state
12
17
  begin
13
18
  if value and value.type == :lvasgn and @state == :statement
@@ -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,53 @@
1
+ # helper methods shared between MRI CGI/server and Opal implementations
2
+ require 'ruby2js'
3
+
4
+ module Ruby2JS
5
+ module Demo
6
+ def self.parse_autoimports(mappings)
7
+ autoimports = {}
8
+
9
+ mappings = mappings.gsub(/\s+|"|'/, '')
10
+
11
+ while mappings and not mappings.empty?
12
+ if mappings =~ /^(\w+):([^,]+)(,(.*))?$/
13
+ # symbol: module
14
+ autoimports[$1.to_sym] = $2
15
+ mappings = $4
16
+ elsif mappings =~ /^\[([\w,]+)\]:([^,]+)(,(.*))?$/
17
+ # [symbol, symbol]: module
18
+ mname, mappings = $2, $4
19
+ autoimports[$1.split(/,/).map(&:to_sym)] = mname
20
+ elsif mappings =~ /^(\w+)(,(.*))?$/
21
+ # symbol
22
+ autoimports[$1.to_sym] = $1
23
+ mappings = $3
24
+ elsif not mappings.empty?
25
+ $load_error = "unsupported autoimports mapping: #{mappings}"
26
+ mappings = ''
27
+ end
28
+ end
29
+
30
+ # if nothing is listed, provide a mapping for everything
31
+ autoimports = proc {|name| name.to_s} if autoimports.empty?
32
+
33
+ autoimports
34
+ end
35
+
36
+ def self.parse_defs(mappings)
37
+ defs = {}
38
+
39
+ mappings = mappings.gsub(/\s+|"|'/, '')
40
+
41
+ while mappings =~ /^(\w+):\[(:?@?\w+(,:?@?\w+)*)\](,(.*))?$/
42
+ mappings = $5
43
+ defs[$1.to_sym] = $2.gsub(':', '').split(',').map(&:to_sym)
44
+ end
45
+
46
+ if mappings and not mappings.empty?
47
+ $load_error = "unsupported defs: #{mappings}"
48
+ end
49
+
50
+ defs
51
+ end
52
+ end
53
+ end