ruby-next-core 1.0.1 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5ad6cea22baa599e5a6edeb4fdda1fb7c9bd0b0d5617fb01cf4450374c7d9443
4
- data.tar.gz: fa39e7d51cdf70c32dc2067000023e42dbe3b23bd1d8bf520e06a3a028d63b8d
3
+ metadata.gz: d760d668ee7358474931d4a69ebff41ff99aa2d7e300fdbe58aa023e38644422
4
+ data.tar.gz: f7a982bae326176fb4ac5b991f8b61be10648efe5cfc501a4d81f14b974bc55b
5
5
  SHA512:
6
- metadata.gz: e7df787bfa90d036bbd45c7bd2dbc85f3513d922b3606dfde12ade0196f7ccb6789aba263d407fc012c7b4625d38b4aa6d7df05a5ac4e784b3b9cfc5db356b0f
7
- data.tar.gz: a4c364d32b19003c219670096dd6420d415663445d68deb7e92c8d9763cc705884e7ab7e43fed81c6c084450f6a32e14bd8b50f24629256cb064c84a8bb87583
6
+ metadata.gz: 5093c8523a20d95a073254f09e2226e766df69e9ba48692686f34693ba132fe2d5f6d5b08a558d9ef7f7be2842784aded96a181903577d9fbcef060dc8f8c108
7
+ data.tar.gz: 17df494938c41ce179f64776cb6ce13a966c97ac3ba3f6ac32e0d1d6da7fc38ee7689dc65ef4f6480208ba5190d288f292b03830bcec43756911c0e2092b6669
data/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  ## master
4
4
 
5
+ ## 1.0.3 (2024-04-18)
6
+
7
+ - Fix RuboCop compatibility.
8
+
9
+ ## 1.0.2 (2024-02-23)
10
+
11
+ - Automatically mark context as dirty if `#safe_rewrite` modifies source code. ([@palkan][])
12
+
13
+ - Add `context.path` to rewriters to access the current file path (if any). ([@palkan][])
14
+
5
15
  ## 1.0.1 (2024-01-28)
6
16
 
7
17
  - Fix using Data with inheritance (`class X < Data.define(...)`). ([@palkan][])
data/README.md CHANGED
@@ -170,7 +170,7 @@ Ruby 3.2 has introduced a new core class—[Data](https://bugs.ruby-lang.org/iss
170
170
 
171
171
  If you want to opt-out from loading Data backport, you must set the `RUBY_NEXT_DISABLE_DATA` env variable to `true`.
172
172
 
173
- #### Known limitations
173
+ #### Known limitations when using Data
174
174
 
175
175
  Currently, passing Hash as a last positional argument to `Data.new` is not supported in Ruby <3.0 (due to the difference in keyword arguments handling). We recommend always using keyword arguments when initializing Data objects.
176
176
 
@@ -565,6 +565,9 @@ Wonder what would happen if Ruby get a null coalescing operator (`??=`) or some
565
565
 
566
566
  Ruby Next allows you to write your own syntax rewriters. Full-featured rewriters (used by Ruby Next itself) operate on AST and usually require parser modifications. However, we also support text-based rewriters which can be used to experiment with new syntax much quicker without dealing with grammars, parsers and syntax trees.
567
567
 
568
+ > [!TIP]
569
+ > You can experiment with Ruby Next rewriters at our [online playground][playground]!
570
+
568
571
  To implement a text-based rewriter, you need to create a new class inherited from `RubyNext::Language::Rewriters::Text` and implementing either `#rewrite` or `#safe_rewrite` method. For example, the method reference operator (`.:`) could be implemented as follows:
569
572
 
570
573
  ```ruby
@@ -576,11 +579,7 @@ class MethodReferenceRewriter < RubyNext::Language::Rewriters::Text
576
579
  MIN_SUPPORTED_VERSION = Gem::Version.new(RubyNext::NEXT_VERSION)
577
580
 
578
581
  def safe_rewrite(source)
579
- source.gsub(/\.:([\w_]+)/) do |match|
580
- context.track! self
581
-
582
- ".method(:#{$1})"
583
- end
582
+ source.gsub(/\.:([\w_]+)/, '.method(:\1)')
584
583
  end
585
584
  end
586
585
 
@@ -588,9 +587,28 @@ end
588
587
  RubyNext::Language.rewriters << MethodReferenceRewriter
589
588
  ```
590
589
 
591
- The `context` object is responsible for tracking if the rewriter was used for the current file. You must call the `context.track!` method to mark the file as _dirty_ (i.e., it should be transpiled). The input parameter (`source`) is the Ruby source code of the file being transpiled and the output must be the transpiled source code.
590
+ The `#safe_rewrite` method operates on the normalized source code (i.e., without comments and string literals). It's useful when you want to avoid transpiling inside strings or comments. If you want to transpile the original contents, you can use the `#rewrite` method instead. For example, if you want to rewrite comments:
591
+
592
+ ```ruby
593
+
594
+ class NoteDateRewriter < RubyNext::Language::Rewriters::Text
595
+ NAME = "note-comment-date"
596
+ MIN_SUPPORTED_VERSION = Gem::Version.new(RubyNext::NEXT_VERSION)
597
+
598
+ def rewrite(source)
599
+ source.gsub("# NOTE:") do |_match|
600
+ context.track!(self)
601
+ "# NOTE (#{Date.today}):"
602
+ end
603
+ end
604
+ end
605
+
606
+ RubyNext::Language.rewriters << NoteDateRewriter
607
+ ```
608
+
609
+ Note that we use the `context` object in the example above. It is responsible for tracking if the rewriter was used for the current file. You must call the `context.track!` method to mark the file as _dirty_ (i.e., it should be transpiled). The input parameter (`source`) is the Ruby source code of the file being transpiled and the output must be the transpiled source code. When using `#safe_rewrite`, marking content as dirty explicitly is not necessary.
592
610
 
593
- The `#safe_rewrite` method operates on the normalized source code (i.e., without comments and string literals). It's useful when you want to avoid transpiling inside strings or comments. If you want to transpile the original contents, you can use the `#rewrite` method instead.
611
+ ### Using parser combinators (Paco)
594
612
 
595
613
  Under the hood, `#safe_rewrite` uses [Paco][] to parse the source and separate string literals from the rest of the code. You can also leverage [Paco][] in your text rewriters, if you want more control on the parsing process. For better experience, we provide a DSL to define a custom parser and the `#parse` method to use it. Here is an example of implementing the `.:` operator using a Paco parser:
596
614
 
@@ -689,3 +707,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
689
707
  [require-hooks]: https://github.com/ruby-next/require-hooks
690
708
  [Natalie]: https://natalie-lang.org
691
709
  [Paco]: https://github.com/ruby-next/paco
710
+ [playground]: https://ruby-next.github.io
data/bin/parse CHANGED
@@ -17,3 +17,4 @@ contents =
17
17
 
18
18
  ast = RubyNext::Language.parse(contents)
19
19
  puts ast
20
+ puts "\n // Parsed with #{RubyNext::Language.parser_class}"
data/bin/transform CHANGED
@@ -12,8 +12,8 @@ begin
12
12
  rescue LoadError
13
13
  end
14
14
 
15
- ENV["RUBY_NEXT_EDGE"] = "1"
16
- ENV["RUBY_NEXT_PROPOSED"] = "1"
15
+ ENV["RUBY_NEXT_EDGE"] ||= "1"
16
+ ENV["RUBY_NEXT_PROPOSED"] ||= "1"
17
17
 
18
18
  require "ruby-next/language"
19
19
  require "ruby-next/language/rewriters/runtime"
@@ -44,3 +44,4 @@ OptionParser.new do |opts|
44
44
  end.parse!
45
45
 
46
46
  puts RubyNext::Language.transform(contents, **transform_opts)
47
+ puts "\n // Parsed with #{RubyNext::Language.parser_class}"
@@ -196,7 +196,7 @@ module RubyNext
196
196
 
197
197
  rewriters = specified_rewriters || Language.rewriters.select { |rw| rw.unsupported_version?(version) }
198
198
 
199
- context = Language::TransformContext.new
199
+ context = Language::TransformContext.new(path: path)
200
200
 
201
201
  new_contents = Language.transform contents, context: context, rewriters: rewriters
202
202
 
@@ -68,7 +68,7 @@ module RubyNext
68
68
  def build_location(trace_locations)
69
69
  # The caller_locations behaviour depends on implementaion,
70
70
  # e.g. in JRuby https://github.com/jruby/jruby/issues/6055
71
- while trace_locations.first.label != "patch"
71
+ while trace_locations.first.base_label != "patch"
72
72
  trace_locations.shift
73
73
  end
74
74
 
@@ -29,9 +29,10 @@ module RubyNext
29
29
  RewriterNotFoundError = Class.new(StandardError)
30
30
 
31
31
  class TransformContext
32
- attr_reader :versions, :use_ruby_next
32
+ attr_reader :versions, :use_ruby_next, :path
33
33
 
34
- def initialize
34
+ def initialize(path: nil)
35
+ @path = path
35
36
  # Minimum supported RubyNext version
36
37
  @min_version = MIN_SUPPORTED_VERSION
37
38
  @dirty = false
@@ -104,7 +105,7 @@ module RubyNext
104
105
  @runtime
105
106
  end
106
107
 
107
- def transform(source, rewriters: self.rewriters, using: RubyNext::Core.refine?, context: TransformContext.new)
108
+ def transform(source, rewriters: self.rewriters, using: RubyNext::Core.refine?, path: nil, context: TransformContext.new(path: path))
108
109
  text_rewriters, ast_rewriters = rewriters.partition(&:text?)
109
110
 
110
111
  retried = 0
@@ -196,7 +196,7 @@ module RubyNext
196
196
 
197
197
  rewriters = specified_rewriters || Language.rewriters.select { |rw| rw.unsupported_version?(version) }
198
198
 
199
- context = Language::TransformContext.new
199
+ context = Language::TransformContext.new(path: path)
200
200
 
201
201
  new_contents = Language.transform contents, context: context, rewriters: rewriters
202
202
 
@@ -20,11 +20,16 @@ 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".
23
+ # Currently (23.x), it still doesn't support pattern matching, although claims to be "like 3.1".
24
24
  # So, we fallback to 2.6.5 (since we cannot use 2.7).
25
+ # TruffleRuby 24.x seems to support pattern matching.
25
26
  if defined?(TruffleRuby)
26
27
  def current_ruby_version
27
- "2.6.5"
28
+ if RUBY_ENGINE_VERSION >= "24.0.0"
29
+ "3.1.0"
30
+ else
31
+ "2.6.5"
32
+ end
28
33
  end
29
34
  else
30
35
  def current_ruby_version
@@ -68,7 +68,7 @@ module RubyNext
68
68
  def build_location(trace_locations)
69
69
  # The caller_locations behaviour depends on implementaion,
70
70
  # e.g. in JRuby https://github.com/jruby/jruby/issues/6055
71
- while trace_locations.first.label != "patch"
71
+ while trace_locations.first.base_label != "patch"
72
72
  trace_locations.shift
73
73
  end
74
74
 
@@ -4,7 +4,7 @@ module RubyNext
4
4
  module Language
5
5
  module PacoParsers
6
6
  class StringLiterals < Base
7
- PAIRS = {"[" => "]", "{" => "}", "<" => ">"}.freeze
7
+ PAIRS = {"[" => "]", "{" => "}", "(" => ")"}.freeze
8
8
 
9
9
  def default
10
10
  all_strings.fmap do |result|
@@ -24,11 +24,15 @@ module RubyNext
24
24
  # heredoc_expanded
25
25
  end
26
26
 
27
+ def literal_start
28
+ alt(string("{"), string("("), string("["))
29
+ end
30
+
27
31
  def quoted
28
32
  seq(
29
- string("%q"),
30
- any_char.bind do |char|
31
- end_symbol = string(PAIRS[char] || char)
33
+ alt(string("%q"), string("%s"), string("%r"), string("%i"), string("%w")),
34
+ literal_start.bind do |char|
35
+ end_symbol = string(PAIRS.fetch(char))
32
36
  escapable_string(succeed(char), end_symbol)
33
37
  end
34
38
  )
@@ -40,9 +44,9 @@ module RubyNext
40
44
 
41
45
  def quoted_expanded
42
46
  seq(
43
- alt(string("%Q"), string("%")),
44
- any_char.bind do |char|
45
- end_symbol = string(PAIRS[char] || char)
47
+ alt(string("%Q"), string("%"), string("%W"), string("%I")),
48
+ literal_start.bind do |char|
49
+ end_symbol = string(PAIRS.fetch(char))
46
50
  escapable_string(succeed(char), end_symbol, interpolate: true)
47
51
  end
48
52
  )
@@ -109,7 +109,9 @@ module RubyNext
109
109
  # Rewrite source code by ignoring string literals and comments
110
110
  def rewrite(source)
111
111
  Normalizer.new.normalizing(source) do |normalized|
112
- safe_rewrite(normalized)
112
+ safe_rewrite(normalized).tap do |rewritten|
113
+ context.track!(self) if rewritten != normalized
114
+ end
113
115
  end
114
116
  end
115
117
 
@@ -36,7 +36,7 @@ end
36
36
  module RuboCop
37
37
  class ProcessedSource
38
38
  module ParserClassExt
39
- def parser_class(version)
39
+ def parser_class(version, *__rest__)
40
40
  return super unless version == RUBY_NEXT_VERSION
41
41
 
42
42
  require "parser/rubynext"
@@ -198,7 +198,7 @@ module RubyNext
198
198
 
199
199
  rewriters = specified_rewriters || Language.rewriters.select { |rw| rw.unsupported_version?(version) }
200
200
 
201
- context = Language::TransformContext.new
201
+ context = Language::TransformContext.new(path: path)
202
202
 
203
203
  new_contents = Language.transform contents, context: context, rewriters: rewriters
204
204
 
@@ -20,11 +20,16 @@ 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".
23
+ # Currently (23.x), it still doesn't support pattern matching, although claims to be "like 3.1".
24
24
  # So, we fallback to 2.6.5 (since we cannot use 2.7).
25
+ # TruffleRuby 24.x seems to support pattern matching.
25
26
  if defined?(TruffleRuby)
26
27
  def current_ruby_version
27
- "2.6.5"
28
+ if RUBY_ENGINE_VERSION >= "24.0.0"
29
+ "3.1.0"
30
+ else
31
+ "2.6.5"
32
+ end
28
33
  end
29
34
  else
30
35
  def current_ruby_version
@@ -36,7 +36,7 @@ RubyNext::Core.singleton_class.module_eval do
36
36
 
37
37
  begin
38
38
  if defined?(::RubyNext::Language) && ::RubyNext::Language.runtime?
39
- new_source = ::RubyNext::Language.transform(buffer.join, rewriters: RubyNext::Language.current_rewriters, using: false)
39
+ new_source = ::RubyNext::Language.transform(buffer.join, rewriters: RubyNext::Language.current_rewriters, using: false, path: source_file)
40
40
  # Transformed successfully => valid method => evaluate transpiled code
41
41
  import << [new_source, source_file, lineno]
42
42
  buffer.clear
@@ -68,7 +68,7 @@ module RubyNext
68
68
  def build_location(trace_locations)
69
69
  # The caller_locations behaviour depends on implementaion,
70
70
  # e.g. in JRuby https://github.com/jruby/jruby/issues/6055
71
- while trace_locations.first.label != "patch"
71
+ while trace_locations.first.base_label != "patch"
72
72
  trace_locations.shift
73
73
  end
74
74
 
@@ -4,7 +4,7 @@ module RubyNext
4
4
  module Language
5
5
  module PacoParsers
6
6
  class StringLiterals < Base
7
- PAIRS = {"[" => "]", "{" => "}", "<" => ">"}.freeze
7
+ PAIRS = {"[" => "]", "{" => "}", "(" => ")"}.freeze
8
8
 
9
9
  def default
10
10
  all_strings.fmap do |result|
@@ -24,11 +24,15 @@ module RubyNext
24
24
  # heredoc_expanded
25
25
  end
26
26
 
27
+ def literal_start
28
+ alt(string("{"), string("("), string("["))
29
+ end
30
+
27
31
  def quoted
28
32
  seq(
29
- string("%q"),
30
- any_char.bind do |char|
31
- end_symbol = string(PAIRS[char] || char)
33
+ alt(string("%q"), string("%s"), string("%r"), string("%i"), string("%w")),
34
+ literal_start.bind do |char|
35
+ end_symbol = string(PAIRS.fetch(char))
32
36
  escapable_string(succeed(char), end_symbol)
33
37
  end
34
38
  )
@@ -40,9 +44,9 @@ module RubyNext
40
44
 
41
45
  def quoted_expanded
42
46
  seq(
43
- alt(string("%Q"), string("%")),
44
- any_char.bind do |char|
45
- end_symbol = string(PAIRS[char] || char)
47
+ alt(string("%Q"), string("%"), string("%W"), string("%I")),
48
+ literal_start.bind do |char|
49
+ end_symbol = string(PAIRS.fetch(char))
46
50
  escapable_string(succeed(char), end_symbol, interpolate: true)
47
51
  end
48
52
  )
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  begin
4
- require "parser/prism"
4
+ require "prism"
5
+ require "prism/translation/parser"
5
6
  rescue LoadError
6
7
  require "parser/ruby33"
7
8
  end
@@ -71,9 +72,16 @@ module RubyNext
71
72
  unless parser_class
72
73
  self.parser_class = if defined?(::Parser::RubyNext)
73
74
  ::Parser::RubyNext
74
- elsif defined?(::Parser::Prism)
75
- parser_syntax_errors << ::Prism::ParserCompiler::CompilationError
76
- ::Parser::Prism
75
+ elsif defined?(::Prism::Translation::Parser)
76
+ Class.new(::Prism::Translation::Parser) do
77
+ # Use this callback to ignore some parse-level errors, such as parsing numbered parameters
78
+ # when transpiling for older Ruby versions
79
+ def valid_error?(error)
80
+ !error.message.include?("is reserved for numbered parameters")
81
+ end
82
+ end.tap do |clazz|
83
+ Language.const_set(:PrismParser, clazz)
84
+ end
77
85
  else
78
86
  ::Parser::Ruby33
79
87
  end
@@ -41,7 +41,7 @@ module RubyNext
41
41
  when :lvar
42
42
  node.children[0]
43
43
  else
44
- raise ArgumentError, "Unsupport ipair node: #{node}"
44
+ raise SyntaxError, "#{node} is not allowed as a local variable name"
45
45
  end
46
46
  end
47
47
  end
@@ -36,10 +36,8 @@ module RubyNext
36
36
  false
37
37
  end
38
38
 
39
- private
40
-
41
- def transform(source)
42
- Language.transform(source, rewriters: [self], using: false)
39
+ def transform(source, **opts)
40
+ Language.transform(source, rewriters: [self], using: false, **opts)
43
41
  end
44
42
  end
45
43
 
@@ -50,7 +50,11 @@ module RubyNext
50
50
  end
51
51
 
52
52
  def it?(node)
53
- node.is_a?(Parser::AST::Node) && node.type == :send && node.children[0].nil? && node.children[1] == :it && node.children[2].nil?
53
+ node.is_a?(Parser::AST::Node) && (
54
+ node.type == :send && node.children[0].nil? && node.children[1] == :it && node.children[2].nil?
55
+ ) || ( # Prism version
56
+ node.type == :lvar && node.children[0] == :"0it"
57
+ )
54
58
  end
55
59
  end
56
60
  end
@@ -12,11 +12,7 @@ module RubyNext
12
12
  MIN_SUPPORTED_VERSION = Gem::Version.new(RubyNext::NEXT_VERSION)
13
13
 
14
14
  def safe_rewrite(source)
15
- source.gsub(/\.:([\w_]+)/) do |match|
16
- context.track! self
17
-
18
- ".method(:#{$1})"
19
- end
15
+ source.gsub(/\.:([\w_]+)/, '.method(:\1)')
20
16
  end
21
17
  end
22
18
  end
@@ -109,7 +109,9 @@ module RubyNext
109
109
  # Rewrite source code by ignoring string literals and comments
110
110
  def rewrite(source)
111
111
  Normalizer.new.normalizing(source) do |normalized|
112
- safe_rewrite(normalized)
112
+ safe_rewrite(normalized).tap do |rewritten|
113
+ context.track!(self) if rewritten != normalized
114
+ end
113
115
  end
114
116
  end
115
117
 
@@ -17,7 +17,7 @@ module RubyNext
17
17
  class << self
18
18
  def load(path, contents)
19
19
  contents ||= File.read(path)
20
- new_contents = transform contents
20
+ new_contents = transform contents, path: path
21
21
 
22
22
  RubyNext.debug_source new_contents, path
23
23
 
@@ -29,9 +29,10 @@ module RubyNext
29
29
  RewriterNotFoundError = Class.new(StandardError)
30
30
 
31
31
  class TransformContext
32
- attr_reader :versions, :use_ruby_next
32
+ attr_reader :versions, :use_ruby_next, :path
33
33
 
34
- def initialize
34
+ def initialize(path: nil)
35
+ @path = path
35
36
  # Minimum supported RubyNext version
36
37
  @min_version = MIN_SUPPORTED_VERSION
37
38
  @dirty = false
@@ -104,7 +105,7 @@ module RubyNext
104
105
  @runtime
105
106
  end
106
107
 
107
- def transform(source, rewriters: self.rewriters, using: RubyNext::Core.refine?, context: TransformContext.new)
108
+ def transform(source, rewriters: self.rewriters, using: RubyNext::Core.refine?, path: nil, context: TransformContext.new(path: path))
108
109
  text_rewriters, ast_rewriters = rewriters.partition(&:text?)
109
110
 
110
111
  retried = 0
@@ -36,7 +36,7 @@ end
36
36
  module RuboCop
37
37
  class ProcessedSource
38
38
  module ParserClassExt
39
- def parser_class(version)
39
+ def parser_class(version, *)
40
40
  return super unless version == RUBY_NEXT_VERSION
41
41
 
42
42
  require "parser/rubynext"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RubyNext
4
- VERSION = "1.0.1"
4
+ VERSION = "1.0.3"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-next-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vladimir Dementyev
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-01-28 00:00:00.000000000 Z
11
+ date: 2024-04-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: require-hooks
@@ -209,7 +209,7 @@ metadata:
209
209
  homepage_uri: https://github.com/ruby-next/ruby-next
210
210
  source_code_uri: https://github.com/ruby-next/ruby-next
211
211
  funding_uri: https://github.com/sponsors/palkan
212
- post_install_message:
212
+ post_install_message:
213
213
  rdoc_options: []
214
214
  require_paths:
215
215
  - lib
@@ -224,8 +224,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
224
224
  - !ruby/object:Gem::Version
225
225
  version: '0'
226
226
  requirements: []
227
- rubygems_version: 3.4.20
228
- signing_key:
227
+ rubygems_version: 3.4.19
228
+ signing_key:
229
229
  specification_version: 4
230
230
  summary: Ruby Next core functionality
231
231
  test_files: []