ruby-next-core 0.14.0 → 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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +70 -0
  3. data/README.md +163 -56
  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 +12 -4
  7. data/lib/.rbnext/2.1/ruby-next/language.rb +62 -12
  8. data/lib/.rbnext/2.3/ruby-next/commands/nextify.rb +97 -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/args_forward.rb +134 -0
  13. data/lib/.rbnext/2.3/ruby-next/language/rewriters/2.7/pattern_matching.rb +122 -47
  14. data/lib/.rbnext/2.3/ruby-next/language/rewriters/base.rb +6 -32
  15. data/lib/.rbnext/2.3/ruby-next/utils.rb +3 -22
  16. data/lib/.rbnext/2.6/ruby-next/core/data.rb +163 -0
  17. data/lib/.rbnext/2.7/ruby-next/core/data.rb +163 -0
  18. data/lib/.rbnext/2.7/ruby-next/core.rb +12 -4
  19. data/lib/.rbnext/2.7/ruby-next/language/paco_parsers/string_literals.rb +109 -0
  20. data/lib/.rbnext/2.7/ruby-next/language/rewriters/2.7/pattern_matching.rb +1095 -0
  21. data/lib/.rbnext/2.7/ruby-next/language/rewriters/text.rb +132 -0
  22. data/lib/.rbnext/3.2/ruby-next/commands/base.rb +55 -0
  23. data/lib/.rbnext/3.2/ruby-next/language/rewriters/2.7/pattern_matching.rb +1095 -0
  24. data/lib/.rbnext/3.2/ruby-next/rubocop.rb +210 -0
  25. data/lib/ruby-next/cli.rb +10 -15
  26. data/lib/ruby-next/commands/nextify.rb +99 -3
  27. data/lib/ruby-next/config.rb +31 -4
  28. data/lib/ruby-next/core/data.rb +163 -0
  29. data/lib/ruby-next/core/matchdata/deconstruct.rb +9 -0
  30. data/lib/ruby-next/core/matchdata/deconstruct_keys.rb +20 -0
  31. data/lib/ruby-next/core/matchdata/named_captures.rb +11 -0
  32. data/lib/ruby-next/core/proc/compose.rb +0 -1
  33. data/lib/ruby-next/core/refinement/import.rb +44 -36
  34. data/lib/ruby-next/core/time/deconstruct_keys.rb +30 -0
  35. data/lib/ruby-next/core.rb +11 -3
  36. data/lib/ruby-next/irb.rb +24 -0
  37. data/lib/ruby-next/language/bootsnap.rb +2 -25
  38. data/lib/ruby-next/language/eval.rb +4 -4
  39. data/lib/ruby-next/language/paco_parser.rb +7 -0
  40. data/lib/ruby-next/language/paco_parsers/base.rb +47 -0
  41. data/lib/ruby-next/language/paco_parsers/comments.rb +26 -0
  42. data/lib/ruby-next/language/paco_parsers/string_literals.rb +109 -0
  43. data/lib/ruby-next/language/parser.rb +31 -6
  44. data/lib/ruby-next/language/rewriters/2.5/rescue_within_block.rb +41 -0
  45. data/lib/ruby-next/language/rewriters/2.7/args_forward.rb +57 -0
  46. data/lib/ruby-next/language/rewriters/2.7/pattern_matching.rb +120 -45
  47. data/lib/ruby-next/language/rewriters/3.0/args_forward_leading.rb +2 -2
  48. data/lib/ruby-next/language/rewriters/3.1/oneline_pattern_parensless.rb +1 -1
  49. data/lib/ruby-next/language/rewriters/3.1/shorthand_hash.rb +2 -1
  50. data/lib/ruby-next/language/rewriters/3.2/anonymous_restargs.rb +104 -0
  51. data/lib/ruby-next/language/rewriters/abstract.rb +57 -0
  52. data/lib/ruby-next/language/rewriters/base.rb +6 -32
  53. data/lib/ruby-next/language/rewriters/edge/it_param.rb +58 -0
  54. data/lib/ruby-next/language/rewriters/edge.rb +12 -0
  55. data/lib/ruby-next/language/rewriters/proposed/bind_vars_pattern.rb +3 -0
  56. data/lib/ruby-next/language/rewriters/proposed/method_reference.rb +9 -20
  57. data/lib/ruby-next/language/rewriters/text.rb +132 -0
  58. data/lib/ruby-next/language/runtime.rb +9 -86
  59. data/lib/ruby-next/language/setup.rb +5 -2
  60. data/lib/ruby-next/language/unparser.rb +5 -0
  61. data/lib/ruby-next/language.rb +62 -12
  62. data/lib/ruby-next/pry.rb +90 -0
  63. data/lib/ruby-next/rubocop.rb +2 -0
  64. data/lib/ruby-next/utils.rb +3 -22
  65. data/lib/ruby-next/version.rb +1 -1
  66. data/lib/uby-next/irb.rb +3 -0
  67. data/lib/uby-next/pry.rb +3 -0
  68. data/lib/uby-next.rb +2 -2
  69. metadata +70 -10
@@ -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
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # rubocop:disable Style/LambdaCall
4
3
  RubyNext::Core.patch Proc, name: "ProcCompose", method: :<<, version: "2.6" do
5
4
  <<-RUBY
6
5
  def <<(other)
@@ -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
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"
@@ -62,7 +62,7 @@ module RubyNext
62
62
  mod_name = singleton? ? singleton.name : mod.name
63
63
  camelized_method_name = method_name.to_s.split("_").map(&:capitalize).join
64
64
 
65
- "#{mod_name}#{camelized_method_name}".gsub(/\W/, "") # rubocop:disable Performance/StringReplacement
65
+ "#{mod_name}#{camelized_method_name}".gsub(/\W/, "")
66
66
  end
67
67
 
68
68
  def build_location(trace_locations)
@@ -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"
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ruby-next"
4
+ # Include RubyNext into TOPLEVEL_BINDING for polyfills to work
5
+ eval("using RubyNext", TOPLEVEL_BINDING, __FILE__, __LINE__)
6
+
7
+ require "ruby-next/language"
8
+
9
+ # IRB extension to transpile code before evaluating
10
+ module RubyNext
11
+ module IRBExt
12
+ def eval(statements, *args)
13
+ new_statements = ::RubyNext::Language.transform(
14
+ statements,
15
+ rewriters: ::RubyNext::Language.current_rewriters,
16
+ using: false
17
+ )
18
+
19
+ super(new_statements, *args)
20
+ end
21
+ end
22
+ end
23
+
24
+ IRB::WorkSpace.prepend(RubyNext::IRBExt)
@@ -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
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyNext
4
+ module Language
5
+ module Rewriters
6
+ class RescueWithinBlock < Base
7
+ NAME = "rescue-within-block"
8
+ SYNTAX_PROBE = "lambda do
9
+ raise 'err'
10
+ rescue
11
+ $! # => #<RuntimeError: err>
12
+ end.call"
13
+
14
+ MIN_SUPPORTED_VERSION = Gem::Version.new("2.5.0")
15
+
16
+ def on_block(block_node)
17
+ exception_node = block_node.children.find do |node|
18
+ node && (node.type == :rescue || node.type == :ensure)
19
+ end
20
+
21
+ return super(block_node) unless exception_node
22
+
23
+ context.track! self
24
+
25
+ insert_before(exception_node.loc.expression, "begin;")
26
+ insert_after(exception_node.loc.expression, ";end")
27
+
28
+ new_children = block_node.children.map do |child|
29
+ next s(:kwbegin, exception_node) if child == exception_node
30
+
31
+ child
32
+ end
33
+
34
+ process(
35
+ block_node.updated(:block, new_children)
36
+ )
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -45,8 +45,65 @@ module RubyNext
45
45
  process_fargs(node, fargs)
46
46
  end
47
47
 
48
+ def on_def(node)
49
+ return super unless forward_arg?(node.children[1])
50
+
51
+ new_node = super
52
+
53
+ name = node.children[0]
54
+
55
+ insert_after(node.loc.expression, "; respond_to?(:ruby2_keywords, true) && (ruby2_keywords :#{name})")
56
+
57
+ s(:begin,
58
+ new_node,
59
+ ruby2_keywords_node(nil, name))
60
+ end
61
+
62
+ def on_defs(node)
63
+ return super unless forward_arg?(node.children[2])
64
+
65
+ new_node = super
66
+
67
+ receiver = node.children[0]
68
+ name = node.children[1]
69
+
70
+ # Using self.ruby2_keywords :name results in undefined method error,
71
+ # singleton_class works as expected
72
+ receiver = s(:send, nil, :singleton_class) if receiver.type == :self
73
+
74
+ receiver_name =
75
+ case receiver.type
76
+ when :send
77
+ receiver.children[1]
78
+ when :const
79
+ receiver.children[1]
80
+ end
81
+
82
+ insert_after(node.loc.expression, "; #{receiver_name}.respond_to?(:ruby2_keywords, true) && (#{receiver_name}.send(:ruby2_keywords, :#{name}))")
83
+
84
+ s(:begin,
85
+ new_node,
86
+ ruby2_keywords_node(receiver, name))
87
+ end
88
+
48
89
  private
49
90
 
91
+ def ruby2_keywords_node(receiver, name)
92
+ s(:and,
93
+ s(:send, receiver, :respond_to?,
94
+ s(:sym, :ruby2_keywords), s(:true)),
95
+ s(:begin,
96
+ s(:send, receiver, :send,
97
+ s(:sym, :ruby2_keywords),
98
+ s(:sym, name))))
99
+ end
100
+
101
+ def forward_arg?(args)
102
+ return false unless args&.children
103
+
104
+ args.children.any? { |arg| arg.type == :forward_arg }
105
+ end
106
+
50
107
  def extract_fargs(node)
51
108
  node.children.find { |child| child.is_a?(::Parser::AST::Node) && child.type == :forwarded_args }
52
109
  end