ruby2js 3.4.0 → 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: df66ef5836777a349c4f702cb4482631c3260f95f82d2f292ae914dfc9de5d86
4
- data.tar.gz: 6d4d513cdc4130c3e2a1af390441fd8e0642dd74b331498e4ab60799965c072b
3
+ metadata.gz: 7fa9e6949c8d7d1f3f5cb549dc234a2b559d032bc9b5b236d123f68b62ecd763
4
+ data.tar.gz: e89542553f4db3caa7ceda112542a3a7fceb468d23d8a15cb9b416954c054d1f
5
5
  SHA512:
6
- metadata.gz: b6b1f96c9e312700bd774f1d6344c21f67c48da1a9fea50f927ade02910541c56aa31fab988bd9a9e16a136e7112cbc830bfdda80bfa6486f5015e9a2edcf360
7
- data.tar.gz: 4b151db4cfe119e206ef46ac89b870754a531e67cf6b6026aec4d1639563220079db3d37d3b95e2070bb8d0c4f8bfdeead16bcb76f10d2a593d6fa4b7de224b3
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
@@ -81,6 +78,18 @@ require 'ruby2js/filter/functions'
81
78
  puts Ruby2JS.convert('"2A".to_i(16)')
82
79
  ```
83
80
 
81
+ Host variable substitution:
82
+
83
+ ```ruby
84
+ puts Ruby2JS.convert("@name", ivars: {:@name => "Joe"})
85
+ ```
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
+
84
93
  Enable ES2015 support:
85
94
 
86
95
  ```ruby
@@ -129,7 +138,7 @@ Introduction
129
138
 
130
139
  JavaScript is a language where `0` is considered `false`, strings are
131
140
  immutable, and the behaviors for operators like `==` are, at best,
132
- [convoluted](http://zero.milosz.ca/).
141
+ [convoluted](https://zero.milosz.ca/).
133
142
 
134
143
  Any attempt to bridge the semantics of Ruby and JavaScript will involve
135
144
  trade-offs. Consider the following expression:
@@ -144,10 +153,10 @@ quite different if `a` is a Hash.
144
153
 
145
154
  One way to resolve this is to change the way indexing operators are evaluated,
146
155
  and to provide a runtime library that adds properties to global JavaScript
147
- 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/)
148
157
  takes. It is a fine approach, with a number of benefits. It also has some
149
158
  notable drawbacks. For example,
150
- [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])
151
160
  and
152
161
  [compatibility with other frameworks](https://github.com/opal/opal/issues/400).
153
162
 
@@ -169,7 +178,7 @@ negative at runtime.
169
178
  This quickly gets into gray areas. `each` in Ruby is a common method that
170
179
  facilitates iteration over arrays. `forEach` is the JavaScript equivalent.
171
180
  Mapping this is fine until you start using a framework like jQuery which
172
- provides a function named [each](http://api.jquery.com/jQuery.each/).
181
+ provides a function named [each](https://api.jquery.com/jQuery.each/).
173
182
 
174
183
  Fortunately, Ruby provides `?` and `!` as legal suffixes for method names,
175
184
  Ruby2js filters do an exact match, so if you select a filter that maps `each`
@@ -192,9 +201,7 @@ Ruby2JS::Filter.exclude :each
192
201
  ```
193
202
 
194
203
  Static transformations and runtime libraries aren't aren’t mutually exclusive.
195
- With enough of each, one could reproduce any functionality desired. Just be
196
- forewarned, that implementing a function like `method_missing` would require a
197
- _lot_ of work.
204
+ With enough of each, one could reproduce any functionality desired.
198
205
 
199
206
  Integrations
200
207
  ---
@@ -237,8 +244,10 @@ the script.
237
244
 
238
245
  * <a id="functions" href="https://github.com/rubys/ruby2js/blob/master/lib/ruby2js/filter/functions.rb">functions</a>
239
246
 
247
+ * `.abs` becomes `Math.abs()`
240
248
  * `.all?` becomes `.every`
241
249
  * `.any?` becomes `.some`
250
+ * `.ceil` becomes `Math.ceil()`
242
251
  * `.chr` becomes `fromCharCode`
243
252
  * `.clear` becomes `.length = 0`
244
253
  * `.delete` becomes `delete target[arg]`
@@ -253,6 +262,7 @@ the script.
253
262
  * `.find_index` becomes `findIndex`
254
263
  * `.first` becomes `[0]`
255
264
  * `.first(n)` becomes `.slice(0, n)`
265
+ * `.floor` becomes `Math.floor()`
256
266
  * `.gsub` becomes `replace(//g)`
257
267
  * `.include?` becomes `.indexOf() != -1`
258
268
  * `.inspect` becomes `JSON.stringify()`
@@ -271,6 +281,7 @@ the script.
271
281
  * `.respond_to?` becomes `right in left`
272
282
  * `.rstrip` becomes `.replace(/s+$/, "")`
273
283
  * `.scan` becomes `.match(//g)`
284
+ * `.sum` becomes `.reduce(function(a, b) {a + b}, 0)`
274
285
  * `.start_with?` becomes `.substring(0, arg.length) == arg`
275
286
  * `.upto(lim)` becomes `for (var i=num; i<=lim; i+=1)`
276
287
  * `.downto(lim)` becomes `for (var i=num; i>=lim; i-=1)`
@@ -317,6 +328,20 @@ the script.
317
328
 
318
329
  * `.class` becomes `.constructor`
319
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
+
320
345
  * <a id="tagged_templates" href="https://github.com/rubys/ruby2js/blob/master/lib/ruby2js/filter/tagged_templates.rb">tagged_templates</a>
321
346
 
322
347
  Allows you to turn certain method calls with a string argument into tagged
@@ -382,6 +407,59 @@ the script.
382
407
  # => export { one, two, three as default }
383
408
  ```
384
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
+
385
463
  * <a id="node" href="https://github.com/rubys/ruby2js/blob/master/spec/node_spec.rb">node</a>
386
464
 
387
465
  * `` `command` `` becomes `child_process.execSync("command", {encoding: "utf8"})`
@@ -389,24 +467,34 @@ the script.
389
467
  * `__dir__` becomes `__dirname`
390
468
  * `Dir.chdir` becomes `process.chdir`
391
469
  * `Dir.entries` becomes `fs.readdirSync`
470
+ * `Dir.home` becomes `os.homedir()`
392
471
  * `Dir.mkdir` becomes `fs.mkdirSync`
393
472
  * `Dir.mktmpdir` becomes `fs.mkdtempSync`
394
473
  * `Dir.pwd` becomes `process.cwd`
395
474
  * `Dir.rmdir` becomes `fs.rmdirSync`
475
+ * `Dir.tmpdir` becomes `os.tmpdir()`
396
476
  * `ENV` becomes `process.env`
397
477
  * `__FILE__` becomes `__filename`
478
+ * `File.absolute_path` becomes `path.resolve`
479
+ * `File.absolute_path?` becomes `path.isAbsolute`
480
+ * `File.basename` becomes `path.basename`
398
481
  * `File.chmod` becomes `fs.chmodSync`
399
482
  * `File.chown` becomes `fs.chownSync`
400
483
  * `File.cp` becomes `fs.copyFileSync`
484
+ * `File.dirname` becomes `path.dirname`
401
485
  * `File.exist?` becomes `fs.existsSync`
486
+ * `File.extname` becomes `path.extname`
487
+ * `File.join` becomes `path.join`
402
488
  * `File.lchmod` becomes `fs.lchmodSync`
403
489
  * `File.link` becomes `fs.linkSync`
404
490
  * `File.ln` becomes `fs.linkSync`
405
491
  * `File.lstat` becomes `fs.lstatSync`
492
+ * `File::PATH_SEPARATOR` becomes `path.delimiter`
406
493
  * `File.read` becomes `fs.readFileSync`
407
494
  * `File.readlink` becomes `fs.readlinkSync`
408
495
  * `File.realpath` becomes `fs.realpathSync`
409
496
  * `File.rename` becomes `fs.renameSync`
497
+ * `File::SEPARATOR` becomes `path.sep`
410
498
  * `File.stat` becomes `fs.statSync`
411
499
  * `File.symlink` becomes `fs.symlinkSync`
412
500
  * `File.truncate` becomes `fs.truncateSync`
@@ -600,8 +688,16 @@ Additionally, the `functions` filter will provide the following conversion:
600
688
  * `Array(x)` becomes `Array.from(x)`
601
689
  * `.inject(n) {}` becomes `.reduce(() => {}, n)`
602
690
 
603
- Finally, keyword arguments and optional keyword arguments will be mapped to
604
- 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.
605
701
 
606
702
  ES2016 support
607
703
  ---
@@ -610,6 +706,9 @@ When option `eslevel: 2016` is provided, the following additional
610
706
  conversion is made:
611
707
 
612
708
  * `a ** b` becomes `a ** b`
709
+
710
+ Additionally the following conversions is added to the `functions` filter:
711
+
613
712
  * `.include?` becomes `.includes`
614
713
 
615
714
  ES2017 support
@@ -620,7 +719,15 @@ conversions are made by the `functions` filter:
620
719
 
621
720
  * `.values()` becomes `Object.values()`
622
721
  * `.entries()` becomes `Object.entries()`
623
- * `.each_pair {}` becomes `for (let [key, value] of Object.entries()) {}'
722
+ * `.each_pair {}` becomes `for (let [key, value] of Object.entries()) {}`
723
+
724
+ async support:
725
+
726
+ * `async def` becomes `async function`
727
+ * `async lambda` becomes `async =>`
728
+ * `async proc` becomes `async =>`
729
+ * `async ->` becomes `async =>`
730
+ * `foo bar, async do...end` becomes `foo(bar, async () => {})`
624
731
 
625
732
  ES2018 support
626
733
  ---
@@ -640,8 +747,8 @@ When option `eslevel: 2019` is provided, the following additional
640
747
  conversion is made by the `functions` filter:
641
748
 
642
749
  * `.flatten` becomes `.flat(Infinity)`
643
- * `.lstrip` becomes `.trimEnd
644
- * `.rstrip` becomes `.trimStart
750
+ * `.lstrip` becomes `.trimEnd`
751
+ * `.rstrip` becomes `.trimStart`
645
752
  * `a.to_h` becomes `Object.fromEntries(a)`
646
753
  * `Hash[a]` becomes `Object.fromEntries(a)`
647
754
 
@@ -667,27 +774,28 @@ conversions are made:
667
774
 
668
775
  * `x ||= 1` becomes `x ||= 1`
669
776
  * `x &&= 1` becomes `x &&= 1`
777
+ * `1000000.000001` becomes `1_000_000.000_001`
670
778
 
671
779
  Picking a Ruby to JS mapping tool
672
780
  ---
673
781
 
674
782
  > dsl — A domain specific language, where code is written in one language and
675
783
  > errors are given in another.
676
- > -- [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)
677
785
 
678
786
  If you simply want to get a job done, and would like a mature and tested
679
787
  framework, and only use one of the many integrations that
680
- [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.
681
789
 
682
790
  ruby2js is for those that want to produce JavaScript that looks like it
683
791
  wasn’t machine generated, and want the absolute bare minimum in terms of
684
792
  limitations as to what JavaScript can be produced.
685
793
 
686
- [Try](http://intertwingly.net/projects/ruby2js.cgi/all) for yourself.
687
- [Compare](http://opalrb.com/try/#code:).
794
+ [Try](https://intertwingly.net/projects/ruby2js) for yourself.
795
+ [Compare](https://opalrb.com/try/#code:).
688
796
 
689
797
  And, of course, the right solution might be to use
690
- [CoffeeScript](http://coffeescript.org/) instead.
798
+ [CoffeeScript](https://coffeescript.org/) instead.
691
799
 
692
800
  License
693
801
  ---
@@ -17,6 +17,7 @@ module Ruby2JS
17
17
 
18
18
  @@eslevel_default = 2009 # ecmascript 5
19
19
  @@strict_default = false
20
+ @@module_default = nil
20
21
 
21
22
  def self.eslevel_default
22
23
  @@eslevel_default
@@ -34,6 +35,14 @@ module Ruby2JS
34
35
  @@strict_default = level
35
36
  end
36
37
 
38
+ def self.module_default
39
+ @@module_default
40
+ end
41
+
42
+ def self.module_default=(module_type)
43
+ @@module_default = module_type
44
+ end
45
+
37
46
  module Filter
38
47
  DEFAULTS = []
39
48
 
@@ -52,11 +61,19 @@ module Ruby2JS
52
61
  include Ruby2JS::Filter
53
62
  BINARY_OPERATORS = Converter::OPERATORS[2..-1].flatten
54
63
 
64
+ attr_accessor :prepend_list, :disable_autoimports
65
+
55
66
  def initialize(comments)
56
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
+
57
74
  @ast = nil
58
75
  @exclude_methods = []
59
- @esm = false
76
+ @prepend_list = Set.new
60
77
  end
61
78
 
62
79
  def options=(options)
@@ -129,6 +146,7 @@ module Ruby2JS
129
146
  def on_method(node); on_send(node); end
130
147
  def on_prop(node); on_array(node); end
131
148
  def on_prototype(node); on_begin(node); end
149
+ def on_send!(node); on_send(node); end
132
150
  def on_sendw(node); on_send(node); end
133
151
  def on_undefined?(node); on_defined?(node); end
134
152
  def on_nil(node); end
@@ -162,6 +180,7 @@ module Ruby2JS
162
180
  def self.convert(source, options={})
163
181
  options[:eslevel] ||= @@eslevel_default
164
182
  options[:strict] = @@strict_default if options[:strict] == nil
183
+ options[:module] ||= @@module_default || :esm
165
184
 
166
185
  if Proc === source
167
186
  file,line = source.source_location
@@ -175,7 +194,7 @@ module Ruby2JS
175
194
  source = ast.loc.expression.source_buffer.source
176
195
  else
177
196
  ast, comments = parse( source, options[:file] )
178
- comments = Parser::Source::Comment.associate(ast, comments) if ast
197
+ comments = ast ? Parser::Source::Comment.associate(ast, comments) : {}
179
198
  end
180
199
 
181
200
  filters = (options[:filters] || Filter::DEFAULTS)
@@ -193,6 +212,12 @@ module Ruby2JS
193
212
 
194
213
  filter.options = options
195
214
  ast = filter.process(ast)
215
+
216
+ unless filter.prepend_list.empty?
217
+ prepend = filter.prepend_list.sort_by {|ast| ast.type == :import ? 0 : 1}
218
+ prepend.reject! {|ast| ast.type == :import} if filter.disable_autoimports
219
+ ast = Parser::AST::Node.new(:begin, [*prepend, ast])
220
+ end
196
221
  end
197
222
 
198
223
  ruby2js = Ruby2JS::Converter.new(ast, comments)
@@ -203,6 +228,7 @@ module Ruby2JS
203
228
  ruby2js.strict = options[:strict]
204
229
  ruby2js.comparison = options[:comparison] || :equality
205
230
  ruby2js.or = options[:or] || :logical
231
+ ruby2js.module_type = options[:module] || :esm
206
232
  ruby2js.underscored_private = (options[:eslevel] < 2020) || options[:underscored_private]
207
233
  if ruby2js.binding and not ruby2js.ivars
208
234
  ruby2js.ivars = ruby2js.binding.eval \
@@ -221,6 +247,8 @@ module Ruby2JS
221
247
 
222
248
  ruby2js.timestamp options[:file]
223
249
 
250
+ ruby2js.file_name = options[:file] || ast&.loc&.expression&.source_buffer&.name || ''
251
+
224
252
  ruby2js
225
253
  end
226
254
 
@@ -3,9 +3,11 @@ require 'ruby2js/serializer'
3
3
  module Ruby2JS
4
4
  class Error < NotImplementedError
5
5
  def initialize(message, ast)
6
- message += ' at ' + ast.loc.expression.source_buffer.name.to_s
7
- message += ':' + ast.loc.expression.line.inspect
8
- message += ':' + ast.loc.expression.column.to_s
6
+ if ast.loc
7
+ message += ' at ' + ast.loc.expression.source_buffer.name.to_s
8
+ message += ':' + ast.loc.expression.line.inspect
9
+ message += ':' + ast.loc.expression.column.to_s
10
+ end
9
11
  super(message)
10
12
  end
11
13
  end
@@ -128,7 +130,7 @@ module Ruby2JS
128
130
  Parser::AST::Node.new(type, args)
129
131
  end
130
132
 
131
- attr_accessor :strict, :eslevel, :comparison, :or, :underscored_private
133
+ attr_accessor :strict, :eslevel, :module_type, :comparison, :or, :underscored_private
132
134
 
133
135
  def es2015
134
136
  @eslevel >= 2015
@@ -246,7 +248,7 @@ module Ruby2JS
246
248
  if ast.loc and ast.loc.expression
247
249
  filename = ast.loc.expression.source_buffer.name
248
250
  if filename and not filename.empty?
249
- @timestamps[filename] ||= File.mtime(filename)
251
+ @timestamps[filename] ||= File.mtime(filename) rescue nil
250
252
  end
251
253
  end
252
254
 
@@ -9,6 +9,11 @@ module Ruby2JS
9
9
 
10
10
  handle :block do |call, args, block|
11
11
 
12
+ if es2017 and call.children.last == s(:send, nil, :async)
13
+ return parse call.updated(nil, [*call.children[0..-2],
14
+ s(:send, nil, :async, s(:block, s(:send, nil, :proc), args, block))])
15
+ end
16
+
12
17
  if \
13
18
  @state == :statement and args.children.length == 1 and
14
19
  call.children.first and call.children.first.type == :begin and