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| 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_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 = 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_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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# 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_circuitObject



156
157
158
159
160
161
162
# 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_whileObject



164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# 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