ruby-next-core 0.15.3 → 1.0.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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +34 -0
  3. data/README.md +127 -54
  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 +59 -12
  8. data/lib/.rbnext/2.3/ruby-next/commands/nextify.rb +83 -3
  9. data/lib/.rbnext/2.3/ruby-next/config.rb +79 -0
  10. data/lib/.rbnext/2.3/ruby-next/core/data.rb +163 -0
  11. data/lib/.rbnext/2.3/ruby-next/language/eval.rb +4 -4
  12. data/lib/.rbnext/2.3/ruby-next/language/rewriters/2.7/pattern_matching.rb +2 -2
  13. data/lib/.rbnext/2.3/ruby-next/language/rewriters/base.rb +6 -32
  14. data/lib/.rbnext/2.3/ruby-next/utils.rb +3 -22
  15. data/lib/.rbnext/2.6/ruby-next/core/data.rb +163 -0
  16. data/lib/.rbnext/2.7/ruby-next/core/data.rb +163 -0
  17. data/lib/.rbnext/2.7/ruby-next/core.rb +10 -2
  18. data/lib/.rbnext/2.7/ruby-next/language/paco_parsers/string_literals.rb +109 -0
  19. data/lib/.rbnext/2.7/ruby-next/language/rewriters/2.7/pattern_matching.rb +2 -2
  20. data/lib/.rbnext/2.7/ruby-next/language/rewriters/text.rb +132 -0
  21. data/lib/.rbnext/3.2/ruby-next/commands/base.rb +55 -0
  22. data/lib/.rbnext/3.2/ruby-next/language/rewriters/2.7/pattern_matching.rb +1095 -0
  23. data/lib/.rbnext/3.2/ruby-next/rubocop.rb +210 -0
  24. data/lib/ruby-next/commands/nextify.rb +85 -3
  25. data/lib/ruby-next/config.rb +29 -2
  26. data/lib/ruby-next/core/data.rb +163 -0
  27. data/lib/ruby-next/core/matchdata/deconstruct.rb +9 -0
  28. data/lib/ruby-next/core/matchdata/deconstruct_keys.rb +20 -0
  29. data/lib/ruby-next/core/matchdata/named_captures.rb +11 -0
  30. data/lib/ruby-next/core/refinement/import.rb +44 -36
  31. data/lib/ruby-next/core/time/deconstruct_keys.rb +30 -0
  32. data/lib/ruby-next/core.rb +10 -2
  33. data/lib/ruby-next/irb.rb +2 -2
  34. data/lib/ruby-next/language/bootsnap.rb +2 -25
  35. data/lib/ruby-next/language/eval.rb +4 -4
  36. data/lib/ruby-next/language/paco_parser.rb +7 -0
  37. data/lib/ruby-next/language/paco_parsers/base.rb +47 -0
  38. data/lib/ruby-next/language/paco_parsers/comments.rb +26 -0
  39. data/lib/ruby-next/language/paco_parsers/string_literals.rb +109 -0
  40. data/lib/ruby-next/language/parser.rb +31 -6
  41. data/lib/ruby-next/language/rewriters/3.0/args_forward_leading.rb +2 -2
  42. data/lib/ruby-next/language/rewriters/3.1/oneline_pattern_parensless.rb +1 -1
  43. data/lib/ruby-next/language/rewriters/3.1/shorthand_hash.rb +2 -1
  44. data/lib/ruby-next/language/rewriters/3.2/anonymous_restargs.rb +104 -0
  45. data/lib/ruby-next/language/rewriters/abstract.rb +57 -0
  46. data/lib/ruby-next/language/rewriters/base.rb +6 -32
  47. data/lib/ruby-next/language/rewriters/edge/it_param.rb +58 -0
  48. data/lib/ruby-next/language/rewriters/edge.rb +12 -0
  49. data/lib/ruby-next/language/rewriters/proposed/bind_vars_pattern.rb +3 -0
  50. data/lib/ruby-next/language/rewriters/proposed/method_reference.rb +9 -20
  51. data/lib/ruby-next/language/rewriters/text.rb +132 -0
  52. data/lib/ruby-next/language/runtime.rb +9 -86
  53. data/lib/ruby-next/language/setup.rb +5 -2
  54. data/lib/ruby-next/language/unparser.rb +5 -0
  55. data/lib/ruby-next/language.rb +59 -12
  56. data/lib/ruby-next/pry.rb +1 -1
  57. data/lib/ruby-next/rubocop.rb +2 -0
  58. data/lib/ruby-next/utils.rb +3 -22
  59. data/lib/ruby-next/version.rb +1 -1
  60. data/lib/uby-next.rb +2 -2
  61. metadata +63 -10
@@ -4,57 +4,65 @@
4
4
  # So, we use a defined method instead (and transpile source code to use it).
5
5
  # NOTE: We have to transpile the source code anyway, since we need to pass a binding.
6
6
  RubyNext::Core.singleton_class.module_eval do
7
- def import_methods(other, bind)
7
+ def import_methods(*others, bind)
8
8
  import = []
9
9
 
10
- other.instance_methods(false).each do |mid|
11
- # check for non-Ruby methods
12
- meth = other.instance_method(mid)
13
- location = meth.source_location
10
+ # First, validate passed modules
11
+ others.each do |other|
12
+ raise TypeError, "wrong argument type #{other.class} (expected Module)" unless other.is_a?(::Module)
13
+ raise TypeError, "wrong argument type Class (expected Module)" if other.is_a?(::Class)
14
+ end
14
15
 
15
- if location.nil? || location.first.match?(/(<internal:|resource:\/truffleruby\/core)/)
16
- raise ArgumentError, "Can't import method: #{other}##{mid} from #{location}"
17
- end
16
+ others.each do |other|
17
+ (other.instance_methods(false) + other.private_instance_methods(false)).each do |mid|
18
+ # check for non-Ruby methods
19
+ meth = other.instance_method(mid)
20
+ location = meth.source_location
18
21
 
19
- source_file, lineno = *location
22
+ if location.nil? || location.first.match?(/(<internal:|resource:\/truffleruby\/core|uri:classloader:\/jruby)/)
23
+ raise ArgumentError, "Can't import method which is not defined with Ruby code: #{other}##{mid} from #{location}"
24
+ end
20
25
 
21
- raise ArgumentError, "Can't import dynamicly added methods: #{other}##{mid}" unless File.file?(source_file)
26
+ source_file, lineno = *location
22
27
 
23
- lines = File.open(source_file).readlines
28
+ raise ArgumentError, "Can't import dynamicly added methods: #{other}##{mid}" unless File.file?(source_file)
24
29
 
25
- buffer = []
30
+ lines = File.open(source_file).readlines
26
31
 
27
- lines[(lineno - 1)..-1].each do |line|
28
- buffer << line + "\n"
32
+ buffer = []
29
33
 
30
- begin
31
- if defined?(::RubyNext::Language) && ::RubyNext::Language.runtime?
32
- new_source = ::RubyNext::Language.transform(buffer.join, rewriters: RubyNext::Language.current_rewriters, using: false)
33
- # Transformed successfully => valid method => evaluate transpiled code
34
- import << [new_source, source_file, lineno]
35
- buffer.clear
36
- break
37
- end
34
+ lines[(lineno - 1)..-1].each do |line|
35
+ buffer << line + "\n"
36
+
37
+ begin
38
+ if defined?(::RubyNext::Language) && ::RubyNext::Language.runtime?
39
+ new_source = ::RubyNext::Language.transform(buffer.join, rewriters: RubyNext::Language.current_rewriters, using: false)
40
+ # Transformed successfully => valid method => evaluate transpiled code
41
+ import << [new_source, source_file, lineno]
42
+ buffer.clear
43
+ break
44
+ end
38
45
 
39
- # Borrowed from https://github.com/banister/method_source/blob/81d039c966ffd95d26e12eb2e205c0eb8377f49d/lib/method_source/code_helpers.rb#L66
40
- catch(:valid) do
41
- eval("BEGIN{throw :valid}\nObject.new.instance_eval { #{buffer.join} }") # rubocop:disable all
46
+ # Borrowed from https://github.com/banister/method_source/blob/81d039c966ffd95d26e12eb2e205c0eb8377f49d/lib/method_source/code_helpers.rb#L66
47
+ catch(:valid) do
48
+ eval("BEGIN{throw :valid}\nObject.new.instance_eval { #{buffer.join} }") # rubocop:disable all
49
+ end
50
+ break
51
+ rescue SyntaxError
42
52
  end
43
- break
44
- rescue SyntaxError
45
53
  end
46
- end
47
54
 
48
- import << [buffer.join, source_file, lineno] unless buffer.empty?
49
- end
55
+ import << [buffer.join, source_file, lineno] unless buffer.empty?
56
+ end
50
57
 
51
- import.each do |(definition, file, lino)|
52
- Kernel.eval definition, bind, file, lino
53
- end
58
+ import.each do |(definition, file, lino)|
59
+ Kernel.eval definition, bind, file, lino
60
+ end
54
61
 
55
- # Copy constants (they could be accessed from methods)
56
- other.constants.each do |name|
57
- Kernel.eval "#{name} = #{other}::#{name}", bind # rubocop:disable Style/EvalWithLocation
62
+ # Copy constants (they could be accessed from methods)
63
+ other.constants.each do |name|
64
+ Kernel.eval "#{name} = #{other}::#{name}", bind # rubocop:disable Style/EvalWithLocation
65
+ end
58
66
  end
59
67
  end
60
68
  end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ RubyNext::Core.patch Time, method: :deconstruct_keys, version: "3.2" do
4
+ <<-'RUBY'
5
+ def deconstruct_keys(keys)
6
+ raise TypeError, "wrong argument type #{keys.class} (expected Array or nil)" if keys && !keys.is_a?(Array)
7
+
8
+ if !keys
9
+ return {
10
+ year: year,
11
+ month: month,
12
+ day: day,
13
+ yday: yday,
14
+ wday: wday,
15
+ hour: hour,
16
+ min: min,
17
+ sec: sec,
18
+ subsec: subsec,
19
+ dst: dst?,
20
+ zone: zone
21
+ }
22
+ end
23
+
24
+ keys.each_with_object({}) do |key, hash|
25
+ hash[key] = public_send(key) if key.is_a?(Symbol) && respond_to?(key)
26
+ hash
27
+ end
28
+ end
29
+ RUBY
30
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "set"
3
+ require "set" # rubocop:disable Lint/RedundantRequireStatement
4
4
 
5
5
  require "ruby-next/config"
6
6
  require "ruby-next/utils"
@@ -78,7 +78,7 @@ module RubyNext
78
78
  end
79
79
 
80
80
  def native_location?(location)
81
- location.nil? || location.first.match?(/(<internal:|resource:\/truffleruby\/core)/)
81
+ location.nil? || location.first.match?(/(<internal:|resource:\/truffleruby\/core|uri:classloader:\/jruby)/)
82
82
  end
83
83
  end
84
84
 
@@ -197,6 +197,11 @@ require "ruby-next/core/matchdata/match"
197
197
  require "ruby-next/core/enumerable/compact"
198
198
  require "ruby-next/core/integer/try_convert"
199
199
 
200
+ require "ruby-next/core/matchdata/deconstruct"
201
+ require "ruby-next/core/matchdata/deconstruct_keys"
202
+ require "ruby-next/core/matchdata/named_captures"
203
+ require "ruby-next/core/time/deconstruct_keys"
204
+
200
205
  # Generate refinements
201
206
  RubyNext.module_eval do
202
207
  RubyNext::Core.patches.refined.each do |mod, patches|
@@ -210,3 +215,6 @@ RubyNext.module_eval do
210
215
  end
211
216
  end
212
217
  end
218
+
219
+ # Load backports
220
+ require "ruby-next/core/data" unless ENV["RUBY_NEXT_DISABLE_DATA"] == "true"
data/lib/ruby-next/irb.rb CHANGED
@@ -9,14 +9,14 @@ require "ruby-next/language"
9
9
  # IRB extension to transpile code before evaluating
10
10
  module RubyNext
11
11
  module IRBExt
12
- def evaluate(context, statements, *args)
12
+ def eval(statements, *args)
13
13
  new_statements = ::RubyNext::Language.transform(
14
14
  statements,
15
15
  rewriters: ::RubyNext::Language.current_rewriters,
16
16
  using: false
17
17
  )
18
18
 
19
- super(context, new_statements, *args)
19
+ super(new_statements, *args)
20
20
  end
21
21
  end
22
22
  end
@@ -1,28 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "ruby-next"
4
- require "ruby-next/utils"
5
- require "ruby-next/language"
3
+ warn "[DEPRECATED] Using ruby-next/language/bootsnap is deprecated. Please use ruby-next/language/runtime instead."
6
4
 
7
- RubyNext::Language.runtime!
8
-
9
- # Patch bootsnap to transform source code.
10
- # Based on https://github.com/kddeisz/preval/blob/master/lib/preval.rb
11
- load_iseq = RubyVM::InstructionSequence.method(:load_iseq)
12
-
13
- if load_iseq.source_location[0].include?("/bootsnap/")
14
- Bootsnap::CompileCache::ISeq.singleton_class.prepend(
15
- Module.new do
16
- def input_to_storage(source, path, *)
17
- return super unless RubyNext::Language.transformable?(path)
18
- source = RubyNext::Language.transform(source, rewriters: RubyNext::Language.current_rewriters)
19
-
20
- RubyNext.debug_source(source, path)
21
-
22
- RubyVM::InstructionSequence.compile(source, path, path).to_binary
23
- rescue SyntaxError
24
- raise Bootsnap::CompileCache::Uncompilable, "syntax error"
25
- end
26
- end
27
- )
28
- end
5
+ require "ruby-next/language/runtime"
@@ -11,7 +11,7 @@ module RubyNext
11
11
  using: bind&.receiver == TOPLEVEL_BINDING.receiver || bind&.receiver&.is_a?(Module)
12
12
  )
13
13
  RubyNext.debug_source(new_source, "(#{caller_locations(1, 1).first})")
14
- super new_source, bind, *args
14
+ super(new_source, bind, *args)
15
15
  end
16
16
  end
17
17
  end
@@ -25,7 +25,7 @@ module RubyNext
25
25
  source = args.shift
26
26
  new_source = ::RubyNext::Language::Runtime.transform(source, using: false)
27
27
  RubyNext.debug_source(new_source, "(#{caller_locations(1, 1).first})")
28
- super new_source, *args
28
+ super(new_source, *args)
29
29
  end
30
30
  end
31
31
  end
@@ -39,7 +39,7 @@ module RubyNext
39
39
  new_source = ::RubyNext::Language::Runtime.transform(source, using: false)
40
40
 
41
41
  RubyNext.debug_source(new_source, "(#{caller_locations(1, 1).first})")
42
- super new_source, *args
42
+ super(new_source, *args)
43
43
  end
44
44
 
45
45
  def class_eval(*args, &block)
@@ -48,7 +48,7 @@ module RubyNext
48
48
  source = args.shift
49
49
  new_source = ::RubyNext::Language::Runtime.transform(source, using: false)
50
50
  RubyNext.debug_source(new_source, "(#{caller_locations(1, 1).first})")
51
- super new_source, *args
51
+ super(new_source, *args)
52
52
  end
53
53
  end
54
54
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "paco"
4
+
5
+ require "ruby-next/language/paco_parsers/base"
6
+ require "ruby-next/language/paco_parsers/comments"
7
+ require "ruby-next/language/paco_parsers/string_literals"
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyNext
4
+ module Language
5
+ module PacoParsers
6
+ class Base
7
+ include Paco
8
+
9
+ def parse(io)
10
+ default.parse(io)
11
+ end
12
+
13
+ private
14
+
15
+ def anything_between(left, right)
16
+ seq(
17
+ left,
18
+ many(not_followed_by(right).bind { any_char }).join,
19
+ right
20
+ ).join
21
+ end
22
+
23
+ def starting_string(str)
24
+ index.bind do |index|
25
+ (index.column > 1) ? failed("1 column") : string(str)
26
+ end
27
+ end
28
+
29
+ def balanced(l, r, inner)
30
+ left = string(l)
31
+ right = string(r)
32
+
33
+ many(
34
+ alt(
35
+ seq(
36
+ left,
37
+ lazy { balanced(l, r, inner) },
38
+ right
39
+ ),
40
+ not_followed_by(right).bind { inner }
41
+ )
42
+ )
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyNext
4
+ module Language
5
+ module PacoParsers
6
+ class Comments < Base
7
+ def default
8
+ alt(
9
+ line_comment,
10
+ block_comment
11
+ )
12
+ end
13
+
14
+ # Matches a Ruby line comment (from `#` till the end of the line)
15
+ def line_comment
16
+ anything_between(string("#"), end_of_line)
17
+ end
18
+
19
+ # Matches a Ruby block comment (from `=begin` till `=end`)
20
+ def block_comment
21
+ anything_between(starting_string("=begin"), starting_string("=end"))
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyNext
4
+ module Language
5
+ module PacoParsers
6
+ class StringLiterals < Base
7
+ PAIRS = {"[" => "]", "{" => "}", "<" => ">"}.freeze
8
+
9
+ def default
10
+ all_strings.fmap do |result|
11
+ reduce_tokens(result.flatten)
12
+ end
13
+ end
14
+
15
+ def all_strings
16
+ alt(
17
+ single_quoted,
18
+ double_quoted,
19
+ external_cmd_exec,
20
+ quoted,
21
+ quoted_expanded
22
+ )
23
+ # heredoc,
24
+ # heredoc_expanded
25
+ end
26
+
27
+ def quoted
28
+ seq(
29
+ string("%q"),
30
+ any_char.bind do |char|
31
+ end_symbol = string(PAIRS[char] || char)
32
+ escapable_string(succeed(char), end_symbol)
33
+ end
34
+ )
35
+ end
36
+
37
+ def single_quoted
38
+ escapable_string(string("'"))
39
+ end
40
+
41
+ def quoted_expanded
42
+ seq(
43
+ alt(string("%Q"), string("%")),
44
+ any_char.bind do |char|
45
+ end_symbol = string(PAIRS[char] || char)
46
+ escapable_string(succeed(char), end_symbol, interpolate: true)
47
+ end
48
+ )
49
+ end
50
+
51
+ def external_cmd_exec
52
+ escapable_string(string("`"), interpolate: true)
53
+ end
54
+
55
+ def double_quoted
56
+ escapable_string(string('"'), interpolate: true)
57
+ end
58
+
59
+ def escapable_string(left, right = nil, interpolate: false)
60
+ right ||= left
61
+ seq(
62
+ left,
63
+ many(
64
+ alt(
65
+ *[
66
+ seq(string("\\"), right).fmap { [:literal, _1] },
67
+ interpolate ? seq(
68
+ string('#{'),
69
+ lazy { alt(balanced("{", "}", alt(all_strings, any_char)), many(none_of("}"))) },
70
+ string("}")
71
+ ) : nil,
72
+ not_followed_by(right).bind { any_char }.fmap { [:literal, _1] }
73
+ ].compact
74
+ )
75
+ ),
76
+ right
77
+ )
78
+ end
79
+
80
+ private
81
+
82
+ def reduce_tokens(tokens)
83
+ state = :literal
84
+
85
+ tokens.each_with_object([]) do |v, acc|
86
+ if v == :literal
87
+ acc << [:literal, +""] unless state == :literal
88
+ state = :next_literal
89
+ next acc
90
+ end
91
+
92
+ if state == :next_literal
93
+ state = :literal
94
+ acc.last[1] << v
95
+ next acc
96
+ end
97
+
98
+ if state == :literal
99
+ acc << [:code, +""]
100
+ end
101
+
102
+ state = :code
103
+ acc.last[1] << v
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -1,6 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "parser/rubynext"
3
+ begin
4
+ require "parser/prism"
5
+ rescue LoadError
6
+ require "parser/ruby33"
7
+ end
4
8
 
5
9
  module RubyNext
6
10
  module Language
@@ -22,11 +26,18 @@ module RubyNext
22
26
  unless method_defined?(:match_pattern_p)
23
27
  include BuilderExt
24
28
  end
29
+
30
+ def check_reserved_for_numparam(name, loc)
31
+ # We don't want to raise SyntaxError, 'cause we want to use _x vars for older Rubies.
32
+ # The exception should be raised by Ruby itself for versions supporting numbered parameters
33
+ end
25
34
  end
26
35
 
27
36
  class << self
37
+ attr_accessor :parser_class, :parser_syntax_errors
38
+
28
39
  def parser
29
- ::Parser::RubyNext.new(Builder.new).tap do |prs|
40
+ parser_class.new(Builder.new).tap do |prs|
30
41
  prs.diagnostics.tap do |diagnostics|
31
42
  diagnostics.all_errors_are_fatal = true
32
43
  end
@@ -39,8 +50,8 @@ module RubyNext
39
50
  end
40
51
 
41
52
  parser.parse(buffer)
42
- rescue ::Parser::SyntaxError => e
43
- raise ::SyntaxError, e.message
53
+ rescue *parser_syntax_errors => e
54
+ raise ::SyntaxError, e.message, e.backtrace
44
55
  end
45
56
 
46
57
  def parse_with_comments(source, file = "(string)")
@@ -49,8 +60,22 @@ module RubyNext
49
60
  end
50
61
 
51
62
  parser.parse_with_comments(buffer)
52
- rescue ::Parser::SyntaxError => e
53
- raise ::SyntaxError, e.message
63
+ rescue *parser_syntax_errors => e
64
+ raise ::SyntaxError, e.message, e.backtrace
65
+ end
66
+ end
67
+
68
+ self.parser_syntax_errors = [::Parser::SyntaxError]
69
+
70
+ # Set up default parser
71
+ unless parser_class
72
+ self.parser_class = if defined?(::Parser::RubyNext)
73
+ ::Parser::RubyNext
74
+ elsif defined?(::Parser::Prism)
75
+ parser_syntax_errors << ::Prism::ParserCompiler::CompilationError
76
+ ::Parser::Prism
77
+ else
78
+ ::Parser::Ruby33
54
79
  end
55
80
  end
56
81
  end
@@ -50,7 +50,7 @@ module RubyNext
50
50
 
51
51
  return false unless fargs
52
52
 
53
- node.children.index(fargs) > (node.type == :send ? 2 : 0)
53
+ node.children.index(fargs) > ((node.type == :send) ? 2 : 0)
54
54
  end
55
55
 
56
56
  def method_with_leading_arg(node)
@@ -62,7 +62,7 @@ module RubyNext
62
62
  end
63
63
 
64
64
  def def_with_leading_farg(node)
65
- args = node.type == :defs ? node.children[2] : node.children[1]
65
+ args = (node.type == :defs) ? node.children[2] : node.children[1]
66
66
  args = args.children
67
67
 
68
68
  farg = args.detect { |child| child.type == :forward_arg }
@@ -25,7 +25,7 @@ module RubyNext
25
25
 
26
26
  context.track! self
27
27
 
28
- left_p, right_p = pattern.type == :array_pattern ? %w([ ]) : %w[{ }]
28
+ left_p, right_p = (pattern.type == :array_pattern) ? %w([ ]) : %w[{ }]
29
29
 
30
30
  insert_before(pattern.loc.expression, left_p)
31
31
  insert_after(pattern.loc.expression, right_p)
@@ -9,7 +9,8 @@ module RubyNext
9
9
  MIN_SUPPORTED_VERSION = Gem::Version.new("3.1.0")
10
10
 
11
11
  def on_pair(node)
12
- return super(node) unless node.children[0].loc.last_column == node.children[1].loc.last_column
12
+ return super(node) unless (node.children[0].loc.last_column == node.children[1].loc.last_column) &&
13
+ (node.children[1].loc.first_line == node.children[1].loc.last_line)
13
14
 
14
15
  context.track! self
15
16
 
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyNext
4
+ module Language
5
+ module Rewriters
6
+ class AnonymousRestArgs < Base
7
+ NAME = "anonymous-rest-args"
8
+ SYNTAX_PROBE = "obj = Object.new; def obj.foo(*) bar(*); end"
9
+ MIN_SUPPORTED_VERSION = Gem::Version.new("3.2.0")
10
+
11
+ REST = :__rest__
12
+ KWREST = :__kwrest__
13
+
14
+ def on_args(node)
15
+ rest = node.children.find { |child| child.is_a?(::Parser::AST::Node) && child.type == :restarg && child.children.first.nil? }
16
+ kwrest = node.children.find { |child| child.is_a?(::Parser::AST::Node) && child.type == :kwrestarg && child.children.first.nil? }
17
+
18
+ return super unless rest || kwrest
19
+
20
+ context.track! self
21
+
22
+ replace(rest.loc.expression, "*#{REST}") if rest
23
+ replace(kwrest.loc.expression, "**#{KWREST}") if kwrest
24
+
25
+ new_args = node.children.map do |child|
26
+ if child == rest
27
+ s(:restarg, REST)
28
+ elsif child == kwrest
29
+ s(:kwrestarg, KWREST)
30
+ else
31
+ child
32
+ end
33
+ end
34
+
35
+ node.updated(:args, new_args)
36
+ end
37
+
38
+ def on_send(node)
39
+ return super unless forwarded_args?(node)
40
+
41
+ process_send_args(node)
42
+ end
43
+
44
+ def on_super(node)
45
+ return super unless forwarded_args?(node)
46
+
47
+ process_send_args(node)
48
+ end
49
+
50
+ private
51
+
52
+ def forwarded_args?(node)
53
+ node.children.each do |child|
54
+ next unless child.is_a?(::Parser::AST::Node)
55
+
56
+ if child.type == :forwarded_restarg
57
+ return true
58
+ elsif child.type == :kwargs
59
+ child.children.each do |kwarg|
60
+ next unless kwarg.is_a?(::Parser::AST::Node)
61
+
62
+ return true if kwarg.type == :forwarded_kwrestarg
63
+ end
64
+ end
65
+ end
66
+
67
+ false
68
+ end
69
+
70
+ def process_send_args(node)
71
+ process(
72
+ node.updated(
73
+ nil,
74
+ node.children.map do |child|
75
+ next child unless child.is_a?(::Parser::AST::Node)
76
+
77
+ if child.type == :forwarded_restarg
78
+ replace(child.loc.expression, "*#{REST}")
79
+ s(:ksplat, s(:lvar, REST))
80
+ elsif child.type == :kwargs
81
+ child.updated(
82
+ nil,
83
+ child.children.map do |kwarg|
84
+ next kwarg unless kwarg.is_a?(::Parser::AST::Node)
85
+
86
+ if kwarg.type == :forwarded_kwrestarg
87
+ replace(kwarg.loc.expression, "**#{KWREST}")
88
+ s(:kwsplat, s(:lvar, KWREST))
89
+ else
90
+ kwarg
91
+ end
92
+ end
93
+ )
94
+ else
95
+ child
96
+ end
97
+ end
98
+ )
99
+ )
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end