deep-cover-core 0.6.3.pre

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +4 -0
  3. data/.rspec_all +3 -0
  4. data/.rubocop.yml +1 -0
  5. data/Gemfile +11 -0
  6. data/deep_cover_core.gemspec +46 -0
  7. data/lib/deep-cover.rb +3 -0
  8. data/lib/deep_cover/analyser/base.rb +104 -0
  9. data/lib/deep_cover/analyser/branch.rb +41 -0
  10. data/lib/deep_cover/analyser/covered_code_source.rb +21 -0
  11. data/lib/deep_cover/analyser/function.rb +14 -0
  12. data/lib/deep_cover/analyser/node.rb +54 -0
  13. data/lib/deep_cover/analyser/per_char.rb +38 -0
  14. data/lib/deep_cover/analyser/per_line.rb +41 -0
  15. data/lib/deep_cover/analyser/ruby25_like_branch.rb +211 -0
  16. data/lib/deep_cover/analyser/statement.rb +33 -0
  17. data/lib/deep_cover/analyser/stats.rb +54 -0
  18. data/lib/deep_cover/analyser/subset.rb +27 -0
  19. data/lib/deep_cover/analyser.rb +23 -0
  20. data/lib/deep_cover/auto_run.rb +71 -0
  21. data/lib/deep_cover/autoload_tracker.rb +215 -0
  22. data/lib/deep_cover/backports.rb +22 -0
  23. data/lib/deep_cover/base.rb +117 -0
  24. data/lib/deep_cover/basics.rb +22 -0
  25. data/lib/deep_cover/builtin_takeover.rb +10 -0
  26. data/lib/deep_cover/config.rb +104 -0
  27. data/lib/deep_cover/config_setter.rb +33 -0
  28. data/lib/deep_cover/core_ext/autoload_overrides.rb +112 -0
  29. data/lib/deep_cover/core_ext/coverage_replacement.rb +61 -0
  30. data/lib/deep_cover/core_ext/exec_callbacks.rb +27 -0
  31. data/lib/deep_cover/core_ext/instruction_sequence_load_iseq.rb +32 -0
  32. data/lib/deep_cover/core_ext/load_overrides.rb +19 -0
  33. data/lib/deep_cover/core_ext/require_overrides.rb +28 -0
  34. data/lib/deep_cover/coverage/analysis.rb +42 -0
  35. data/lib/deep_cover/coverage/persistence.rb +84 -0
  36. data/lib/deep_cover/coverage.rb +125 -0
  37. data/lib/deep_cover/covered_code.rb +145 -0
  38. data/lib/deep_cover/custom_requirer.rb +187 -0
  39. data/lib/deep_cover/flag_comment_associator.rb +68 -0
  40. data/lib/deep_cover/load.rb +66 -0
  41. data/lib/deep_cover/memoize.rb +48 -0
  42. data/lib/deep_cover/module_override.rb +39 -0
  43. data/lib/deep_cover/node/arguments.rb +51 -0
  44. data/lib/deep_cover/node/assignments.rb +273 -0
  45. data/lib/deep_cover/node/base.rb +155 -0
  46. data/lib/deep_cover/node/begin.rb +27 -0
  47. data/lib/deep_cover/node/block.rb +61 -0
  48. data/lib/deep_cover/node/branch.rb +32 -0
  49. data/lib/deep_cover/node/case.rb +113 -0
  50. data/lib/deep_cover/node/collections.rb +31 -0
  51. data/lib/deep_cover/node/const.rb +12 -0
  52. data/lib/deep_cover/node/def.rb +40 -0
  53. data/lib/deep_cover/node/empty_body.rb +32 -0
  54. data/lib/deep_cover/node/exceptions.rb +79 -0
  55. data/lib/deep_cover/node/if.rb +73 -0
  56. data/lib/deep_cover/node/keywords.rb +86 -0
  57. data/lib/deep_cover/node/literals.rb +100 -0
  58. data/lib/deep_cover/node/loops.rb +74 -0
  59. data/lib/deep_cover/node/mixin/can_augment_children.rb +65 -0
  60. data/lib/deep_cover/node/mixin/check_completion.rb +18 -0
  61. data/lib/deep_cover/node/mixin/child_can_be_empty.rb +27 -0
  62. data/lib/deep_cover/node/mixin/executed_after_children.rb +15 -0
  63. data/lib/deep_cover/node/mixin/execution_location.rb +66 -0
  64. data/lib/deep_cover/node/mixin/filters.rb +47 -0
  65. data/lib/deep_cover/node/mixin/flow_accounting.rb +71 -0
  66. data/lib/deep_cover/node/mixin/has_child.rb +145 -0
  67. data/lib/deep_cover/node/mixin/has_child_handler.rb +75 -0
  68. data/lib/deep_cover/node/mixin/has_tracker.rb +46 -0
  69. data/lib/deep_cover/node/mixin/is_statement.rb +20 -0
  70. data/lib/deep_cover/node/mixin/rewriting.rb +35 -0
  71. data/lib/deep_cover/node/mixin/wrapper.rb +15 -0
  72. data/lib/deep_cover/node/module.rb +66 -0
  73. data/lib/deep_cover/node/root.rb +20 -0
  74. data/lib/deep_cover/node/send.rb +161 -0
  75. data/lib/deep_cover/node/short_circuit.rb +42 -0
  76. data/lib/deep_cover/node/splat.rb +15 -0
  77. data/lib/deep_cover/node/variables.rb +16 -0
  78. data/lib/deep_cover/node.rb +23 -0
  79. data/lib/deep_cover/parser_ext/range.rb +21 -0
  80. data/lib/deep_cover/problem_with_diagnostic.rb +63 -0
  81. data/lib/deep_cover/reporter/base.rb +68 -0
  82. data/lib/deep_cover/reporter/html/base.rb +14 -0
  83. data/lib/deep_cover/reporter/html/index.rb +59 -0
  84. data/lib/deep_cover/reporter/html/site.rb +68 -0
  85. data/lib/deep_cover/reporter/html/source.rb +136 -0
  86. data/lib/deep_cover/reporter/html/template/assets/32px.png +0 -0
  87. data/lib/deep_cover/reporter/html/template/assets/40px.png +0 -0
  88. data/lib/deep_cover/reporter/html/template/assets/deep_cover.css +291 -0
  89. data/lib/deep_cover/reporter/html/template/assets/deep_cover.css.sass +336 -0
  90. data/lib/deep_cover/reporter/html/template/assets/jquery-3.2.1.min.js +4 -0
  91. data/lib/deep_cover/reporter/html/template/assets/jquery-3.2.1.min.map +1 -0
  92. data/lib/deep_cover/reporter/html/template/assets/jstree.css +1108 -0
  93. data/lib/deep_cover/reporter/html/template/assets/jstree.js +8424 -0
  94. data/lib/deep_cover/reporter/html/template/assets/jstreetable.js +1069 -0
  95. data/lib/deep_cover/reporter/html/template/assets/throbber.gif +0 -0
  96. data/lib/deep_cover/reporter/html/template/index.html.erb +75 -0
  97. data/lib/deep_cover/reporter/html/template/source.html.erb +35 -0
  98. data/lib/deep_cover/reporter/html.rb +15 -0
  99. data/lib/deep_cover/reporter/istanbul.rb +184 -0
  100. data/lib/deep_cover/reporter/text.rb +58 -0
  101. data/lib/deep_cover/reporter/tree/util.rb +86 -0
  102. data/lib/deep_cover/reporter.rb +10 -0
  103. data/lib/deep_cover/tools/blank.rb +25 -0
  104. data/lib/deep_cover/tools/builtin_coverage.rb +55 -0
  105. data/lib/deep_cover/tools/camelize.rb +13 -0
  106. data/lib/deep_cover/tools/content_tag.rb +11 -0
  107. data/lib/deep_cover/tools/covered.rb +9 -0
  108. data/lib/deep_cover/tools/execute_sample.rb +34 -0
  109. data/lib/deep_cover/tools/format.rb +18 -0
  110. data/lib/deep_cover/tools/format_char_cover.rb +19 -0
  111. data/lib/deep_cover/tools/format_generated_code.rb +27 -0
  112. data/lib/deep_cover/tools/indent_string.rb +26 -0
  113. data/lib/deep_cover/tools/merge.rb +16 -0
  114. data/lib/deep_cover/tools/number_lines.rb +22 -0
  115. data/lib/deep_cover/tools/our_coverage.rb +11 -0
  116. data/lib/deep_cover/tools/profiling.rb +68 -0
  117. data/lib/deep_cover/tools/render_template.rb +13 -0
  118. data/lib/deep_cover/tools/require_relative_dir.rb +12 -0
  119. data/lib/deep_cover/tools/scan_match_datas.rb +10 -0
  120. data/lib/deep_cover/tools/silence_warnings.rb +18 -0
  121. data/lib/deep_cover/tools/slice.rb +9 -0
  122. data/lib/deep_cover/tools/strip_heredoc.rb +18 -0
  123. data/lib/deep_cover/tools/truncate_backtrace.rb +32 -0
  124. data/lib/deep_cover/tools.rb +22 -0
  125. data/lib/deep_cover/tracker_bucket.rb +50 -0
  126. data/lib/deep_cover/tracker_hits_per_path.rb +35 -0
  127. data/lib/deep_cover/tracker_storage.rb +76 -0
  128. data/lib/deep_cover/tracker_storage_per_path.rb +34 -0
  129. data/lib/deep_cover/version.rb +5 -0
  130. data/lib/deep_cover.rb +22 -0
  131. metadata +329 -0
@@ -0,0 +1,273 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'const'
4
+ require_relative 'literals'
5
+ require_relative 'keywords'
6
+
7
+ module DeepCover
8
+ class Node
9
+ class VariableAssignment < Node
10
+ has_child var_name: Symbol
11
+ has_child value: [Node, nil]
12
+ executed_loc_keys :name, :operator
13
+
14
+ def execution_count
15
+ return super unless value
16
+ value.flow_completion_count
17
+ end
18
+ end
19
+ Cvasgn = Gvasgn = Ivasgn = Lvasgn = VariableAssignment
20
+
21
+ class Casgn < Node
22
+ has_child cbase: [Cbase, Const, nil, Self, Begin, Kwbegin]
23
+ has_child var_name: Symbol
24
+ has_child value: [Node, nil]
25
+
26
+ def execution_count
27
+ return super unless value
28
+ value.flow_completion_count
29
+ end
30
+ end
31
+
32
+ class Mlhs < Node
33
+ has_extra_children being_set: Node
34
+ # TODO
35
+ end
36
+
37
+ module BackwardsStrategy
38
+ # Instead of assuming our parent tracks our entry and we are responsible
39
+ # for tracking our completion, we go the other way and assume our parent
40
+ # tracks our completion and we are responsible for our entry.
41
+ def flow_completion_count
42
+ if (s = next_sibling)
43
+ s.flow_entry_count
44
+ else
45
+ parent.flow_completion_count
46
+ end
47
+ end
48
+
49
+ def flow_entry_count
50
+ if (first_child = children_nodes.first)
51
+ first_child.flow_entry_count
52
+ else
53
+ flow_completion_count
54
+ end
55
+ end
56
+ end
57
+
58
+ # Some multiple assignments are the only cases where Ruby
59
+ # syntax rules won't allow us to insert tracking code
60
+ # where we'd like it to be run.
61
+ #
62
+ # For example:
63
+ #
64
+ # method.a, b, method_2.c = [...]
65
+ #
66
+ # We'd like to add a tracker after the call to `a=` and
67
+ # before the assignment to b and the call to `method_2`.
68
+ #
69
+ # We can't really do this with simple insertions, so
70
+ # we temporarily of strategy for BackwardsStrategy
71
+ #
72
+ class Masgn < Node
73
+ class BackwardsNode < Node
74
+ include BackwardsStrategy
75
+ end
76
+
77
+ # We can't rewrite `self.bar` within a multiple assignment in the
78
+ # case that `bar=` is private, so remain conservative and don't add
79
+ # trackers.
80
+ class SelfReceiver < BackwardsNode
81
+ executed_loc_keys :expression
82
+ end
83
+
84
+ class ConstantCbase < BackwardsNode
85
+ end
86
+
87
+ class DynamicReceiverWrap < Node
88
+ include Wrapper
89
+ has_tracker :entry
90
+ has_child actual_receiver: Node
91
+ def rewrite
92
+ '(%{entry_tracker};%{node})'
93
+ end
94
+ alias_method :flow_entry_count, :entry_tracker_hits
95
+ end
96
+
97
+ class Setter < Node
98
+ include BackwardsStrategy
99
+ has_child receiver: {self: SelfReceiver, Parser::AST::Node => DynamicReceiverWrap}
100
+ has_child method_name: Symbol
101
+ has_child arg: [Node, nil] # When method is :[]=
102
+ executed_loc_keys :dot, :selector_begin, :selector_end
103
+
104
+ def loc_hash
105
+ base = super
106
+ if method_name == :[]=
107
+ selector = base[:selector]
108
+ {
109
+ expression: base[:expression],
110
+ selector_begin: selector.resize(1),
111
+ # The = is implicit, so only backtrack the end by one
112
+ selector_end: Parser::Source::Range.new(selector.source_buffer, selector.end_pos - 1, selector.end_pos),
113
+ }
114
+ else
115
+ {
116
+ dot: base[:dot],
117
+ expression: base[:expression],
118
+ selector_begin: base[:selector],
119
+ selector_end: nil, # ,
120
+ }
121
+ end
122
+ end
123
+
124
+ def execution_count
125
+ receiver.flow_completion_count
126
+ end
127
+ end
128
+
129
+ class ConstantScopeWrapper < Node
130
+ include Wrapper
131
+ has_tracker :entry
132
+ has_child actual_node: Node
133
+
134
+ def rewrite
135
+ '(%{entry_tracker};%{node})'
136
+ end
137
+
138
+ def flow_entry_count
139
+ entry_tracker_hits
140
+ end
141
+ end
142
+
143
+ class ConstantAssignment < Node
144
+ include BackwardsStrategy
145
+ has_child scope: [nil], remap: {cbase: ConstantCbase, Parser::AST::Node => ConstantScopeWrapper}
146
+ has_child constant_name: Symbol
147
+
148
+ def execution_count
149
+ scope ? scope.flow_completion_count : super
150
+ end
151
+ end
152
+
153
+ class VariableAssignment < Node
154
+ include BackwardsStrategy
155
+ has_child var_name: Symbol
156
+ end
157
+
158
+ BASE_MAP = {
159
+ cvasgn: VariableAssignment, gvasgn: VariableAssignment,
160
+ ivasgn: VariableAssignment, lvasgn: VariableAssignment,
161
+ casgn: ConstantAssignment,
162
+ send: Setter,
163
+ }.freeze
164
+ class Splat < Node
165
+ include BackwardsStrategy
166
+ has_child rest_arg: [nil], remap: BASE_MAP
167
+ executed_loc_keys :operator
168
+ end
169
+
170
+ class LeftSide < Node
171
+ include BackwardsStrategy
172
+ has_extra_children receivers: {
173
+ splat: Splat,
174
+ mlhs: LeftSide,
175
+ **BASE_MAP,
176
+ }
177
+ executed_loc_keys # none
178
+
179
+ def flow_completion_count
180
+ parent.flow_completion_count
181
+ end
182
+ end
183
+
184
+ check_completion
185
+
186
+ has_child left: {mlhs: LeftSide}
187
+ has_child value: Node
188
+
189
+ executed_loc_keys :operator
190
+
191
+ def execution_count
192
+ value.flow_completion_count
193
+ end
194
+
195
+ def children_nodes_in_flow_order
196
+ [value, left]
197
+ end
198
+ end
199
+
200
+ class VariableOperatorAssign < Node
201
+ has_child var_name: Symbol
202
+ end
203
+
204
+ class ConstantOperatorAssign < Node
205
+ has_child scope: [Node, nil]
206
+ has_child const_name: Symbol
207
+ def execution_count
208
+ flow_completion_count
209
+ end
210
+ end
211
+
212
+ class SendOperatorAssign < Node
213
+ has_child receiver: [Node, nil]
214
+ has_child method_name: Symbol
215
+ has_extra_children arguments: Node
216
+ executed_loc_keys :dot, :selector_begin, :selector_end, :operator
217
+
218
+ def loc_hash
219
+ base = super
220
+ hash = {expression: base[:expression], begin: base[:begin], end: base[:end], dot: base[:dot]}
221
+ selector = base[:selector]
222
+
223
+ if [:[], :[]=].include?(method_name)
224
+ hash[:selector_begin] = selector.resize(1)
225
+ hash[:selector_end] = Parser::Source::Range.new(selector.source_buffer, selector.end_pos - 1, selector.end_pos)
226
+ else
227
+ hash[:selector_begin] = base[:selector]
228
+ end
229
+
230
+ hash
231
+ end
232
+ end
233
+
234
+ # foo += bar
235
+ class OpAsgn < Node
236
+ check_completion
237
+ has_tracker :reader
238
+ has_child receiver: {
239
+ lvasgn: VariableOperatorAssign, ivasgn: VariableOperatorAssign,
240
+ cvasgn: VariableOperatorAssign, gvasgn: VariableOperatorAssign,
241
+ casgn: Casgn, # TODO
242
+ send: SendOperatorAssign,
243
+ }
244
+ has_child operator: Symbol
245
+ has_child value: Node, rewrite: '(%{reader_tracker};%{node})', flow_entry_count: :reader_tracker_hits
246
+ executed_loc_keys :operator
247
+
248
+ def execution_count
249
+ flow_completion_count
250
+ end
251
+ end
252
+
253
+ # foo ||= bar, foo &&= base
254
+ class BooleanAssignment < Node
255
+ check_completion
256
+ has_tracker :long_branch
257
+ has_child receiver: {
258
+ lvasgn: VariableOperatorAssign, ivasgn: VariableOperatorAssign,
259
+ cvasgn: VariableOperatorAssign, gvasgn: VariableOperatorAssign,
260
+ casgn: ConstantOperatorAssign,
261
+ send: SendOperatorAssign,
262
+ }
263
+ has_child value: Node, rewrite: '(%{long_branch_tracker};%{node})', flow_entry_count: :long_branch_tracker_hits
264
+ executed_loc_keys :operator
265
+
266
+ def execution_count
267
+ flow_completion_count
268
+ end
269
+ end
270
+
271
+ OrAsgn = AndAsgn = BooleanAssignment
272
+ end
273
+ end
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeepCover
4
+ # Base class to handle covered nodes.
5
+ class Node
6
+ include Mixin
7
+ include HasTracker
8
+ include HasChild
9
+ include HasChildHandler
10
+ include CanAugmentChildren
11
+ include Rewriting
12
+ extend CheckCompletion
13
+ include FlowAccounting
14
+ include IsStatement
15
+ include ExecutionLocation
16
+ include ChildCanBeEmpty
17
+ include Filters
18
+ extend Filters::ClassMethods
19
+
20
+ attr_reader :index, :parent, :children, :base_node
21
+
22
+ def initialize(base_node, parent:, index: 0, base_children: base_node.children)
23
+ @base_node = base_node
24
+ @parent = parent
25
+ @index = index
26
+ @children = []
27
+ begin
28
+ @children = augment_children(base_children)
29
+ initialize_siblings
30
+ super()
31
+ rescue StandardError => e
32
+ diagnose(e)
33
+ end
34
+ end
35
+
36
+ ### Public API
37
+
38
+ # Search self and descendants for a particular Class or type
39
+ def find_all(lookup)
40
+ case lookup
41
+ when ::Module
42
+ each_node.grep(lookup)
43
+ when ::Symbol
44
+ each_node.find_all { |n| n.type == lookup }
45
+ when ::String
46
+ each_node.find_all { |n| n.source == lookup }
47
+ when ::Regexp
48
+ each_node.find_all { |n| n.source =~ lookup }
49
+ else
50
+ raise ::TypeError, "Expected class or symbol, got #{lookup.class}: #{lookup.inspect}"
51
+ end
52
+ end
53
+
54
+ # Shortcut to access children
55
+ def [](lookup)
56
+ if lookup.is_a?(Integer)
57
+ children.fetch(lookup)
58
+ else
59
+ found = find_all(lookup)
60
+ case found.size
61
+ when 1
62
+ found.first
63
+ when 0
64
+ raise "No children of type #{lookup}"
65
+ else
66
+ raise "Ambiguous lookup #{lookup}, found #{found}."
67
+ end
68
+ end
69
+ end
70
+
71
+ # Shortcut to create a node from source code
72
+ def self.[](source)
73
+ CoveredCode.new(source: source).execute_code.covered_ast
74
+ end
75
+
76
+ def children_nodes
77
+ children.select { |c| c.is_a? Node }
78
+ end
79
+ alias_method :children_nodes_in_flow_order, :children_nodes
80
+
81
+ attr_accessor :next_sibling
82
+ attr_accessor :previous_sibling
83
+ protected :next_sibling=, :previous_sibling=
84
+ def initialize_siblings
85
+ children_nodes_in_flow_order.each_cons(2) do |child, next_child|
86
+ child.next_sibling = next_child
87
+ next_child.previous_sibling = child
88
+ end
89
+ end
90
+ private :initialize_siblings
91
+
92
+ # Adapted from https://github.com/whitequark/ast/blob/master/lib/ast/node.rb
93
+ def to_s(indent = 0)
94
+ [
95
+ ' ' * indent,
96
+ '(',
97
+ fancy_type,
98
+ *children.map do |child, idx|
99
+ if child.is_a?(Node)
100
+ "\n#{child.to_s(indent + 1)}"
101
+ else
102
+ " #{child.inspect}"
103
+ end
104
+ end,
105
+ ')',
106
+ ].join
107
+ end
108
+
109
+ alias_method :inspect, :to_s
110
+ ### Internal API
111
+
112
+ def covered_code
113
+ parent.covered_code
114
+ end
115
+
116
+ def type
117
+ return base_node.type if base_node.respond_to? :type
118
+ self.class.name.split('::').last.to_sym
119
+ end
120
+
121
+ def each_node(order = :postorder, &block)
122
+ return to_enum :each_node, order unless block_given?
123
+ yield self unless order == :postorder
124
+ children_nodes.each do |child|
125
+ child.each_node(order, &block)
126
+ end
127
+ yield self if order == :postorder
128
+ self
129
+ end
130
+
131
+ def fancy_type
132
+ class_name = self.class.to_s.gsub(/^DeepCover::/, '').gsub(/^Node::/, '')
133
+ t = type.to_s
134
+ t.casecmp(class_name) == 0 ? t : "#{t}[#{class_name}]"
135
+ end
136
+
137
+ private
138
+
139
+ def diagnose(exception)
140
+ msg = if self.class == Node
141
+ "Unknown node type encountered: #{base_node.type}"
142
+ else
143
+ "Node class #{self.class} incorrectly defined"
144
+ end
145
+ warn [msg,
146
+ 'Attempting to continue, but this node will not be handled properly',
147
+ ('Its subnodes will be ignored' if children.empty?),
148
+ 'Source:',
149
+ expression,
150
+ 'Original exception:',
151
+ exception.inspect,
152
+ ].join("\n")
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeepCover
4
+ class Node
5
+ class Begin < Node
6
+ has_extra_children expressions: Node,
7
+ is_statement: true
8
+
9
+ def is_statement
10
+ false
11
+ end
12
+
13
+ def executed_loc_keys
14
+ # Begin is a generic grouping used in different contexts.
15
+ case loc_hash[:begin] && loc_hash[:begin].source
16
+ when nil, '(', 'begin'
17
+ []
18
+ when 'else', '#{'
19
+ %i[begin end]
20
+ else
21
+ warn 'Unknown context for Begin node'
22
+ []
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'send'
4
+ require_relative 'keywords'
5
+
6
+ module DeepCover
7
+ class Node
8
+ module WithBlock
9
+ def flow_completion_count
10
+ parent.flow_completion_count
11
+ end
12
+
13
+ def execution_count
14
+ last = children_nodes.last
15
+ return last.flow_completion_count if last
16
+ super
17
+ end
18
+ end
19
+
20
+ class SendWithBlock < SendBase
21
+ include WithBlock
22
+ end
23
+
24
+ class SuperWithBlock < Node
25
+ include WithBlock
26
+ has_extra_children arguments: Node
27
+ end
28
+
29
+ class Block < Node
30
+ check_completion
31
+ has_tracker :body
32
+ has_child call: {send: SendWithBlock, zsuper: SuperWithBlock, super: SuperWithBlock, csend: Csend}
33
+ has_child args: Args
34
+ has_child body: Node,
35
+ can_be_empty: -> { base_node.loc.end.begin },
36
+ rewrite: '%{body_tracker};%{local}=nil;%{node}',
37
+ flow_entry_count: :body_tracker_hits,
38
+ is_statement: true
39
+ executed_loc_keys # none
40
+
41
+ def children_nodes_in_flow_order
42
+ [call, args] # Similarly to a def, the body is actually not part of the flow of this node...
43
+ end
44
+
45
+ alias_method :rewrite_for_completion, :rewrite
46
+ def rewrite
47
+ if call.is_a?(Csend)
48
+ rewrite_for_completion.gsub('%{node}', Csend::REWRITE_SUFFIX)
49
+ else
50
+ rewrite_for_completion
51
+ end
52
+ end
53
+ end
54
+
55
+ # &foo
56
+ class BlockPass < Node
57
+ has_child block: Node
58
+ # TODO
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'empty_body'
4
+
5
+ module DeepCover
6
+ class Node
7
+ module Branch
8
+ def flow_completion_count
9
+ branches.map(&:flow_completion_count).inject(0, :+)
10
+ end
11
+
12
+ # Define in sublasses:
13
+ def branches
14
+ raise NotImplementedError
15
+ end
16
+
17
+ # Also define flow_entry_count
18
+ end
19
+
20
+ class TrivialBranch < Node::EmptyBody
21
+ def initialize(other_branch:, condition:, position: true)
22
+ @condition = condition
23
+ @other_branch = other_branch
24
+ super(nil, parent: condition.parent, position: position)
25
+ end
26
+
27
+ def flow_entry_count
28
+ @condition.flow_completion_count - @other_branch.flow_entry_count
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'branch'
4
+
5
+ module DeepCover
6
+ class Node
7
+ class WhenCondition < Node
8
+ include Wrapper
9
+ has_tracker :entry
10
+ # Using && instead of ; solves a weird bug in jruby 9.1.7.0 and 9.1.9.0 (probably before too).
11
+ # The following will only print 'test' once
12
+ # class EqEqEq; def ===(other); puts 'test'; end; end
13
+ # eqeqeq = EqEqEq.new
14
+ # case 1; when eqeqeq; end
15
+ # case 1; when (3;eqeqeq); end
16
+ # See https://github.com/jruby/jruby/issues/4804
17
+ # This is solved in jruby 9.2.0.0, better keep the workaround
18
+ # for compatibility.
19
+ has_child condition: Node, rewrite: '((%{entry_tracker}) && %{node})',
20
+ flow_entry_count: :entry_tracker_hits
21
+ executed_loc_keys []
22
+
23
+ def flow_entry_count
24
+ entry_tracker_hits
25
+ end
26
+
27
+ def flow_completion_count
28
+ condition.flow_completion_count
29
+ end
30
+
31
+ def loc_hash
32
+ condition.loc_hash
33
+ end
34
+ end
35
+
36
+ class WhenSplatCondition < Node
37
+ has_tracker :entry
38
+ check_completion inner: '(%{entry_tracker};[%{node}])', outer: '*%{node}'
39
+ has_child receiver: Node
40
+
41
+ def flow_entry_count
42
+ entry_tracker_hits
43
+ end
44
+ end
45
+
46
+ class When < Node
47
+ has_tracker :body_entry
48
+ has_extra_children matches: {splat: WhenSplatCondition, Parser::AST::Node => WhenCondition}
49
+ has_child body: Node,
50
+ can_be_empty: -> {
51
+ if (after_then = base_node.loc.begin)
52
+ after_then.end
53
+ else
54
+ base_node.loc.expression.end.succ
55
+ end
56
+ },
57
+ rewrite: '%{body_entry_tracker};%{local}=nil;%{node}',
58
+ is_statement: true,
59
+ flow_entry_count: :body_entry_tracker_hits
60
+
61
+ def flow_entry_count
62
+ matches.first.flow_entry_count
63
+ end
64
+
65
+ def execution_count
66
+ matches.first.flow_completion_count
67
+ end
68
+
69
+ def flow_completion_count
70
+ body.flow_completion_count + next_sibling.flow_entry_count
71
+ end
72
+ end
73
+
74
+ class Case < Node
75
+ include Branch
76
+ has_tracker :else_entry
77
+ has_child evaluate: [Node, nil]
78
+ has_extra_children whens: When
79
+ has_child else: Node,
80
+ can_be_empty: -> { base_node.loc.end.begin },
81
+ rewrite: -> { "#{'else;' unless has_else?}(%{else_entry_tracker};%{local}=nil;%{node})" },
82
+ executed_loc_keys: [:else],
83
+ is_statement: true,
84
+ flow_entry_count: :else_entry_tracker_hits
85
+
86
+ executed_loc_keys :begin, :keyword
87
+
88
+ def branches
89
+ whens.map(&:body) << self.else
90
+ end
91
+
92
+ def branches_summary(of_branches = branches)
93
+ texts = []
94
+ n = of_branches.size
95
+ if of_branches.include? self.else
96
+ texts << "#{'implicit ' unless has_else?}else"
97
+ n -= 1
98
+ end
99
+ texts.unshift "#{n} when clause#{'s' if n > 1}" if n > 0
100
+ texts.join(' and ')
101
+ end
102
+
103
+ def execution_count
104
+ return evaluate.flow_completion_count if evaluate
105
+ flow_entry_count
106
+ end
107
+
108
+ def has_else?
109
+ !!base_node.loc.to_hash[:else]
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'splat'
4
+
5
+ module DeepCover
6
+ class Node
7
+ module SimpleIfEmpty
8
+ def simple_literal?
9
+ children.empty?
10
+ end
11
+ end
12
+
13
+ class Array < Node
14
+ include SimpleIfEmpty
15
+ has_extra_children elements: Node
16
+ executed_loc_keys :begin, :end
17
+ end
18
+
19
+ class Pair < Node
20
+ has_child key: Node
21
+ has_child value: Node
22
+ executed_loc_keys :operator
23
+ end
24
+
25
+ class Hash < Node
26
+ include SimpleIfEmpty
27
+ has_extra_children elements: [Pair, Kwsplat]
28
+ executed_loc_keys :begin, :end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeepCover
4
+ class Node::Const < Node
5
+ check_completion
6
+ has_child scope: [Node, nil]
7
+ has_child const_name: Symbol
8
+ end
9
+
10
+ class Node::Cbase < Node
11
+ end
12
+ end