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
@@ -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"
@@ -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
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
@@ -53,5 +64,16 @@ module RubyNext
53
64
  raise ::SyntaxError, e.message
54
65
  end
55
66
  end
67
+
68
+ # Set up default parser
69
+ unless parser_class
70
+ self.parser_class = if defined?(::Parser::RubyNext)
71
+ ::Parser::RubyNext
72
+ elsif defined?(::Parser::Prism)
73
+ ::Parser::Prism
74
+ else
75
+ ::Parser::Ruby33
76
+ end
77
+ end
56
78
  end
57
79
  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)
@@ -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
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyNext
4
+ module Language
5
+ module Rewriters
6
+ class Abstract < ::Parser::TreeRewriter
7
+ NAME = "custom-rewriter"
8
+ SYNTAX_PROBE = "1 = [}"
9
+ MIN_SUPPORTED_VERSION = Gem::Version.new(RubyNext::NEXT_VERSION)
10
+
11
+ class << self
12
+ # Returns true if the syntax is not supported
13
+ # by the current Ruby (performs syntax check, not version check)
14
+ def unsupported_syntax?
15
+ save_verbose, $VERBOSE = $VERBOSE, nil
16
+ eval_mid = Kernel.respond_to?(:eval_without_ruby_next) ? :eval_without_ruby_next : :eval
17
+ Kernel.send eval_mid, self::SYNTAX_PROBE, nil, __FILE__, __LINE__
18
+ false
19
+ rescue SyntaxError, StandardError
20
+ true
21
+ ensure
22
+ $VERBOSE = save_verbose
23
+ end
24
+
25
+ # Returns true if the syntax is supported
26
+ # by the specified version
27
+ def unsupported_version?(version)
28
+ version < self::MIN_SUPPORTED_VERSION
29
+ end
30
+
31
+ def text?
32
+ false
33
+ end
34
+
35
+ def ast?
36
+ false
37
+ end
38
+
39
+ private
40
+
41
+ def transform(source)
42
+ Language.transform(source, rewriters: [self], using: false)
43
+ end
44
+ end
45
+
46
+ def initialize(context)
47
+ @context = context
48
+ super()
49
+ end
50
+
51
+ private
52
+
53
+ attr_reader :context
54
+ end
55
+ end
56
+ end
57
+ end