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.
- checksums.yaml +7 -0
- data/.rspec +4 -0
- data/.rspec_all +3 -0
- data/.rubocop.yml +1 -0
- data/Gemfile +11 -0
- data/deep_cover_core.gemspec +46 -0
- data/lib/deep-cover.rb +3 -0
- data/lib/deep_cover/analyser/base.rb +104 -0
- data/lib/deep_cover/analyser/branch.rb +41 -0
- data/lib/deep_cover/analyser/covered_code_source.rb +21 -0
- data/lib/deep_cover/analyser/function.rb +14 -0
- data/lib/deep_cover/analyser/node.rb +54 -0
- data/lib/deep_cover/analyser/per_char.rb +38 -0
- data/lib/deep_cover/analyser/per_line.rb +41 -0
- data/lib/deep_cover/analyser/ruby25_like_branch.rb +211 -0
- data/lib/deep_cover/analyser/statement.rb +33 -0
- data/lib/deep_cover/analyser/stats.rb +54 -0
- data/lib/deep_cover/analyser/subset.rb +27 -0
- data/lib/deep_cover/analyser.rb +23 -0
- data/lib/deep_cover/auto_run.rb +71 -0
- data/lib/deep_cover/autoload_tracker.rb +215 -0
- data/lib/deep_cover/backports.rb +22 -0
- data/lib/deep_cover/base.rb +117 -0
- data/lib/deep_cover/basics.rb +22 -0
- data/lib/deep_cover/builtin_takeover.rb +10 -0
- data/lib/deep_cover/config.rb +104 -0
- data/lib/deep_cover/config_setter.rb +33 -0
- data/lib/deep_cover/core_ext/autoload_overrides.rb +112 -0
- data/lib/deep_cover/core_ext/coverage_replacement.rb +61 -0
- data/lib/deep_cover/core_ext/exec_callbacks.rb +27 -0
- data/lib/deep_cover/core_ext/instruction_sequence_load_iseq.rb +32 -0
- data/lib/deep_cover/core_ext/load_overrides.rb +19 -0
- data/lib/deep_cover/core_ext/require_overrides.rb +28 -0
- data/lib/deep_cover/coverage/analysis.rb +42 -0
- data/lib/deep_cover/coverage/persistence.rb +84 -0
- data/lib/deep_cover/coverage.rb +125 -0
- data/lib/deep_cover/covered_code.rb +145 -0
- data/lib/deep_cover/custom_requirer.rb +187 -0
- data/lib/deep_cover/flag_comment_associator.rb +68 -0
- data/lib/deep_cover/load.rb +66 -0
- data/lib/deep_cover/memoize.rb +48 -0
- data/lib/deep_cover/module_override.rb +39 -0
- data/lib/deep_cover/node/arguments.rb +51 -0
- data/lib/deep_cover/node/assignments.rb +273 -0
- data/lib/deep_cover/node/base.rb +155 -0
- data/lib/deep_cover/node/begin.rb +27 -0
- data/lib/deep_cover/node/block.rb +61 -0
- data/lib/deep_cover/node/branch.rb +32 -0
- data/lib/deep_cover/node/case.rb +113 -0
- data/lib/deep_cover/node/collections.rb +31 -0
- data/lib/deep_cover/node/const.rb +12 -0
- data/lib/deep_cover/node/def.rb +40 -0
- data/lib/deep_cover/node/empty_body.rb +32 -0
- data/lib/deep_cover/node/exceptions.rb +79 -0
- data/lib/deep_cover/node/if.rb +73 -0
- data/lib/deep_cover/node/keywords.rb +86 -0
- data/lib/deep_cover/node/literals.rb +100 -0
- data/lib/deep_cover/node/loops.rb +74 -0
- data/lib/deep_cover/node/mixin/can_augment_children.rb +65 -0
- data/lib/deep_cover/node/mixin/check_completion.rb +18 -0
- data/lib/deep_cover/node/mixin/child_can_be_empty.rb +27 -0
- data/lib/deep_cover/node/mixin/executed_after_children.rb +15 -0
- data/lib/deep_cover/node/mixin/execution_location.rb +66 -0
- data/lib/deep_cover/node/mixin/filters.rb +47 -0
- data/lib/deep_cover/node/mixin/flow_accounting.rb +71 -0
- data/lib/deep_cover/node/mixin/has_child.rb +145 -0
- data/lib/deep_cover/node/mixin/has_child_handler.rb +75 -0
- data/lib/deep_cover/node/mixin/has_tracker.rb +46 -0
- data/lib/deep_cover/node/mixin/is_statement.rb +20 -0
- data/lib/deep_cover/node/mixin/rewriting.rb +35 -0
- data/lib/deep_cover/node/mixin/wrapper.rb +15 -0
- data/lib/deep_cover/node/module.rb +66 -0
- data/lib/deep_cover/node/root.rb +20 -0
- data/lib/deep_cover/node/send.rb +161 -0
- data/lib/deep_cover/node/short_circuit.rb +42 -0
- data/lib/deep_cover/node/splat.rb +15 -0
- data/lib/deep_cover/node/variables.rb +16 -0
- data/lib/deep_cover/node.rb +23 -0
- data/lib/deep_cover/parser_ext/range.rb +21 -0
- data/lib/deep_cover/problem_with_diagnostic.rb +63 -0
- data/lib/deep_cover/reporter/base.rb +68 -0
- data/lib/deep_cover/reporter/html/base.rb +14 -0
- data/lib/deep_cover/reporter/html/index.rb +59 -0
- data/lib/deep_cover/reporter/html/site.rb +68 -0
- data/lib/deep_cover/reporter/html/source.rb +136 -0
- data/lib/deep_cover/reporter/html/template/assets/32px.png +0 -0
- data/lib/deep_cover/reporter/html/template/assets/40px.png +0 -0
- data/lib/deep_cover/reporter/html/template/assets/deep_cover.css +291 -0
- data/lib/deep_cover/reporter/html/template/assets/deep_cover.css.sass +336 -0
- data/lib/deep_cover/reporter/html/template/assets/jquery-3.2.1.min.js +4 -0
- data/lib/deep_cover/reporter/html/template/assets/jquery-3.2.1.min.map +1 -0
- data/lib/deep_cover/reporter/html/template/assets/jstree.css +1108 -0
- data/lib/deep_cover/reporter/html/template/assets/jstree.js +8424 -0
- data/lib/deep_cover/reporter/html/template/assets/jstreetable.js +1069 -0
- data/lib/deep_cover/reporter/html/template/assets/throbber.gif +0 -0
- data/lib/deep_cover/reporter/html/template/index.html.erb +75 -0
- data/lib/deep_cover/reporter/html/template/source.html.erb +35 -0
- data/lib/deep_cover/reporter/html.rb +15 -0
- data/lib/deep_cover/reporter/istanbul.rb +184 -0
- data/lib/deep_cover/reporter/text.rb +58 -0
- data/lib/deep_cover/reporter/tree/util.rb +86 -0
- data/lib/deep_cover/reporter.rb +10 -0
- data/lib/deep_cover/tools/blank.rb +25 -0
- data/lib/deep_cover/tools/builtin_coverage.rb +55 -0
- data/lib/deep_cover/tools/camelize.rb +13 -0
- data/lib/deep_cover/tools/content_tag.rb +11 -0
- data/lib/deep_cover/tools/covered.rb +9 -0
- data/lib/deep_cover/tools/execute_sample.rb +34 -0
- data/lib/deep_cover/tools/format.rb +18 -0
- data/lib/deep_cover/tools/format_char_cover.rb +19 -0
- data/lib/deep_cover/tools/format_generated_code.rb +27 -0
- data/lib/deep_cover/tools/indent_string.rb +26 -0
- data/lib/deep_cover/tools/merge.rb +16 -0
- data/lib/deep_cover/tools/number_lines.rb +22 -0
- data/lib/deep_cover/tools/our_coverage.rb +11 -0
- data/lib/deep_cover/tools/profiling.rb +68 -0
- data/lib/deep_cover/tools/render_template.rb +13 -0
- data/lib/deep_cover/tools/require_relative_dir.rb +12 -0
- data/lib/deep_cover/tools/scan_match_datas.rb +10 -0
- data/lib/deep_cover/tools/silence_warnings.rb +18 -0
- data/lib/deep_cover/tools/slice.rb +9 -0
- data/lib/deep_cover/tools/strip_heredoc.rb +18 -0
- data/lib/deep_cover/tools/truncate_backtrace.rb +32 -0
- data/lib/deep_cover/tools.rb +22 -0
- data/lib/deep_cover/tracker_bucket.rb +50 -0
- data/lib/deep_cover/tracker_hits_per_path.rb +35 -0
- data/lib/deep_cover/tracker_storage.rb +76 -0
- data/lib/deep_cover/tracker_storage_per_path.rb +34 -0
- data/lib/deep_cover/version.rb +5 -0
- data/lib/deep_cover.rb +22 -0
- 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
|