class SimpleCov::SourceFile


source lines and featuring helpers to interpret that data.
Representation of a source file including it’s coverage data, source code,

def branches

Return all the branches inside current source file
def branches
  @branches ||= build_branches
end

def branches_coverage_percent

def branches_coverage_percent
  return 100.0 if no_branches?
  return 0.0 if covered_branches.empty?
  Float(covered_branches.size * 100.0 / total_branches.size.to_f)
end

def branches_for_line(line_number)

def branches_for_line(line_number)
  branches_report.fetch(line_number, [])
end

def branches_report

Return hash with key of line number and branch coverage count as value
def branches_report
  @branches_report ||= build_branches_report
end

def build_branch(branch_data, hit_count, condition_start_line, condition_id)

def build_branch(branch_data, hit_count, condition_start_line, condition_id)
  type, id, start_line, _start_col, end_line, _end_col = branch_data
  SourceFile::Branch.new(
    # rubocop these are keyword args please let me keep them, thank you
    # rubocop:disable Style/HashSyntax
    start_line: start_line,
    end_line:   end_line,
    coverage:   hit_count,
    inline:     start_line == condition_start_line,
    positive:   positive_branch?(condition_id, id, type)
    # rubocop:enable Style/HashSyntax
  )
end

def build_branches

Returns:
  • (Array) -
def build_branches
  coverage_branch_data = coverage.fetch(:branches, {})
  branches = coverage_branch_data.flat_map do |condition, coverage_branches|
    build_branches_from(condition, coverage_branches)
  end
  process_skipped_branches(branches)
end

def build_branches_from(condition, branches)

def build_branches_from(condition, branches)
  # the format handed in from the coverage data is like this:
  #
  #     [:then, 4, 6, 6, 6, 10]
  #
  # which is [type, id, start_line, start_col, end_line, end_col]
  condition_type, condition_id, condition_start_line, * = restore_ruby_data_structure(condition)
  branches
    .map { |branch_data, hit_count| [restore_ruby_data_structure(branch_data), hit_count] }
    .reject { |branch_data, _hit_count| ignore_branch?(branch_data, condition_type, condition_start_line) }
    .map { |branch_data, hit_count| build_branch(branch_data, hit_count, condition_start_line, condition_id) }
end

def build_branches_report

Returns:
  • (Hash) -
def build_branches_report
  branches.reject(&:skipped?).each_with_object({}) do |branch, coverage_statistics|
    coverage_statistics[branch.report_line] ||= []
    coverage_statistics[branch.report_line] << branch.report
  end
end

def build_lines

def build_lines
  coverage_exceeding_source_warn if coverage[:lines].size > src.size
  lines = src.map.with_index(1) do |src, i|
    SimpleCov::SourceFile::Line.new(src, i, coverage[:lines][i - 1])
  end
  process_skipped_lines(lines)
end

def build_no_cov_chunks

def build_no_cov_chunks
  no_cov_lines = src.map.with_index(1).select { |line, _index| LinesClassifier.no_cov_line?(line) }
  warn "uneven number of nocov comments detected" if no_cov_lines.size.odd?
  no_cov_lines.each_slice(2).map do |(_line_start, index_start), (_line_end, index_end)|
    index_start..index_end
  end
end

def coverage_exceeding_source_warn

Warning to identify condition from Issue #56
def coverage_exceeding_source_warn
  warn "Warning: coverage data provided by Coverage [#{coverage[:lines].size}] exceeds number of lines in #{filename} [#{src.size}]"
end

def covered_branches

Returns:
  • (Array) -
def covered_branches
  @covered_branches ||= branches.select(&:covered?)
end

def covered_lines

Returns all covered lines as SimpleCov::SourceFile::Line
def covered_lines
  @covered_lines ||= lines.select(&:covered?)
end

def covered_percent

The coverage for this file in percent. 0 if the file has no coverage lines
def covered_percent
  return 100.0 if no_lines?
  return 0.0 if relevant_lines.zero?
  Float(covered_lines.size * 100.0 / relevant_lines.to_f)
end

def covered_strength

def covered_strength
  return 0.0 if relevant_lines.zero?
  (lines_strength / relevant_lines.to_f).round(1)
end

def ignore_branch?(branch_data, condition_type, condition_start_line)

def ignore_branch?(branch_data, condition_type, condition_start_line)
  branch_type = branch_data[0]
  branch_start_line = branch_data[2]
  # branch coverage always reports case to be with an else branch even when
  # there is no else branch to be covered, it's noticable by the reported start
  # line being the same as that of the condition/case
  condition_type == :case &&
    branch_type == :else &&
    condition_start_line == branch_start_line
end

def initialize(filename, coverage)

def initialize(filename, coverage)
  @filename = filename.to_s
  @coverage = coverage
end

def line(number)

Access SimpleCov::SourceFile::Line source lines by line number
def line(number)
  lines[number - 1]
end

def line_with_missed_branch?(line_number)

Returns:
  • (Boolean) -

Parameters:
  • line_number (Integer) --
def line_with_missed_branch?(line_number)
  branches_for_line(line_number).select { |count, _sign| count.zero? }.any?
end

def lines

and thus including coverage data. Aliased as :source_lines
Returns all source lines for this file as instances of SimpleCov::SourceFile::Line,
def lines
  @lines ||= build_lines
end

def lines_of_code

Returns the number of relevant lines (covered + missed)
def lines_of_code
  covered_lines.size + missed_lines.size
end

def lines_strength

def lines_strength
  lines.map(&:coverage).compact.reduce(:+)
end

def missed_branches

Returns:
  • (Array) -
def missed_branches
  @missed_branches ||= branches.select(&:missed?)
end

def missed_lines

as instances of SimpleCov::SourceFile::Line
Returns all lines that should have been, but were not covered
def missed_lines
  @missed_lines ||= lines.select(&:missed?)
end

def never_lines

SimpleCov::SourceFile::Line instances
Returns all lines that are not relevant for coverage as
def never_lines
  @never_lines ||= lines.select(&:never?)
end

def no_branches?

def no_branches?
  total_branches.empty?
end

def no_cov_chunks

no_cov_chunks is zero indexed to work directly with the array holding the lines
def no_cov_chunks
  @no_cov_chunks ||= build_no_cov_chunks
end

def no_lines?

def no_lines?
  lines.length.zero? || (lines.length == never_lines.size)
end

def positive_branch?(condition_id, branch_id, branch_type)

Returns:
  • (Boolean) -
def positive_branch?(condition_id, branch_id, branch_type)
  return true if branch_type == :when
  branch_id == (1 + condition_id)
end

def process_skipped_branches(branches)

def process_skipped_branches(branches)
  return branches if no_cov_chunks.empty?
  branches.each do |branch|
    branch.skipped! if no_cov_chunks.any? { |no_cov_chunk| branch.overlaps_with?(no_cov_chunk) }
  end
  branches
end

def process_skipped_lines(lines)

def process_skipped_lines(lines)
  # the array the lines are kept in is 0-based whereas the line numbers in the nocov
  # chunks are 1-based and are expected to be like this in other parts (and it's also
  # arguably more understandable)
  no_cov_chunks.each { |chunk| lines[(chunk.begin - 1)..(chunk.end - 1)].each(&:skipped!) }
  lines
end

def project_filename

The path to this source file relative to the projects directory
def project_filename
  @filename.sub(Regexp.new("^#{Regexp.escape(SimpleCov.root)}"), "")
end

def relevant_lines

def relevant_lines
  lines.size - never_lines.size - skipped_lines.size
end

def restore_ruby_data_structure(structure)


See #801

our data structures that we write so eval isn't _too_ bad.
We should probably do something different here, but as it stands these are

don't make their way back to us intact e.g. just as a string or a symbol (currently keys are symbolized).
Since we are dumping to and loading from JSON, and we have arrays as keys those
def restore_ruby_data_structure(structure)
  # Tests use the real data structures (except for integration tests) so no need to
  # put them through here.
  return structure if structure.is_a?(Array)
  # as of right now the keys are still symbolized
  # rubocop:disable Security/Eval
  eval structure.to_s
  # rubocop:enable Security/Eval
end

def skipped_lines

Returns all lines that were skipped as SimpleCov::SourceFile::Line instances
def skipped_lines
  @skipped_lines ||= lines.select(&:skipped?)
end

def src

The source code for this file. Aliased as :source
def src
  # We intentionally read source code lazily to
  # suppress reading unused source code.
  @src ||= File.open(filename, "rb", &:readlines)
end

def total_branches

Return the relevant branches to source file
def total_branches
  covered_branches + missed_branches
end