[Rails] Ripper互換コードを削除し、Prism単一パーサーへ移行

rails/rails

背景

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つに統合され、パーサー切り替えのロジックが削除されました。

技術的な意義

この変更により、以下のメリットが得られます:

  1. コードベースの削減: 約500行以上のコードが削除され、保守負荷が軽減
  2. 複雑性の低減: 条件分岐やフォールバック実装が不要になり、コードが読みやすく
  3. 統一されたパース処理: Prismの高速で正確なパース処理に一本化
  4. テストの簡素化: 2つのパーサー実装をテストする必要がなくなり、テストケースも削減

Ruby 3.3以降では、Prismがデフォルトで利用可能になったことで、このような大規模な簡素化が可能になりました。Rails開発チームは、最新のRuby機能を積極的に活用し、フレームワーク自体の品質向上を継続しています。

記事メタデータ

Generated by:
Claude Sonnet 4.5 for DiffDaily

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

ガイドライン準拠 ✓ PASS

記事構成とDiffDaily Styleへの準拠状況

記事構成の必須3要素(Title, Context, Technical Detail)がすべて明確に記載されています。コードブロック前後の空行やファイル名付きシンタックスハイライトなど、カスタムMarkdown構文もガイドライン通りに正しく使用されており、可読性が非常に高いです。

  • 記事構成(Title、Context、Technical Detail)
  • DiffDaily Styleガイド準拠
  • カスタムMarkdown活用
  • 対象読者への適合性
技術的整合性 ✓ PASS

技術的な正確性と表現の適切性

記事で引用されているコードスニペットは、PRのDiff内容と正確に一致しています。RenderParser, DependencyTracker, SourceAnnotationExtractorなど、複数のコンポーネントにまたがる変更点を、それぞれ的確なコード例と共に技術的に正確に解説できています。

  • 技術用語の正確性
  • コード例の正確性
  • 説明の技術的正確性
PR内容との整合性 ✓ PASS

元のPR情報との一致度

PRのタイトル「Remove dead compatibility code with ripper」の内容を忠実に反映しており、ハルシネーション(根拠のない主張)は見られません。削除されたコード行数の概算(約500行以上)もPRの実際の差分と一致しており、信頼性が高いです。背景説明で関連PR(#56511)に言及している点も、読者の文脈理解を助けています。

  • タイトル・説明の一致
  • Diff内容の正確な反映
  • 推測の排除