# frozen_string_literal: true module RSpec module Mock module MigrationAnalytics module Tracker class Rspec < RSpec::Mock::MigrationAnalytics::Tracker::Base PATTERNS = { block_start: /\b(rspec_mock)\s+(?:do|\{)/, block_end: /(?:\}|\bend\b)/, single_line_block: /rspec_mock\s*{/, expect: /\bexpect\b/, allow: /\ballow\b/, have_received: /\bhave_received\b/, mock_chain: /\.(to_receive|to\s+receive|and_return|and_raise)/, mock_related: /\b(?: expect| allow| receive| and_return| and_raise| have_received| instance_double| class_double| object_double| double )\b/x, end_brace: /\}\s*$/, empty_or_comment: /^\s*(?:#.*)?$/ }.freeze def scan_line(line, line_number) return if line.strip.empty? || line.strip.start_with?('#') line.split(';').each do |statement| stripped_statement = statement.strip track_rspec_mock_blocks(stripped_statement) track_rspec_mock_usage(stripped_statement, line_number) end end private def mock_related?(line) line.match?(RSpec::Mock::MigrationAnalytics::Tracker::Rspec::PATTERNS[:mock_related]) end def determine_rspec_mock_type(line) case line when RSpec::Mock::MigrationAnalytics::Tracker::Rspec::PATTERNS[:have_received] then 'spy verification' when RSpec::Mock::MigrationAnalytics::Tracker::Rspec::PATTERNS[:expect] then 'expect mock' when RSpec::Mock::MigrationAnalytics::Tracker::Rspec::PATTERNS[:allow] then 'allow mock' when RSpec::Mock::MigrationAnalytics::Tracker::Rspec::PATTERNS[:mock_chain] then 'mock chain' else 'rspec mock related' end end def track_rspec_mock_blocks(line) case line when RSpec::Mock::MigrationAnalytics::Tracker::Rspec::PATTERNS[:single_line_block] self.in_mock_block = true when RSpec::Mock::MigrationAnalytics::Tracker::Rspec::PATTERNS[:block_start] self.in_mock_block = true self.block_level += 1 when RSpec::Mock::MigrationAnalytics::Tracker::Rspec::PATTERNS[:block_end] self.block_level -= 1 self.in_mock_block = false if block_level.zero? end # Reset block state for single-line blocks self.in_mock_block = false if line.end_with?('}') end def track_rspec_mock_usage(line, line_number) return unless (in_mock_block? || line.match?(RSpec::Mock::MigrationAnalytics::Tracker::Rspec::PATTERNS[:single_line_block])) && mock_related?(line) locations << { line_number: line_number, content: line, type: determine_rspec_mock_type(line) } end end end end end end