ruby2js 1.15.1 → 2.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 (53) hide show
  1. data/lib/ruby2js.rb +36 -36
  2. data/lib/ruby2js/converter.rb +59 -20
  3. data/lib/ruby2js/converter/arg.rb +1 -1
  4. data/lib/ruby2js/converter/args.rb +1 -1
  5. data/lib/ruby2js/converter/array.rb +3 -4
  6. data/lib/ruby2js/converter/begin.rb +15 -1
  7. data/lib/ruby2js/converter/block.rb +6 -5
  8. data/lib/ruby2js/converter/boolean.rb +1 -1
  9. data/lib/ruby2js/converter/break.rb +1 -1
  10. data/lib/ruby2js/converter/case.rb +27 -7
  11. data/lib/ruby2js/converter/casgn.rb +5 -2
  12. data/lib/ruby2js/converter/class.rb +41 -11
  13. data/lib/ruby2js/converter/const.rb +1 -1
  14. data/lib/ruby2js/converter/cvar.rb +4 -3
  15. data/lib/ruby2js/converter/cvasgn.rb +5 -6
  16. data/lib/ruby2js/converter/def.rb +15 -3
  17. data/lib/ruby2js/converter/defined.rb +1 -1
  18. data/lib/ruby2js/converter/defs.rb +7 -3
  19. data/lib/ruby2js/converter/dstr.rb +3 -3
  20. data/lib/ruby2js/converter/for.rb +7 -10
  21. data/lib/ruby2js/converter/hash.rb +70 -34
  22. data/lib/ruby2js/converter/if.rb +35 -13
  23. data/lib/ruby2js/converter/in.rb +1 -1
  24. data/lib/ruby2js/converter/ivasgn.rb +1 -1
  25. data/lib/ruby2js/converter/kwbegin.rb +20 -20
  26. data/lib/ruby2js/converter/literal.rb +1 -1
  27. data/lib/ruby2js/converter/logical.rb +4 -8
  28. data/lib/ruby2js/converter/next.rb +1 -1
  29. data/lib/ruby2js/converter/nil.rb +1 -1
  30. data/lib/ruby2js/converter/nthref.rb +1 -1
  31. data/lib/ruby2js/converter/opasgn.rb +3 -3
  32. data/lib/ruby2js/converter/regexp.rb +12 -9
  33. data/lib/ruby2js/converter/return.rb +3 -3
  34. data/lib/ruby2js/converter/self.rb +2 -2
  35. data/lib/ruby2js/converter/send.rb +31 -30
  36. data/lib/ruby2js/converter/super.rb +8 -11
  37. data/lib/ruby2js/converter/sym.rb +1 -1
  38. data/lib/ruby2js/converter/undef.rb +9 -2
  39. data/lib/ruby2js/converter/var.rb +1 -1
  40. data/lib/ruby2js/converter/vasgn.rb +13 -5
  41. data/lib/ruby2js/converter/while.rb +2 -1
  42. data/lib/ruby2js/converter/whilepost.rb +2 -1
  43. data/lib/ruby2js/converter/xstr.rb +4 -3
  44. data/lib/ruby2js/execjs.rb +3 -3
  45. data/lib/ruby2js/filter/camelCase.rb +8 -8
  46. data/lib/ruby2js/filter/functions.rb +64 -65
  47. data/lib/ruby2js/filter/react.rb +44 -16
  48. data/lib/ruby2js/filter/require.rb +4 -1
  49. data/lib/ruby2js/filter/underscore.rb +21 -21
  50. data/lib/ruby2js/serializer.rb +347 -0
  51. data/lib/ruby2js/version.rb +3 -3
  52. data/ruby2js.gemspec +3 -3
  53. metadata +3 -2
@@ -45,7 +45,10 @@ module Ruby2JS
45
45
  end
46
46
 
47
47
  @options[:file2] = filename
48
- process Ruby2JS.parse(File.read(filename), filename)
48
+ ast, comments = Ruby2JS.parse(File.read(filename), filename)
49
+ @comments.merge! Parser::Source::Comment.associate(ast, comments)
50
+ @comments[node] += @comments[ast]
51
+ process ast
49
52
  ensure
50
53
  if file2
51
54
  @options[:file2] = file2
@@ -11,42 +11,42 @@ module Ruby2JS
11
11
  if [:clone, :shuffle, :size, :compact, :flatten, :invert, :values,
12
12
  :uniq].include? node.children[1]
13
13
  if node.is_method? and node.children.length == 2
14
- process s(:send, s(:lvar, :_), node.children[1], node.children[0])
14
+ process S(:send, s(:lvar, :_), node.children[1], node.children[0])
15
15
  else
16
16
  super
17
17
  end
18
18
  elsif node.children[1] == :sample and node.children.length <= 3
19
- process s(:send, s(:lvar, :_), :sample, node.children[0],
19
+ process S(:send, s(:lvar, :_), :sample, node.children[0],
20
20
  *node.children[2..-1])
21
21
  elsif node.children[1] == :has_key? and node.children.length == 3
22
- process s(:send, s(:lvar, :_), :has, node.children[0],
22
+ process S(:send, s(:lvar, :_), :has, node.children[0],
23
23
  node.children[2])
24
24
  elsif node.children[1] == :sort and node.children.length == 2
25
25
  if node.is_method?
26
- process s(:send, s(:lvar, :_), :sortBy, node.children[0],
26
+ process S(:send, s(:lvar, :_), :sortBy, node.children[0],
27
27
  s(:attr, s(:lvar, :_), :identity))
28
28
  else
29
29
  super
30
30
  end
31
31
  elsif node.children[1] == :map
32
32
  if node.children.length == 3 and node.children[2].type == :block_pass
33
- process s(:send, s(:lvar, :_), :pluck, node.children[0],
33
+ process S(:send, s(:lvar, :_), :pluck, node.children[0],
34
34
  node.children[2].children.first)
35
35
  else
36
36
  super
37
37
  end
38
38
  elsif node.children[1] == :merge and node.children.length >= 3
39
- process s(:send, s(:lvar, :_), :extend, s(:hash), node.children[0],
39
+ process S(:send, s(:lvar, :_), :extend, s(:hash), node.children[0],
40
40
  *node.children[2..-1])
41
41
  elsif node.children[1] == :merge! and node.children.length >= 3
42
- process s(:send, s(:lvar, :_), :extend, node.children[0],
42
+ process S(:send, s(:lvar, :_), :extend, node.children[0],
43
43
  *node.children[2..-1])
44
44
  elsif node.children[1] == :zip and node.children.length >= 3
45
- process s(:send, s(:lvar, :_), :zip, node.children[0],
45
+ process S(:send, s(:lvar, :_), :zip, node.children[0],
46
46
  *node.children[2..-1])
47
47
  elsif node.children[1] == :invoke
48
48
  if node.children.length >= 3 and node.children.last.type==:block_pass
49
- process s(:send, s(:lvar, :_), :invoke, node.children[0],
49
+ process S(:send, s(:lvar, :_), :invoke, node.children[0],
50
50
  node.children.last.children.first,
51
51
  *node.children[2..-2])
52
52
  else
@@ -54,7 +54,7 @@ module Ruby2JS
54
54
  end
55
55
  elsif [:where, :find_by].include? node.children[1]
56
56
  method = node.children[1] == :where ? :where : :findWhere
57
- process s(:send, s(:lvar, :_), method, node.children[0],
57
+ process S(:send, s(:lvar, :_), method, node.children[0],
58
58
  *node.children[2..-1])
59
59
  elsif node.children[1] == :reduce
60
60
  if node.children.length == 3 and node.children[2].type == :sym
@@ -62,7 +62,7 @@ module Ruby2JS
62
62
  # output: _.reduce(_.rest(a),
63
63
  # proc {|memo, item| return memo+item},
64
64
  # a[0])
65
- process s(:send, s(:lvar, :_), :reduce,
65
+ process S(:send, s(:lvar, :_), :reduce,
66
66
  s(:send, s(:lvar, :_), :rest, node.children.first),
67
67
  s(:block, s(:send, nil, :proc),
68
68
  s(:args, s(:arg, :memo), s(:arg, :item)),
@@ -75,7 +75,7 @@ module Ruby2JS
75
75
  elsif node.children.length == 4 and node.children[3].type == :sym
76
76
  # input: a.reduce(n, :+)
77
77
  # output: _.reduce(a, proc {|memo, item| return memo+item}, n)
78
- process s(:send, s(:lvar, :_), :reduce, node.children.first,
78
+ process S(:send, s(:lvar, :_), :reduce, node.children.first,
79
79
  s(:block, s(:send, nil, :proc),
80
80
  s(:args, s(:arg, :memo), s(:arg, :item)),
81
81
  s(:autoreturn, s(:send, s(:lvar, :memo),
@@ -90,7 +90,7 @@ module Ruby2JS
90
90
  # input: a.compact!
91
91
  # output: a.splice(0, a.length, *a.compact)
92
92
  target = node.children.first
93
- process s(:send, target, :splice, s(:int, 0),
93
+ process S(:send, target, :splice, s(:int, 0),
94
94
  s(:attr, target, :length), s(:splat, s(:send, target,
95
95
  :"#{node.children[1].to_s[0..-2]}", *node.children[2..-1])))
96
96
  else
@@ -104,14 +104,14 @@ module Ruby2JS
104
104
  # input: a.sort_by {}
105
105
  # output: _.sortBy {return expression}
106
106
  method = call.children[1].to_s.sub(/\_by$/,'By').to_sym
107
- process s(:block, s(:send, s(:lvar, :_), method,
107
+ process S(:block, s(:send, s(:lvar, :_), method,
108
108
  call.children.first), node.children[1],
109
109
  s(:autoreturn, node.children[2]))
110
110
  elsif [:find, :reject].include? call.children[1]
111
111
  if call.children.length == 2
112
112
  # input: a.find {|item| item > 0}
113
113
  # output: _.find(a) {|item| return item > 0}
114
- process s(:block, s(:send, s(:lvar, :_), call.children[1],
114
+ process S(:block, s(:send, s(:lvar, :_), call.children[1],
115
115
  call.children.first), node.children[1],
116
116
  s(:autoreturn, node.children[2]))
117
117
  else
@@ -121,7 +121,7 @@ module Ruby2JS
121
121
  elsif call.children[1] == :times and call.children.length == 2
122
122
  # input: 5.times {|i| console.log i}
123
123
  # output: _.find(5) {|i| console.log(i)}
124
- process s(:block, s(:send, s(:lvar, :_), call.children[1],
124
+ process S(:block, s(:send, s(:lvar, :_), call.children[1],
125
125
  call.children.first), node.children[1], node.children[2])
126
126
 
127
127
  elsif call.children[1] == :reduce
@@ -130,7 +130,7 @@ module Ruby2JS
130
130
  # output: _.reduce(_.rest(a),
131
131
  # proc {|memo, item| return memo+item},
132
132
  # a[0])
133
- process s(:send, s(:lvar, :_), :reduce,
133
+ process S(:call, s(:lvar, :_), :reduce,
134
134
  s(:send, s(:lvar, :_), :rest, call.children.first),
135
135
  s(:block, s(:send, nil, :proc),
136
136
  node.children[1], s(:autoreturn, node.children[2])),
@@ -138,7 +138,7 @@ module Ruby2JS
138
138
  elsif call.children.length == 3
139
139
  # input: a.reduce(n) {|memo, item| memo+item}
140
140
  # output: _.reduce(a, proc {|memo, item| return memo+item}, n)
141
- process s(:send, s(:lvar, :_), :reduce, call.children.first,
141
+ process S(:call, s(:lvar, :_), :reduce, call.children.first,
142
142
  s(:block, s(:send, nil, :proc),
143
143
  node.children[1], s(:autoreturn, node.children[2])),
144
144
  call.children[2])
@@ -149,7 +149,7 @@ module Ruby2JS
149
149
  # output: a.splice(0, a.length, *a.map {expression})
150
150
  method = :"#{call.children[1].to_s[0..-2]}"
151
151
  target = call.children.first
152
- process s(:send, target, :splice, s(:splat, s(:send, s(:array,
152
+ process S(:call, target, :splice, s(:splat, s(:send, s(:array,
153
153
  s(:int, 0), s(:attr, target, :length)), :concat,
154
154
  s(:block, s(:send, target, method, *call.children[2..-1]),
155
155
  *node.children[1..-1]))))
@@ -165,10 +165,10 @@ module Ruby2JS
165
165
 
166
166
  def on_irange(node)
167
167
  if node.children.last.type == :int
168
- process s(:send, s(:lvar, :_), :range, node.children.first,
168
+ process S(:call, s(:lvar, :_), :range, node.children.first,
169
169
  s(:int, node.children.last.children.last+1))
170
170
  else
171
- process s(:send, s(:lvar, :_), :range, node.children.first,
171
+ process S(:call, s(:lvar, :_), :range, node.children.first,
172
172
  s(:send, node.children.last, :+, s(:int, 1)))
173
173
  end
174
174
  end
@@ -0,0 +1,347 @@
1
+ module Ruby2JS
2
+ class Token < String
3
+ attr_accessor :loc
4
+ attr_accessor :ast
5
+
6
+ def initialize(string, ast)
7
+ super(string.to_s)
8
+ @ast = ast
9
+ @loc = ast.location if ast
10
+ end
11
+ end
12
+
13
+ class Line < Array
14
+ attr_accessor :indent
15
+
16
+ def initialize(*args)
17
+ super(args)
18
+ @indent = 0
19
+ end
20
+
21
+ def comment?
22
+ first = find {|token| !token.empty?}
23
+ first and first.start_with? '//'
24
+ end
25
+
26
+ def empty?
27
+ all? {|line| line.empty?}
28
+ end
29
+
30
+ def to_s
31
+ if empty?
32
+ ''
33
+ elsif ['case ', 'default:'].include? self[0]
34
+ ' ' * ([0,indent-2].max) + join
35
+ else
36
+ ' ' * indent + join
37
+ end
38
+ end
39
+ end
40
+
41
+ class Serializer
42
+ attr_reader :timestamps
43
+
44
+ def initialize
45
+ @sep = '; '
46
+ @nl = ''
47
+ @ws = ' '
48
+
49
+ @width = 80
50
+ @indent = 0
51
+
52
+ @lines = [Line.new]
53
+ @line = @lines.last
54
+ @timestamps = {}
55
+ end
56
+
57
+ def timestamp(file)
58
+ @timestamps[file] = File.mtime(file) if file and File.exist?(file)
59
+ end
60
+
61
+ def uptodate?
62
+ return false if @timestamps.empty?
63
+ return @timestamps.all? {|file, mtime| File.mtime(file) == mtime}
64
+ end
65
+
66
+ def mtime
67
+ return Time.now if @timestamps.empty?
68
+ return @timestamps.values.max
69
+ end
70
+
71
+ def enable_vertical_whitespace
72
+ @sep = ";\n"
73
+ @nl = "\n"
74
+ @ws = @nl
75
+ @indent = 2
76
+ end
77
+
78
+ # indent multi-line parameter lists, array constants, blocks
79
+ def reindent(lines)
80
+ indent = 0
81
+ lines.each do |line|
82
+ first = line.find {|token| !token.empty?}
83
+ if first
84
+ last = line[line.rindex {|token| !token.empty?}]
85
+ indent -= @indent if ')}]'.include? first[0]
86
+ line.indent = indent
87
+ indent += @indent if '({['.include? last[-1]
88
+ else
89
+ line.indent = indent
90
+ end
91
+ end
92
+ end
93
+
94
+ # add horizontal (indentation) and vertical (blank lines) whitespace
95
+ def respace
96
+ return if @indent == 0
97
+ reindent @lines
98
+
99
+ (@lines.length-3).downto(0) do |i|
100
+ if
101
+ @lines[i].length == 0
102
+ then
103
+ @lines.delete i
104
+ elsif
105
+ @lines[i+1].comment? and not @lines[i].comment? and
106
+ @lines[i].indent == @lines[i+1].indent
107
+ then
108
+ # before a comment
109
+ @lines.insert i+1, Line.new
110
+ elsif
111
+ @lines[i].indent == @lines[i+1].indent and
112
+ @lines[i+1].indent < @lines[i+2].indent and
113
+ not @lines[i].comment?
114
+ then
115
+ # start of indented block
116
+ @lines.insert i+1, Line.new
117
+ elsif
118
+ @lines[i].indent > @lines[i+1].indent and
119
+ @lines[i+1].indent == @lines[i+2].indent and
120
+ not @lines[i+2].empty?
121
+ then
122
+ # end of indented block
123
+ @lines.insert i+2, Line.new
124
+ end
125
+ end
126
+ end
127
+
128
+ # add a single token to the current line
129
+ def put(string)
130
+ unless String === string and string.include? "\n"
131
+ @line << Token.new(string, @ast)
132
+ else
133
+ parts = string.split("\n")
134
+ @line << Token.new(parts.shift, @ast)
135
+ @lines += parts.map {|part| Line.new(Token.new(part, @ast))}
136
+ @lines << Line.new if string.end_with?("\n")
137
+ @line = @lines.last
138
+ end
139
+ end
140
+
141
+ # add a single token to the current line and then advance to next line
142
+ def puts(string)
143
+ unless String === string and string.include? "\n"
144
+ @line << Token.new(string, @ast)
145
+ else
146
+ put string
147
+ end
148
+
149
+ @line = Line.new
150
+ @lines << @line
151
+ end
152
+
153
+ # advance to next line and then add a single token to the current line
154
+ def sput(string)
155
+ unless String === string and string.include? "\n"
156
+ @line = Line.new(Token.new(string, @ast))
157
+ @lines << @line
158
+ else
159
+ @line = Line.new
160
+ @lines << @line
161
+ put string
162
+ end
163
+ end
164
+
165
+ # current location: [line number, token number]
166
+ def output_location
167
+ [@lines.length-1, @line.length]
168
+ end
169
+
170
+ # insert a line into the output
171
+ def insert(mark, line)
172
+ if mark.last == 0
173
+ @lines.insert(mark.first, Line.new(Token.new(line.chomp, @ast)))
174
+ else
175
+ @lines[mark.first].insert(mark.last, Token.new(line, @ast))
176
+ end
177
+ end
178
+
179
+ # capture (and remove) tokens from the output stream
180
+ def capture(&block)
181
+ mark = output_location
182
+ block.call
183
+ lines = @lines.slice!(mark.first+1..-1)
184
+ @line = @lines.last
185
+
186
+ if lines.empty?
187
+ lines = [@line.slice!(mark.last..-1)]
188
+ elsif @line.length != mark.last
189
+ lines.unshift @line.slice!(mark.last..-1)
190
+ end
191
+
192
+ lines.map(&:join).join(@ws)
193
+ end
194
+
195
+ # wrap long statements in curly braces
196
+ def wrap
197
+ puts '{'
198
+ mark = output_location
199
+ yield
200
+
201
+ if
202
+ @lines.length > mark.first+1 or
203
+ @lines[mark.first-1].join.length + @line.join.length >= @width
204
+ then
205
+ sput '}'
206
+ else
207
+ @line = @lines[mark.first-1]
208
+ @line[-1..-1] = @lines.pop
209
+ end
210
+ end
211
+
212
+ # compact small expressions into a single line
213
+ def compact
214
+ mark = output_location
215
+ yield
216
+ return unless @lines.length - mark.first > 1
217
+ return if @indent == 0
218
+
219
+ # survey what we have to work with, keeping track of a possible
220
+ # split of the last argument or value
221
+ work = []
222
+ indent = len = 0
223
+ trail = split = nil
224
+ slice = @lines[mark.first..-1]
225
+ reindent(slice)
226
+ slice.each_with_index do |line, index|
227
+ if line.first.start_with? '//'
228
+ len += @width # comments are a deal breaker
229
+ else
230
+ (work.push ' '; len += 1) if trail == line.indent and @indent > 0
231
+ len += line.map(&:length).inject(&:+)
232
+ work += line
233
+
234
+ if trail == @indent and line.indent == @indent
235
+ split = [len, work.length, index]
236
+ break if len >= @width - 10
237
+ end
238
+ trail = line.indent
239
+ end
240
+ end
241
+
242
+ if len < @width - 10
243
+ # full collapse
244
+ @lines[mark.first..-1] = [Line.new(*work)]
245
+ @line = @lines.last
246
+ elsif split and split[0] < @width-10
247
+ if slice[split[2]].indent < slice[split[2]+1].indent
248
+ # collapse all but the last argument (typically a hash or function)
249
+ close = slice.pop
250
+ slice[-1].push *close
251
+ @lines[mark.first] = Line.new(*work[0..split[1]-1])
252
+ @lines[mark.first+1..-1] = slice[split[2]+1..-1]
253
+ @line = @lines.last
254
+ end
255
+ end
256
+ end
257
+
258
+ # return the output as a string
259
+ def to_s
260
+ return @str if @str
261
+ respace
262
+ @lines.map(&:to_s).join(@nl)
263
+ end
264
+
265
+ def to_str
266
+ @str ||= to_s
267
+ end
268
+
269
+ def +(value)
270
+ to_s+value
271
+ end
272
+
273
+ BASE64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
274
+
275
+ # https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit
276
+ def vlq(*mark)
277
+ if @mark[0] == mark[0]
278
+ return if @mark[-3..-1] == mark[-3..-1]
279
+ @mappings << ',' unless @mappings == ''
280
+ end
281
+
282
+ while @mark[0] < mark[0]
283
+ @mappings << ';'
284
+ @mark[0] += 1
285
+ @mark[1] = 0
286
+ end
287
+
288
+ diffs = mark.zip(@mark).map {|a,b| a-b}
289
+ @mark = mark
290
+
291
+ diffs[1..4].each do |diff|
292
+ if diff < 0
293
+ data = (-diff << 1) + 1
294
+ else
295
+ data = diff << 1
296
+ end
297
+
298
+ encoded = ''
299
+
300
+ begin
301
+ digit = data & 0b11111
302
+ data >>= 5
303
+ digit |= 0b100000 if data > 0
304
+ encoded << BASE64[digit]
305
+ end while data > 0
306
+
307
+ @mappings << encoded
308
+ end
309
+ end
310
+
311
+ def sourcemap
312
+ respace
313
+
314
+ @mappings = ''
315
+ sources = []
316
+ @mark = [0, 0, 0, 0, 0]
317
+
318
+ @lines.each_with_index do |line, row|
319
+ col = line.indent
320
+ line.each do |token|
321
+ if token != ' ' and token.loc
322
+ pos = token.loc.expression.begin_pos
323
+
324
+ buffer = token.loc.expression.source_buffer
325
+ source_index = sources.index(buffer)
326
+ if not source_index
327
+ source_index = sources.length
328
+ timestamp buffer.name
329
+ sources << buffer
330
+ end
331
+
332
+ split = buffer.source[0...pos].split("\n")
333
+ vlq row, col, source_index, split.length-1, split.last.to_s.length
334
+ end
335
+ col += token.length
336
+ end
337
+ end
338
+
339
+ @sourcemap = {
340
+ version: 3,
341
+ file: @ast.loc.expression.source_buffer.name,
342
+ sources: sources.map(&:name),
343
+ mappings: @mappings
344
+ }
345
+ end
346
+ end
347
+ end