[Rails] Ripper互換コードを削除し、Prism単一パーサーへ移行
背景
Rails 8.0では最小サポートバージョンがRuby 3.3に引き上げられました(#56511)。Ruby 3.3以降では、新しいRubyパーサーであるPrismがデフォルトgemとして必ず含まれるため、従来のRipperパーサーとの互換性コードが不要になりました。
このPRは、Ripperパーサーのフォールバック実装を完全に削除し、Prismパーサーに一本化することで、コードベースをシンプルにします。
削除された互換コード
1. RenderParserの統合
ActionViewのRenderParserでは、これまでRipperとPrismの両方に対応するため、別々のパーサークラスが存在していました。
変更前の構造:
module ActionView
module RenderParser
class Base
# 共通実装
end
end
end
RipperRenderParser(350行)とPrismRenderParser(139行)という2つの実装が存在していました。
変更後:
require "prism"
module ActionView
class RenderParser
def initialize(name, code)
@name = name
@code = code
end
def render_calls
queue = [Prism.parse(@code).value]
templates = []
while (node = queue.shift)
queue.concat(node.compact_child_nodes)
next unless node.is_a?(Prism::CallNode)
# ...
end
templates
end
end
end
Prism実装のみが残り、モジュール構造からクラスへと簡素化されました。2つのパーサーファイル(計489行)が削除され、単一の実装に統合されています。
2. DependencyTrackerの簡素化
RubyTrackerでは、パーサークラスを外部から注入する仕組みが不要になりました。
変更前:
def initialize(name, template, view_paths = nil, parser_class: RenderParser::Default)
@name, @template, @view_paths = name, template, view_paths
@parser_class = parser_class
end
private
def render_dependencies
@parser_class.new(@name, compiled_source).render_calls.filter_map do |render_call|
render_call.gsub(%r|/_|, "/")
end
end
変更後:
def initialize(name, template, view_paths = nil)
@name, @template, @view_paths = name, template, view_paths
end
private
def render_dependencies
RenderParser.new(@name, compiled_source).render_calls.filter_map do |render_call|
render_call.gsub(%r|/_|, "/")
end
end
parser_classパラメータが削除され、直接RenderParserを呼び出すようになりました。
3. SourceAnnotationExtractorの簡素化
RailtiesのSourceAnnotationExtractorでは、Prismが利用できない場合のRipperフォールバックが削除されました。
変更前:
begin
require "prism"
rescue LoadError
require "ripper"
end
class ParserExtractor < Struct.new(:pattern)
if defined?(Prism)
def annotations(file)
result = Prism.parse_file(file)
# Prism実装
end
else
class Parser < Ripper
# Ripper実装(約30行)
end
def annotations(file)
contents = File.read(file, encoding: Encoding::BINARY)
parser = Parser.new(contents, pattern: pattern).tap(&:parse)
parser.error? ? [] : parser.comments
end
end
end
変更後:
require "prism"
class ParserExtractor < Struct.new(:pattern)
def annotations(file)
result = Prism.parse_file(file)
return [] unless result.success?
result.comments.filter_map do |comment|
Annotation.new(comment.location.start_line, $1, $2) if comment.location.slice =~ pattern
end
end
end
条件分岐とRipperクラス定義が完全に削除され、Prism実装のみが残りました。
4. TestParserの大幅な簡素化
テストファイルのパース処理も同様に簡素化されました。
変更前:
begin
require "prism"
rescue LoadError
end
if defined?(Prism)
module Rails::TestUnit::TestParser
# Prism実装(約40行)
end
return
end
# Ripper実装(約80行のフォールバック)
変更後:
require "prism"
module Rails
module TestUnit
module TestParser
@begins_to_ends = {}
def self.definition_for(method)
filepath, start_line = method.source_location
@begins_to_ends[filepath] ||= ranges(filepath)
return unless end_line = @begins_to_ends[filepath][start_line]
[filepath, start_line..end_line]
end
private
def self.ranges(filepath)
queue = [Prism.parse_file(filepath).value]
begins_to_ends = {}
while (node = queue.shift)
case node.type
when :def_node, :call_node
begins_to_ends[node.location.start_line] = node.location.end_line
end
queue.concat(node.compact_child_nodes)
end
begins_to_ends
end
end
end
end
約120行のコードが削減され、条件分岐のない明確な実装になりました。
テストコードの変更
テストでも、Ripper/Prism両方をテストする構造が不要になりました。
変更前:
module RubyTrackerTests
def make_tracker(name, template, view_paths = nil)
ActionView::DependencyTracker::RubyTracker.new(
name, template, view_paths, parser_class: parser_class
)
end
end
class RipperRubyTrackerTest < ActiveSupport::TestCase
include RubyTrackerTests
def parser_class; ActionView::RenderParser::RipperRenderParser; end
end
class PrismRubyTrackerTest < ActiveSupport::TestCase
include RubyTrackerTests
def parser_class; ActionView::RenderParser::PrismRenderParser; end
end
変更後:
class RubyTrackerTest < Minitest::Test
include SharedTrackerTests
def make_tracker(name, template, view_paths = nil)
ActionView::DependencyTracker::RubyTracker.new(name, template, view_paths)
end
end
2つのテストクラスが1つに統合され、パーサー切り替えのロジックが削除されました。
技術的な意義
この変更により、以下のメリットが得られます:
- コードベースの削減: 約500行以上のコードが削除され、保守負荷が軽減
- 複雑性の低減: 条件分岐やフォールバック実装が不要になり、コードが読みやすく
- 統一されたパース処理: Prismの高速で正確なパース処理に一本化
- テストの簡素化: 2つのパーサー実装をテストする必要がなくなり、テストケースも削減
Ruby 3.3以降では、Prismがデフォルトで利用可能になったことで、このような大規模な簡素化が可能になりました。Rails開発チームは、最新のRuby機能を積極的に活用し、フレームワーク自体の品質向上を継続しています。