Class: Teek::MethodCoverageService Private

Inherits:
Object
  • Object
show all
Defined in:
lib/teek/method_coverage_service.rb

Overview

This class is part of a private API. You should avoid using this class if possible, as it may be removed or be changed in the future.

Transforms SimpleCov line coverage data into per-method coverage.

Merges coverage data from all test suite result files, uses Prism to parse Ruby source files and find method definitions, then maps SimpleCov line coverage to calculate per-method percentages.

Examples:

service = MethodCoverageService.new(
  coverage_dir: "coverage",
  source_dirs: ["lib"]
)
service.call
# => writes coverage/method_coverage.json

Defined Under Namespace

Classes: MethodVisitor

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(coverage_dir:, source_dirs: ["lib"], output_path: nil) ⇒ MethodCoverageService

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns a new instance of MethodCoverageService.



25
26
27
28
29
# File 'lib/teek/method_coverage_service.rb', line 25

def initialize(coverage_dir:, source_dirs: ["lib"], output_path: nil)
  @coverage_dir = coverage_dir
  @source_dirs = source_dirs
  @output_path = output_path || File.join(coverage_dir, "method_coverage.json")
end

Instance Attribute Details

#coverage_dirObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



23
24
25
# File 'lib/teek/method_coverage_service.rb', line 23

def coverage_dir
  @coverage_dir
end

#output_pathObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



23
24
25
# File 'lib/teek/method_coverage_service.rb', line 23

def output_path
  @output_path
end

#source_dirsObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



23
24
25
# File 'lib/teek/method_coverage_service.rb', line 23

def source_dirs
  @source_dirs
end

Instance Method Details

#callObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/teek/method_coverage_service.rb', line 31

def call
  coverage_files = Dir.glob(File.join(coverage_dir, "results", "*", "coverage.json"))
  resultset_files = Dir.glob(File.join(coverage_dir, "results", "*", ".resultset.json"))
  if coverage_files.empty? && resultset_files.empty?
    warn "No coverage files found in #{coverage_dir}/results/"
    return nil
  end

  coverage_data = load_and_merge_coverage(coverage_files)
  result = {}

  # Collect all methods from all files
  all_methods = []
  source_files.each do |file|
    all_methods.concat(extract_methods(file))
  end

  # Group by class path and calculate coverage
  all_methods.group_by { |m| m[:class_path] }.each do |class_path, methods|
    class_result = { "class_methods" => {}, "instance_methods" => {} }
    total_covered = 0
    total_relevant = 0

    methods.each do |method|
      file_coverage = coverage_data[method[:file]]
      next unless file_coverage

      cov = calculate_coverage(file_coverage, method[:start_line], method[:end_line])
      next unless cov

      # Store [percent, lines_string] - compact format
      method_data = [cov[:percent], cov[:lines]]
      if method[:scope] == :class
        class_result["class_methods"][method[:name]] = method_data
      else
        class_result["instance_methods"][method[:name]] = method_data
      end

      total_covered += cov[:covered]
      total_relevant += cov[:relevant]
    end

    next if class_result["class_methods"].empty? && class_result["instance_methods"].empty?

    if total_relevant > 0
      class_result["total"] = (total_covered.to_f / total_relevant * 100).round(1)
    end

    result[class_path] = class_result
  end

  File.write(output_path, JSON.pretty_generate(result))
  puts "Generated method coverage: #{output_path} (#{result.size} classes/modules)"
  result
end