ruby2js 3.0.9 → 3.0.10

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