ruby-next-core 0.15.3 → 1.0.0.rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +28 -0
  3. data/README.md +118 -48
  4. data/bin/mspec +11 -0
  5. data/lib/.rbnext/2.1/ruby-next/commands/nextify.rb +295 -0
  6. data/lib/.rbnext/2.1/ruby-next/core.rb +10 -2
  7. data/lib/.rbnext/2.1/ruby-next/language.rb +54 -10
  8. data/lib/.rbnext/2.3/ruby-next/commands/nextify.rb +82 -2
  9. data/lib/.rbnext/2.3/ruby-next/config.rb +79 -0
  10. data/lib/.rbnext/2.3/ruby-next/core/data.rb +159 -0
  11. data/lib/.rbnext/2.3/ruby-next/language/rewriters/2.7/pattern_matching.rb +2 -2
  12. data/lib/.rbnext/2.3/ruby-next/language/rewriters/base.rb +6 -32
  13. data/lib/.rbnext/2.3/ruby-next/utils.rb +3 -22
  14. data/lib/.rbnext/2.6/ruby-next/core/data.rb +159 -0
  15. data/lib/.rbnext/2.7/ruby-next/core/data.rb +159 -0
  16. data/lib/.rbnext/2.7/ruby-next/core.rb +10 -2
  17. data/lib/.rbnext/2.7/ruby-next/language/paco_parsers/string_literals.rb +109 -0
  18. data/lib/.rbnext/2.7/ruby-next/language/rewriters/2.7/pattern_matching.rb +2 -2
  19. data/lib/.rbnext/2.7/ruby-next/language/rewriters/text.rb +132 -0
  20. data/lib/.rbnext/3.2/ruby-next/commands/base.rb +55 -0
  21. data/lib/.rbnext/3.2/ruby-next/language/rewriters/2.7/pattern_matching.rb +1095 -0
  22. data/lib/.rbnext/3.2/ruby-next/rubocop.rb +210 -0
  23. data/lib/ruby-next/commands/nextify.rb +84 -2
  24. data/lib/ruby-next/config.rb +27 -0
  25. data/lib/ruby-next/core/data.rb +159 -0
  26. data/lib/ruby-next/core/matchdata/deconstruct.rb +9 -0
  27. data/lib/ruby-next/core/matchdata/deconstruct_keys.rb +20 -0
  28. data/lib/ruby-next/core/matchdata/named_captures.rb +11 -0
  29. data/lib/ruby-next/core/refinement/import.rb +44 -36
  30. data/lib/ruby-next/core/time/deconstruct_keys.rb +30 -0
  31. data/lib/ruby-next/core.rb +10 -2
  32. data/lib/ruby-next/irb.rb +2 -2
  33. data/lib/ruby-next/language/bootsnap.rb +2 -25
  34. data/lib/ruby-next/language/paco_parser.rb +7 -0
  35. data/lib/ruby-next/language/paco_parsers/base.rb +47 -0
  36. data/lib/ruby-next/language/paco_parsers/comments.rb +26 -0
  37. data/lib/ruby-next/language/paco_parsers/string_literals.rb +109 -0
  38. data/lib/ruby-next/language/parser.rb +24 -2
  39. data/lib/ruby-next/language/rewriters/3.0/args_forward_leading.rb +2 -2
  40. data/lib/ruby-next/language/rewriters/3.1/oneline_pattern_parensless.rb +1 -1
  41. data/lib/ruby-next/language/rewriters/3.2/anonymous_restargs.rb +104 -0
  42. data/lib/ruby-next/language/rewriters/abstract.rb +57 -0
  43. data/lib/ruby-next/language/rewriters/base.rb +6 -32
  44. data/lib/ruby-next/language/rewriters/edge/it_param.rb +58 -0
  45. data/lib/ruby-next/language/rewriters/edge.rb +12 -0
  46. data/lib/ruby-next/language/rewriters/proposed/bind_vars_pattern.rb +3 -0
  47. data/lib/ruby-next/language/rewriters/proposed/method_reference.rb +9 -20
  48. data/lib/ruby-next/language/rewriters/text.rb +132 -0
  49. data/lib/ruby-next/language/runtime.rb +9 -86
  50. data/lib/ruby-next/language/setup.rb +5 -2
  51. data/lib/ruby-next/language/unparser.rb +5 -0
  52. data/lib/ruby-next/language.rb +54 -10
  53. data/lib/ruby-next/pry.rb +1 -1
  54. data/lib/ruby-next/rubocop.rb +2 -0
  55. data/lib/ruby-next/utils.rb +3 -22
  56. data/lib/ruby-next/version.rb +1 -1
  57. data/lib/uby-next.rb +2 -2
  58. metadata +65 -12
@@ -0,0 +1,210 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file contains patches to RuboCop to support
4
+ # edge features and fix some bugs with 2.7+ syntax
5
+
6
+ require "parser/ruby-next/version"
7
+ require "ruby-next/config"
8
+ require "ruby-next/language/parser"
9
+
10
+ module RuboCop
11
+ # Transform Ruby Next parser version to a float, e.g.: "2.8.0.1" => 2.801
12
+ RUBY_NEXT_VERSION = Parser::NEXT_VERSION.match(/(^\d+)\.(.+)$/)[1..-1].map { |part| part.delete(".") }.join(".").to_f
13
+
14
+ class TargetRuby
15
+ class RuboCopNextConfig < RuboCopConfig
16
+ private
17
+
18
+ def find_version
19
+ version = @config.for_all_cops["TargetRubyVersion"]
20
+ return unless version == "next"
21
+
22
+ RUBY_NEXT_VERSION
23
+ end
24
+ end
25
+
26
+ new_rubies = KNOWN_RUBIES + [RUBY_NEXT_VERSION]
27
+ remove_const :KNOWN_RUBIES
28
+ const_set :KNOWN_RUBIES, new_rubies
29
+
30
+ new_sources = [RuboCopNextConfig] + SOURCES
31
+ remove_const :SOURCES
32
+ const_set :SOURCES, new_sources
33
+ end
34
+ end
35
+
36
+ module RuboCop
37
+ class ProcessedSource
38
+ module ParserClassExt
39
+ def parser_class(version)
40
+ return super unless version == RUBY_NEXT_VERSION
41
+
42
+ require "parser/rubynext"
43
+ Parser::RubyNext
44
+ end
45
+ end
46
+
47
+ prepend ParserClassExt
48
+ end
49
+ end
50
+
51
+ # Let's make this file Ruby 2.2 compatible to avoid transpiling
52
+ # rubocop:disable Layout/HeredocIndentation
53
+ module RuboCop
54
+ module AST
55
+ module Traversal
56
+ # Fixed in https://github.com/rubocop-hq/rubocop/pull/7786
57
+ %i[case_match in_pattern find_pattern match_pattern match_pattern_p].each do |type|
58
+ next if method_defined?(:"on_#{type}")
59
+ module_eval(<<-RUBY, __FILE__, __LINE__ + 1)
60
+ def on_#{type}(node)
61
+ node.children.each { |child| send(:"on_\#{child.type}", child) if child }
62
+ nil
63
+ end
64
+ RUBY
65
+ end
66
+ end
67
+
68
+ unless Builder.method_defined?(:match_pattern_p)
69
+ Builder.include RubyNext::Language::BuilderExt
70
+ end
71
+ end
72
+ end
73
+ # rubocop:enable Layout/HeredocIndentation
74
+
75
+ module RuboCop
76
+ module Cop
77
+ # Commissioner class is responsible for processing the AST and delegating
78
+ # work to the specified cops.
79
+ class Commissioner
80
+ def on_meth_ref(node)
81
+ trigger_responding_cops(:on_meth_ref, node)
82
+ end
83
+
84
+ unless method_defined?(:on_numblock)
85
+ def on_numblock(node)
86
+ children = node.children
87
+ child = children[0]
88
+ send(:"on_#{child.type}", child)
89
+ # children[1] is the number of parameters
90
+ return unless (child = children[2])
91
+
92
+ send(:"on_#{child.type}", child)
93
+ end
94
+ end
95
+
96
+ unless method_defined?(:on_def_e)
97
+ def on_def_e(node)
98
+ _name, _args_node, body_node = *node
99
+ send(:"on_#{body_node.type}", body_node)
100
+ end
101
+
102
+ def on_defs_e(node)
103
+ _definee_node, _name, _args_node, body_node = *node
104
+ send(:"on_#{body_node.type}", body_node)
105
+ end
106
+ end
107
+ end
108
+
109
+ Commissioner.prepend(Module.new do
110
+ # Ignore anonymous blocks
111
+ def on_block_pass(node)
112
+ return if node.children == [nil]
113
+
114
+ super
115
+ end
116
+
117
+ def on_blockarg(node)
118
+ return if node.children == [nil]
119
+
120
+ super
121
+ end
122
+ end)
123
+
124
+ module Layout
125
+ require "rubocop/cop/layout/assignment_indentation"
126
+
127
+ POTENTIAL_RIGHT_TYPES = %i[ivasgn lvasgn cvasgn gvasgn casgn masgn].freeze
128
+
129
+ AssignmentIndentation.prepend(Module.new do
130
+ def check_assignment(node, *__rest__)
131
+ return if rightward?(node)
132
+ super
133
+ end
134
+
135
+ private
136
+
137
+ def rightward?(node)
138
+ return unless POTENTIAL_RIGHT_TYPES.include?(node.type)
139
+
140
+ return unless node.loc.operator
141
+
142
+ assignee_loc =
143
+ if node.type == :masgn
144
+ node.children[0].loc.expression
145
+ else
146
+ node.loc.name
147
+ end
148
+
149
+ return false unless assignee_loc
150
+
151
+ assignee_loc.begin_pos > node.loc.operator.end_pos
152
+ end
153
+ end)
154
+
155
+ require "rubocop/cop/layout/empty_line_between_defs"
156
+ EmptyLineBetweenDefs.prepend(Module.new do
157
+ def def_end(node)
158
+ return super unless node.loc.end.nil?
159
+
160
+ node.loc.expression.line
161
+ end
162
+ end)
163
+
164
+ require "rubocop/cop/layout/space_after_colon"
165
+ SpaceAfterColon.prepend(Module.new do
166
+ def on_pair(node)
167
+ return if node.children[0].loc.last_column == node.children[1].loc.last_column
168
+
169
+ super(node)
170
+ end
171
+ end)
172
+ end
173
+
174
+ module Style
175
+ require "rubocop/cop/style/single_line_methods"
176
+ SingleLineMethods.prepend(Module.new do
177
+ def on_def(node)
178
+ return if node.loc.end.nil?
179
+ super
180
+ end
181
+
182
+ def on_defs(node)
183
+ return if node.loc.end.nil?
184
+ super
185
+ end
186
+ end)
187
+
188
+ require "rubocop/cop/style/def_with_parentheses"
189
+ DefWithParentheses.prepend(Module.new do
190
+ def on_def(node)
191
+ return if node.loc.end.nil?
192
+ super
193
+ end
194
+
195
+ def on_defs(node)
196
+ return if node.loc.end.nil?
197
+ super
198
+ end
199
+ end)
200
+
201
+ require "rubocop/cop/style/trailing_method_end_statement"
202
+ TrailingMethodEndStatement.prepend(Module.new do
203
+ def on_def(node)
204
+ return if node.loc.end.nil?
205
+ super
206
+ end
207
+ end)
208
+ end
209
+ end
210
+ end
@@ -10,9 +10,45 @@ 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, :specified_rewriters
13
+ class Stats
14
+ def initialize
15
+ @started_at = ::Process.clock_gettime(Process::CLOCK_MONOTONIC)
16
+ @files = 0
17
+ @scans = 0
18
+ @transpiled_files = 0
19
+ end
20
+
21
+ def file!
22
+ @files += 1
23
+ end
24
+
25
+ def scan!
26
+ @scans += 1
27
+ end
28
+
29
+ def transpiled!
30
+ @transpiled_files += 1
31
+ end
32
+
33
+ def report
34
+ <<~TXT
35
+
36
+ Files processed: #{@files}
37
+ Total scans: #{@scans}
38
+ Files transpiled: #{@transpiled_files}
39
+
40
+ Completed in #{::Process.clock_gettime(Process::CLOCK_MONOTONIC) - @started_at}s
41
+ TXT
42
+ end
43
+ end
44
+
45
+ attr_reader :lib_path, :paths, :out_path, :min_version, :single_version, :specified_rewriters, :overwrite
46
+ alias_method :overwrite?, :overwrite
47
+ attr_reader :stats
14
48
 
15
49
  def run
50
+ @stats = Stats.new
51
+
16
52
  log "RubyNext core strategy: #{RubyNext::Core.strategy}"
17
53
  log "RubyNext transpile mode: #{RubyNext::Language.mode}"
18
54
 
@@ -21,18 +57,24 @@ module RubyNext
21
57
  @min_version ||= MIN_SUPPORTED_VERSION
22
58
 
23
59
  paths.each do |path|
60
+ stats.file!
61
+
24
62
  contents = File.read(path)
25
63
  transpile path, contents
26
64
  end
27
65
 
28
66
  ensure_rbnext!
67
+
68
+ log stats.report
29
69
  end
30
70
 
31
71
  def parse!(args)
32
72
  print_help = false
33
73
  print_rewriters = false
34
74
  rewriter_names = []
75
+ custom_rewriters = []
35
76
  @single_version = false
77
+ @overwrite = false
36
78
 
37
79
  optparser = base_parser do |opts|
38
80
  opts.banner = "Usage: ruby-next nextify DIRECTORY_OR_FILE [options]"
@@ -49,11 +91,17 @@ module RubyNext
49
91
  @single_version = true
50
92
  end
51
93
 
94
+ opts.on("--overwrite", "Overwrite original file") do
95
+ @overwrite = true
96
+ end
97
+
52
98
  opts.on("--edge", "Enable edge (master) Ruby features") do |val|
99
+ ENV["RUBY_NEXT_EDGE"] = val.to_s
53
100
  require "ruby-next/language/rewriters/edge"
54
101
  end
55
102
 
56
103
  opts.on("--proposed", "Enable proposed/experimental Ruby features") do |val|
104
+ ENV["RUBY_NEXT_PROPOSED"] = val.to_s
57
105
  require "ruby-next/language/rewriters/proposed"
58
106
  end
59
107
 
@@ -76,6 +124,10 @@ module RubyNext
76
124
  rewriter_names << val
77
125
  end
78
126
 
127
+ opts.on("--import-rewriter=REWRITERS...", "Specify paths to load custom rewritiers") do |val|
128
+ custom_rewriters << val
129
+ end
130
+
79
131
  opts.on("-h", "--help", "Print help") do
80
132
  print_help = true
81
133
  end
@@ -90,9 +142,14 @@ module RubyNext
90
142
  exit 0
91
143
  end
92
144
 
145
+ # Load custom rewriters
146
+ custom_rewriters.each do |path|
147
+ Kernel.load path
148
+ end
149
+
93
150
  if print_rewriters
94
151
  Language.rewriters.each do |rewriter|
95
- $stdout.puts "#{rewriter::NAME} (\"#{rewriter::SYNTAX_PROBE}\")"
152
+ $stdout.puts "#{rewriter::NAME} (\"#{rewriter::SYNTAX_PROBE}\")#{rewriter.unsupported_syntax? ? " (unsupported)" : ""}"
96
153
  end
97
154
  exit 0
98
155
  end
@@ -119,6 +176,11 @@ module RubyNext
119
176
  end
120
177
  end
121
178
 
179
+ if overwrite? && !single_version?
180
+ $stdout.puts "--overwrite only works with --single-version or explcit rewritires specified (via --rewrite)"
181
+ exit 2
182
+ end
183
+
122
184
  @paths =
123
185
  if File.directory?(lib_path)
124
186
  Dir[File.join(lib_path, "**/*.rb")]
@@ -132,6 +194,8 @@ module RubyNext
132
194
  private
133
195
 
134
196
  def transpile(path, contents, version: min_version)
197
+ stats.scan!
198
+
135
199
  rewriters = specified_rewriters || Language.rewriters.select { |rw| rw.unsupported_version?(version) }
136
200
 
137
201
  context = Language::TransformContext.new
@@ -157,12 +221,20 @@ module RubyNext
157
221
  end
158
222
 
159
223
  def save(contents, path, version)
224
+ stats.transpiled!
225
+
160
226
  return $stdout.puts(contents) if stdout?
161
227
 
162
228
  paths = [Pathname.new(path).relative_path_from(Pathname.new(lib_path))]
163
229
 
164
230
  paths.unshift(version.segments[0..1].join(".")) unless single_version?
165
231
 
232
+ if overwrite?
233
+ overwrite_file_content!(path: path, contents: contents)
234
+
235
+ return
236
+ end
237
+
166
238
  next_path =
167
239
  if next_dir_path.end_with?(".rb")
168
240
  out_path
@@ -179,6 +251,14 @@ module RubyNext
179
251
  log "Generated: #{next_path}"
180
252
  end
181
253
 
254
+ def overwrite_file_content!(path:, contents:)
255
+ unless CLI.dry_run?
256
+ File.write(path, contents)
257
+ end
258
+
259
+ log "Overwritten: #{path}"
260
+ end
261
+
182
262
  def remove_rbnext!
183
263
  return if CLI.dry_run? || stdout?
184
264
 
@@ -195,6 +275,8 @@ module RubyNext
195
275
 
196
276
  return if next_dir_path.end_with?(".rb")
197
277
 
278
+ return if overwrite?
279
+
198
280
  FileUtils.mkdir_p next_dir_path
199
281
  File.write(File.join(next_dir_path, ".keep"), "")
200
282
  end
@@ -20,6 +20,8 @@ module RubyNext
20
20
 
21
21
  class << self
22
22
  # TruffleRuby claims its RUBY_VERSION to be X.Y while not supporting all the features
23
+ # Currently (23.0.1), it still doesn't support pattern matching, although claims to be "like 3.1".
24
+ # So, we fallback to 2.6.5 (since we cannot use 2.7).
23
25
  if defined?(TruffleRuby)
24
26
  def current_ruby_version
25
27
  "2.6.5"
@@ -30,6 +32,15 @@ module RubyNext
30
32
  end
31
33
  end
32
34
 
35
+ # Returns true if we want to use edge syntax
36
+ def edge_syntax?
37
+ %w[y true 1].include?(ENV["RUBY_NEXT_EDGE"])
38
+ end
39
+
40
+ def proposed_syntax?
41
+ %w[y true 1].include?(ENV["RUBY_NEXT_PROPOSED"])
42
+ end
43
+
33
44
  def next_ruby_version(version = current_ruby_version)
34
45
  return if version == Gem::Version.new(NEXT_VERSION)
35
46
 
@@ -46,5 +57,21 @@ module RubyNext
46
57
 
47
58
  Gem::Version.new(nxt)
48
59
  end
60
+
61
+ # Load transpile settings from the RC file (nextify command flags)
62
+ def load_from_rc(path = ".rbnextrc")
63
+ return unless File.exist?(path)
64
+
65
+ require "yaml"
66
+
67
+ args = YAML.load_file(path)&.fetch("nextify", "")&.lines&.flat_map { |line| line.chomp.split(/\s+/) }
68
+
69
+ ENV["RUBY_NEXT_EDGE"] ||= "true" if args.delete("--edge")
70
+ ENV["RUBY_NEXT_PROPOSED"] ||= "true" if args.delete("--proposed")
71
+ ENV["RUBY_NEXT_TRANSPILE_MODE"] ||= "rewrite" if args.delete("--transpile-mode=rewrite")
72
+ ENV["RUBY_NEXT_TRANSPILE_MODE"] ||= "ast" if args.delete("--transpile-mode=ast")
73
+ end
49
74
  end
75
+
76
+ load_from_rc
50
77
  end
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The code below originates from https://github.com/saturnflyer/polyfill-data
4
+
5
+ if !Object.const_defined?(:Data) || !Data.respond_to?(:define)
6
+
7
+ # Drop legacy Data class
8
+ begin
9
+ Object.send(:remove_const, :Data)
10
+ rescue
11
+ nil
12
+ end
13
+
14
+ class Data < Object
15
+ using RubyNext
16
+
17
+ class << self
18
+ undef_method :new
19
+ attr_reader :members
20
+ end
21
+
22
+ def self.define(*args, &block)
23
+ raise ArgumentError if args.any?(/=/)
24
+ if block
25
+ mod = Module.new
26
+ mod.define_singleton_method(:_) do |klass|
27
+ klass.class_eval(&block)
28
+ end
29
+ arity_converter = mod.method(:_)
30
+ end
31
+ klass = ::Class.new(self)
32
+
33
+ klass.instance_variable_set(:@members, args.map(&:to_sym).freeze)
34
+
35
+ klass.define_singleton_method(:new) do |*new_args, **new_kwargs, &block|
36
+ init_kwargs = if new_args.any?
37
+ raise ArgumentError, "unknown arguments #{new_args[members.size..].join(", ")}" if new_args.size > members.size
38
+ members.take(new_args.size).zip(new_args).to_h
39
+ else
40
+ new_kwargs
41
+ end
42
+
43
+ allocate.tap do |instance|
44
+ instance.send(:initialize, **init_kwargs, &block)
45
+ end.freeze
46
+ end
47
+
48
+ class << klass
49
+ alias_method :[], :new
50
+ undef_method :define
51
+ end
52
+
53
+ args.each do |arg|
54
+ if klass.method_defined?(arg)
55
+ raise ArgumentError, "duplicate member #{arg}"
56
+ end
57
+ klass.define_method(arg) do
58
+ @attributes[arg]
59
+ end
60
+ end
61
+
62
+ if arity_converter
63
+ klass.class_eval(&arity_converter)
64
+ end
65
+
66
+ klass
67
+ end
68
+
69
+ def members
70
+ self.class.members
71
+ end
72
+
73
+ def initialize(**kwargs)
74
+ kwargs_size = kwargs.size
75
+ members_size = members.size
76
+
77
+ if kwargs_size > members_size
78
+ extras = kwargs.except(*members).keys
79
+
80
+ if extras.size > 1
81
+ raise ArgumentError, "unknown keywords: #{extras.map { ":#{_1}" }.join(", ")}"
82
+ else
83
+ raise ArgumentError, "unknown keyword: :#{extras.first}"
84
+ end
85
+ elsif kwargs_size < members_size
86
+ missing = members.select { |k| !kwargs.include?(k) }
87
+
88
+ if missing.size > 1
89
+ raise ArgumentError, "missing keywords: #{missing.map { ":#{_1}" }.join(", ")}"
90
+ else
91
+ raise ArgumentError, "missing keyword: :#{missing.first}"
92
+ end
93
+ end
94
+
95
+ @attributes = members.map { |m| [m, kwargs[m]] }.to_h
96
+ end
97
+
98
+ def deconstruct
99
+ @attributes.values
100
+ end
101
+
102
+ def deconstruct_keys(array)
103
+ raise TypeError unless array.is_a?(Array) || array.nil?
104
+ return @attributes if array&.first.nil?
105
+
106
+ @attributes.slice(*array)
107
+ end
108
+
109
+ def to_h(&block)
110
+ @attributes.to_h(&block)
111
+ end
112
+
113
+ def hash
114
+ to_h.hash
115
+ end
116
+
117
+ def eql?(other)
118
+ self.class == other.class && hash == other.hash
119
+ end
120
+
121
+ def ==(other)
122
+ self.class == other.class && to_h == other.to_h
123
+ end
124
+
125
+ def inspect
126
+ attribute_markers = @attributes.map do |key, value|
127
+ insect_key = key.to_s.start_with?("@") ? ":#{key}" : key
128
+ "#{insect_key}=#{value}"
129
+ end.join(", ")
130
+
131
+ display = ["data", self.class.name, attribute_markers].compact.join(" ")
132
+
133
+ "#<#{display}>"
134
+ end
135
+ alias_method :to_s, :inspect
136
+
137
+ def with(**kwargs)
138
+ return self if kwargs.empty?
139
+
140
+ self.class.new(**@attributes.merge(kwargs))
141
+ end
142
+
143
+ private
144
+
145
+ def marshal_dump
146
+ @attributes
147
+ end
148
+
149
+ def marshal_load(attributes)
150
+ @attributes = attributes
151
+ freeze
152
+ end
153
+
154
+ def initialize_copy(source)
155
+ super.freeze
156
+ end
157
+ end
158
+
159
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ RubyNext::Core.patch MatchData, method: :deconstruct, version: "3.2" do
4
+ <<-'RUBY'
5
+ def deconstruct
6
+ captures.map(&:to_str)
7
+ end
8
+ RUBY
9
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ RubyNext::Core.patch MatchData, method: :deconstruct_keys, version: "3.2" do
4
+ <<-'RUBY'
5
+ def deconstruct_keys(keys)
6
+ raise TypeError, "wrong argument type #{keys.class} (expected Array)" if keys && !keys.is_a?(Array)
7
+
8
+ captured = named_captures.transform_keys!(&:to_sym)
9
+ return captured if keys.nil?
10
+
11
+ return {} if keys.size > captured.size
12
+
13
+ keys.each_with_object({}) do |k, acc|
14
+ raise TypeError, "wrong argument type #{k.class} (expected Symbol)" unless Symbol === k
15
+ return acc unless captured.key?(k)
16
+ acc[k] = self[k]
17
+ end
18
+ end
19
+ RUBY
20
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ RubyNext::Core.patch MatchData, method: :named_captures, version: "3.3", supported: "a".match(/a/).method(:named_captures).arity != 0, core_ext: :prepend do
4
+ <<-'RUBY'
5
+ def named_captures(symbolize_names: false)
6
+ return super() unless symbolize_names
7
+
8
+ super().transform_keys!(&:to_sym)
9
+ end
10
+ RUBY
11
+ end