ruby2js 3.0.9 → 3.0.10

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 075b7a63226221fc941c3ccd747285d1bf9a3e94cfefd39926e530c9e41ba0a0
4
- data.tar.gz: 7a01d0dfef5bba7c05393a4059f4899631d6bac7894b98801dfc04634b4fcb84
3
+ metadata.gz: fe86f145da2a356f2f0356636b82b7e3dd03c3c20b18e4f4df73c02e94eb8517
4
+ data.tar.gz: cd4ddff033e394e8752acdf8c28ac36a9c8a9b659514b4e6eed5f1ed6af0d01c
5
5
  SHA512:
6
- metadata.gz: 62e0c730806b6bb3dc2060028d37dee4a181c9aa43be77415ed9cfca016ebae821388ae1b696d6de9a1f634730163b2a2f9d3b95aac321ca6a11ddfabbc1383e
7
- data.tar.gz: ae0a44b9d2a547def9fcc86b5af2dd1e5134881b0224c4e65a80e614215b1803a6be80649270622113829f531070aacfceed99a2edca364f661f2c75521fb214
6
+ metadata.gz: 3dfe608effc755e3e9f17e68710b9ad5833184fa022a8632ca7227ddeabf2894c207a021a4083da617c865e0499807127a7cb0dc8b32f332cc0de21419c887e2
7
+ data.tar.gz: 94fe88ca8908058f69d590602108d586a0b191012603648e072686bd35bf8b3413241f4d04200aff3820f823dd22cf6e1dec9548293c343636632f7405390c7f
data/README.md CHANGED
@@ -19,7 +19,8 @@ For example:
19
19
  parenthesis are used
20
20
  * otherwise Ruby method calls become JavaScript property accesses.
21
21
  * by default, methods and procs return `undefined`
22
- * splats are handled (may need to try around or read transliteration_spec)
22
+ * splats mapped to spread syntax when ES2015 or later is selected, and
23
+ to equivalents using `apply`, `concat`, `slice`, and `arguments` otherwise.
23
24
  * ruby string interpolation is expanded into string + operations
24
25
  * `and` and `or` become `&&` and `||`
25
26
  * `a ** b` becomes `Math.pow(a,b)`
@@ -38,6 +39,9 @@ For example:
38
39
  * Any block becomes and explicit argument `new Promise do; y(); end` becomes `new Promise(function() {y()})`
39
40
  * regular expressions are mapped to js
40
41
  * `raise` becomes `throw`
42
+ * expressions enclosed in backtick operators (\`\`) and `%x{}` literals are
43
+ evaluated in the context of the caller and the results are inserted
44
+ into the generated JavaScript.
41
45
 
42
46
  Ruby attribute accessors, methods defined with no parameters and no
43
47
  parenthesis, as well as setter method definitions, are
data/lib/ruby2js.rb CHANGED
@@ -92,6 +92,7 @@ module Ruby2JS
92
92
  def on_await(node); on_send(node); end
93
93
  def on_call(node); on_send(node); end
94
94
  def on_class_extend(node); on_send(node); end
95
+ def on_class_module(node); on_send(node); end
95
96
  def on_constructor(node); on_def(node); end
96
97
  def on_defm(node); on_defs(node); end
97
98
  def on_defp(node); on_defs(node); end
@@ -39,7 +39,8 @@ module Ruby2JS
39
39
 
40
40
  @ast, @comments, @vars = ast, comments, vars.dup
41
41
  @varstack = []
42
- @scope = true
42
+ @scope = ast
43
+ @inner = nil
43
44
  @rbstack = []
44
45
  @next_token = :return
45
46
 
@@ -66,7 +67,7 @@ module Ruby2JS
66
67
  end
67
68
 
68
69
  def convert
69
- parse( @ast, :statement )
70
+ scope @ast
70
71
 
71
72
  if @strict
72
73
  if @sep == '; '
@@ -81,15 +82,42 @@ module Ruby2JS
81
82
  OPERATORS.index( OPERATORS.find{ |el| el.include? op } ) || -1
82
83
  end
83
84
 
85
+ # define a new scope; primarily determines what variables are visible and deals with hoisting of
86
+ # declarations
84
87
  def scope( ast, args=nil )
85
- scope, @scope = @scope, true
88
+ scope, @scope = @scope, ast
89
+ inner, @inner = @inner, nil
90
+ mark = output_location
86
91
  @varstack.push @vars
87
92
  @vars = args if args
88
93
  @vars = Hash[@vars.map {|key, value| [key, true]}]
94
+
89
95
  parse( ast, :statement )
96
+
97
+ # retroactively add a declaration for 'pending' variables
98
+ vars = @vars.select {|key, value| value == :pending}.keys
99
+ unless vars.empty?
100
+ insert mark, "#{es2015 ? 'let' : 'var'} #{vars.join(', ')}#{@sep}"
101
+ vars.each {|var| @vars[var] = true}
102
+ end
90
103
  ensure
91
104
  @vars = @varstack.pop
92
105
  @scope = scope
106
+ @inner = inner
107
+ end
108
+
109
+ # handle the oddity where javascript considers there to be a scope (e.g. the body of an if statement),
110
+ # whereas Ruby does not.
111
+ def jscope( ast, args=nil )
112
+ @varstack.push @vars
113
+ @vars = args if args
114
+ @vars = Hash[@vars.map {|key, value| [key, true]}]
115
+
116
+ parse( ast, :statement )
117
+ ensure
118
+ pending = @vars.select {|key, value| value == :pending}
119
+ @vars = @varstack.pop
120
+ @vars.merge! pending
93
121
  end
94
122
 
95
123
  def s(type, *args)
@@ -10,8 +10,7 @@ module Ruby2JS
10
10
 
11
11
  handle :case do |expr, *whens, other|
12
12
  begin
13
- scope, @scope = @scope, false
14
- mark = output_location
13
+ inner, @inner = @inner, @ast
15
14
 
16
15
  has_range = whens.any? do |node|
17
16
  node.children.any? {|child| [:irange, :erange].include? child.type}
@@ -61,16 +60,8 @@ module Ruby2JS
61
60
  (put "#{@nl}default:#@ws"; parse other, :statement) if other
62
61
 
63
62
  sput '}'
64
-
65
- if scope
66
- vars = @vars.select {|key, value| value == :pending}.keys
67
- unless vars.empty?
68
- insert mark, "#{es2015 ? 'let' : 'var'} #{vars.join(', ')}#{@sep}"
69
- vars.each {|var| @vars[var] = true}
70
- end
71
- end
72
63
  ensure
73
- @scope = scope
64
+ @inner = inner
74
65
  end
75
66
  end
76
67
  end
@@ -12,11 +12,11 @@ module Ruby2JS
12
12
  # when ++class is encountered; it signals that this construct is
13
13
  # meant to extend an already existing JavaScrpt class.
14
14
 
15
- handle :class, :class_extend do |name, inheritance, *body|
16
- if @ast.type == :class_extend
15
+ handle :class, :class_extend, :class_module do |name, inheritance, *body|
16
+ if @ast.type != :class
17
17
  init = nil
18
18
  else
19
- if es2015 and @ast.type != :class_extend
19
+ if es2015
20
20
  parse @ast.updated(:class2)
21
21
  return
22
22
  end
@@ -37,6 +37,13 @@ module Ruby2JS
37
37
  body.compact!
38
38
  visible = {}
39
39
  body.map! do |m|
40
+ if
41
+ @ast.type == :class_module and m.type == :defs and
42
+ m.children.first == s(:self)
43
+ then
44
+ m = m.updated(:def, m.children[1..-1])
45
+ end
46
+
40
47
  node = if m.type == :def
41
48
  if m.children.first == :initialize
42
49
  # constructor: remove from body and overwrite init function
@@ -115,6 +122,15 @@ module Ruby2JS
115
122
  enumerable: s(:true),
116
123
  configurable: s(:true)})
117
124
  end
125
+ elsif m.children[1] == :include
126
+ s(:send, s(:block, s(:send, nil, :lambda), s(:args),
127
+ s(:begin, *m.children[2..-1].map {|modname|
128
+ s(:for, s(:lvasgn, :$_), modname,
129
+ s(:send, s(:send, name, :prototype), :[]=,
130
+ s(:lvar, :$_), s(:send, modname, :[], s(:lvar, :$_))))
131
+ })), :[])
132
+ elsif [:private, :protected, :public].include? m.children[1]
133
+ raise Error.new("class #{m.children[1]} is not supported", @ast)
118
134
  else
119
135
  # class method call
120
136
  s(:send, name, *m.children[1..-1])
@@ -144,8 +160,19 @@ module Ruby2JS
144
160
  s(:send, s(:attr, name, :prototype),
145
161
  "#{m.children[0].children.first}=",
146
162
  s(:attr, s(:attr, name, :prototype), m.children[1].children.first))
163
+ elsif m.type == :class
164
+ innerclass_name = m.children.first
165
+ if innerclass_name.children.first
166
+ innerclass_name = innerclass_name.updated(nil,
167
+ [s(:attr, innerclass_name.children[0], name),
168
+ innerclass_name.children[1]])
169
+ else
170
+ innerclass_name = innerclass_name.updated(nil,
171
+ [name, innerclass_name.children[1]])
172
+ end
173
+ m.updated(nil, [innerclass_name, *m.children[1..-1]])
147
174
  else
148
- raise Error.new("class #{ m.type }", @ast)
175
+ raise Error.new("class #{ m.type } not supported", @ast)
149
176
  end
150
177
 
151
178
  # associate comments
@@ -191,7 +218,7 @@ module Ruby2JS
191
218
 
192
219
  # collapse sequence to a single assignment
193
220
  if
194
- @ast.type == :class and
221
+ @ast.type != :class_extend and
195
222
  (methods > 1 or (methods == 1 and body[start].type == :prop))
196
223
  then
197
224
  pairs = body[start...start+methods].map do |node|
@@ -214,8 +241,14 @@ module Ruby2JS
214
241
  end
215
242
  replacement
216
243
  end
217
- body[start...start+methods] =
218
- s(:send, name, :prototype=, s(:hash, *pairs.flatten))
244
+
245
+ if @ast.type == :class_module
246
+ body[start...start+methods] =
247
+ s(:casgn, *name.children, s(:hash, *pairs.flatten))
248
+ else
249
+ body[start...start+methods] =
250
+ s(:send, name, :prototype=, s(:hash, *pairs.flatten))
251
+ end
219
252
  end
220
253
  end
221
254
 
@@ -124,7 +124,15 @@ module Ruby2JS
124
124
  var = child_sym.children.first
125
125
  put "set #{var}(#{var}) {#{@nl}this._#{var} = #{var}#@nl}"
126
126
  end
127
+ elsif [:private, :protected, :public].include? m.children[1]
128
+ raise Error.new("class #{m.children[1]} is not supported", @ast)
127
129
  else
130
+ if m.children[1] == :include
131
+ m = m.updated(:begin, m.children[2..-1].map {|mname|
132
+ s(:send, s(:const, nil, :Object), :assign,
133
+ s(:attr, name, :prototype), mname)})
134
+ end
135
+
128
136
  skipped = true
129
137
  end
130
138
 
@@ -139,7 +147,7 @@ module Ruby2JS
139
147
  end
140
148
 
141
149
  if skipped
142
- post << m if skipped
150
+ post << [m, comments] if skipped
143
151
  else
144
152
  comments.reverse.each {|comment| insert location, comment}
145
153
  end
@@ -148,8 +156,9 @@ module Ruby2JS
148
156
  put @nl unless skipped
149
157
  put '}'
150
158
 
151
- post.each do |m|
159
+ post.each do |m, comments|
152
160
  put @sep
161
+ comments.each {|comment| put comment}
153
162
  if m.type == :alias
154
163
  parse name
155
164
  put '.prototype.'
@@ -158,6 +167,17 @@ module Ruby2JS
158
167
  parse name
159
168
  put '.prototype.'
160
169
  put m.children[1].children[0]
170
+ elsif m.type == :class
171
+ innerclass_name = m.children.first
172
+ if innerclass_name.children.first
173
+ innerclass_name = innerclass_name.updated(nil,
174
+ [s(:attr, innerclass_name.children[0], name),
175
+ innerclass_name.children[1]])
176
+ else
177
+ innerclass_name = innerclass_name.updated(nil,
178
+ [name, innerclass_name.children[1]])
179
+ end
180
+ parse m.updated(nil, [innerclass_name, *m.children[1..-1]])
161
181
  else
162
182
  parse m, :statement
163
183
  end
@@ -88,7 +88,10 @@ module Ruby2JS
88
88
  put 'async ' if @ast.type == :async
89
89
 
90
90
  # es2015 fat arrow support
91
- if not name and es2015 and @state != :method and @ast.type != :defm
91
+ if
92
+ not name and es2015 and @state != :method and @ast.type != :defm and
93
+ not @prop
94
+ then
92
95
  put '('; parse args; put ') => '
93
96
 
94
97
  expr = body
@@ -16,46 +16,42 @@ module Ruby2JS
16
16
 
17
17
  if @state == :statement
18
18
  begin
19
- scope, @scope = @scope, false
20
- mark = output_location
19
+ inner, @inner = @inner, @ast
21
20
 
22
21
  # use short form when appropriate
23
22
  unless else_block or then_block.type == :begin
23
+ # "Lexical declaration cannot appear in a single-statement context"
24
+ if [:lvasgn, :gvasgn].include? then_block.type
25
+ @vars[then_block.children.first] ||= :pending
26
+ end
27
+
24
28
  put "if ("; parse condition; put ') '
25
- wrap { parse then_block, :statement }
29
+ wrap { jscope then_block }
26
30
  else
27
31
  put "if ("; parse condition; puts ') {'
28
- parse then_block, :statement
32
+ jscope then_block
29
33
  sput '}'
30
34
 
31
35
  while else_block and else_block.type == :if
32
36
  condition, then_block, else_block = else_block.children
33
37
  if then_block
34
38
  put ' else if ('; parse condition; puts ') {'
35
- parse then_block, :statement
39
+ jscope then_block
36
40
  sput '}'
37
41
  else
38
42
  put ' else if ('; parse s(:not, condition); puts ') {'
39
- parse else_block, :statement
43
+ jscope else_block
40
44
  sput '}'
41
45
  else_block = nil
42
46
  end
43
47
  end
44
48
 
45
49
  if else_block
46
- puts ' else {'; parse else_block, :statement; sput '}'
47
- end
48
- end
49
-
50
- if scope
51
- vars = @vars.select {|key, value| value == :pending}.keys
52
- unless vars.empty?
53
- insert mark, "#{es2015 ? 'let' : 'var'} #{vars.join(', ')}#{@sep}"
54
- vars.each {|var| @vars[var] = true}
50
+ puts ' else {'; jscope else_block; sput '}'
55
51
  end
56
52
  end
57
53
  ensure
58
- @scope = scope
54
+ @inner = inner
59
55
  end
60
56
  else
61
57
  else_block ||= s(:nil)
@@ -29,16 +29,17 @@ module Ruby2JS
29
29
  body, *recovers, otherwise = block.children
30
30
  raise Error.new("block else", @ast) if otherwise
31
31
 
32
- if recovers.any? {|recover| not recover.children[1]}
33
- raise Error.new("recover without exception variable", @ast)
34
- end
35
-
36
32
  var = recovers.first.children[1]
37
33
 
38
34
  if recovers.any? {|recover| recover.children[1] != var}
39
35
  raise Error.new(
40
36
  "multiple recovers with different exception variables", @ast)
41
37
  end
38
+
39
+ if recovers[0..-2].any? {|recover| not recover.children[0]}
40
+ raise Error.new(
41
+ "additional recovers after catchall", @ast)
42
+ end
42
43
  else
43
44
  body = block
44
45
  end
@@ -50,6 +51,8 @@ module Ruby2JS
50
51
  puts "try {"; parse body, :statement; sput '}'
51
52
 
52
53
  if recovers
54
+ var ||= s(:gvar, :$EXCEPTION)
55
+
53
56
  if recovers.length == 1 and not recovers.first.children.first
54
57
  # single catch with no exception named
55
58
  put " catch ("; parse var; puts ") {"
@@ -60,6 +63,7 @@ module Ruby2JS
60
63
  first = true
61
64
  recovers.each do |recover|
62
65
  exceptions, var, recovery = recover.children
66
+ var ||= s(:gvar, :$EXCEPTION)
63
67
 
64
68
  if exceptions
65
69
 
@@ -69,7 +73,11 @@ module Ruby2JS
69
73
  put 'if ('
70
74
  exceptions.children.each_with_index do |exception, index|
71
75
  put ' || ' unless index == 0
72
- parse var; put ' instanceof '; parse exception
76
+ if exception == s(:const, nil, :String)
77
+ put 'typeof '; parse var; put ' == "string"'
78
+ else
79
+ parse var; put ' instanceof '; parse exception
80
+ end
73
81
  end
74
82
  puts ') {'
75
83
  else
@@ -35,7 +35,7 @@ module Ruby2JS
35
35
  end
36
36
 
37
37
  newvars.each do |var|
38
- @vars[var.children.last] ||= (@scope ? true : :pending)
38
+ @vars[var.children.last] ||= (@inner ? :pending : true)
39
39
  end
40
40
 
41
41
  put '['
@@ -6,12 +6,16 @@ module Ruby2JS
6
6
  # (...)
7
7
 
8
8
  handle :module do |name, *body|
9
- symbols = []
10
-
11
9
  while body.length == 1 and body.first.type == :begin
12
10
  body = body.first.children
13
11
  end
14
12
 
13
+ if body.length > 0 and body.all? {|child| child.type == :def}
14
+ parse @ast.updated(:class_module, [name, nil, *body])
15
+ return
16
+ end
17
+
18
+ symbols = []
15
19
  visibility = :public
16
20
  omit = []
17
21
 
@@ -31,7 +31,8 @@ module Ruby2JS
31
31
  if parts.all? {|part| part.type == :str}
32
32
  str = parts.map {|part| part.children.first}.join
33
33
  unless str.scan('/').length - str.scan("\\").length > 3
34
- return put "/#{ str.gsub('/', '\\/') }/#{ opts.join }"
34
+ return put "/#{ str.gsub('\\/', '/').gsub('/', '\\/') }/" +
35
+ opts.join
35
36
  end
36
37
  end
37
38
 
@@ -5,7 +5,11 @@ module Ruby2JS
5
5
  # (gvar :$a)
6
6
 
7
7
  handle :lvar, :gvar do |var|
8
- put var
8
+ if var == :$!
9
+ put '$EXCEPTION'
10
+ else
11
+ put var
12
+ end
9
13
  end
10
14
  end
11
15
  end
@@ -31,11 +31,15 @@ module Ruby2JS
31
31
  end
32
32
  end
33
33
 
34
- if state == :statement and @scope and not @vars.include?(name)
35
- if es2015
36
- var = 'let '
37
- else
38
- var = 'var '
34
+ hoist = false
35
+ if state == :statement and not @vars.include?(name)
36
+ hoist = hoist?(@scope, @inner, name) if @inner
37
+ if not hoist
38
+ if es2015
39
+ var = 'let '
40
+ else
41
+ var = 'var '
42
+ end
39
43
  end
40
44
  end
41
45
 
@@ -44,9 +48,9 @@ module Ruby2JS
44
48
  else
45
49
  put "#{ var }#{ name }"
46
50
  end
47
- ensure
48
- if @scope
49
- @vars[name] = true
51
+
52
+ if not hoist
53
+ @vars[name] ||= true
50
54
  elsif state == :statement
51
55
  @vars[name] ||= :pending
52
56
  else
@@ -55,6 +59,16 @@ module Ruby2JS
55
59
  end
56
60
  end
57
61
 
62
+ # is 'name' referenced outside of inner scope?
63
+ def hoist?(outer, inner, name)
64
+ outer.children.each do |var|
65
+ next if var == inner
66
+ return true if var == name and [:lvar, :gvar].include? outer.type
67
+ return true if Parser::AST::Node === var and hoist?(var, inner, name)
68
+ end
69
+ return false
70
+ end
71
+
58
72
  def multi_assign_declarations
59
73
  undecls = []
60
74
  child = @ast
@@ -32,7 +32,26 @@ module Ruby2JS
32
32
  process S(:send, s(:const, nil, :Object), :keys, target)
33
33
 
34
34
  elsif method == :merge!
35
- process S(:send, s(:const, nil, :Object), :assign, target, *args)
35
+ if es2015
36
+ process S(:send, s(:const, nil, :Object), :assign, target, *args)
37
+ else
38
+ copy = []
39
+
40
+ unless
41
+ target.type == :send and target.children.length == 2 and
42
+ target.children[0] == nil
43
+ then
44
+ copy << s(:gvasgn, :$0, target)
45
+ target = s(:gvar, :$0)
46
+ end
47
+
48
+ s(:send, s(:block, s(:send, nil, :lambda), s(:args),
49
+ s(:begin, *copy, *args.map {|modname|
50
+ s(:for, s(:lvasgn, :$_), modname,
51
+ s(:send, target, :[]=,
52
+ s(:lvar, :$_), s(:send, modname, :[], s(:lvar, :$_))))
53
+ }, s(:return, target))), :[])
54
+ end
36
55
 
37
56
  elsif method == :delete and args.length == 1
38
57
  if not target
data/lib/ruby2js/rails.rb CHANGED
@@ -10,7 +10,15 @@
10
10
  # Using an optional filter:
11
11
  #
12
12
  # $ echo 'require "ruby2js/filter/functions"' > config/initializers/ruby2js.rb
13
-
13
+ #
14
+ # Asset Pipeline:
15
+ #
16
+ # Ruby2JS registers ".rbs" (RuBy Script) extension.
17
+ # You can add "ruby_thing.js.rbs" to your javascript folder
18
+ # and '= require ruby_thing' from other js sources.
19
+ #
20
+ # (options are not yet supported, but by requiring the appropriate files
21
+ # as shown above, you can configure proejct wide.)
14
22
  require 'ruby2js'
15
23
 
16
24
  module Ruby2JS
@@ -24,6 +32,26 @@ module Ruby2JS
24
32
  end
25
33
 
26
34
  ActionView::Template.register_template_handler :rb, Template
35
+
36
+ class SprocketProcessor
37
+ def initialize( file)
38
+ @file = file
39
+ end
40
+ def render(context , _)
41
+ context = context.instance_eval { binding } unless context.is_a? Binding
42
+ Ruby2JS.convert(File.read(@file), binding: context).to_s
43
+ end
44
+ end
45
+
46
+ class Engine < ::Rails::Engine
47
+ engine_name "ruby2js"
48
+
49
+ config.assets.configure do |env|
50
+ env.register_engine '.rbs', SprocketProcessor, mime_type: 'text/javascript', silence_deprecation: true
51
+ end
52
+
53
+ end
54
+
27
55
  end
28
56
 
29
57
  end
@@ -2,7 +2,7 @@ module Ruby2JS
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 3
4
4
  MINOR = 0
5
- TINY = 9
5
+ TINY = 10
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby2js
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.9
4
+ version: 3.0.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Ruby
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-05-17 00:00:00.000000000 Z
11
+ date: 2018-06-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: parser
@@ -130,7 +130,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
130
130
  version: '0'
131
131
  requirements: []
132
132
  rubyforge_project:
133
- rubygems_version: 2.7.6
133
+ rubygems_version: 2.7.4
134
134
  signing_key:
135
135
  specification_version: 4
136
136
  summary: Minimal yet extensible Ruby to JavaScript conversion.