simplecov 0.22.0 → 1.0.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +81 -1
  3. data/LICENSE +1 -1
  4. data/README.md +1009 -511
  5. data/doc/alternate-formatters.md +0 -5
  6. data/doc/commercial-services.md +5 -5
  7. data/exe/simplecov +11 -0
  8. data/lib/minitest/simplecov_plugin.rb +13 -5
  9. data/lib/simplecov/autostart.rb +11 -0
  10. data/lib/simplecov/cli/clean.rb +47 -0
  11. data/lib/simplecov/cli/coverage.rb +91 -0
  12. data/lib/simplecov/cli/diff.rb +151 -0
  13. data/lib/simplecov/cli/dotfile.rb +100 -0
  14. data/lib/simplecov/cli/merge.rb +116 -0
  15. data/lib/simplecov/cli/open.rb +50 -0
  16. data/lib/simplecov/cli/report.rb +84 -0
  17. data/lib/simplecov/cli/run.rb +36 -0
  18. data/lib/simplecov/cli/serve.rb +139 -0
  19. data/lib/simplecov/cli/uncovered.rb +107 -0
  20. data/lib/simplecov/cli.rb +150 -0
  21. data/lib/simplecov/color.rb +74 -0
  22. data/lib/simplecov/combine/branches_combiner.rb +3 -2
  23. data/lib/simplecov/combine/files_combiner.rb +7 -1
  24. data/lib/simplecov/combine/lines_combiner.rb +19 -17
  25. data/lib/simplecov/combine/methods_combiner.rb +26 -0
  26. data/lib/simplecov/combine/results_combiner.rb +5 -4
  27. data/lib/simplecov/command_guesser.rb +46 -32
  28. data/lib/simplecov/configuration/coverage.rb +171 -0
  29. data/lib/simplecov/configuration/coverage_criteria.rb +156 -0
  30. data/lib/simplecov/configuration/filters.rb +195 -0
  31. data/lib/simplecov/configuration/formatting.rb +119 -0
  32. data/lib/simplecov/configuration/ignored_entries.rb +63 -0
  33. data/lib/simplecov/configuration/merging.rb +74 -0
  34. data/lib/simplecov/configuration/thresholds.rb +174 -0
  35. data/lib/simplecov/configuration.rb +79 -405
  36. data/lib/simplecov/coverage_statistics.rb +12 -9
  37. data/lib/simplecov/coverage_violations.rb +148 -0
  38. data/lib/simplecov/defaults.rb +27 -20
  39. data/lib/simplecov/directive.rb +162 -0
  40. data/lib/simplecov/exit_codes/exit_code_handling.rb +8 -2
  41. data/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb +19 -57
  42. data/lib/simplecov/exit_codes/maximum_overall_coverage_check.rb +45 -0
  43. data/lib/simplecov/exit_codes/minimum_coverage_by_file_check.rb +17 -27
  44. data/lib/simplecov/exit_codes/minimum_coverage_by_group_check.rb +41 -0
  45. data/lib/simplecov/exit_codes/minimum_overall_coverage_check.rb +38 -21
  46. data/lib/simplecov/exit_codes.rb +3 -0
  47. data/lib/simplecov/exit_handling.rb +158 -0
  48. data/lib/simplecov/file_list.rb +61 -17
  49. data/lib/simplecov/filter.rb +69 -24
  50. data/lib/simplecov/formatter/base.rb +101 -0
  51. data/lib/simplecov/formatter/html_formatter/public/application.css +1 -0
  52. data/lib/simplecov/formatter/html_formatter/public/application.js +18 -0
  53. data/lib/simplecov/formatter/html_formatter/public/favicon_green.png +0 -0
  54. data/lib/simplecov/formatter/html_formatter/public/favicon_red.png +0 -0
  55. data/lib/simplecov/formatter/html_formatter/public/favicon_yellow.png +0 -0
  56. data/lib/simplecov/formatter/html_formatter/public/index.html +56 -0
  57. data/lib/simplecov/formatter/html_formatter.rb +79 -0
  58. data/lib/simplecov/formatter/json_formatter/errors_formatter.rb +84 -0
  59. data/lib/simplecov/formatter/json_formatter/result_hash_formatter.rb +127 -0
  60. data/lib/simplecov/formatter/json_formatter/source_file_formatter.rb +99 -0
  61. data/lib/simplecov/formatter/json_formatter.rb +77 -0
  62. data/lib/simplecov/formatter/multi_formatter.rb +4 -5
  63. data/lib/simplecov/formatter/simple_formatter.rb +9 -11
  64. data/lib/simplecov/formatter.rb +4 -0
  65. data/lib/simplecov/last_run.rb +10 -3
  66. data/lib/simplecov/lines_classifier.rb +26 -13
  67. data/lib/simplecov/load_global_config.rb +9 -4
  68. data/lib/simplecov/parallel_adapters/base.rb +51 -0
  69. data/lib/simplecov/parallel_adapters/generic.rb +42 -0
  70. data/lib/simplecov/parallel_adapters/parallel_tests.rb +77 -0
  71. data/lib/simplecov/parallel_adapters.rb +83 -0
  72. data/lib/simplecov/parallel_coordination.rb +95 -0
  73. data/lib/simplecov/process.rb +20 -14
  74. data/lib/simplecov/profiles/bundler_filter.rb +1 -1
  75. data/lib/simplecov/profiles/hidden_filter.rb +1 -1
  76. data/lib/simplecov/profiles/rails.rb +24 -10
  77. data/lib/simplecov/profiles/root_filter.rb +6 -5
  78. data/lib/simplecov/profiles/strict.rb +32 -0
  79. data/lib/simplecov/profiles/test_frameworks.rb +1 -4
  80. data/lib/simplecov/profiles.rb +32 -3
  81. data/lib/simplecov/result/missing_source_files_reporter.rb +49 -0
  82. data/lib/simplecov/result/source_file_builder.rb +51 -0
  83. data/lib/simplecov/result.rb +97 -19
  84. data/lib/simplecov/result_adapter.rb +68 -6
  85. data/lib/simplecov/result_merger/legacy_format_adapter.rb +28 -0
  86. data/lib/simplecov/result_merger/resultset_file.rb +38 -0
  87. data/lib/simplecov/result_merger/resultset_store.rb +50 -0
  88. data/lib/simplecov/result_merger.rb +46 -90
  89. data/lib/simplecov/result_processing.rb +162 -0
  90. data/lib/simplecov/simulate_coverage.rb +54 -8
  91. data/lib/simplecov/source_file/branch.rb +1 -3
  92. data/lib/simplecov/source_file/branch_builder.rb +114 -0
  93. data/lib/simplecov/source_file/builder_context.rb +28 -0
  94. data/lib/simplecov/source_file/line.rb +7 -2
  95. data/lib/simplecov/source_file/line_builder.rb +43 -0
  96. data/lib/simplecov/source_file/method.rb +52 -0
  97. data/lib/simplecov/source_file/method_builder.rb +58 -0
  98. data/lib/simplecov/source_file/ruby_data_parser.rb +88 -0
  99. data/lib/simplecov/source_file/skip_chunks.rb +77 -0
  100. data/lib/simplecov/source_file/source_loader.rb +63 -0
  101. data/lib/simplecov/source_file/statistics.rb +57 -0
  102. data/lib/simplecov/source_file.rb +66 -232
  103. data/lib/simplecov/static_coverage_extractor/visitor.rb +193 -0
  104. data/lib/simplecov/static_coverage_extractor.rb +111 -0
  105. data/lib/simplecov/useless_results_remover.rb +16 -7
  106. data/lib/simplecov/version.rb +1 -1
  107. data/lib/simplecov-html.rb +4 -0
  108. data/lib/simplecov.rb +131 -377
  109. data/lib/simplecov_json_formatter.rb +4 -0
  110. data/schemas/coverage-v1.0.schema.json +300 -0
  111. data/schemas/coverage.schema.json +300 -0
  112. metadata +88 -56
  113. data/lib/simplecov/default_formatter.rb +0 -20
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ class SourceFile
5
+ # Builds the `SourceFile::Line` objects for a source file from the
6
+ # raw line-coverage array. Each line carries its source text, its
7
+ # 1-based line number, and the Coverage hit count (or nil for
8
+ # never-counted lines). Applies `# simplecov:disable` /
9
+ # `# :nocov:` block ranges via `skipped!`.
10
+ class LineBuilder
11
+ def initialize(source_file)
12
+ @source_file = source_file
13
+ end
14
+
15
+ def call
16
+ lines = build_lines
17
+ mark_skipped(lines, @source_file.skip_chunks_for(:line))
18
+ lines
19
+ end
20
+
21
+ private
22
+
23
+ # When `:line` coverage is disabled, the Ruby Coverage module
24
+ # doesn't emit "lines" data, so look up `nil` (never-counted) for
25
+ # every position. The source rows are still useful — e.g. for the
26
+ # HTML report's source view — even without per-line hits.
27
+ def build_lines
28
+ line_coverage = @source_file.coverage_data["lines"] || []
29
+ @source_file.src.map.with_index(1) do |src, i|
30
+ SourceFile::Line.new(src, i, line_coverage[i - 1])
31
+ end
32
+ end
33
+
34
+ # The array the lines are kept in is 0-based whereas the line
35
+ # numbers in the chunks are 1-based (more understandable elsewhere),
36
+ # so each range needs to be shifted down by one to slice into the
37
+ # `lines` array.
38
+ def mark_skipped(lines, chunks)
39
+ chunks.each { |chunk| lines[(chunk.begin - 1)..(chunk.end - 1)].each(&:skipped!) }
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ class SourceFile
5
+ # Represents a single method detected in coverage data.
6
+ # Provides helpers similar to Branch for coverage status.
7
+ class Method
8
+ attr_reader :source_file, :coverage, :class_name, :method_name,
9
+ :start_line, :start_col, :end_line, :end_col
10
+
11
+ def initialize(source_file, info, coverage)
12
+ @source_file = source_file
13
+ @class_name, @method_name, @start_line, @start_col, @end_line, @end_col = info
14
+ @coverage = coverage
15
+ end
16
+
17
+ def covered?
18
+ !skipped? && coverage.positive?
19
+ end
20
+
21
+ def skipped?
22
+ return @skipped if defined?(@skipped)
23
+
24
+ @skipped = lines.all?(&:skipped?)
25
+ end
26
+
27
+ # Flag the method as skipped directly, without going through its lines.
28
+ def skipped!
29
+ @skipped = true
30
+ end
31
+
32
+ def missed?
33
+ !skipped? && coverage.zero?
34
+ end
35
+
36
+ def lines
37
+ @lines ||= start_line && end_line ? source_file.lines[(start_line - 1)..(end_line - 1)] : []
38
+ end
39
+
40
+ # Whether this method's source range intersects the given inclusive line range.
41
+ def overlaps_with?(line_range)
42
+ return false unless start_line && end_line
43
+
44
+ start_line <= line_range.end && end_line >= line_range.begin
45
+ end
46
+
47
+ def to_s
48
+ "#{class_name}##{method_name}"
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ class SourceFile
5
+ # Builds the `SourceFile::Method` objects for a source file from
6
+ # the raw method-coverage data. Applies the
7
+ # `ignore_methods :eval_generated` filter and marks methods inside
8
+ # `# simplecov:disable` / `# :nocov:` chunks as skipped.
9
+ class MethodBuilder
10
+ def initialize(source_file)
11
+ @source_file = source_file
12
+ end
13
+
14
+ def call
15
+ methods = @source_file.coverage_data.fetch("methods", {}).filter_map do |info, hit_count|
16
+ info = RubyDataParser.call(info)
17
+ next if eval_generated_to_ignore?(info)
18
+
19
+ SourceFile::Method.new(@source_file, info, hit_count)
20
+ end
21
+
22
+ process_skipped(methods)
23
+ end
24
+
25
+ private
26
+
27
+ # See `BranchBuilder#eval_generated_condition_to_ignore?` for the
28
+ # rationale. Coverage reports an eval'd `def` at the eval caller's
29
+ # line and name, so a method whose `(name, start_line)` is absent
30
+ # from the real-source `def` set is eval-generated. Only consulted
31
+ # when the user has opted in via
32
+ # `SimpleCov.ignore_methods :eval_generated`. See #1046.
33
+ def eval_generated_to_ignore?(info)
34
+ return false unless SimpleCov.ignored_method?(:eval_generated)
35
+
36
+ positions = @source_file.real_source_positions
37
+ # simplecov:disable branch — nil branch fires only when Prism is unavailable
38
+ return false unless positions
39
+
40
+ # simplecov:enable branch
41
+
42
+ _class_name, name, start_line, * = info
43
+ !positions[:methods].include?([name, start_line])
44
+ end
45
+
46
+ def process_skipped(methods)
47
+ chunks = @source_file.skip_chunks_for(:method)
48
+ return methods if chunks.empty?
49
+
50
+ methods.each do |method|
51
+ method.skipped! if chunks.any? { |chunk| method.overlaps_with?(chunk) }
52
+ end
53
+
54
+ methods
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ripper"
4
+
5
+ module SimpleCov
6
+ class SourceFile
7
+ # `Coverage.result` reports condition and method keys as Ruby
8
+ # arrays. When the resultset is round-tripped through JSON those
9
+ # array keys become their stringified inspect form, so this parser
10
+ # walks the literal back into a real Array without using `eval` (see
11
+ # #801). The grammar covers symbols, strings, integers, unary minus,
12
+ # and constant paths — every shape Coverage ever emits.
13
+ module RubyDataParser
14
+ module_function
15
+
16
+ # Tests use the real data structures (except for integration tests)
17
+ # so no need to put them through here.
18
+ def call(structure)
19
+ return structure if structure.is_a?(Array)
20
+
21
+ parse_array_string(structure.to_s)
22
+ end
23
+
24
+ # Parse a string like '[:if, 0, 3, 4, 3, 21]' or
25
+ # '["ClassName", :method1, 2, 2, 5, 5]' back into a Ruby array.
26
+ def parse_array_string(str)
27
+ # Try plain Ripper first; only pre-quote `#<...>` inspect segments
28
+ # if the input isn't already valid Ruby (otherwise we corrupt
29
+ # `"#<Class:Foo>"` strings that *are* valid Ruby literals — exactly
30
+ # the shape simplecov-on-simplecov method-coverage keys take).
31
+ sexp = Ripper.sexp(str) || Ripper.sexp(quote_inspected_class_segments(str))
32
+ # simplecov:disable — defensive: Ripper.sexp returning nil from both passes requires malformed input
33
+ array_node = sexp&.dig(1, 0)
34
+ # simplecov:enable
35
+ raise ArgumentError, "expected array literal: #{str.inspect}" unless array_node && array_node[0] == :array
36
+
37
+ Array(array_node[1]).map { |element| parse_element(element) }
38
+ end
39
+
40
+ def parse_element(node)
41
+ case node[0]
42
+ when :@int, :unary then parse_integer_node(node)
43
+ when :symbol_literal, :dyna_symbol then parse_symbol_node(node)
44
+ when :string_literal then unescape_ruby(string_literal_text(node[1]))
45
+ when :var_ref then node.dig(1, 1) # `Foo`
46
+ when :const_path_ref then "#{parse_element(node[1])}::#{node[2][1]}" # `Foo::Bar`
47
+ else
48
+ # simplecov:disable — defensive fallback for unexpected Ripper node shapes
49
+ raise ArgumentError, "unexpected element: #{node.inspect}"
50
+ # simplecov:enable
51
+ end
52
+ end
53
+
54
+ def parse_integer_node(node)
55
+ node[0] == :@int ? node[1].to_i : -node[2][1].to_i
56
+ end
57
+
58
+ def parse_symbol_node(node)
59
+ if node[0] == :symbol_literal
60
+ node.dig(1, 1, 1).to_sym
61
+ else
62
+ unescape_ruby(string_literal_text(node[1])).to_sym
63
+ end
64
+ end
65
+
66
+ # Concatenate the text fragments of a `:string_content` node. Ripper
67
+ # may emit zero, one, or many `:@tstring_content` children depending
68
+ # on the literal.
69
+ def string_literal_text(string_content)
70
+ Array(string_content[1..]).map { |child| child[1] }.join
71
+ end
72
+
73
+ # Undo the same backslash-prefix escapes the previous hand-rolled
74
+ # parser undid: `\X` → `X` for any X.
75
+ def unescape_ruby(raw)
76
+ raw.gsub(/\\(.)/) { ::Regexp.last_match(1) }
77
+ end
78
+
79
+ # Method coverage keys can contain inspect-format class references
80
+ # like `#<Class:Foo>` or `#<Class:0x...>`, which aren't valid Ruby
81
+ # syntax. Wrap them in quotes so Ripper can parse the surrounding
82
+ # array literal; downstream we treat them as opaque strings.
83
+ def quote_inspected_class_segments(str)
84
+ str.gsub(/#<[^>]*>/) { |segment| %("#{segment.gsub('"', '\\"')}") }
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+
5
+ module SimpleCov
6
+ class SourceFile
7
+ # Computes the set of line ranges that should be excluded from a
8
+ # SourceFile's coverage for each criterion. Two sources contribute:
9
+ #
10
+ # * The deprecated `# :nocov:` block toggle (lines wrapped between
11
+ # even-numbered pairs of nocov markers are excluded from line and
12
+ # branch coverage).
13
+ # * `# simplecov:disable` / `# simplecov:enable` block directives,
14
+ # which can be scoped per-criterion (`# simplecov:disable branch`,
15
+ # etc.) — see `SimpleCov::Directive`.
16
+ class SkipChunks
17
+ @nocov_warned = Set.new
18
+ class << self
19
+ attr_reader :nocov_warned
20
+ end
21
+
22
+ def initialize(filename, src)
23
+ @filename = filename
24
+ @src = src
25
+ end
26
+
27
+ # `:method` ignores nocov chunks (Ruby's Coverage doesn't tie
28
+ # method entries to line ranges); `:line` / `:branch` honor both
29
+ # the nocov chunks and the per-criterion directive ranges.
30
+ def for(criterion)
31
+ if criterion == :method
32
+ directive_chunks.fetch(:method)
33
+ else
34
+ nocov_chunks + directive_chunks.fetch(criterion)
35
+ end
36
+ end
37
+
38
+ # no_cov_chunks is zero indexed to work directly with the array
39
+ # holding the lines.
40
+ def nocov_chunks
41
+ @nocov_chunks ||= build_nocov_chunks
42
+ end
43
+
44
+ def directive_chunks
45
+ @directive_chunks ||= Directive.disabled_ranges(@src)
46
+ end
47
+
48
+ private
49
+
50
+ def build_nocov_chunks
51
+ no_cov_lines = @src.map.with_index(1).select { |line_src, _index| LinesClassifier.no_cov_line?(line_src) }
52
+
53
+ warn_nocov_deprecation(no_cov_lines.first.last) if no_cov_lines.any?
54
+
55
+ # If we have an uneven number of nocovs we assume they go to the
56
+ # end of the file, the source doesn't really matter. Can't deal
57
+ # with this within the each_slice due to differing behavior in
58
+ # JRuby: jruby/jruby#6048
59
+ no_cov_lines << ["", @src.size] if no_cov_lines.size.odd?
60
+
61
+ no_cov_lines.each_slice(2).map do |(_line_src_start, index_start), (_line_src_end, index_end)|
62
+ index_start..index_end
63
+ end
64
+ end
65
+
66
+ # Emit a one-time-per-file deprecation warning pointing the user
67
+ # at the `# simplecov:disable` / `# simplecov:enable` replacement.
68
+ def warn_nocov_deprecation(first_line_number)
69
+ return unless self.class.nocov_warned.add?(@filename)
70
+
71
+ token = SimpleCov.current_nocov_token
72
+ warn "#{@filename}:#{first_line_number}: [DEPRECATION] `# :#{token}:` is deprecated and will be removed " \
73
+ "in a future release. Replace with `# simplecov:disable` / `# simplecov:enable` block comments."
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ class SourceFile
5
+ # Reads a source file into an array of lines, honoring the source's
6
+ # shebang and `coding:` magic comment when present. Always
7
+ # transcodes to UTF-8 with invalid/undef bytes replaced — both for
8
+ # JRuby compatibility and to keep encoding shenanigans in one place
9
+ # (see #866).
10
+ module SourceLoader
11
+ SHEBANG_REGEX = /\A#!/
12
+ RUBY_FILE_ENCODING_MAGIC_COMMENT_REGEX = /\A#\s*(?:-\*-)?\s*(?:en)?coding:\s*(\S+)\s*(?:-\*-)?\s*\z/
13
+
14
+ module_function
15
+
16
+ def call(filename)
17
+ lines = []
18
+ # The default encoding is UTF-8
19
+ File.open(filename, "rb:UTF-8") do |file|
20
+ current_line = file.gets
21
+
22
+ if shebang?(current_line)
23
+ lines << current_line
24
+ current_line = file.gets
25
+ end
26
+
27
+ read_lines(file, lines, current_line)
28
+ end
29
+ end
30
+
31
+ def shebang?(line)
32
+ SHEBANG_REGEX.match?(line)
33
+ end
34
+
35
+ def read_lines(file, lines, current_line)
36
+ return lines unless current_line
37
+
38
+ set_encoding_based_on_magic_comment(file, current_line)
39
+ lines.concat([current_line], ensure_remove_undefs(file.readlines))
40
+ end
41
+
42
+ # Encoding magic comment must be placed at first line except for
43
+ # shebang.
44
+ def set_encoding_based_on_magic_comment(file, line)
45
+ if (match = RUBY_FILE_ENCODING_MAGIC_COMMENT_REGEX.match(line))
46
+ file.set_encoding(match[1], "UTF-8")
47
+ end
48
+ end
49
+
50
+ # invalid/undef replace are technically not really necessary but
51
+ # nice to have and work around a JRuby incompatibility. Setting
52
+ # these options on `file.set_encoding` doesn't seem to work
53
+ # properly, so it has to be done here.
54
+ def ensure_remove_undefs(file_lines)
55
+ file_lines.each do |line|
56
+ # simplecov:disable — defensive: only fires for non-UTF-8 source files
57
+ line.encode!("UTF-8", invalid: :replace, undef: :replace) unless line.encoding == Encoding::UTF_8
58
+ # simplecov:enable
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ class SourceFile
5
+ # Builds the `CoverageStatistics` triple (`:line`, `:branch`, `:method`)
6
+ # for a SourceFile, regardless of which criteria were actually enabled
7
+ # during the run — disabled or empty criteria collapse to 0/0/0 so
8
+ # downstream consumers don't have to special-case enable-state.
9
+ class Statistics
10
+ def initialize(source_file)
11
+ @source_file = source_file
12
+ end
13
+
14
+ def call
15
+ {
16
+ **line_statistics,
17
+ **branch_statistics,
18
+ **method_statistics
19
+ }
20
+ end
21
+
22
+ private
23
+
24
+ def line_statistics
25
+ sf = @source_file
26
+ {
27
+ line: CoverageStatistics.new(
28
+ total_strength: sf.lines.sum { |line| line.coverage.to_i },
29
+ covered: sf.covered_lines.size,
30
+ missed: sf.missed_lines.size,
31
+ omitted: sf.never_lines.size
32
+ )
33
+ }
34
+ end
35
+
36
+ def branch_statistics
37
+ sf = @source_file
38
+ # Files added via track_files but never loaded/required have no
39
+ # branch data. Report 0% instead of misleading 100% (see #902).
40
+ if sf.not_loaded? && sf.covered_branches.empty? && sf.missed_branches.empty?
41
+ return {branch: CoverageStatistics.new(covered: 0, missed: 0, percent: 0.0)}
42
+ end
43
+
44
+ {branch: CoverageStatistics.new(covered: sf.covered_branches.size, missed: sf.missed_branches.size)}
45
+ end
46
+
47
+ def method_statistics
48
+ sf = @source_file
49
+ if sf.not_loaded? && sf.covered_methods.empty? && sf.missed_methods.empty?
50
+ return {method: CoverageStatistics.new(covered: 0, missed: 0, percent: 0.0)}
51
+ end
52
+
53
+ {method: CoverageStatistics.new(covered: sf.covered_methods.size, missed: sf.missed_methods.size)}
54
+ end
55
+ end
56
+ end
57
+ end