ruby-next-core 0.15.3 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
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