ruby2js 3.5.3 → 3.6.0

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: 2a0cc1907c261ee4de0bf29def31b92148ceb0b1247035db8553515106cc800b
4
- data.tar.gz: 8141a424cf50daa57e96aea4d2a601b283377a05769e455c0c8f4ffadd3690ae
3
+ metadata.gz: 7fa9e6949c8d7d1f3f5cb549dc234a2b559d032bc9b5b236d123f68b62ecd763
4
+ data.tar.gz: e89542553f4db3caa7ceda112542a3a7fceb468d23d8a15cb9b416954c054d1f
5
5
  SHA512:
6
- metadata.gz: 2836e4593b6f4fa15be750e8c83231e90c62d5bce75768119d4ba54b7b3459601018744eba95952db906d1e5ca64a40658d6449348239056cb9297647f424f88
7
- data.tar.gz: f009c9829447798d8b1a21fc2cf6e6033cb21c22999d73f7289de08d03edbbd1ce35b1a6f125693803758e779a779379cbd78ed291a9224e4b7cbb0a257ef1ad
6
+ metadata.gz: a46d662b6be3a5e7a7242c1249705dcc5b705bfa984443ebedbb766c241aff365ce6f45fbea6b774f6646ce807c6d7f595becb841866f1a878ae65f69b409430
7
+ data.tar.gz: 27091e81578f5cebad30132ef54c3e08a1a32516233a0cab084c15dca5b361c7a2c7602dd3ba886d57db23301bb5a7d54d106cabba7a5a71d5989fc0b959533d
data/README.md CHANGED
@@ -40,9 +40,6 @@ For example:
40
40
  * Any block becomes and explicit argument `new Promise do; y(); end` becomes `new Promise(function() {y()})`
41
41
  * regular expressions are mapped to js
42
42
  * `raise` becomes `throw`
43
- * expressions enclosed in backtick operators (\`\`) and `%x{}` literals are
44
- evaluated in the context of the caller and the results are inserted
45
- into the generated JavaScript.
46
43
 
47
44
  Ruby attribute accessors, methods defined with no parameters and no
48
45
  parenthesis, as well as setter method definitions, are
@@ -87,6 +84,12 @@ Host variable substitution:
87
84
  puts Ruby2JS.convert("@name", ivars: {:@name => "Joe"})
88
85
  ```
89
86
 
87
+ Host expression evalution -- potentially unsafe, use only if you trust
88
+ the source being converted::
89
+ ```ruby
90
+ i = 7; puts Ruby2JS.convert("i = `i`", binding: binding)
91
+ ```
92
+
90
93
  Enable ES2015 support:
91
94
 
92
95
  ```ruby
@@ -135,7 +138,7 @@ Introduction
135
138
 
136
139
  JavaScript is a language where `0` is considered `false`, strings are
137
140
  immutable, and the behaviors for operators like `==` are, at best,
138
- [convoluted](http://zero.milosz.ca/).
141
+ [convoluted](https://zero.milosz.ca/).
139
142
 
140
143
  Any attempt to bridge the semantics of Ruby and JavaScript will involve
141
144
  trade-offs. Consider the following expression:
@@ -150,10 +153,10 @@ quite different if `a` is a Hash.
150
153
 
151
154
  One way to resolve this is to change the way indexing operators are evaluated,
152
155
  and to provide a runtime library that adds properties to global JavaScript
153
- objects to handle this. This is the approach that [Opal](http://opalrb.com/)
156
+ objects to handle this. This is the approach that [Opal](https://opalrb.com/)
154
157
  takes. It is a fine approach, with a number of benefits. It also has some
155
158
  notable drawbacks. For example,
156
- [readability](http://opalrb.com/try/#code:a%20%3D%20%22abc%22%3B%20puts%20a[-1])
159
+ [readability](https://opalrb.com/try/#code:a%20%3D%20%22abc%22%3B%20puts%20a[-1])
157
160
  and
158
161
  [compatibility with other frameworks](https://github.com/opal/opal/issues/400).
159
162
 
@@ -175,7 +178,7 @@ negative at runtime.
175
178
  This quickly gets into gray areas. `each` in Ruby is a common method that
176
179
  facilitates iteration over arrays. `forEach` is the JavaScript equivalent.
177
180
  Mapping this is fine until you start using a framework like jQuery which
178
- provides a function named [each](http://api.jquery.com/jQuery.each/).
181
+ provides a function named [each](https://api.jquery.com/jQuery.each/).
179
182
 
180
183
  Fortunately, Ruby provides `?` and `!` as legal suffixes for method names,
181
184
  Ruby2js filters do an exact match, so if you select a filter that maps `each`
@@ -198,9 +201,7 @@ Ruby2JS::Filter.exclude :each
198
201
  ```
199
202
 
200
203
  Static transformations and runtime libraries aren't aren’t mutually exclusive.
201
- With enough of each, one could reproduce any functionality desired. Just be
202
- forewarned, that implementing a function like `method_missing` would require a
203
- _lot_ of work.
204
+ With enough of each, one could reproduce any functionality desired.
204
205
 
205
206
  Integrations
206
207
  ---
@@ -280,6 +281,7 @@ the script.
280
281
  * `.respond_to?` becomes `right in left`
281
282
  * `.rstrip` becomes `.replace(/s+$/, "")`
282
283
  * `.scan` becomes `.match(//g)`
284
+ * `.sum` becomes `.reduce(function(a, b) {a + b}, 0)`
283
285
  * `.start_with?` becomes `.substring(0, arg.length) == arg`
284
286
  * `.upto(lim)` becomes `for (var i=num; i<=lim; i+=1)`
285
287
  * `.downto(lim)` becomes `for (var i=num; i>=lim; i-=1)`
@@ -326,6 +328,20 @@ the script.
326
328
 
327
329
  * `.class` becomes `.constructor`
328
330
 
331
+ * <a id="active_functions" href="https://github.com/rubys/ruby2js/blob/master/lib/ruby2js/filter/active_functions.rb">active_functions</a>
332
+
333
+ Provides functionality inspired by Rails' ActiveSupport. Works in conjunction
334
+ with the tiny NPM dependency `@ruby2js/active-functions` which must be added to
335
+ your application.
336
+
337
+ * `value.blank?` becomes `blank$(value)`
338
+ * `value.present?` becomes `present$(value)`
339
+ * `value.presence` becomes `presence$(value)`
340
+
341
+ Note: these conversions are only done if eslevel >= 2015. Import statements
342
+ will be added to the top of the code output automatically. By default they
343
+ will be `@ruby2js/active-functions`, but you can pass an `import_from_skypack: true` option to `convert` to use the Skypack CDN instead.
344
+
329
345
  * <a id="tagged_templates" href="https://github.com/rubys/ruby2js/blob/master/lib/ruby2js/filter/tagged_templates.rb">tagged_templates</a>
330
346
 
331
347
  Allows you to turn certain method calls with a string argument into tagged
@@ -391,6 +407,59 @@ the script.
391
407
  # => export { one, two, three as default }
392
408
  ```
393
409
 
410
+ If the "autoexports" option is `true`, all top level modules, classes,
411
+ methods and constants will automatically be exported.
412
+
413
+ The esm filter also provides a way to specify "autoimports" when you run the
414
+ conversion. It will add the relevant import statements automatically whenever
415
+ a particular class or function name is referenced. These can be either default
416
+ or named exports. Simply provide an `autoimports` hash with one or more keys
417
+ to the `Ruby2JS.convert` method. Examples:
418
+
419
+ ```ruby
420
+ require "ruby2js/filter/esm"
421
+ puts Ruby2JS.convert('class MyElement < LitElement; end',
422
+ eslevel: 2020, autoimports: {[:LitElement] => "lit-element"})
423
+ ```
424
+
425
+ ```js
426
+ // JavaScript output:
427
+ import { LitElement } from "lit-element"
428
+ class MyElement extends LitElement {}
429
+ ```
430
+
431
+ ```ruby
432
+ require "ruby2js/filter/esm"
433
+ puts Ruby2JS.convert('AWN.new({position: "top-right"}).success("Hello World")',
434
+ eslevel: 2020, autoimports: {:AWN => "awesome-notifications"})
435
+ ```
436
+
437
+ ```js
438
+ // JavaScript output:
439
+ import AWN from "awesome-notifications"
440
+ new AWN({position: "top-right"}).success("Hello World")
441
+ ```
442
+
443
+ The esm filter is able to recognize if you are defining a class or function
444
+ within the code itself and it won't add that import statement accordingly.
445
+ If for some reason you wish to disable autoimports entirely on a file-by-file
446
+ basis (for instance when using the Webpack loader), you can add a magic comment
447
+ to the top of the code:
448
+
449
+ ```ruby
450
+ require "ruby2js/filter/esm"
451
+ puts Ruby2JS.convert(
452
+ "# autoimports: false\n" +
453
+ 'AWN.new({position: "top-right"}).success("Hello World")',
454
+ eslevel: 2020, autoimports: {:AWN => "awesome-notifications"}
455
+ )
456
+ ```
457
+
458
+ ```js
459
+ // autoimports: false
460
+ new AWN({position: "top-right"}).success("Hello World")
461
+ ```
462
+
394
463
  * <a id="node" href="https://github.com/rubys/ruby2js/blob/master/spec/node_spec.rb">node</a>
395
464
 
396
465
  * `` `command` `` becomes `child_process.execSync("command", {encoding: "utf8"})`
@@ -398,24 +467,34 @@ the script.
398
467
  * `__dir__` becomes `__dirname`
399
468
  * `Dir.chdir` becomes `process.chdir`
400
469
  * `Dir.entries` becomes `fs.readdirSync`
470
+ * `Dir.home` becomes `os.homedir()`
401
471
  * `Dir.mkdir` becomes `fs.mkdirSync`
402
472
  * `Dir.mktmpdir` becomes `fs.mkdtempSync`
403
473
  * `Dir.pwd` becomes `process.cwd`
404
474
  * `Dir.rmdir` becomes `fs.rmdirSync`
475
+ * `Dir.tmpdir` becomes `os.tmpdir()`
405
476
  * `ENV` becomes `process.env`
406
477
  * `__FILE__` becomes `__filename`
478
+ * `File.absolute_path` becomes `path.resolve`
479
+ * `File.absolute_path?` becomes `path.isAbsolute`
480
+ * `File.basename` becomes `path.basename`
407
481
  * `File.chmod` becomes `fs.chmodSync`
408
482
  * `File.chown` becomes `fs.chownSync`
409
483
  * `File.cp` becomes `fs.copyFileSync`
484
+ * `File.dirname` becomes `path.dirname`
410
485
  * `File.exist?` becomes `fs.existsSync`
486
+ * `File.extname` becomes `path.extname`
487
+ * `File.join` becomes `path.join`
411
488
  * `File.lchmod` becomes `fs.lchmodSync`
412
489
  * `File.link` becomes `fs.linkSync`
413
490
  * `File.ln` becomes `fs.linkSync`
414
491
  * `File.lstat` becomes `fs.lstatSync`
492
+ * `File::PATH_SEPARATOR` becomes `path.delimiter`
415
493
  * `File.read` becomes `fs.readFileSync`
416
494
  * `File.readlink` becomes `fs.readlinkSync`
417
495
  * `File.realpath` becomes `fs.realpathSync`
418
496
  * `File.rename` becomes `fs.renameSync`
497
+ * `File::SEPARATOR` becomes `path.sep`
419
498
  * `File.stat` becomes `fs.statSync`
420
499
  * `File.symlink` becomes `fs.symlinkSync`
421
500
  * `File.truncate` becomes `fs.truncateSync`
@@ -609,8 +688,16 @@ Additionally, the `functions` filter will provide the following conversion:
609
688
  * `Array(x)` becomes `Array.from(x)`
610
689
  * `.inject(n) {}` becomes `.reduce(() => {}, n)`
611
690
 
612
- Finally, keyword arguments and optional keyword arguments will be mapped to
613
- parameter detructuring.
691
+ Keyword arguments and optional keyword arguments will be mapped to
692
+ parameter destructuring.
693
+
694
+ Classes defined with a `method_missing` method will emit a `Proxy` object
695
+ for each instance that will forward calls. Note that in order to forward
696
+ arguments, this proxy will return a function that will need to be called,
697
+ making it impossible to proxy attributes/getters. As a special accommodation,
698
+ if the `method_missing` method is defined to only accept a single parameter
699
+ it will be called with only the method name, and it is free to return
700
+ either values or functions.
614
701
 
615
702
  ES2016 support
616
703
  ---
@@ -619,6 +706,9 @@ When option `eslevel: 2016` is provided, the following additional
619
706
  conversion is made:
620
707
 
621
708
  * `a ** b` becomes `a ** b`
709
+
710
+ Additionally the following conversions is added to the `functions` filter:
711
+
622
712
  * `.include?` becomes `.includes`
623
713
 
624
714
  ES2017 support
@@ -629,7 +719,7 @@ conversions are made by the `functions` filter:
629
719
 
630
720
  * `.values()` becomes `Object.values()`
631
721
  * `.entries()` becomes `Object.entries()`
632
- * `.each_pair {}` becomes `for (let [key, value] of Object.entries()) {}'
722
+ * `.each_pair {}` becomes `for (let [key, value] of Object.entries()) {}`
633
723
 
634
724
  async support:
635
725
 
@@ -657,8 +747,8 @@ When option `eslevel: 2019` is provided, the following additional
657
747
  conversion is made by the `functions` filter:
658
748
 
659
749
  * `.flatten` becomes `.flat(Infinity)`
660
- * `.lstrip` becomes `.trimEnd
661
- * `.rstrip` becomes `.trimStart
750
+ * `.lstrip` becomes `.trimEnd`
751
+ * `.rstrip` becomes `.trimStart`
662
752
  * `a.to_h` becomes `Object.fromEntries(a)`
663
753
  * `Hash[a]` becomes `Object.fromEntries(a)`
664
754
 
@@ -684,27 +774,28 @@ conversions are made:
684
774
 
685
775
  * `x ||= 1` becomes `x ||= 1`
686
776
  * `x &&= 1` becomes `x &&= 1`
777
+ * `1000000.000001` becomes `1_000_000.000_001`
687
778
 
688
779
  Picking a Ruby to JS mapping tool
689
780
  ---
690
781
 
691
782
  > dsl — A domain specific language, where code is written in one language and
692
783
  > errors are given in another.
693
- > -- [Devil’s Dictionary of Programming](http://programmingisterrible.com/post/65781074112/devils-dictionary-of-programming)
784
+ > -- [Devil’s Dictionary of Programming](https://programmingisterrible.com/post/65781074112/devils-dictionary-of-programming)
694
785
 
695
786
  If you simply want to get a job done, and would like a mature and tested
696
787
  framework, and only use one of the many integrations that
697
- [Opal](http://opalrb.com/) provides, then Opal is the way to go right now.
788
+ [Opal](https://opalrb.com/) provides, then Opal is the way to go right now.
698
789
 
699
790
  ruby2js is for those that want to produce JavaScript that looks like it
700
791
  wasn’t machine generated, and want the absolute bare minimum in terms of
701
792
  limitations as to what JavaScript can be produced.
702
793
 
703
- [Try](http://intertwingly.net/projects/ruby2js.cgi/all) for yourself.
704
- [Compare](http://opalrb.com/try/#code:).
794
+ [Try](https://intertwingly.net/projects/ruby2js) for yourself.
795
+ [Compare](https://opalrb.com/try/#code:).
705
796
 
706
797
  And, of course, the right solution might be to use
707
- [CoffeeScript](http://coffeescript.org/) instead.
798
+ [CoffeeScript](https://coffeescript.org/) instead.
708
799
 
709
800
  License
710
801
  ---
@@ -61,13 +61,18 @@ module Ruby2JS
61
61
  include Ruby2JS::Filter
62
62
  BINARY_OPERATORS = Converter::OPERATORS[2..-1].flatten
63
63
 
64
- attr_accessor :prepend_list
64
+ attr_accessor :prepend_list, :disable_autoimports
65
65
 
66
66
  def initialize(comments)
67
67
  @comments = comments
68
+
69
+ # check if magic comment is present:
70
+ first_comment = @comments.values.first&.map(&:text)&.first
71
+ @disable_autoimports = first_comment&.include?(" autoimports: false")
72
+ @disable_autoexports = first_comment&.include?(" autoexports: false")
73
+
68
74
  @ast = nil
69
75
  @exclude_methods = []
70
- @esm = false
71
76
  @prepend_list = Set.new
72
77
  end
73
78
 
@@ -141,6 +146,7 @@ module Ruby2JS
141
146
  def on_method(node); on_send(node); end
142
147
  def on_prop(node); on_array(node); end
143
148
  def on_prototype(node); on_begin(node); end
149
+ def on_send!(node); on_send(node); end
144
150
  def on_sendw(node); on_send(node); end
145
151
  def on_undefined?(node); on_defined?(node); end
146
152
  def on_nil(node); end
@@ -188,7 +194,7 @@ module Ruby2JS
188
194
  source = ast.loc.expression.source_buffer.source
189
195
  else
190
196
  ast, comments = parse( source, options[:file] )
191
- comments = Parser::Source::Comment.associate(ast, comments) if ast
197
+ comments = ast ? Parser::Source::Comment.associate(ast, comments) : {}
192
198
  end
193
199
 
194
200
  filters = (options[:filters] || Filter::DEFAULTS)
@@ -207,9 +213,10 @@ module Ruby2JS
207
213
  filter.options = options
208
214
  ast = filter.process(ast)
209
215
 
210
- if filter.prepend_list
216
+ unless filter.prepend_list.empty?
211
217
  prepend = filter.prepend_list.sort_by {|ast| ast.type == :import ? 0 : 1}
212
- ast = Parser::AST::Node.new(:begin, [*prepend, ast], location: ast.location)
218
+ prepend.reject! {|ast| ast.type == :import} if filter.disable_autoimports
219
+ ast = Parser::AST::Node.new(:begin, [*prepend, ast])
213
220
  end
214
221
  end
215
222
 
@@ -240,6 +247,8 @@ module Ruby2JS
240
247
 
241
248
  ruby2js.timestamp options[:file]
242
249
 
250
+ ruby2js.file_name = options[:file] || ast&.loc&.expression&.source_buffer&.name || ''
251
+
243
252
  ruby2js
244
253
  end
245
254
 
@@ -248,7 +248,7 @@ module Ruby2JS
248
248
  if ast.loc and ast.loc.expression
249
249
  filename = ast.loc.expression.source_buffer.name
250
250
  if filename and not filename.empty?
251
- @timestamps[filename] ||= File.mtime(filename)
251
+ @timestamps[filename] ||= File.mtime(filename) rescue nil
252
252
  end
253
253
  end
254
254
 
@@ -9,11 +9,22 @@ module Ruby2JS
9
9
  # NOTE: this is the es2015 version of class
10
10
 
11
11
  handle :class2 do |name, inheritance, *body|
12
+ body.compact!
13
+ while body.length == 1 and body.first.type == :begin
14
+ body = body.first.children
15
+ end
16
+
17
+ proxied = body.find do |node|
18
+ node.type == :def and node.children.first == :method_missing
19
+ end
20
+
12
21
  if name.type == :const and name.children.first == nil
13
22
  put 'class '
14
23
  parse name
24
+ put '$' if proxied
15
25
  else
16
26
  parse name
27
+ put '$' if proxied
17
28
  put ' = class'
18
29
  end
19
30
 
@@ -24,11 +35,6 @@ module Ruby2JS
24
35
 
25
36
  put " {"
26
37
 
27
- body.compact!
28
- while body.length == 1 and body.first.type == :begin
29
- body = body.first.children
30
- end
31
-
32
38
  begin
33
39
  class_name, @class_name = @class_name, name
34
40
  class_parent, @class_parent = @class_parent, inheritance
@@ -64,21 +70,21 @@ module Ruby2JS
64
70
  walk[child] if child.is_a? Parser::AST::Node
65
71
  end
66
72
 
67
- if ast.type == :send and ast.children.first == nil
68
- if ast.children[1] == :attr_accessor
69
- ast.children[2..-1].each_with_index do |child_sym, index2|
70
- ivars << :"@#{child_sym.children.first}"
71
- end
72
- elsif ast.children[1] == :attr_reader
73
- ast.children[2..-1].each_with_index do |child_sym, index2|
74
- ivars << :"@#{child_sym.children.first}"
75
- end
76
- elsif ast.children[1] == :attr_writer
77
- ast.children[2..-1].each_with_index do |child_sym, index2|
78
- ivars << :"@#{child_sym.children.first}"
79
- end
80
- end
81
- end
73
+ if ast.type == :send and ast.children.first == nil
74
+ if ast.children[1] == :attr_accessor
75
+ ast.children[2..-1].each_with_index do |child_sym, index2|
76
+ ivars << :"@#{child_sym.children.first}"
77
+ end
78
+ elsif ast.children[1] == :attr_reader
79
+ ast.children[2..-1].each_with_index do |child_sym, index2|
80
+ ivars << :"@#{child_sym.children.first}"
81
+ end
82
+ elsif ast.children[1] == :attr_writer
83
+ ast.children[2..-1].each_with_index do |child_sym, index2|
84
+ ivars << :"@#{child_sym.children.first}"
85
+ end
86
+ end
87
+ end
82
88
 
83
89
  end
84
90
  walk[@ast]
@@ -299,6 +305,43 @@ module Ruby2JS
299
305
  end
300
306
  end
301
307
 
308
+ if proxied
309
+ put @sep
310
+
311
+ rename = name.updated(nil, [name.children.first, name.children.last.to_s + '$'])
312
+
313
+ if proxied.children[1].children.length == 1
314
+ # special case: if method_missing only has on argument, call it
315
+ # directly (i.e., don't pass arguments). This enables
316
+ # method_missing to return instance attributes (getters) as well
317
+ # as bound functions (methods).
318
+ forward = s(:send, s(:lvar, :obj), :method_missing, s(:lvar, :prop))
319
+ else
320
+ # normal case: return a function which, when called, will call
321
+ # method_missing with method name and arguments.
322
+ forward = s(:block, s(:send, nil, :proc), s(:args, s(:restarg, :args)),
323
+ s(:send, s(:lvar, :obj), :method_missing, s(:lvar, :prop),
324
+ s(:splat, s(:lvar, :args))))
325
+ end
326
+
327
+ proxy = s(:return, s(:send, s(:const, nil, :Proxy), :new,
328
+ s(:send, rename, :new, s(:splat, s(:lvar, :args))),
329
+ s(:hash, s(:pair, s(:sym, :get), s(:block, s(:send, nil, :proc),
330
+ s(:args, s(:arg, :obj), s(:arg, :prop)),
331
+ s(:if, s(:in?, s(:lvar, :prop), s(:lvar, :obj)),
332
+ s(:return, s(:send, s(:lvar, :obj), :[], s(:lvar, :prop))),
333
+ s(:return, forward))))))
334
+ )
335
+
336
+ if name.children.first == nil
337
+ proxy = s(:def, name.children.last, s(:args, s(:restarg, :args)), proxy)
338
+ else
339
+ proxy = s(:defs, *name.children, s(:args, s(:restarg, :args)), proxy)
340
+ end
341
+
342
+ parse proxy
343
+ end
344
+
302
345
  ensure
303
346
  @class_name = class_name
304
347
  @class_parent = class_parent
@@ -131,7 +131,7 @@ module Ruby2JS
131
131
  style = :statement
132
132
  end
133
133
 
134
- if args.children.length == 1 and style == :expression
134
+ if args.children.length == 1 and args.children.first.type != :restarg and style == :expression
135
135
  parse args; put ' => '
136
136
  else
137
137
  put '('; parse args; put ') => '
@@ -82,7 +82,7 @@ module Ruby2JS
82
82
  elsif node.respond_to?(:type) && node.children[1] == :default
83
83
  put 'default '
84
84
  args[0] = node.children[2]
85
- elsif node.respond_to?(:type) && node.type == :lvasgn
85
+ elsif node.respond_to?(:type) && [:lvasgn, :casgn].include?(node.type)
86
86
  if node.children[0] == :default
87
87
  put 'default '
88
88
  args[0] = node.children[1]
@@ -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].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1_")
24
+ parts[1].gsub!(/(\d\d\d)(?=\d)/, "\\1_") if parts[1]
25
+ parts.join('.')
14
26
  end
15
27
  end
16
28
  end
@@ -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 \
@@ -280,7 +281,7 @@ module Ruby2JS
280
281
  else
281
282
  put 'await ' if @ast.type == :await
282
283
 
283
- if not ast.is_method?
284
+ if not ast.is_method? and ast.type != :send!
284
285
  if receiver
285
286
  (group_receiver ? group(receiver) : parse(receiver))
286
287
  put ".#{ method }"
@@ -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,43 @@
1
+ require 'ruby2js'
2
+
3
+ module Ruby2JS
4
+ module Filter
5
+ module ActiveFunctions
6
+ include SEXP
7
+
8
+ def on_send(node)
9
+ target, method, *args = node.children
10
+
11
+ if es2015 and method == :blank?
12
+ create_or_update_import("blank$")
13
+ process node.updated :send, [nil, "blank$", target]
14
+ elsif es2015 and method == :present?
15
+ create_or_update_import("present$")
16
+ process node.updated :send, [nil, "present$", target]
17
+ elsif es2015 and method == :presence
18
+ create_or_update_import("presence$")
19
+ process node.updated :send, [nil, "presence$", target]
20
+ else
21
+ super
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def create_or_update_import(token)
28
+ af_import = @options[:import_from_skypack] ? "https://cdn.skypack.dev/@ruby2js/active-functions" : "@ruby2js/active-functions"
29
+
30
+ if found_node = prepend_list.find {|ast| ast.type == :import && ast.children.first == af_import}
31
+ unless found_node.children.find {|child| child == token}
32
+ prepend_list.delete found_node
33
+ prepend_list << s(:import, found_node.children.first, found_node.children.last.push(s(:const, nil, token)))
34
+ end
35
+ else
36
+ prepend_list << s(:import, af_import, [s(:const, nil, token)])
37
+ end
38
+ end
39
+ end
40
+
41
+ DEFAULTS.push ActiveFunctions
42
+ end
43
+ end
@@ -9,8 +9,9 @@ module Ruby2JS
9
9
  module CamelCase
10
10
  include SEXP
11
11
 
12
- WHITELIST = %w{
12
+ ALLOWLIST = %w{
13
13
  attr_accessor
14
+ method_missing
14
15
  }
15
16
 
16
17
  CAPS_EXCEPTIONS = {
@@ -37,7 +38,7 @@ module Ruby2JS
37
38
  node = super
38
39
  return node unless [:send, :csend, :attr].include? node.type
39
40
 
40
- if node.children[0] == nil and WHITELIST.include? node.children[1].to_s
41
+ if node.children[0] == nil and ALLOWLIST.include? node.children[1].to_s
41
42
  node
42
43
  elsif node.children[0] && [:ivar, :cvar].include?(node.children[0].type)
43
44
  S(node.type, s(node.children[0].type, camelCase(node.children[0].children[0])),
@@ -61,7 +62,7 @@ module Ruby2JS
61
62
  def handle_generic_node(node, node_type)
62
63
  return node if node.type != node_type
63
64
 
64
- if node.children[0] =~ /_.*\w$/
65
+ if node.children[0] =~ /_.*\w$/ and !ALLOWLIST.include?(node.children[0].to_s)
65
66
  S(node_type , camelCase(node.children[0]), *node.children[1..-1])
66
67
  else
67
68
  node
@@ -7,30 +7,62 @@ module Ruby2JS
7
7
  module ESM
8
8
  include SEXP
9
9
 
10
- def initialize(*args)
11
- super
12
- @esm = true # signal for other filters
13
- @esm_imports = nil
14
- end
15
-
16
10
  def options=(options)
17
11
  super
12
+ @esm_autoexports = options[:autoexports] && !@disable_autoexports
18
13
  @esm_autoimports = options[:autoimports]
19
- return unless @esm_autoimports
14
+ @esm_explicit_tokens = Set.new
20
15
  end
21
16
 
22
17
  def process(node)
23
- return super if @esm_imports or not @esm_autoimports
24
- @esm_imports = Set.new
25
- result = super
18
+ return super unless @esm_autoexports
19
+ @esm_autoexports = false
26
20
 
27
- if @esm_imports.empty?
28
- result
29
- else
30
- s(:begin, *@esm_imports.to_a.map {|token|
31
- s(:import, @esm_autoimports[token], s(:const, nil, token))
32
- }, result)
21
+ list = [node]
22
+ while list.length == 1 and list.first.type == :begin
23
+ list = list.first.children.dup
24
+ end
25
+
26
+ list.map! do |child|
27
+ replacement = child
28
+
29
+ if [:module, :class].include? child.type and
30
+ child.children.first.type == :const and
31
+ child.children.first.children.first == nil \
32
+ then
33
+ replacement = s(:export, child)
34
+ elsif child.type == :casgn and child.children.first == nil
35
+ replacement = s(:export, child)
36
+ elsif child.type == :def
37
+ replacement = s(:export, child)
38
+ end
39
+
40
+ if replacement != child and @comments[child]
41
+ @comments[replacement] = @comments[child]
42
+ end
43
+
44
+ replacement
33
45
  end
46
+
47
+ process s(:begin, *list)
48
+ end
49
+
50
+ def on_class(node)
51
+ @esm_explicit_tokens << node.children.first.children.last
52
+
53
+ super
54
+ end
55
+
56
+ def on_def(node)
57
+ @esm_explicit_tokens << node.children.first
58
+
59
+ super
60
+ end
61
+
62
+ def on_lvasgn(node)
63
+ @esm_explicit_tokens << node.children.first
64
+
65
+ super
34
66
  end
35
67
 
36
68
  def on_send(node)
@@ -59,6 +91,8 @@ module Ruby2JS
59
91
  args[0].children[2].children[2].type == :str
60
92
  # import name from "file.js"
61
93
  # => import name from "file.js"
94
+ @esm_explicit_tokens << args[0].children[1]
95
+
62
96
  s(:import,
63
97
  [args[0].children[2].children[2].children[0]],
64
98
  process(s(:attr, nil, args[0].children[1])))
@@ -72,15 +106,20 @@ module Ruby2JS
72
106
  # => import Stuff as * from "file.js"
73
107
  # import [ Some, Stuff ], from: "file.js"
74
108
  # => import { Some, Stuff } from "file.js"
75
- imports = (args[0].type == :const || args[0].type == :send) ?
76
- process(args[0]) :
109
+ imports = if args[0].type == :const || args[0].type == :send
110
+ @esm_explicit_tokens << args[0].children.last
111
+ process(args[0])
112
+ else
113
+ args[0].children.each {|i| @esm_explicit_tokens << i.children.last}
77
114
  process_all(args[0].children)
115
+ end
116
+
78
117
  s(:import, args[1].children, imports) unless args[1].nil?
79
118
  end
80
119
  elsif method == :export
81
120
  s(:export, *process_all(args))
82
- elsif @esm_imports and args.length == 0 and @esm_autoimports[method]
83
- @esm_imports.add(method)
121
+ elsif target.nil? and found_import = find_autoimport(method)
122
+ prepend_list << s(:import, found_import[0], found_import[1])
84
123
  super
85
124
  else
86
125
  super
@@ -88,12 +127,31 @@ module Ruby2JS
88
127
  end
89
128
 
90
129
  def on_const(node)
91
- return super unless @esm_autoimports
92
- if node.children.first == nil and @esm_autoimports[node.children.last]
93
- @esm_imports.add(node.children.last)
130
+ if node.children.first == nil and found_import = find_autoimport(node.children.last)
131
+ prepend_list << s(:import, found_import[0], found_import[1])
94
132
  end
133
+
95
134
  super
96
135
  end
136
+
137
+ def on_export(node)
138
+ s(:export, *process_all(node.children))
139
+ end
140
+ end
141
+
142
+ private
143
+
144
+ def find_autoimport(token)
145
+ return nil if @esm_autoimports.nil?
146
+ return nil if @esm_explicit_tokens.include?(token)
147
+
148
+ token = camelCase(token) if respond_to?(:camelCase)
149
+
150
+ if @esm_autoimports[token]
151
+ [@esm_autoimports[token], s(:const, nil, token)]
152
+ elsif found_key = @esm_autoimports.keys.find {|key| key.is_a?(Array) && key.include?(token)}
153
+ [@esm_autoimports[found_key], found_key.map {|key| s(:const, nil, key)}]
154
+ end
97
155
  end
98
156
 
99
157
  DEFAULTS.push ESM
@@ -540,6 +540,11 @@ module Ruby2JS
540
540
  elsif method == :floor and args.length == 0
541
541
  process S(:send, s(:const, nil, :Math), :floor, target)
542
542
 
543
+ elsif method == :sum and args.length == 0
544
+ process S(:send, target, :reduce, s(:block, s(:send, nil, :proc),
545
+ s(:args, s(:arg, :a), s(:arg, :b)),
546
+ s(:send, s(:lvar, :a), :+, s(:lvar, :b))), s(:int, 0))
547
+
543
548
  else
544
549
  super
545
550
  end
@@ -14,6 +14,10 @@ module Ruby2JS
14
14
 
15
15
  IMPORT_FS = s(:import, ['fs'], s(:attr, nil, :fs))
16
16
 
17
+ IMPORT_OS = s(:import, ['os'], s(:attr, nil, :os))
18
+
19
+ IMPORT_PATH = s(:import, ['path'], s(:attr, nil, :path))
20
+
17
21
  SETUP_ARGV = s(:lvasgn, :ARGV, s(:send, s(:attr,
18
22
  s(:attr, nil, :process), :argv), :slice, s(:int, 2)))
19
23
 
@@ -31,11 +35,11 @@ module Ruby2JS
31
35
  prepend_list << IMPORT_CHILD_PROCESS
32
36
 
33
37
  if args.length == 1
34
- s(:send, s(:attr, nil, :child_process), :execSync,
38
+ S(:send, s(:attr, nil, :child_process), :execSync,
35
39
  process(args.first),
36
40
  s(:hash, s(:pair, s(:sym, :stdio), s(:str, 'inherit'))))
37
41
  else
38
- s(:send, s(:attr, nil, :child_process), :execFileSync,
42
+ S(:send, s(:attr, nil, :child_process), :execFileSync,
39
43
  process(args.first), s(:array, *process_all(args[1..-1])),
40
44
  s(:hash, s(:pair, s(:sym, :stdio), s(:str, 'inherit'))))
41
45
  end
@@ -57,7 +61,7 @@ module Ruby2JS
57
61
  then
58
62
  if method == :read and args.length == 1
59
63
  prepend_list << IMPORT_FS
60
- s(:send, s(:attr, nil, :fs), :readFileSync, *process_all(args),
64
+ S(:send, s(:attr, nil, :fs), :readFileSync, *process_all(args),
61
65
  s(:str, 'utf8'))
62
66
 
63
67
  elsif method == :write and args.length == 2
@@ -111,15 +115,15 @@ module Ruby2JS
111
115
 
112
116
  elsif method == :symlink and args.length == 2
113
117
  prepend_list << IMPORT_FS
114
- s(:send, s(:attr, nil, :fs), :symlinkSync, *process_all(args))
118
+ S(:send, s(:attr, nil, :fs), :symlinkSync, *process_all(args))
115
119
 
116
120
  elsif method == :truncate and args.length == 2
117
121
  prepend_list << IMPORT_FS
118
- s(:send, s(:attr, nil, :fs), :truncateSync, *process_all(args))
122
+ S(:send, s(:attr, nil, :fs), :truncateSync, *process_all(args))
119
123
 
120
124
  elsif [:stat, :lstat].include? method and args.length == 1
121
125
  prepend_list << IMPORT_FS
122
- s(:send, s(:attr, nil, :fs), method.to_s + 'Sync',
126
+ S(:send, s(:attr, nil, :fs), method.to_s + 'Sync',
123
127
  process(args.first))
124
128
 
125
129
  elsif method == :unlink and args.length == 1
@@ -128,6 +132,29 @@ module Ruby2JS
128
132
  S(:send, s(:attr, nil, :fs), :unlinkSync, process(file))
129
133
  })
130
134
 
135
+ elsif target.children.last == :File
136
+ if method == :absolute_path
137
+ prepend_list << IMPORT_PATH
138
+ S(:send, s(:attr, nil, :path), :resolve,
139
+ *process_all(args.reverse))
140
+ elsif method == :absolute_path?
141
+ prepend_list << IMPORT_PATH
142
+ S(:send, s(:attr, nil, :path), :isAbsolute, *process_all(args))
143
+ elsif method == :basename
144
+ prepend_list << IMPORT_PATH
145
+ S(:send, s(:attr, nil, :path), :basename, *process_all(args))
146
+ elsif method == :dirname
147
+ prepend_list << IMPORT_PATH
148
+ S(:send, s(:attr, nil, :path), :dirname, *process_all(args))
149
+ elsif method == :extname
150
+ prepend_list << IMPORT_PATH
151
+ S(:send, s(:attr, nil, :path), :extname, *process_all(args))
152
+ elsif method == :join
153
+ prepend_list << IMPORT_PATH
154
+ S(:send, s(:attr, nil, :path), :join, *process_all(args))
155
+ else
156
+ super
157
+ end
131
158
  else
132
159
  super
133
160
  end
@@ -163,7 +190,7 @@ module Ruby2JS
163
190
  S(:send, s(:attr, nil, :process), :chdir, *process_all(args))
164
191
 
165
192
  elsif method == :pwd and args.length == 0
166
- s(:send, s(:attr, nil, :process), :cwd)
193
+ S(:send!, s(:attr, nil, :process), :cwd)
167
194
 
168
195
  elsif method == :rmdir and args.length == 1
169
196
  prepend_list << IMPORT_FS
@@ -173,11 +200,11 @@ module Ruby2JS
173
200
 
174
201
  elsif method == :ln and args.length == 2
175
202
  prepend_list << IMPORT_FS
176
- s(:send, s(:attr, nil, :fs), :linkSync, *process_all(args))
203
+ S(:send, s(:attr, nil, :fs), :linkSync, *process_all(args))
177
204
 
178
205
  elsif method == :ln_s and args.length == 2
179
206
  prepend_list << IMPORT_FS
180
- s(:send, s(:attr, nil, :fs), :symlinkSync, *process_all(args))
207
+ S(:send, s(:attr, nil, :fs), :symlinkSync, *process_all(args))
181
208
 
182
209
  elsif method == :rm and args.length == 1
183
210
  prepend_list << IMPORT_FS
@@ -224,16 +251,16 @@ module Ruby2JS
224
251
  if method == :chdir and args.length == 1
225
252
  S(:send, s(:attr, nil, :process), :chdir, *process_all(args))
226
253
  elsif method == :pwd and args.length == 0
227
- s(:send, s(:attr, nil, :process), :cwd)
254
+ S(:send!, s(:attr, nil, :process), :cwd)
228
255
  elsif method == :entries
229
256
  prepend_list << IMPORT_FS
230
- s(:send, s(:attr, nil, :fs), :readdirSync, *process_all(args))
257
+ S(:send, s(:attr, nil, :fs), :readdirSync, *process_all(args))
231
258
  elsif method == :mkdir and args.length == 1
232
259
  prepend_list << IMPORT_FS
233
- s(:send, s(:attr, nil, :fs), :mkdirSync, process(args.first))
260
+ S(:send, s(:attr, nil, :fs), :mkdirSync, process(args.first))
234
261
  elsif method == :rmdir and args.length == 1
235
262
  prepend_list << IMPORT_FS
236
- s(:send, s(:attr, nil, :fs), :rmdirSync, process(args.first))
263
+ S(:send, s(:attr, nil, :fs), :rmdirSync, process(args.first))
237
264
  elsif method == :mktmpdir and args.length <=1
238
265
  prepend_list << IMPORT_FS
239
266
  if args.length == 0
@@ -244,7 +271,14 @@ module Ruby2JS
244
271
  prefix = args.first
245
272
  end
246
273
 
247
- s(:send, s(:attr, nil, :fs), :mkdtempSync, process(prefix))
274
+ S(:send, s(:attr, nil, :fs), :mkdtempSync, process(prefix))
275
+ elsif method == :home and args.length == 0
276
+ prepend_list << IMPORT_OS
277
+ S(:send!, s(:attr, nil, :os), :homedir)
278
+ elsif method == :tmpdir and args.length == 0
279
+ prepend_list << IMPORT_OS
280
+ S(:send!, s(:attr, nil, :os), :tmpdir)
281
+
248
282
  else
249
283
  super
250
284
  end
@@ -285,6 +319,16 @@ module Ruby2JS
285
319
  S(:attr, s(:attr, nil, :process), :stdout)
286
320
  elsif node.children == [nil, :STDERR]
287
321
  S(:attr, s(:attr, nil, :process), :stderr)
322
+ elsif node.children.first == s(:const, nil, :File)
323
+ if node.children.last == :SEPARATOR
324
+ prepend_list << IMPORT_PATH
325
+ S(:attr, s(:attr, nil, :path), :sep)
326
+ elsif node.children.last == :PATH_SEPARATOR
327
+ prepend_list << IMPORT_PATH
328
+ S(:attr, s(:attr, nil, :path), :delimiter)
329
+ else
330
+ super
331
+ end
288
332
  else
289
333
  super
290
334
  end
@@ -42,6 +42,7 @@ module Ruby2JS
42
42
 
43
43
  class Serializer
44
44
  attr_reader :timestamps
45
+ attr_accessor :file_name
45
46
 
46
47
  def initialize
47
48
  @sep = '; '
@@ -56,6 +57,7 @@ module Ruby2JS
56
57
  @timestamps = {}
57
58
 
58
59
  @ast = nil
60
+ @file_name = ''
59
61
  end
60
62
 
61
63
  def timestamp(file)
@@ -363,7 +365,7 @@ module Ruby2JS
363
365
 
364
366
  @sourcemap = {
365
367
  version: 3,
366
- file: @ast.loc.expression.source_buffer.name,
368
+ file: @file_name,
367
369
  sources: sources.map(&:name),
368
370
  mappings: @mappings
369
371
  }
@@ -1,8 +1,8 @@
1
1
  module Ruby2JS
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 3
4
- MINOR = 5
5
- TINY = 3
4
+ MINOR = 6
5
+ TINY = 0
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
@@ -9,7 +9,7 @@ Gem::Specification.new do |s|
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
11
11
  s.require_paths = ["lib".freeze]
12
- s.authors = ["Sam Ruby".freeze]
12
+ s.authors = ["Sam Ruby".freeze, "Jared White".freeze]
13
13
  s.description = " The base package maps Ruby syntax to JavaScript semantics.\n Filters may be provided to add Ruby-specific or framework specific\n behavior.\n".freeze
14
14
  s.email = "rubys@intertwingly.net".freeze
15
15
  s.files = %w(ruby2js.gemspec README.md) + Dir.glob("{lib}/**/*")
metadata CHANGED
@@ -1,14 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby2js
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.5.3
4
+ version: 3.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Ruby
8
- autorequire:
8
+ - Jared White
9
+ autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2020-11-24 00:00:00.000000000 Z
12
+ date: 2020-12-26 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: parser
@@ -121,10 +122,10 @@ files:
121
122
  - lib/ruby2js/es2021/strict.rb
122
123
  - lib/ruby2js/execjs.rb
123
124
  - lib/ruby2js/filter.rb
125
+ - lib/ruby2js/filter/active_functions.rb
124
126
  - lib/ruby2js/filter/camelCase.rb
125
127
  - lib/ruby2js/filter/cjs.rb
126
128
  - lib/ruby2js/filter/esm.rb
127
- - lib/ruby2js/filter/esm_migration.rb
128
129
  - lib/ruby2js/filter/functions.rb
129
130
  - lib/ruby2js/filter/jquery.rb
130
131
  - lib/ruby2js/filter/matchAll.rb
@@ -150,7 +151,7 @@ homepage: http://github.com/rubys/ruby2js
150
151
  licenses:
151
152
  - MIT
152
153
  metadata: {}
153
- post_install_message:
154
+ post_install_message:
154
155
  rdoc_options: []
155
156
  require_paths:
156
157
  - lib
@@ -165,8 +166,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
165
166
  - !ruby/object:Gem::Version
166
167
  version: '0'
167
168
  requirements: []
168
- rubygems_version: 3.2.0.rc.1
169
- signing_key:
169
+ rubygems_version: 3.1.4
170
+ signing_key:
170
171
  specification_version: 4
171
172
  summary: Minimal yet extensible Ruby to JavaScript conversion.
172
173
  test_files: []
@@ -1,72 +0,0 @@
1
- require 'ruby2js'
2
-
3
- module Ruby2JS
4
- module Filter
5
- module ESMMigration
6
- include SEXP
7
-
8
- def initialize(*args)
9
- @esm_include = nil
10
- super
11
- end
12
-
13
- def process(node)
14
- return super if @esm_include
15
- @esm_include = Set.new
16
- @esm_exclude = Set.new
17
- @esm_export = nil
18
- result = super
19
-
20
- esm_walk(result)
21
-
22
- inventory = (@esm_include - @esm_exclude).to_a.sort
23
-
24
- if inventory.empty? and not @esm_export
25
- result
26
- else
27
- list = inventory.map do |name|
28
- if name == "React" and defined? Ruby2JS::Filter::React
29
- s(:import, "#{name.downcase}", s(:const, nil, name))
30
- elsif not %w(JSON Object).include? name
31
- s(:import, "./#{name.downcase}.js", s(:const, nil, name))
32
- end
33
- end
34
-
35
- list.push result
36
-
37
- if @esm_export
38
- list.push s(:export, :default, s(:const, nil, @esm_export))
39
- end
40
-
41
- s(:begin, *list.compact)
42
- end
43
- end
44
-
45
- # gather constants
46
- def esm_walk(node)
47
- # extract ivars and cvars
48
- if node.type == :const and node.children.first == nil
49
- @esm_include << node.children.last.to_s
50
- elsif node.type == :xnode
51
- name = node.children.first
52
- @esm_include << name unless name.empty? or name =~ /^[a-z]/
53
- elsif node.type == :casgn and node.children.first == nil
54
- @esm_exclude << node.children[1].to_s
55
- elsif node.type == :class and node.children.first.type == :const
56
- if node.children.first.children.first == nil
57
- name = node.children.first.children.last.to_s
58
- @esm_exclude << name
59
- @esm_export ||= name
60
- end
61
- end
62
-
63
- # recurse
64
- node.children.each do |child|
65
- esm_walk(child) if Parser::AST::Node === child
66
- end
67
- end
68
- end
69
-
70
- DEFAULTS.push ESMMigration
71
- end
72
- end