ruby-next-core 0.9.1 → 0.10.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +42 -0
  3. data/README.md +20 -6
  4. data/lib/.rbnext/2.3/ruby-next/commands/core_ext.rb +167 -0
  5. data/lib/.rbnext/2.3/ruby-next/commands/nextify.rb +201 -0
  6. data/lib/.rbnext/2.3/ruby-next/language/eval.rb +66 -0
  7. data/lib/.rbnext/2.3/ruby-next/language/rewriters/base.rb +121 -0
  8. data/lib/.rbnext/2.3/ruby-next/language/rewriters/endless_range.rb +63 -0
  9. data/lib/.rbnext/2.3/ruby-next/language/rewriters/pattern_matching.rb +944 -0
  10. data/lib/.rbnext/2.3/ruby-next/language/rewriters/right_hand_assignment.rb +107 -0
  11. data/lib/.rbnext/2.3/ruby-next/utils.rb +65 -0
  12. data/lib/ruby-next.rb +8 -6
  13. data/lib/ruby-next/cli.rb +2 -2
  14. data/lib/ruby-next/commands/core_ext.rb +1 -1
  15. data/lib/ruby-next/commands/nextify.rb +44 -10
  16. data/lib/ruby-next/core.rb +27 -21
  17. data/lib/ruby-next/core/array/deconstruct.rb +9 -9
  18. data/lib/ruby-next/core/array/difference_union_intersection.rb +12 -12
  19. data/lib/ruby-next/core/constants/no_matching_pattern_error.rb +3 -3
  20. data/lib/ruby-next/core/enumerable/filter.rb +8 -8
  21. data/lib/ruby-next/core/enumerable/filter_map.rb +25 -25
  22. data/lib/ruby-next/core/enumerable/tally.rb +7 -7
  23. data/lib/ruby-next/core/enumerator/produce.rb +12 -12
  24. data/lib/ruby-next/core/hash/deconstruct_keys.rb +9 -9
  25. data/lib/ruby-next/core/hash/except.rb +11 -0
  26. data/lib/ruby-next/core/hash/merge.rb +8 -8
  27. data/lib/ruby-next/core/kernel/then.rb +2 -2
  28. data/lib/ruby-next/core/proc/compose.rb +11 -11
  29. data/lib/ruby-next/core/string/split.rb +6 -6
  30. data/lib/ruby-next/core/struct/deconstruct.rb +2 -2
  31. data/lib/ruby-next/core/struct/deconstruct_keys.rb +17 -17
  32. data/lib/ruby-next/core/symbol/end_with.rb +4 -4
  33. data/lib/ruby-next/core/symbol/start_with.rb +4 -4
  34. data/lib/ruby-next/core/time/ceil.rb +6 -6
  35. data/lib/ruby-next/core/time/floor.rb +4 -4
  36. data/lib/ruby-next/core/unboundmethod/bind_call.rb +4 -4
  37. data/lib/ruby-next/core_ext.rb +1 -1
  38. data/lib/ruby-next/language.rb +30 -6
  39. data/lib/ruby-next/language/proposed.rb +3 -0
  40. data/lib/ruby-next/language/rewriters/args_forward.rb +24 -20
  41. data/lib/ruby-next/language/rewriters/base.rb +1 -1
  42. data/lib/ruby-next/language/rewriters/endless_method.rb +26 -3
  43. data/lib/ruby-next/language/rewriters/endless_range.rb +1 -0
  44. data/lib/ruby-next/language/rewriters/find_pattern.rb +44 -0
  45. data/lib/ruby-next/language/rewriters/method_reference.rb +2 -1
  46. data/lib/ruby-next/language/rewriters/numbered_params.rb +1 -0
  47. data/lib/ruby-next/language/rewriters/pattern_matching.rb +103 -12
  48. data/lib/ruby-next/language/rewriters/right_hand_assignment.rb +74 -11
  49. data/lib/ruby-next/language/rewriters/safe_navigation.rb +87 -0
  50. data/lib/ruby-next/language/rewriters/shorthand_hash.rb +47 -0
  51. data/lib/ruby-next/language/rewriters/squiggly_heredoc.rb +36 -0
  52. data/lib/ruby-next/logging.rb +1 -1
  53. data/lib/ruby-next/rubocop.rb +91 -9
  54. data/lib/ruby-next/setup_self.rb +22 -0
  55. data/lib/ruby-next/version.rb +1 -1
  56. data/lib/uby-next.rb +8 -4
  57. metadata +21 -7
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyNext
4
+ module Language
5
+ module Rewriters
6
+ class RightHandAssignment < Base
7
+ NAME = "right-hand-assignment"
8
+ SYNTAX_PROBE = "1 + 2 => a"
9
+ MIN_SUPPORTED_VERSION = Gem::Version.new("3.0.0")
10
+
11
+ def on_rasgn(node)
12
+ context.track! self
13
+
14
+ node = super(node)
15
+
16
+ val_node, asgn_node = *node
17
+
18
+ remove(val_node.loc.expression.end.join(asgn_node.loc.expression))
19
+ insert_before(val_node.loc.expression, "#{asgn_node.loc.expression.source} = ")
20
+
21
+ asgn_node.updated(
22
+ nil,
23
+ asgn_node.children + [val_node]
24
+ )
25
+ end
26
+
27
+ def on_vasgn(node)
28
+ return super(node) unless rightward?(node)
29
+
30
+ context.track! self
31
+
32
+ name, val_node = *node
33
+
34
+ remove(val_node.loc.expression.end.join(node.loc.name))
35
+ insert_before(val_node.loc.expression, "#{name} = ")
36
+
37
+ super(node)
38
+ end
39
+
40
+ def on_casgn(node)
41
+ return super(node) unless rightward?(node)
42
+
43
+ context.track! self
44
+
45
+ scope_node, name, val_node = *node
46
+
47
+ if scope_node
48
+ scope = scope_node.type == :cbase ? scope_node.loc.expression.source : "#{scope_node.loc.expression.source}::"
49
+ name = "#{scope}#{name}"
50
+ end
51
+
52
+ remove(val_node.loc.expression.end.join(node.loc.name))
53
+ insert_before(val_node.loc.expression, "#{name} = ")
54
+
55
+ super(node)
56
+ end
57
+
58
+ def on_mrasgn(node)
59
+ context.track! self
60
+
61
+ node = super(node)
62
+
63
+ lhs, rhs = *node
64
+
65
+ replace(lhs.loc.expression.end.join(rhs.loc.expression), ")")
66
+ insert_before(lhs.loc.expression, "#{rhs.loc.expression.source} = (")
67
+
68
+ node.updated(
69
+ :masgn,
70
+ [rhs, lhs]
71
+ )
72
+ end
73
+
74
+ def on_masgn(node)
75
+ return super(node) unless rightward?(node)
76
+
77
+ context.track! self
78
+
79
+ rhs, lhs = *node
80
+
81
+ replace(lhs.loc.expression.end.join(rhs.loc.expression), ")")
82
+ insert_before(lhs.loc.expression, "#{rhs.loc.expression.source} = (")
83
+
84
+ super(node)
85
+ end
86
+
87
+ private
88
+
89
+ def rightward?(node)
90
+ # Location could be empty for node built by rewriters
91
+ return false unless ((!node.loc.nil?) || nil) && node.loc.operator
92
+
93
+ assignee_loc =
94
+ if node.type == :masgn
95
+ node.children[0].loc.expression
96
+ else
97
+ node.loc.name
98
+ end
99
+
100
+ return false unless assignee_loc
101
+
102
+ assignee_loc.begin_pos > node.loc.operator.end_pos
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyNext
4
+ module Utils
5
+ module_function
6
+
7
+ if $LOAD_PATH.respond_to?(:resolve_feature_path)
8
+ def resolve_feature_path(feature)
9
+ ((!$LOAD_PATH.resolve_feature_path(feature).nil?) || nil) && $LOAD_PATH.resolve_feature_path(feature).last
10
+ rescue LoadError
11
+ end
12
+ else
13
+ def resolve_feature_path(path)
14
+ if File.file?(relative = File.expand_path(path))
15
+ path = relative
16
+ end
17
+
18
+ path = "#{path}.rb" if File.extname(path).empty?
19
+
20
+ return path if Pathname.new(path).absolute?
21
+
22
+ $LOAD_PATH.find do |lp|
23
+ lpath = File.join(lp, path)
24
+ return File.realpath(lpath) if File.file?(lpath)
25
+ end
26
+ end
27
+ end
28
+
29
+ def source_with_lines(source, path)
30
+ source.lines.map.with_index do |line, i|
31
+ "#{(i + 1).to_s.rjust(4)}: #{line}"
32
+ end.tap do |lines|
33
+ lines.unshift " 0: # source: #{path}"
34
+ end
35
+ end
36
+
37
+ # Returns true if modules refinement is supported in current version
38
+ def refine_modules?
39
+ @refine_modules ||=
40
+ begin
41
+ # Make sure that including modules within refinements works
42
+ # See https://github.com/oracle/truffleruby/issues/2026
43
+ eval <<-RUBY, TOPLEVEL_BINDING, __FILE__, __LINE__ + 1
44
+ module RubyNext::Utils::A; end
45
+ class RubyNext::Utils::B
46
+ include RubyNext::Utils::A
47
+ end
48
+ using(Module.new do
49
+ refine RubyNext::Utils::A do
50
+ include(Module.new do
51
+ def i_am_refinement
52
+ "yes, you are!"
53
+ end
54
+ end)
55
+ end
56
+ end)
57
+ RubyNext::Utils::B.new.i_am_refinement
58
+ RUBY
59
+ true
60
+ rescue TypeError, NoMethodError
61
+ false
62
+ end
63
+ end
64
+ end
65
+ end
@@ -4,17 +4,18 @@ require "ruby-next/version"
4
4
 
5
5
  module RubyNext
6
6
  # Mininum Ruby version supported by RubyNext
7
- MIN_SUPPORTED_VERSION = Gem::Version.new("2.5.0")
7
+ MIN_SUPPORTED_VERSION = Gem::Version.new("2.2.0")
8
8
 
9
9
  # Where to store transpiled files (relative from the project LOAD_PATH, usually `lib/`)
10
10
  RUBY_NEXT_DIR = ".rbnext"
11
11
 
12
12
  # Defines last minor version for every major version
13
13
  LAST_MINOR_VERSIONS = {
14
- 2 => 8
14
+ 2 => 8, # 2.8 is required for backward compatibility: some gems already uses it
15
+ 3 => 0
15
16
  }.freeze
16
17
 
17
- LATEST_VERSION = [2, 8].freeze
18
+ LATEST_VERSION = [3, 0].freeze
18
19
 
19
20
  class << self
20
21
  def next_version(version = RUBY_VERSION)
@@ -33,7 +34,8 @@ module RubyNext
33
34
  end
34
35
  end
35
36
 
36
- require_relative "ruby-next/core"
37
- require_relative "ruby-next/core_ext" if RubyNext::Core.core_ext?
38
- require_relative "ruby-next/logging"
37
+ require "ruby-next/setup_self"
38
+ require "ruby-next/core"
39
+ require "ruby-next/core_ext" if RubyNext::Core.core_ext?
40
+ require "ruby-next/logging"
39
41
  end
@@ -61,7 +61,7 @@ module RubyNext
61
61
  def maybe_print_help
62
62
  return unless @print_help
63
63
 
64
- STDOUT.puts optparser.help
64
+ $stdout.puts optparser.help
65
65
  exit 0
66
66
  end
67
67
 
@@ -85,7 +85,7 @@ module RubyNext
85
85
  opts.banner = "Usage: ruby-next COMMAND [options]"
86
86
 
87
87
  opts.on("-v", "--version", "Print version") do
88
- STDOUT.puts RubyNext::VERSION
88
+ $stdout.puts RubyNext::VERSION
89
89
  exit 0
90
90
  end
91
91
 
@@ -160,7 +160,7 @@ module RubyNext
160
160
  # remove empty lines
161
161
  new_src.gsub!(/^\s+$/, "")
162
162
  # remove traling blank lines
163
- new_src.gsub!(/\n\z/, "")
163
+ new_src.delete_suffix!("\n")
164
164
  new_src
165
165
  end
166
166
  end
@@ -10,7 +10,7 @@ module RubyNext
10
10
  class Nextify < Base
11
11
  using RubyNext
12
12
 
13
- attr_reader :lib_path, :paths, :out_path, :min_version, :single_version
13
+ attr_reader :lib_path, :paths, :out_path, :min_version, :single_version, :specified_rewriters
14
14
 
15
15
  def run
16
16
  log "RubyNext core strategy: #{RubyNext::Core.strategy}"
@@ -18,6 +18,8 @@ module RubyNext
18
18
 
19
19
  remove_rbnext!
20
20
 
21
+ @min_version ||= MIN_SUPPORTED_VERSION
22
+
21
23
  paths.each do |path|
22
24
  contents = File.read(path)
23
25
  transpile path, contents
@@ -26,7 +28,8 @@ module RubyNext
26
28
 
27
29
  def parse!(args)
28
30
  print_help = false
29
- @min_version = MIN_SUPPORTED_VERSION
31
+ print_rewriters = false
32
+ rewriter_names = []
30
33
  @single_version = false
31
34
 
32
35
  optparser = base_parser do |opts|
@@ -63,6 +66,14 @@ module RubyNext
63
66
  Core.strategy = :core_ext unless val
64
67
  end
65
68
 
69
+ opts.on("--list-rewriters", "List available rewriters") do |val|
70
+ print_rewriters = true
71
+ end
72
+
73
+ opts.on("--rewrite=REWRITERS...", "Specify particular Ruby features to rewrite") do |val|
74
+ rewriter_names << val
75
+ end
76
+
66
77
  opts.on("-h", "--help", "Print help") do
67
78
  print_help = true
68
79
  end
@@ -77,12 +88,35 @@ module RubyNext
77
88
  exit 0
78
89
  end
79
90
 
91
+ if print_rewriters
92
+ Language.rewriters.each do |rewriter|
93
+ $stdout.puts "#{rewriter::NAME} (\"#{rewriter::SYNTAX_PROBE}\")"
94
+ end
95
+ exit 0
96
+ end
97
+
80
98
  unless lib_path&.then(&File.method(:exist?))
81
99
  $stdout.puts "Path not found: #{lib_path}"
82
100
  $stdout.puts optparser.help
83
101
  exit 2
84
102
  end
85
103
 
104
+ if rewriter_names.any? && min_version
105
+ $stdout.puts "--rewrite cannot be used with --min-version simultaneously"
106
+ exit 2
107
+ end
108
+
109
+ @specified_rewriters =
110
+ if rewriter_names.any?
111
+ begin
112
+ Language.select_rewriters(*rewriter_names)
113
+ rescue Language::RewriterNotFoundError => error
114
+ $stdout.puts error.message
115
+ $stdout.puts "Try --list-rewriters to see list of available rewriters"
116
+ exit 2
117
+ end
118
+ end
119
+
86
120
  @paths =
87
121
  if File.directory?(lib_path)
88
122
  Dir[File.join(lib_path, "**/*.rb")]
@@ -96,16 +130,11 @@ module RubyNext
96
130
  private
97
131
 
98
132
  def transpile(path, contents, version: min_version)
99
- rewriters = Language.rewriters.select { |rw| rw.unsupported_version?(version) }
133
+ rewriters = specified_rewriters || Language.rewriters.select { |rw| rw.unsupported_version?(version) }
100
134
 
101
135
  context = Language::TransformContext.new
102
136
 
103
- new_contents =
104
- if Gem::Version.new(version) >= Gem::Version.new("2.7.0") && !defined?(Unparser::Emitter::CaseMatch)
105
- Language.rewrite contents, context: context, rewriters: rewriters
106
- else
107
- Language.transform contents, context: context, rewriters: rewriters
108
- end
137
+ new_contents = Language.transform contents, context: context, rewriters: rewriters
109
138
 
110
139
  return unless context.dirty?
111
140
 
@@ -119,6 +148,9 @@ module RubyNext
119
148
 
120
149
  # Then, generate the source code for the next version
121
150
  transpile path, contents, version: version
151
+ rescue SyntaxError, StandardError => e
152
+ warn "Failed to transpile #{path}: #{e.class} — #{e.message}"
153
+ exit 1
122
154
  end
123
155
 
124
156
  def save(contents, path, version)
@@ -161,7 +193,9 @@ module RubyNext
161
193
  out_path == "stdout"
162
194
  end
163
195
 
164
- alias single_version? single_version
196
+ def single_version?
197
+ single_version || specified_rewriters
198
+ end
165
199
  end
166
200
  end
167
201
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "set"
4
4
 
5
- require_relative "utils"
5
+ require "ruby-next/utils"
6
6
 
7
7
  module RubyNext
8
8
  module Core
@@ -25,7 +25,7 @@ module RubyNext
25
25
  @supported = supported.nil? ? mod.method_defined?(method_name) : supported
26
26
  # define whether running Ruby has a native implementation for this method
27
27
  # for that, we check the source_location (which is nil for C defined methods)
28
- @native = native.nil? ? (supported? && mod.instance_method(method_name).source_location.nil?) : native
28
+ @native = native.nil? ? (supported? && native_location?(mod.instance_method(method_name).source_location)) : native
29
29
  end
30
30
  @singleton = singleton
31
31
  @refineables = Array(refineable)
@@ -75,6 +75,10 @@ module RubyNext
75
75
 
76
76
  [trace_location.absolute_path, trace_location.lineno + 2]
77
77
  end
78
+
79
+ def native_location?(location)
80
+ location.nil? || location.first.match?(/(<internal:|resource:\/truffleruby\/core)/)
81
+ end
78
82
  end
79
83
 
80
84
  # Registry for patches
@@ -144,29 +148,29 @@ end
144
148
 
145
149
  require "backports/2.5" if RubyNext::Core.backports?
146
150
 
147
- require_relative "core/kernel/then"
151
+ require "ruby-next/core/kernel/then"
148
152
 
149
- require_relative "core/proc/compose"
153
+ require "ruby-next/core/proc/compose"
150
154
 
151
- require_relative "core/enumerable/tally"
152
- require_relative "core/enumerable/filter"
153
- require_relative "core/enumerable/filter_map"
155
+ require "ruby-next/core/enumerable/tally"
156
+ require "ruby-next/core/enumerable/filter"
157
+ require "ruby-next/core/enumerable/filter_map"
154
158
 
155
- require_relative "core/enumerator/produce"
159
+ require "ruby-next/core/enumerator/produce"
156
160
 
157
- require_relative "core/array/difference_union_intersection"
161
+ require "ruby-next/core/array/difference_union_intersection"
158
162
 
159
- require_relative "core/hash/merge"
163
+ require "ruby-next/core/hash/merge"
160
164
 
161
- require_relative "core/string/split"
165
+ require "ruby-next/core/string/split"
162
166
 
163
- require_relative "core/symbol/start_with"
164
- require_relative "core/symbol/end_with"
167
+ require "ruby-next/core/symbol/start_with"
168
+ require "ruby-next/core/symbol/end_with"
165
169
 
166
- require_relative "core/unboundmethod/bind_call"
170
+ require "ruby-next/core/unboundmethod/bind_call"
167
171
 
168
- require_relative "core/time/floor"
169
- require_relative "core/time/ceil"
172
+ require "ruby-next/core/time/floor"
173
+ require "ruby-next/core/time/ceil"
170
174
 
171
175
  # Core extensions required for pattern matching
172
176
  # Required for pattern matching with refinements
@@ -175,11 +179,13 @@ unless defined?(NoMatchingPatternError)
175
179
  end
176
180
  end
177
181
 
178
- require_relative "core/constants/no_matching_pattern_error"
179
- require_relative "core/array/deconstruct"
180
- require_relative "core/hash/deconstruct_keys"
181
- require_relative "core/struct/deconstruct"
182
- require_relative "core/struct/deconstruct_keys"
182
+ require "ruby-next/core/constants/no_matching_pattern_error"
183
+ require "ruby-next/core/array/deconstruct"
184
+ require "ruby-next/core/hash/deconstruct_keys"
185
+ require "ruby-next/core/struct/deconstruct"
186
+ require "ruby-next/core/struct/deconstruct_keys"
187
+
188
+ require "ruby-next/core/hash/except"
183
189
 
184
190
  # Generate refinements
185
191
  RubyNext.module_eval do
@@ -1,21 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  RubyNext::Core.patch Array, method: :deconstruct, version: "2.7" do
4
- <<~RUBY
5
- def deconstruct
6
- self
7
- end
4
+ <<-RUBY
5
+ def deconstruct
6
+ self
7
+ end
8
8
  RUBY
9
9
  end
10
10
 
11
11
  # We need to hack `respond_to?` in Ruby 2.5, since it's not working with refinements
12
12
  if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.6")
13
13
  RubyNext::Core.patch refineable: Array, name: "ArrayRespondToDeconstruct", method: :deconstruct, version: "2.7" do
14
- <<~RUBY
15
- def respond_to?(mid, *)
16
- return true if mid == :deconstruct
17
- super
18
- end
14
+ <<-RUBY
15
+ def respond_to?(mid, *)
16
+ return true if mid == :deconstruct
17
+ super
18
+ end
19
19
  RUBY
20
20
  end
21
21
  end