class DeepCover::Analyser::Ruby25LikeBranch::NodeCoverageExtrator
This is the class doing the work. Since everything is about the node, the class delegates missing methods to the node, simplifying the code.
Public Class Methods
new(node = nil)
click to toggle source
# File lib/deep_cover/analyser/ruby25_like_branch.rb, line 30 def initialize(node = nil) self.node = node @loc_index = 0 end
Public Instance Methods
branch_coverage(node)
click to toggle source
# File lib/deep_cover/analyser/ruby25_like_branch.rb, line 38 def branch_coverage(node) self.node = node case node when Node::Case handle_case when Node::Csend handle_csend when Node::If handle_if when Node::ShortCircuit handle_short_circuit when Node::Until, Node::While, Node::UntilPost, Node::WhilePost handle_until_while end end
handle_case()
click to toggle source
# File lib/deep_cover/analyser/ruby25_like_branch.rb, line 54 def handle_case cond_info = [:case, *node_loc_infos] sub_keys = [:when] * (branches.size - 1) + [:else] empty_fallbacks = whens.map { |w| wrap_rwhitespace_and_comments_if_ruby25(w.loc_hash[:begin] || w.loc_hash[:expression]).end } empty_fallbacks.map!(&:begin) if loc_hash[:else] empty_fallbacks << wrap_rwhitespace_and_comments_if_ruby25(loc_hash[:else]).end else # DeepCover manually inserts a `else` for Case when there isn't one for tracker purposes. # The normal behavior of ruby25's branch coverage when there is no else is to return the loc of the node # So we sent that fallback. empty_fallbacks << expression end branches_locs = whens.map do |when_node| next when_node.body if when_node.body.is_a?(Node::EmptyBody) start_at = when_node.loc_hash[:begin] start_at = start_at.wrap_rwhitespace_and_comments.end if start_at start_at ||= when_node.body.expression.begin end_at = when_node.body.expression.end start_at.with(end_pos: end_at.end_pos) end branches_locs << node.else clauses_infos = infos_for_branches(branches_locs, sub_keys, empty_fallbacks, execution_counts: branches.map(&:execution_count)) [cond_info, clauses_infos] end
handle_csend()
click to toggle source
# File lib/deep_cover/analyser/ruby25_like_branch.rb, line 87 def handle_csend # csend wraps the comment but not the newlines node_range = wrap_rwhitespace_and_comments_if_ruby25(node.expression, whitespaces: /\A[ \t\r\f]+/) cond_info = [:"&.", *node_loc_infos(node_range)] false_branch, true_branch = branches [cond_info, {[:then, *node_loc_infos(node_range)] => true_branch.execution_count, [:else, *node_loc_infos(node_range)] => false_branch.execution_count, }, ] end
handle_if()
click to toggle source
# File lib/deep_cover/analyser/ruby25_like_branch.rb, line 98 def handle_if key = style == :unless ? :unless : :if node_range = extend_elsif_range cond_info = [key, *node_loc_infos(node_range)] sub_keys = [:then, :else] if style == :ternary empty_fallback_locs = [nil, nil] else first_clause_fallback = wrap_rwhitespace_and_comments_if_ruby25(loc_hash[:begin]) if loc_hash[:begin] first_clause_fallback ||= wrap_rwhitespace_and_comments_if_ruby25(condition.expression) # Ruby26 wraps the comments but only on the same line # No need for condition since Ruby25 wraps all of them first_clause_fallback = first_clause_fallback.wrap_final_comment.end else_loc = loc_hash[:else] if else_loc second_clause_fallback = wrap_rwhitespace_and_comments_if_ruby25(else_loc).end elsif !modifier? second_clause_fallback = root_if_node.loc_hash[:end].begin end empty_fallback_locs = [first_clause_fallback, second_clause_fallback] end # loc can be nil if the clause can't be empty, such as ternary and modifer if/unless if key == :unless sub_keys.reverse! empty_fallback_locs.reverse! end branches_locs = branches execution_counts = branches_locs.map(&:execution_count) if modifier? branches_locs = branches_locs.map do |branch| if branch.is_a?(Node::Kwbegin) if branch.instructions.empty? wrap_rwhitespace_and_comments_if_ruby25(branch.loc_hash[:begin]).end elsif branch.instructions.first.is_a?(Node::Ensure) # Kernel.binding.pry end_pos = wrap_rwhitespace_and_comments_if_ruby25(branch.instructions.last.expression).end_pos wrap_rwhitespace_and_comments_if_ruby25(branch.loc_hash[:begin]).end.with(end_pos: end_pos) else end_pos = branch.instructions.last.expression.end_pos branch.loc_hash[:begin].wrap_rwhitespace_and_comments.end.with(end_pos: end_pos) end else branch end end end branches_locs[1] = extend_elsif_range(branches_locs[1]) clauses_infos = infos_for_branches(branches_locs, sub_keys, empty_fallback_locs, execution_counts: execution_counts, node_range: node_range) [cond_info, clauses_infos] end
handle_short_circuit()
click to toggle source
# File lib/deep_cover/analyser/ruby25_like_branch.rb, line 156 def handle_short_circuit cond_info = [operator, *node_loc_infos] sub_keys = [:then, :else] sub_keys.reverse! if node.is_a?(Node::Or) [cond_info, infos_for_branches(branches, sub_keys, [nil, nil])] end
handle_until_while()
click to toggle source
# File lib/deep_cover/analyser/ruby25_like_branch.rb, line 164 def handle_until_while key = loc_hash[:keyword].source.to_sym base_info = [key, *node_loc_infos] body_node = if node.is_a?(Node::WhilePost) || node.is_a?(Node::UntilPost) if !body.instructions.empty? end_pos = body.instructions.last.expression.end_pos body.instructions.first.expression.with(end_pos: end_pos) else wrap_rwhitespace_and_comments_if_ruby25(body.loc_hash[:begin]).end end elsif body.is_a?(Node::Begin) && !body.expressions.empty? end_pos = body.expressions.last.expression.end_pos body.expressions.first.expression.with(end_pos: end_pos) elsif body.is_a?(Node::EmptyBody) wrap_rwhitespace_and_comments_if_ruby25(condition.loc_hash[:expression]).end else body end [base_info, {[:body, *node_loc_infos(body_node)] => body.execution_count}] end
Protected Instance Methods
extend_elsif_range(possible_elsif = node)
click to toggle source
If the actual else clause (final one) of an if…elsif…end is empty, then Ruby25 wraps the final whitespace
# File lib/deep_cover/analyser/ruby25_like_branch.rb, line 189 def extend_elsif_range(possible_elsif = node) return possible_elsif unless possible_elsif.is_a?(Node::If) && possible_elsif.style == :elsif deepest_if = possible_elsif.deepest_elsif_node if deepest_if.false_branch.is_a?(Node::EmptyBody) return wrap_rwhitespace_and_comments_if_ruby25(possible_elsif.expression) end possible_elsif end
infos_for_branch(branch, key, empty_fallback_loc, execution_count: nil, node_range: node)
click to toggle source
# File lib/deep_cover/analyser/ruby25_like_branch.rb, line 198 def infos_for_branch(branch, key, empty_fallback_loc, execution_count: nil, node_range: node) if !branch.is_a?(Node::EmptyBody) loc = branch elsif branch.expression # There is clause, but it is empty loc = empty_fallback_loc else # There is no clause loc = node_range end execution_count ||= branch.execution_count [[key, *node_loc_infos(loc)], execution_count] end
infos_for_branches(branches, keys, empty_fallback_locs, execution_counts: [], node_range: node)
click to toggle source
# File lib/deep_cover/analyser/ruby25_like_branch.rb, line 213 def infos_for_branches(branches, keys, empty_fallback_locs, execution_counts: [], node_range: node) branches_infos = branches.map.with_index do |branch, i| infos_for_branch(branch, keys[i], empty_fallback_locs[i], execution_count: execution_counts[i], node_range: node_range) end branches_infos.to_h end
node_loc_infos(node_or_range = node)
click to toggle source
# File lib/deep_cover/analyser/ruby25_like_branch.rb, line 220 def node_loc_infos(node_or_range = node) source_range = node_or_range.is_a?(Node) ? node_or_range.expression : node_or_range @loc_index += 1 [@loc_index, source_range.line, source_range.column, source_range.last_line, source_range.last_column] end
wrap_rwhitespace_and_comments_if_ruby25(expression, whitespaces: /\A\s+/)
click to toggle source
# File lib/deep_cover/analyser/ruby25_like_branch.rb, line 227 def wrap_rwhitespace_and_comments_if_ruby25(expression, whitespaces: /\A\s+/) if RUBY_VERSION.start_with?('2.5') expression.wrap_rwhitespace_and_comments(whitespaces: whitespaces) else expression end end