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
@@ -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