Class: DeepCover::Analyser::Ruby25LikeBranch::NodeCoverageExtrator

Inherits:
SimpleDelegator
  • Object
show all
Defined in:
lib/deep_cover/analyser/ruby25_like_branch.rb

Overview

This is the class doing the work. Since everything is about the node, the class delegates missing methods to the node, simplifying the code.

Instance Method Summary collapse

Constructor Details

#initialize(node = nil) ⇒ NodeCoverageExtrator

Returns a new instance of NodeCoverageExtrator.



30
31
32
33
# File 'lib/deep_cover/analyser/ruby25_like_branch.rb', line 30

def initialize(node = nil)
  self.node = node
  @loc_index = 0
end

Instance Method Details

#branch_coverage(node) ⇒ Object



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# 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_caseObject



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/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| (w.loc_hash[:begin] || w.loc_hash[:expression]).wrap_rwhitespace_and_comments.end }
  empty_fallbacks.map!(&:begin)

  if loc_hash[:else]
    empty_fallbacks << loc_hash[:end].begin
  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_csendObject



87
88
89
90
91
92
93
94
95
96
# File 'lib/deep_cover/analyser/ruby25_like_branch.rb', line 87

def handle_csend
  # csend wraps the comment but not the newlines
  node_range = node.expression.wrap_rwhitespace_and_comments(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_ifObject



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# 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
    else_loc = loc_hash[:else]

    first_clause_fallback = loc_hash[:begin]
    if first_clause_fallback
      first_clause_fallback = first_clause_fallback.wrap_rwhitespace_and_comments.end
    elsif else_loc
      first_clause_fallback = else_loc.begin
    end

    if else_loc
      second_clause_fallback = else_loc.wrap_rwhitespace_and_comments.end
    end
    end_loc = root_if_node.loc_hash[:end]
    end_loc = end_loc.begin if end_loc

    empty_fallback_locs = [first_clause_fallback || end_loc, second_clause_fallback || end_loc]
  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)
  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_circuitObject



140
141
142
143
144
145
146
# File 'lib/deep_cover/analyser/ruby25_like_branch.rb', line 140

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_whileObject



148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/deep_cover/analyser/ruby25_like_branch.rb', line 148

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
                  body.loc_hash[:end].begin
                end
              elsif body.is_a?(Node::Begin) && !node.body.expressions.empty?
                end_pos = body.expressions.last.expression.end_pos
                body.expressions.first.expression.with(end_pos: end_pos)
              else
                body
              end

  [base_info, {[:body, *node_loc_infos(body_node)] => body.execution_count}]
end