deep-cover-core 0.6.3.pre

Sign up to get free protection for your applications and to get access to all the features.
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