lib/simplecov/combine/methods_combiner.rb



# frozen_string_literal: true

require_relative "../source_file/ruby_data_parser"

module SimpleCov
  module Combine
    #
    # Combine different method coverage results on a single file.
    #
    # Should be called through `SimpleCov.combine`.
    module MethodsCombiner
    module_function

      #
      # Return merged methods or the existing methods if other is missing.
      #
      # Method coverage maps `[class, name, start_line, start_col, end_line,
      # end_col]` keys to hit counts. Keys are matched on their SOURCE
      # identity — the location, ignoring the class and name elements —
      # because Ruby records one entry per defined method: the same
      # `define_method` block defined onto different classes, or under
      # different names, in different processes arrives with different
      # receivers or names for the same source method, and matching on the
      # full key would keep both, letting a never-called copy's 0 shadow a
      # covered method after merge (issue #1234). Combining sums the hit
      # counts for matching methods and preserves methods that only appear
      # in one result.
      #
      # @return [Hash]
      #
      def combine(coverage_a, coverage_b)
        merged = {} #: Hash[untyped, [untyped, Integer]]
        [coverage_a, coverage_b].each_with_object(merged) do |coverage, memo|
          coverage.each do |key, count|
            method_key = source_identity(key)
            retained_key, existing = memo[method_key] || [key, 0]
            memo[method_key] = [retained_key, existing + count]
          end
        end

        merged.values.to_h
      end

      def source_identity(key)
        _class_name, _method_name, *location = SourceFile::RubyDataParser.call(key)
        location
      end
    end
  end
end