ruby-next-core 1.0.1 → 1.0.2

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: ac9ab37024b70ac4ba553ceca25b7b63a0029ccf356787295b5d6509eb2b5461
4
+ data.tar.gz: a27ef393520aae1513047c234f999993ff0bc1dd6614e7aca8c5acf02561a7af
5
5
  SHA512:
6
- metadata.gz: e7df787bfa90d036bbd45c7bd2dbc85f3513d922b3606dfde12ade0196f7ccb6789aba263d407fc012c7b4625d38b4aa6d7df05a5ac4e784b3b9cfc5db356b0f
7
- data.tar.gz: a4c364d32b19003c219670096dd6420d415663445d68deb7e92c8d9763cc705884e7ab7e43fed81c6c084450f6a32e14bd8b50f24629256cb064c84a8bb87583
6
+ metadata.gz: 00b81f2df63fce2fb38da1470315a1d1fa2ae1e4ebc69b150712a9f02f2e5a07c7bb8f2776ae109ec4b3fd259567f674cf6df209f0d06fb4fa6e542467f44c7f
7
+ data.tar.gz: 7f201ea784a2707d0aec127086d5b76c1475fa7c60e984988ddb7c37262d6168cc86139112b71ea466502eba330e1fd6cf98fe6c674266dfb7e4b5c62d75d7b4
data/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## master
4
4
 
5
+ ## 1.0.2 (2024-02-23)
6
+
7
+ - Automatically mark context as dirty if `#safe_rewrite` modifies source code. ([@palkan][])
8
+
9
+ - Add `context.path` to rewriters to access the current file path (if any). ([@palkan][])
10
+
5
11
  ## 1.0.1 (2024-01-28)
6
12
 
7
13
  - 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
 
@@ -576,11 +576,7 @@ class MethodReferenceRewriter < RubyNext::Language::Rewriters::Text
576
576
  MIN_SUPPORTED_VERSION = Gem::Version.new(RubyNext::NEXT_VERSION)
577
577
 
578
578
  def safe_rewrite(source)
579
- source.gsub(/\.:([\w_]+)/) do |match|
580
- context.track! self
581
-
582
- ".method(:#{$1})"
583
- end
579
+ source.gsub(/\.:([\w_]+)/, '.method(:\1)')
584
580
  end
585
581
  end
586
582
 
@@ -588,9 +584,28 @@ end
588
584
  RubyNext::Language.rewriters << MethodReferenceRewriter
589
585
  ```
590
586
 
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.
587
+ 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:
588
+
589
+ ```ruby
590
+
591
+ class NoteDateRewriter < RubyNext::Language::Rewriters::Text
592
+ NAME = "note-comment-date"
593
+ MIN_SUPPORTED_VERSION = Gem::Version.new(RubyNext::NEXT_VERSION)
594
+
595
+ def rewrite(source)
596
+ source.gsub("# NOTE:") do |_match|
597
+ context.track!(self)
598
+ "# NOTE (#{Date.today}):"
599
+ end
600
+ end
601
+ end
602
+
603
+ RubyNext::Language.rewriters << NoteDateRewriter
604
+ ```
605
+
606
+ 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
607
 
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.
608
+ ### Using parser combinators (Paco)
594
609
 
595
610
  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
611
 
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
 
@@ -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
 
@@ -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
 
@@ -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
@@ -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.2"
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.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vladimir Dementyev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-01-28 00:00:00.000000000 Z
11
+ date: 2024-02-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: require-hooks