ruby-next-core 0.10.5 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +41 -0
  3. data/README.md +25 -16
  4. data/lib/.rbnext/2.1/ruby-next/core.rb +206 -0
  5. data/lib/.rbnext/2.1/ruby-next/language.rb +227 -0
  6. data/lib/.rbnext/2.3/ruby-next/commands/nextify.rb +2 -2
  7. data/lib/.rbnext/2.3/ruby-next/language/eval.rb +4 -4
  8. data/lib/.rbnext/2.3/ruby-next/language/rewriters/base.rb +23 -5
  9. data/lib/.rbnext/2.3/ruby-next/language/rewriters/endless_range.rb +1 -1
  10. data/lib/.rbnext/2.3/ruby-next/language/rewriters/pattern_matching.rb +20 -17
  11. data/lib/.rbnext/2.3/ruby-next/utils.rb +1 -1
  12. data/lib/.rbnext/2.7/ruby-next/core.rb +206 -0
  13. data/lib/ruby-next/commands/nextify.rb +1 -1
  14. data/lib/ruby-next/config.rb +50 -0
  15. data/lib/ruby-next/core/array/deconstruct.rb +1 -1
  16. data/lib/ruby-next/core/array/intersect.rb +9 -0
  17. data/lib/ruby-next/core/constants/frozen_error.rb +15 -0
  18. data/lib/ruby-next/core/constants/no_matching_pattern_error.rb +1 -1
  19. data/lib/ruby-next/core/enumerable/tally.rb +46 -7
  20. data/lib/ruby-next/core/hash/deconstruct_keys.rb +1 -1
  21. data/lib/ruby-next/core/struct/deconstruct_keys.rb +3 -3
  22. data/lib/ruby-next/core.rb +8 -4
  23. data/lib/ruby-next/core_ext.rb +3 -1
  24. data/lib/ruby-next/language/bootsnap.rb +1 -1
  25. data/lib/ruby-next/language/edge.rb +0 -6
  26. data/lib/ruby-next/language/eval.rb +3 -3
  27. data/lib/ruby-next/language/parser.rb +16 -0
  28. data/lib/ruby-next/language/rewriters/args_forward.rb +14 -6
  29. data/lib/ruby-next/language/rewriters/args_forward_leading.rb +75 -0
  30. data/lib/ruby-next/language/rewriters/base.rb +19 -1
  31. data/lib/ruby-next/language/rewriters/in_pattern.rb +56 -0
  32. data/lib/ruby-next/language/rewriters/method_reference.rb +1 -1
  33. data/lib/ruby-next/language/rewriters/numeric_literals.rb +41 -0
  34. data/lib/ruby-next/language/rewriters/pattern_matching.rb +18 -15
  35. data/lib/ruby-next/language/rewriters/required_kwargs.rb +39 -0
  36. data/lib/ruby-next/language/rewriters/safe_navigation.rb +42 -29
  37. data/lib/ruby-next/language/rewriters/shorthand_hash.rb +1 -1
  38. data/lib/ruby-next/language/setup.rb +7 -4
  39. data/lib/ruby-next/language/unparser.rb +5 -0
  40. data/lib/ruby-next/language.rb +62 -44
  41. data/lib/ruby-next/rubocop.rb +9 -18
  42. data/lib/ruby-next/setup_self.rb +5 -3
  43. data/lib/ruby-next/version.rb +1 -1
  44. data/lib/ruby-next.rb +5 -38
  45. metadata +20 -18
  46. data/lib/.rbnext/2.3/ruby-next/language/rewriters/right_hand_assignment.rb +0 -117
  47. data/lib/ruby-next/language/rewriters/right_hand_assignment.rb +0 -117
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 34691e69e3f0725ece379addac8297e0b69e9ed0a55fb1a06bfe3ca970213eae
4
- data.tar.gz: 23edfba446641d2658d754f3678eb79de8ee332a4d481d4714887769392ad272
3
+ metadata.gz: 1e73900618b0d9da915699c40c610736e19919ac0d11bce7c7dc58e7d5ddb84b
4
+ data.tar.gz: b07917f44a763467f4bed145ae1224ba8aeb03eaba7924b2787c681df4b704f8
5
5
  SHA512:
6
- metadata.gz: 0d41912b15fd77ed3a1d2d2665a3375a212a1815e26e85724d2e0960af1daa5c714b126efca1c5621dc76b16af3d612c35a077be3472ca607548a757dea253ac
7
- data.tar.gz: 02d860b6d0730f48b990c6c8dd9a3ef49e022ea7a0ae637a64cd0c29a74ee1e6c2b0f4cdf0ed25886029a49d9e2df8044b9e016aead9733ca322fd3579093949
6
+ metadata.gz: fb7bc9bc3416a8b6df9e99f346fb15c2c0ae0a9d63bba739adaa6c604110eaa753a5d7b00d98443400e92d7233fba7b4fda2a43e866ce85ef04ed46cd7e89a4c
7
+ data.tar.gz: 0b6410607425aaa0afb66309fa07739e72e3b8cbf94addf85d501f136755159db968225b6d1752a1bda77911592252d48e2d2c5421dae5798d5741ef6ec33616
data/CHANGELOG.md CHANGED
@@ -2,6 +2,46 @@
2
2
 
3
3
  ## master
4
4
 
5
+ ## 0.13.0 (2021-09-27)
6
+
7
+ - Added `Enumerable#tally` with the resulting hash. ([@skryukov][])
8
+
9
+ - Added `Array#intersect?`. ([@skryukov][])
10
+
11
+ ## 0.12.0 (2021-01-12)
12
+
13
+ - Added required keyword arguments rewriter. ([@palkan][])
14
+
15
+ Required kwargs were introduced in 2.1. Now we make them possible in 2.0.
16
+
17
+ - Added numeric literals rewriter. ([@palkan][])
18
+
19
+ Now it's possible to generate Ruby 2.0 compatible code from `2i + 1/3r`.
20
+
21
+ - Fixed several safe navigation (`&.`) bugs. ([@palkan][])
22
+
23
+ See [#68](https://github.com/ruby-next/ruby-next/issues/68) and [#69](https://github.com/ruby-next/ruby-next/issues/69).
24
+
25
+ ## 0.11.1 (2020-12-28)
26
+
27
+ - Use separate _namespace_ for proposed features to avoid conflicts with new Ruby version. ([@palkan][])
28
+
29
+ Previously, we used the upcoming Ruby version number for proposed features (e.g., `3.0.0`), which broke
30
+ the load path setup, since transpiled files were not loaded anymore.
31
+ Now that's fixed by using a _virtual_ version number for proposals (`1995.next.0`).
32
+
33
+ ## 0.11.0 (2020-12-24) 🎄
34
+
35
+ - Extended proposed shorthand Hash syntax to support kwargs as well. ([@palkan][])
36
+
37
+ You can try it: `x = 1; y = 2; foo(x:, y:) # => foo(x: x, y: y)`.
38
+
39
+ - **Rewrite mode is used by default in transpiler**. ([@palkan][])
40
+
41
+ - Move 3.0 features to stable features. ([@palkan][])
42
+
43
+ - Refactor `a => x` and `a in x` to comply with Ruby 3.0. ([@palkan][])
44
+
5
45
  ## 0.10.5 (2020-10-13)
6
46
 
7
47
  - Fix Unparser 0.5.0 compatibility. ([@palkan][])
@@ -247,3 +287,4 @@ p a #=> 1
247
287
  [@palkan]: https://github.com/palkan
248
288
  [backports]: https://github.com/marcandre/backports
249
289
  [@sl4vr]: https://github.com/sl4vr
290
+ [@skryukov]: https://github.com/skryukov
data/README.md CHANGED
@@ -21,8 +21,20 @@ That's why Ruby Next implements the `master` features as fast as possible.
21
21
 
22
22
  Read more about the motivation behind the Ruby Next in this post: [Ruby Next: Make all Rubies quack alike](https://evilmartians.com/chronicles/ruby-next-make-all-rubies-quack-alike).
23
23
 
24
- <a href="https://evilmartians.com/?utm_source=ruby-next">
25
- <img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg" alt="Sponsored by Evil Martians" width="236" height="54"></a>
24
+ <table style="border:none;">
25
+ <tr>
26
+ <td>
27
+ <a href="https://evilmartians.com/?utm_source=ruby-next">
28
+ <img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg" alt="Sponsored by Evil Martians" width="236" height="54">
29
+ </a>
30
+ </td>
31
+ <td>
32
+ <a href="http://www.digitalfukuoka.jp/topics/169">
33
+ <img src="http://www.digitalfukuoka.jp/javascripts/kcfinder/upload/images/excellence.jpg" width="200">
34
+ </a>
35
+ </td>
36
+ </tr>
37
+ </table>
26
38
 
27
39
  ## Posts
28
40
 
@@ -93,8 +105,7 @@ def greet(val) =
93
105
  '👽'
94
106
  end
95
107
 
96
- greet(hello: 'martian') => greeting
97
- puts greeting
108
+ puts greet(hello: 'martian')
98
109
  "
99
110
 
100
111
  => 👽
@@ -179,17 +190,17 @@ In the AST mode, we parse the source code into AST, modifies this AST and **gene
179
190
 
180
191
  In the rewrite mode, we apply changes to the source code itself, thus, keeping the original formatting of the unaffected code (in a similar way to RuboCop's autocorrect feature).
181
192
 
182
- By default, we use the AST mode. That could likely change in the future when we collect enough feedback on the rewrite mode and fix potential bugs.
183
-
184
193
  The main benefit of the rewrite mode is that it preserves the original code line numbers and layout, which is especially useful in debugging.
185
194
 
195
+ By default, we use the rewrite mode. If you found a bug with rewrite mode which is not reproducible in the AST mode, please, let us know.
196
+
186
197
  You can change the transpiler mode:
187
198
 
188
199
  - From code by setting `RubyNext::Language.mode = :ast` or `RubyNext::Language.mode = :rewrite`.
189
- - Via environmental variable `RUBY_NEXT_TRANSPILE_MODE=rewrite`.
200
+ - Via environmental variable `RUBY_NEXT_TRANSPILE_MODE=ast`.
190
201
  - Via CLI option ([see below](#cli)).
191
202
 
192
- **NOTE:** For the time being, Unparser [doesn't support](https://github.com/mbj/unparser/pull/142) new Ruby 2.7 AST nodes, so we always use rewrite mode in Ruby 2.7+.
203
+ **NOTE:** For the time being, Unparser doesn't support Ruby 3.0 AST nodes, so we always use rewrite mode in Ruby 3.0+.
193
204
 
194
205
  ## CLI
195
206
 
@@ -220,7 +231,7 @@ Usage: ruby-next nextify DIRECTORY_OR_FILE [options]
220
231
 
221
232
  The behaviour depends on whether you transpile a single file or a directory:
222
233
 
223
- - When transpiling a directory, the `.rbnext` subfolder is created within the target folder with subfolders for each supported Ruby versions (e.g., `.rbnext/2.6`, `.rbnext/2.7`). If you want to create only a single version (the smallest), you can also pass `--single-version` flag. In that case, no version directory is created (i.e., transpiled files go into `.rbnext`).
234
+ - When transpiling a directory, the `.rbnext` subfolder is created within the target folder with subfolders for each supported Ruby versions (e.g., `.rbnext/2.6`, `.rbnext/2.7`, `.rbnext/3.0`). If you want to create only a single version (the smallest), you can also pass `--single-version` flag. In that case, no version directory is created (i.e., transpiled files go into `.rbnext`).
224
235
 
225
236
  - When transpiling a file and providing the output path as a _file_ path, only a single version is created. For example:
226
237
 
@@ -323,7 +334,7 @@ due to the way feature resolving works in Ruby (scanning the `$LOAD_PATH` and ha
323
334
 
324
335
  If you're using [runtime mode](#runtime-usage) a long with `setup_gem_load_path` (e.g., in tests), the transpiled files are ignored (i.e., we do not modify `$LOAD_PATH`).
325
336
 
326
- \* Ruby Next avoids storing duplicates; instead, only the code for the earlier version is created and is assumed to be used with other versions. For example, if the transpiled code is the same for Ruby 2.5 and Ruby 2.6, only the `.rbnext/2.7/path/to/file.rb` is kept. That's why multiple entries are added to the `$LOAD_PATH` (`.rbnext/2.6` and `.rbnext/2.7` in the specified order for Ruby 2.5 and only `.rbnext/2.7` for Ruby 2.6).
337
+ \* Ruby Next avoids storing duplicates; instead, only the code for the earlier version is created and is assumed to be used with other versions. For example, if the transpiled code is the same for Ruby 2.5 and Ruby 2.6, only the `.rbnext/2.7/path/to/file.rb` is kept. That's why multiple entries are added to the `$LOAD_PATH` (`.rbnext/2.6`, `.rbnext/2.7`, and `.rbnext/3.0` in the specified order for Ruby 2.5, and `.rbnext/2.7` and `.rbnext/3.0` for Ruby 2.6).
327
338
 
328
339
  ### Transpiled files vs. VCS vs. installing from source
329
340
 
@@ -446,6 +457,8 @@ AllCops:
446
457
 
447
458
  We currently provide support for Ruby 2.2, 2.3 and 2.4.
448
459
 
460
+ **NOTE:** By "support" here we mean using `ruby-next` CLI and runtime transpiling. Transpiled code may run on Ruby 2.0+.
461
+
449
462
  Ruby Next itself relies on 2.5 features and contains polyfills only for version 2.5+ (and that won't change).
450
463
  Thus, to make it work with <2.5 we need to backport some APIs ourselves.
451
464
 
@@ -509,17 +522,13 @@ require "ruby-next/language/runtime"
509
522
 
510
523
  ### Supported edge features
511
524
 
512
- - "Endless" method definition (`def foo() = 42`) ([#16746](https://bugs.ruby-lang.org/issues/16746)).
513
-
514
- - Right-hand assignment (`13.divmod(5) => a,b`) ([#15921](https://bugs.ruby-lang.org/issues/15921)).
515
-
516
- - Find pattern (`[0, 1, 2] in [*, 1 => a, *c]`) ([#16828](https://bugs.ruby-lang.org/issues/16828)).
525
+ `Array#intersect?` ([#15198](https://bugs.ruby-lang.org/issues/15198))
517
526
 
518
527
  ### Supported proposed features
519
528
 
520
529
  - _Method reference_ operator (`.:`) ([#13581](https://bugs.ruby-lang.org/issues/13581)).
521
530
 
522
- - Shorthand Hash notation (`data = {x, y}`) ([#15236](https://bugs.ruby-lang.org/issues/15236)).
531
+ - Shorthand Hash/kwarg notation (`data = {x, y}` or `foo(x:, y:)`) ([#15236](https://bugs.ruby-lang.org/issues/15236)).
523
532
 
524
533
  ## Contributing
525
534
 
@@ -0,0 +1,206 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+
5
+ require "ruby-next/config"
6
+ require "ruby-next/utils"
7
+
8
+ module RubyNext
9
+ module Core
10
+ # Patch contains the extension implementation
11
+ # and meta information (e.g., Ruby version).
12
+ class Patch
13
+ attr_reader :refineables, :name, :mod, :method_name, :version, :body, :singleton, :core_ext, :supported, :native, :location
14
+
15
+ # Create a new patch for module/class (mod)
16
+ # with the specified uniq name
17
+ #
18
+ # `core_ext` defines the strategy for core extensions:
19
+ # - :patch — extend class directly
20
+ # - :prepend — extend class by prepending a module (e.g., when needs `super`)
21
+ def initialize(mod = nil, method: ::Kernel.raise(::ArgumentError, "missing keyword: method"), version: ::Kernel.raise(::ArgumentError, "missing keyword: version"), name: nil, supported: nil, native: nil, location: nil, refineable: mod, core_ext: :patch, singleton: nil)
22
+ @mod = mod
23
+ @method_name = method
24
+ @version = version
25
+ if method_name && mod
26
+ @supported = supported.nil? ? mod.method_defined?(method_name) : supported
27
+ # define whether running Ruby has a native implementation for this method
28
+ # for that, we check the source_location (which is nil for C defined methods)
29
+ @native = native.nil? ? (supported? && native_location?(mod.instance_method(method_name).source_location)) : native
30
+ end
31
+ @singleton = singleton
32
+ @refineables = Array(refineable)
33
+ @body = yield
34
+ @core_ext = core_ext
35
+ @location = location || build_location(caller_locations(1, 5))
36
+ @name = name || build_module_name
37
+ end
38
+
39
+ def prepend?
40
+ core_ext == :prepend
41
+ end
42
+
43
+ def core_ext?
44
+ !mod.nil?
45
+ end
46
+
47
+ alias supported? supported
48
+ alias native? native
49
+ alias singleton? singleton
50
+
51
+ def to_module
52
+ Module.new.tap do |ext|
53
+ ext.module_eval(body, *location)
54
+
55
+ RubyNext::Core.const_set(name, ext)
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ def build_module_name
62
+ mod_name = singleton? ? singleton.name : mod.name
63
+ camelized_method_name = method_name.to_s.split("_").map(&:capitalize).join
64
+
65
+ "#{mod_name}#{camelized_method_name}".gsub(/\W/, "")
66
+ end
67
+
68
+ def build_location(trace_locations)
69
+ # The caller_locations behaviour depends on implementaion,
70
+ # e.g. in JRuby https://github.com/jruby/jruby/issues/6055
71
+ while trace_locations.first.label != "patch"
72
+ trace_locations.shift
73
+ end
74
+
75
+ trace_location = trace_locations[1]
76
+
77
+ [trace_location.absolute_path, trace_location.lineno + 2]
78
+ end
79
+
80
+ def native_location?(location)
81
+ location.nil? || location.first.match?(/(<internal:|resource:\/truffleruby\/core)/)
82
+ end
83
+ end
84
+
85
+ # Registry for patches
86
+ class Patches
87
+ attr_reader :extensions, :refined
88
+
89
+ def initialize
90
+ @names = Set.new
91
+ @extensions = Hash.new { |h, k| h[k] = [] }
92
+ @refined = Hash.new { |h, k| h[k] = [] }
93
+ end
94
+
95
+ # Register new patch
96
+ def <<(patch)
97
+ raise ArgumentError, "Patch already registered: #{patch.name}" if @names.include?(patch.name)
98
+ @names << patch.name
99
+ @extensions[patch.mod] << patch if patch.core_ext?
100
+ patch.refineables.each { |r| @refined[r] << patch } unless patch.native?
101
+ end
102
+ end
103
+
104
+ class << self
105
+ STRATEGIES = %i[refine core_ext backports].freeze
106
+
107
+ attr_reader :strategy
108
+
109
+ def strategy=(val)
110
+ raise ArgumentError, "Unknown strategy: #{val}. Available: #{STRATEGIES.join(",")}" unless STRATEGIES.include?(val)
111
+ @strategy = val
112
+ end
113
+
114
+ def refine?
115
+ strategy == :refine
116
+ end
117
+
118
+ def core_ext?
119
+ strategy == :core_ext || strategy == :backports
120
+ end
121
+
122
+ def backports?
123
+ strategy == :backports
124
+ end
125
+
126
+ def patch(*__rest__, &__block__)
127
+ patches << Patch.new(*__rest__, &__block__)
128
+ end
129
+
130
+ # Inject `using RubyNext` at the top of the source code
131
+ def inject!(contents)
132
+ if contents.frozen?
133
+ contents = contents.sub(/^(\s*[^#\s].*)/, 'using RubyNext;\1')
134
+ else
135
+ contents.sub!(/^(\s*[^#\s].*)/, 'using RubyNext;\1')
136
+ end
137
+ contents
138
+ end
139
+
140
+ def patches
141
+ @patches ||= Patches.new
142
+ end
143
+ end
144
+
145
+ # Use refinements by default
146
+ self.strategy = ENV.fetch("RUBY_NEXT_CORE_STRATEGY", "refine").to_sym
147
+ end
148
+ end
149
+
150
+ require "backports/2.5" if RubyNext::Core.backports?
151
+
152
+ require "ruby-next/core/kernel/then"
153
+
154
+ require "ruby-next/core/proc/compose"
155
+
156
+ require "ruby-next/core/enumerable/tally"
157
+ require "ruby-next/core/enumerable/filter"
158
+ require "ruby-next/core/enumerable/filter_map"
159
+
160
+ require "ruby-next/core/enumerator/produce"
161
+
162
+ require "ruby-next/core/array/difference_union_intersection"
163
+
164
+ require "ruby-next/core/hash/merge"
165
+
166
+ require "ruby-next/core/string/split"
167
+
168
+ require "ruby-next/core/symbol/start_with"
169
+ require "ruby-next/core/symbol/end_with"
170
+
171
+ require "ruby-next/core/unboundmethod/bind_call"
172
+
173
+ require "ruby-next/core/time/floor"
174
+ require "ruby-next/core/time/ceil"
175
+
176
+ # Core extensions required for pattern matching
177
+ # Required for pattern matching with refinements
178
+ unless defined?(NoMatchingPatternError)
179
+ class NoMatchingPatternError < StandardError
180
+ end
181
+ end
182
+
183
+ require "ruby-next/core/constants/no_matching_pattern_error"
184
+ require "ruby-next/core/constants/frozen_error"
185
+ require "ruby-next/core/array/deconstruct"
186
+ require "ruby-next/core/hash/deconstruct_keys"
187
+ require "ruby-next/core/struct/deconstruct"
188
+ require "ruby-next/core/struct/deconstruct_keys"
189
+
190
+ require "ruby-next/core/hash/except"
191
+
192
+ require "ruby-next/core/array/intersect"
193
+
194
+ # Generate refinements
195
+ RubyNext.module_eval do
196
+ RubyNext::Core.patches.refined.each do |mod, patches|
197
+ # Only refine modules when supported
198
+ next unless mod.is_a?(Class) || RubyNext::Utils.refine_modules?
199
+
200
+ refine mod do
201
+ patches.each do |patch|
202
+ module_eval(patch.body, *patch.location)
203
+ end
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,227 @@
1
+ # frozen_string_literal: true
2
+
3
+ gem "ruby-next-parser", ">= 2.8.0.3"
4
+ gem "unparser", ">= 0.4.7"
5
+
6
+ require "set"
7
+
8
+ require "ruby-next"
9
+
10
+ module RubyNext
11
+ # Language module contains tools to transpile newer Ruby syntax
12
+ # into an older one.
13
+ #
14
+ # It works the following way:
15
+ # - Takes a Ruby source code as input
16
+ # - Generates the AST using the edge parser (via the `parser` gem)
17
+ # - Pass this AST through the list of processors (one feature = one processor)
18
+ # - Each processor may modify the AST
19
+ # - Generates a transpiled source code from the transformed AST (via the `unparser` gem)
20
+ module Language
21
+ using RubyNext
22
+
23
+ require "ruby-next/language/parser"
24
+ require "ruby-next/language/unparser"
25
+
26
+ RewriterNotFoundError = Class.new(StandardError)
27
+
28
+ class TransformContext
29
+ attr_reader :versions, :use_ruby_next
30
+
31
+ def initialize
32
+ # Minimum supported RubyNext version
33
+ @min_version = MIN_SUPPORTED_VERSION
34
+ @dirty = false
35
+ @versions = Set.new
36
+ @use_ruby_next = false
37
+ end
38
+
39
+ # Called by rewriter when it performs transfomrations
40
+ def track!(rewriter)
41
+ @dirty = true
42
+ versions << rewriter.class::MIN_SUPPORTED_VERSION
43
+ end
44
+
45
+ def use_ruby_next!
46
+ @use_ruby_next = true
47
+ end
48
+
49
+ alias use_ruby_next? use_ruby_next
50
+
51
+ def dirty?
52
+ @dirty == true
53
+ end
54
+
55
+ def min_version
56
+ versions.min
57
+ end
58
+
59
+ def sorted_versions
60
+ versions.to_a.sort
61
+ end
62
+ end
63
+
64
+ class << self
65
+ attr_accessor :rewriters
66
+ attr_reader :watch_dirs
67
+
68
+ attr_accessor :strategy
69
+
70
+ MODES = %i[rewrite ast].freeze
71
+
72
+ attr_reader :mode
73
+
74
+ def mode=(val)
75
+ raise ArgumentError, "Unknown mode: #{val}. Available: #{MODES.join(",")}" unless MODES.include?(val)
76
+ @mode = val
77
+ end
78
+
79
+ def rewrite?
80
+ mode == :rewrite?
81
+ end
82
+
83
+ def ast?
84
+ mode == :ast
85
+ end
86
+
87
+ def runtime!
88
+ require "ruby-next/language/rewriters/runtime"
89
+
90
+ @runtime = true
91
+ end
92
+
93
+ def runtime?
94
+ @runtime
95
+ end
96
+
97
+ def transform(source, rewriters: self.rewriters, using: RubyNext::Core.refine?, context: TransformContext.new)
98
+ retried = 0
99
+ new_source = nil
100
+ begin
101
+ new_source =
102
+ if mode == :rewrite
103
+ rewrite(source, rewriters: rewriters, using: using, context: context)
104
+ else
105
+ regenerate(source, rewriters: rewriters, using: using, context: context)
106
+ end
107
+ rescue Unparser::UnknownNodeError => err
108
+ RubyNext.warn "Ruby Next fallbacks to \"rewrite\" transpiling mode since the version of Unparser you use doesn't support some syntax yet: #{err.message}.\n" \
109
+ "Try upgrading the Unparser or set transpiling mode to \"rewrite\" in case you use some edge or experimental syntax."
110
+ self.mode = :rewrite
111
+ retried += 1
112
+ retry unless retried > 1
113
+ raise
114
+ end
115
+
116
+ return new_source unless RubyNext::Core.refine?
117
+ return new_source unless using && context.use_ruby_next?
118
+
119
+ Core.inject! new_source.dup
120
+ end
121
+
122
+ def transformable?(path)
123
+ watch_dirs.any? { |dir| path.start_with?(dir) }
124
+ end
125
+
126
+ # Rewriters required for the current version
127
+ def current_rewriters
128
+ @current_rewriters ||= rewriters.select(&:unsupported_syntax?)
129
+ end
130
+
131
+ # This method guarantees that rewriters will be returned in order they defined in Language module
132
+ def select_rewriters(*names)
133
+ rewriters_delta = names - rewriters.map { |rewriter| rewriter::NAME }
134
+ if rewriters_delta.any?
135
+ raise RewriterNotFoundError, "Rewriters not found: #{rewriters_delta.join(",")}"
136
+ end
137
+
138
+ rewriters.select { |rewriter| names.include?(rewriter::NAME) }
139
+ end
140
+
141
+ private
142
+
143
+ def regenerate(source, rewriters: ::Kernel.raise(::ArgumentError, "missing keyword: rewriters"), using: ::Kernel.raise(::ArgumentError, "missing keyword: using"), context: ::Kernel.raise(::ArgumentError, "missing keyword: context"))
144
+ parse_with_comments(source).then do |(ast, comments)|
145
+ rewriters.inject(ast) do |tree, rewriter|
146
+ rewriter.new(context).process(tree)
147
+ end.then do |new_ast|
148
+ next source unless context.dirty?
149
+
150
+ Unparser.unparse(new_ast, comments)
151
+ end
152
+ end
153
+ end
154
+
155
+ def rewrite(source, rewriters: ::Kernel.raise(::ArgumentError, "missing keyword: rewriters"), using: ::Kernel.raise(::ArgumentError, "missing keyword: using"), context: ::Kernel.raise(::ArgumentError, "missing keyword: context"))
156
+ rewriters.inject(source) do |src, rewriter|
157
+ buffer = Parser::Source::Buffer.new("<dynamic>")
158
+ buffer.source = src
159
+
160
+ rewriter.new(context).rewrite(buffer, parse(src))
161
+ end.then do |new_source|
162
+ next source unless context.dirty?
163
+
164
+ new_source
165
+ end
166
+ end
167
+
168
+ attr_writer :watch_dirs
169
+ end
170
+
171
+ self.rewriters = []
172
+ self.watch_dirs = %w[app lib spec test].map { |path| File.join(Dir.pwd, path) }
173
+ self.mode = ENV.fetch("RUBY_NEXT_TRANSPILE_MODE", "rewrite").to_sym
174
+
175
+ require "ruby-next/language/rewriters/base"
176
+
177
+ require "ruby-next/language/rewriters/squiggly_heredoc"
178
+ rewriters << Rewriters::SquigglyHeredoc
179
+
180
+ require "ruby-next/language/rewriters/safe_navigation"
181
+ rewriters << Rewriters::SafeNavigation
182
+
183
+ require "ruby-next/language/rewriters/numeric_literals"
184
+ rewriters << Rewriters::NumericLiterals
185
+
186
+ require "ruby-next/language/rewriters/required_kwargs"
187
+ rewriters << Rewriters::RequiredKwargs
188
+
189
+ require "ruby-next/language/rewriters/args_forward"
190
+ rewriters << Rewriters::ArgsForward
191
+
192
+ # Must be added after general args forward rewriter to become
193
+ # no-op in Ruby <2.7
194
+ require "ruby-next/language/rewriters/args_forward_leading"
195
+ rewriters << Rewriters::ArgsForwardLeading
196
+
197
+ require "ruby-next/language/rewriters/numbered_params"
198
+ rewriters << Rewriters::NumberedParams
199
+
200
+ require "ruby-next/language/rewriters/pattern_matching"
201
+ rewriters << Rewriters::PatternMatching
202
+
203
+ # Must be added after general pattern matching rewriter to become
204
+ # no-op in Ruby <2.7
205
+ require "ruby-next/language/rewriters/find_pattern"
206
+ rewriters << Rewriters::FindPattern
207
+
208
+ require "ruby-next/language/rewriters/in_pattern"
209
+ rewriters << Rewriters::InPattern
210
+
211
+ # Put endless range in the end, 'cause Parser fails to parse it in
212
+ # pattern matching
213
+ require "ruby-next/language/rewriters/endless_range"
214
+ rewriters << Rewriters::EndlessRange
215
+
216
+ require "ruby-next/language/rewriters/endless_method"
217
+ RubyNext::Language.rewriters << RubyNext::Language::Rewriters::EndlessMethod
218
+
219
+ if ENV["RUBY_NEXT_EDGE"] == "1"
220
+ require "ruby-next/language/edge"
221
+ end
222
+
223
+ if ENV["RUBY_NEXT_PROPOSED"] == "1"
224
+ require "ruby-next/language/proposed"
225
+ end
226
+ end
227
+ end
@@ -57,7 +57,7 @@ module RubyNext
57
57
 
58
58
  opts.on(
59
59
  "--transpile-mode=MODE",
60
- "Transpiler mode (ast or rewrite). Default: ast"
60
+ "Transpiler mode (ast or rewrite). Default: rewrite"
61
61
  ) do |val|
62
62
  Language.mode = val.to_sym
63
63
  end
@@ -95,7 +95,7 @@ module RubyNext
95
95
  exit 0
96
96
  end
97
97
 
98
- unless ((!lib_path.nil? || nil) && lib_path.then(&File.method(:exist?)))
98
+ unless ((((__safe_lvar__ = lib_path) || true) && (!__safe_lvar__.nil? || nil)) && __safe_lvar__.then(&File.method(:exist?)))
99
99
  $stdout.puts "Path not found: #{lib_path}"
100
100
  $stdout.puts optparser.help
101
101
  exit 2
@@ -8,7 +8,7 @@ module RubyNext
8
8
  def eval(source, bind = nil, *args)
9
9
  new_source = ::RubyNext::Language::Runtime.transform(
10
10
  source,
11
- using: ((!bind.nil? || nil) && bind.receiver) == TOPLEVEL_BINDING.receiver || ((!((!bind.nil? || nil) && bind.receiver).nil? || nil) && ((!bind.nil? || nil) && bind.receiver).is_a?(Module))
11
+ using: ((((__safe_lvar__ = bind) || true) && (!__safe_lvar__.nil? || nil)) && __safe_lvar__.receiver) == TOPLEVEL_BINDING.receiver || ((((__safe_lvar__ = ((((__safe_lvar__ = bind) || true) && (!__safe_lvar__.nil? || nil)) && __safe_lvar__.receiver)) || true) && (!__safe_lvar__.nil? || nil)) && __safe_lvar__.is_a?(Module))
12
12
  )
13
13
  RubyNext.debug_source(new_source, "(#{caller_locations(1, 1).first})")
14
14
  super new_source, bind, *args
@@ -20,7 +20,7 @@ module RubyNext
20
20
  module InstanceEval # :nodoc:
21
21
  refine Object do
22
22
  def instance_eval(*args, &block)
23
- return super(*args, &block) if block_given?
23
+ return super(*args, &block) if block
24
24
 
25
25
  source = args.shift
26
26
  new_source = ::RubyNext::Language::Runtime.transform(source, using: false)
@@ -33,7 +33,7 @@ module RubyNext
33
33
  module ClassEval
34
34
  refine Module do
35
35
  def module_eval(*args, &block)
36
- return super(*args, &block) if block_given?
36
+ return super(*args, &block) if block
37
37
 
38
38
  source = args.shift
39
39
  new_source = ::RubyNext::Language::Runtime.transform(source, using: false)
@@ -42,7 +42,7 @@ module RubyNext
42
42
  end
43
43
 
44
44
  def class_eval(*args, &block)
45
- return super(*args, &block) if block_given?
45
+ return super(*args, &block) if block
46
46
 
47
47
  source = args.shift
48
48
  new_source = ::RubyNext::Language::Runtime.transform(source, using: false)