# frozen_string_literal: truemoduleSimpleCov## Representation of a source file including it's coverage data, source code,# source lines and featuring helpers to interpret that data.#classSourceFile# The full path to this source file (e.g. /User/colszowka/projects/simplecov/lib/simplecov/source_file.rb)attr_reader:filename# The array of coverage data received from the Coverage.resultattr_reader:coveragedefinitialize(filename,coverage)@filename=filename@coverage=coverageend# The path to this source file relative to the projects directorydefproject_filename@filename.sub(Regexp.new("^#{Regexp.escape(SimpleCov.root)}"),"")end# The source code for this file. Aliased as :sourcedefsrc# We intentionally read source code lazily to# suppress reading unused source code.@src||=File.open(filename,"rb",&:readlines)endaliassourcesrc# Returns all source lines for this file as instances of SimpleCov::SourceFile::Line,# and thus including coverage data. Aliased as :source_linesdeflines@lines||=build_linesendaliassource_lineslines# Returns all covered lines as SimpleCov::SourceFile::Linedefcovered_lines@covered_lines||=lines.select(&:covered?)end# Returns all lines that should have been, but were not covered# as instances of SimpleCov::SourceFile::Linedefmissed_lines@missed_lines||=lines.select(&:missed?)end# Returns all lines that are not relevant for coverage as# SimpleCov::SourceFile::Line instancesdefnever_lines@never_lines||=lines.select(&:never?)end# Returns all lines that were skipped as SimpleCov::SourceFile::Line instancesdefskipped_lines@skipped_lines||=lines.select(&:skipped?)end# Returns the number of relevant lines (covered + missed)deflines_of_codecovered_lines.size+missed_lines.sizeenddefbuild_linescoverage_exceeding_source_warnifcoverage["lines"].size>src.sizelines=src.map.with_index(1)do|src,i|SimpleCov::SourceFile::Line.new(src,i,coverage["lines"][i-1])endprocess_skipped_lines(lines)end# no_cov_chunks is zero indexed to work directly with the array holding the linesdefno_cov_chunks@no_cov_chunks||=build_no_cov_chunksenddefbuild_no_cov_chunksno_cov_lines=src.map.with_index(1).select{|line,_index|LinesClassifier.no_cov_line?(line)}warn"uneven number of nocov comments detected"ifno_cov_lines.size.odd?no_cov_lines.each_slice(2).mapdo|(_line_start,index_start),(_line_end,index_end)|index_start..index_endendenddefprocess_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!)}linesend# Warning to identify condition from Issue #56defcoverage_exceeding_source_warnwarn"Warning: coverage data provided by Coverage [#{coverage['lines'].size}] exceeds number of lines in #{filename} [#{src.size}]"end# Access SimpleCov::SourceFile::Line source lines by line numberdefline(number)lines[number-1]end# The coverage for this file in percent. 0 if the file has no coverage linesdefcovered_percentreturn100.0ifno_lines?return0.0ifrelevant_lines.zero?Float(covered_lines.size*100.0/relevant_lines.to_f)enddefcovered_strengthreturn0.0ifrelevant_lines.zero?(lines_strength/relevant_lines.to_f).round(1)enddefno_lines?lines.length.zero?||(lines.length==never_lines.size)enddeflines_strengthlines.map(&:coverage).compact.reduce(:+)enddefrelevant_lineslines.size-never_lines.size-skipped_lines.sizeend## Return all the branches inside current source filedefbranches@branches||=build_branchesenddefno_branches?total_branches.empty?enddefbranches_coverage_percentreturn100.0ifno_branches?return0.0ifcovered_branches.empty?Float(covered_branches.size*100.0/total_branches.size.to_f)end## Return the relevant branches to source filedeftotal_branchescovered_branches+missed_branchesend## Return hash with key of line number and branch coverage count as valuedefbranches_report@branches_report||=build_branches_reportend## Related to source file branches statistics## Call recursive method that transform our static hash to array of objects# @return [Array]#defbuild_branchescoverage_branch_data=coverage.fetch("branches",{})branches=coverage_branch_data.flat_mapdo|condition,coverage_branches|build_branches_from(condition,coverage_branches)endprocess_skipped_branches(branches)enddefprocess_skipped_branches(branches)returnbranchesifno_cov_chunks.empty?branches.eachdo|branch|branch.skipped!ifno_cov_chunks.any?{|no_cov_chunk|branch.overlaps_with?(no_cov_chunk)}endbranchesend# Since we are dumping to and loading from JSON, and we have arrays as keys those# don't make their way back to us intact e.g. just as a string## We should probably do something different here, but as it stands these are# our data structures that we write so eval isn't _too_ bad.## See #801#defrestore_ruby_data_structure(structure)# Tests use the real data structures (except for integration tests) so no need to# put them through here.returnstructureifstructure.is_a?(Array)# rubocop:disable Security/Evalevalstructure# rubocop:enable Security/Evalenddefbuild_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.mapdo|branch_data,hit_count|branch_data=restore_ruby_data_structure(branch_data)build_branch(branch_data,hit_count,condition_start_line)endenddefbuild_branch(branch_data,hit_count,condition_start_line)type,_id,start_line,_start_col,end_line,_end_col=branch_dataSourceFile::Branch.new(start_line: start_line,end_line: end_line,coverage: hit_count,inline: start_line==condition_start_line,type: type)end## Select the covered branches# Here we user tree schema because some conditions like case may have additional# else that is not in declared inside the code but given by default by coverage report## @return [Array]#defcovered_branches@covered_branches||=branches.select(&:covered?)end## Select the missed branches with coverage equal to zero## @return [Array]#defmissed_branches@missed_branches||=branches.select(&:missed?)enddefbranches_for_line(line_number)branches_report.fetch(line_number,[])end## Check if any branches missing on given line number## @param [Integer] line_number## @return [Boolean]#defline_with_missed_branch?(line_number)branches_for_line(line_number).select{|_type,count|count.zero?}.any?end## Build full branches report# Root branches represent the wrapper of all condition state that# have inside the branches## @return [Hash]#defbuild_branches_reportbranches.reject(&:skipped?).each_with_object({})do|branch,coverage_statistics|coverage_statistics[branch.report_line]||=[]coverage_statistics[branch.report_line]<<branch.reportendendendend