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,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'arguments'
|
4
|
+
|
5
|
+
module DeepCover
|
6
|
+
class Node::Def < Node
|
7
|
+
check_completion
|
8
|
+
has_tracker :method_call
|
9
|
+
has_child method_name: Symbol
|
10
|
+
has_child signature: Args
|
11
|
+
has_child body: Node,
|
12
|
+
rewrite: '%{method_call_tracker};%{local}=nil;%{node}',
|
13
|
+
can_be_empty: -> { base_node.loc.end.begin },
|
14
|
+
is_statement: true,
|
15
|
+
flow_entry_count: :method_call_tracker_hits
|
16
|
+
executed_loc_keys :keyword, :name
|
17
|
+
|
18
|
+
def children_nodes_in_flow_order
|
19
|
+
[]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Node::Defs < Node
|
24
|
+
check_completion
|
25
|
+
has_tracker :method_call
|
26
|
+
has_child singleton: Node, rewrite: '(%{node})'
|
27
|
+
has_child method_name: Symbol
|
28
|
+
has_child signature: Args
|
29
|
+
has_child body: Node,
|
30
|
+
rewrite: '%{method_call_tracker};%{local}=nil;%{node}',
|
31
|
+
can_be_empty: -> { base_node.loc.end.begin },
|
32
|
+
is_statement: true,
|
33
|
+
flow_entry_count: :method_call_tracker_hits
|
34
|
+
executed_loc_keys :keyword, :name, :operator
|
35
|
+
|
36
|
+
def children_nodes_in_flow_order
|
37
|
+
[singleton]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DeepCover
|
4
|
+
class Node::EmptyBody < Node
|
5
|
+
def initialize(base_node, parent:, index: 0, position: ChildCanBeEmpty.last_empty_position)
|
6
|
+
@position = position
|
7
|
+
super(base_node, parent: parent, index: index, base_children: [])
|
8
|
+
end
|
9
|
+
|
10
|
+
def type
|
11
|
+
:EmptyBody
|
12
|
+
end
|
13
|
+
|
14
|
+
def loc_hash
|
15
|
+
return {} if @position == true
|
16
|
+
{expression: @position}
|
17
|
+
end
|
18
|
+
|
19
|
+
def is_statement
|
20
|
+
false
|
21
|
+
end
|
22
|
+
|
23
|
+
# When parent rewrites us, the %{node} must always be at the beginning because our location can
|
24
|
+
# also be rewritten by out parent, and we want the rewrite to be after it.
|
25
|
+
def rewriting_rules
|
26
|
+
rules = super
|
27
|
+
rules.map do |expression, rule|
|
28
|
+
[expression, "%{node};#{rule.sub('%{node}', 'nil;')}"]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'variables'
|
4
|
+
require_relative 'collections'
|
5
|
+
|
6
|
+
module DeepCover
|
7
|
+
class Node
|
8
|
+
class Resbody < Node
|
9
|
+
has_tracker :entered_body
|
10
|
+
has_child exception: [Node::Array, nil]
|
11
|
+
has_child assignment: [Lvasgn, nil], flow_entry_count: :entered_body_tracker_hits
|
12
|
+
has_child body: Node,
|
13
|
+
can_be_empty: -> { (base_node.loc.begin || base_node.loc.expression.succ).end },
|
14
|
+
flow_entry_count: :entered_body_tracker_hits,
|
15
|
+
is_statement: true,
|
16
|
+
rewrite: '(%{entered_body_tracker};%{local}=nil;%{node})'
|
17
|
+
|
18
|
+
def is_statement
|
19
|
+
false
|
20
|
+
end
|
21
|
+
|
22
|
+
def execution_count
|
23
|
+
entered_body_tracker_hits
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class Rescue < Node
|
28
|
+
has_child watched_body: Node,
|
29
|
+
can_be_empty: -> { base_node.loc.expression.begin },
|
30
|
+
is_statement: true
|
31
|
+
has_extra_children resbodies: Resbody
|
32
|
+
has_child else: Node,
|
33
|
+
can_be_empty: -> { base_node.loc.expression.end },
|
34
|
+
flow_entry_count: :execution_count,
|
35
|
+
is_statement: true
|
36
|
+
executed_loc_keys :else
|
37
|
+
|
38
|
+
def is_statement
|
39
|
+
false
|
40
|
+
end
|
41
|
+
|
42
|
+
def flow_completion_count
|
43
|
+
resbodies.map(&:flow_completion_count).inject(0, :+) + self.else.flow_completion_count
|
44
|
+
end
|
45
|
+
|
46
|
+
def execution_count
|
47
|
+
watched_body.flow_completion_count
|
48
|
+
end
|
49
|
+
|
50
|
+
def resbodies_flow_entry_count(child)
|
51
|
+
prev = child.previous_sibling
|
52
|
+
|
53
|
+
if prev.equal? watched_body
|
54
|
+
prev.flow_entry_count - prev.flow_completion_count
|
55
|
+
else # RESBODIES
|
56
|
+
if prev.exception # rubocop:disable Style/IfInsideElse
|
57
|
+
prev.exception.flow_completion_count - prev.execution_count
|
58
|
+
else
|
59
|
+
prev.flow_entry_count - prev.execution_count
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class Ensure < Node
|
66
|
+
has_child body: Node,
|
67
|
+
can_be_empty: -> { base_node.loc.expression.begin },
|
68
|
+
is_statement: true
|
69
|
+
has_child ensure: Node,
|
70
|
+
can_be_empty: -> { base_node.loc.expression.end.succ },
|
71
|
+
is_statement: true,
|
72
|
+
flow_entry_count: -> { body.flow_entry_count }
|
73
|
+
|
74
|
+
def flow_completion_count
|
75
|
+
body.flow_completion_count
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'branch'
|
4
|
+
|
5
|
+
module DeepCover
|
6
|
+
class Node
|
7
|
+
class If < Node
|
8
|
+
include Branch
|
9
|
+
has_tracker :truthy
|
10
|
+
has_child condition: Node, rewrite: '((%{node}) && %{truthy_tracker})'
|
11
|
+
has_child true_branch: Node,
|
12
|
+
executed_loc_keys: -> { :else if style == :unless },
|
13
|
+
flow_entry_count: :truthy_tracker_hits,
|
14
|
+
is_statement: true
|
15
|
+
has_child false_branch: Node,
|
16
|
+
executed_loc_keys: -> { [:else, :colon] if style != :unless },
|
17
|
+
flow_entry_count: -> { condition.flow_completion_count - truthy_tracker_hits },
|
18
|
+
is_statement: true
|
19
|
+
executed_loc_keys :keyword, :question
|
20
|
+
|
21
|
+
def child_can_be_empty(child, name)
|
22
|
+
return false if name == :condition || style == :ternary
|
23
|
+
if (name == :true_branch) == [:if, :elsif].include?(style)
|
24
|
+
(base_node.loc.begin || base_node.children[0].loc.expression.succ).end
|
25
|
+
elsif has_else?
|
26
|
+
base_node.loc.else.end.succ
|
27
|
+
else
|
28
|
+
true # implicit else
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def branches
|
33
|
+
[true_branch, false_branch]
|
34
|
+
end
|
35
|
+
|
36
|
+
def branches_summary(of_branches = branches)
|
37
|
+
of_branches.map do |jump|
|
38
|
+
"#{'implicit ' if jump.is_a?(EmptyBody) && !has_else?}#{jump == false_branch ? 'falsy' : 'truthy'} branch"
|
39
|
+
end.join(' and ')
|
40
|
+
end
|
41
|
+
|
42
|
+
def execution_count
|
43
|
+
condition.flow_completion_count
|
44
|
+
end
|
45
|
+
|
46
|
+
# returns on of %i[ternary if unless elsif]
|
47
|
+
def style
|
48
|
+
keyword = loc_hash[:keyword]
|
49
|
+
keyword ? keyword.source.to_sym : :ternary
|
50
|
+
end
|
51
|
+
|
52
|
+
def root_if_node
|
53
|
+
if style != :elsif
|
54
|
+
self
|
55
|
+
else
|
56
|
+
parent.root_if_node
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def deepest_elsif_node
|
61
|
+
return if style != :elsif
|
62
|
+
return self if loc_hash[:else] && loc_hash[:else].source == 'else'
|
63
|
+
return self if false_branch.is_a?(EmptyBody)
|
64
|
+
false_branch.deepest_elsif_node
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
def has_else?
|
69
|
+
!!base_node.loc.to_hash[:else]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'variables'
|
4
|
+
require_relative 'literals'
|
5
|
+
|
6
|
+
module DeepCover
|
7
|
+
class Node
|
8
|
+
class Kwbegin < Node
|
9
|
+
has_extra_children instructions: Node,
|
10
|
+
is_statement: true
|
11
|
+
|
12
|
+
def is_statement
|
13
|
+
false
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class Return < Node
|
18
|
+
has_extra_children values: Node
|
19
|
+
# TODO
|
20
|
+
end
|
21
|
+
|
22
|
+
class Super < Node
|
23
|
+
check_completion
|
24
|
+
has_extra_children arguments: Node
|
25
|
+
# TODO
|
26
|
+
end
|
27
|
+
Zsuper = Super # Zsuper is super with no parenthesis (same arguments as caller)
|
28
|
+
|
29
|
+
class Yield < Node
|
30
|
+
has_extra_children arguments: Node
|
31
|
+
# TODO
|
32
|
+
end
|
33
|
+
|
34
|
+
class Break < Node
|
35
|
+
has_extra_children arguments: Node
|
36
|
+
# TODO: Anything special needed for the arguments?
|
37
|
+
|
38
|
+
def flow_completion_count
|
39
|
+
0
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class Next < Node
|
44
|
+
has_extra_children arguments: Node
|
45
|
+
# TODO: Anything special needed for the arguments?
|
46
|
+
|
47
|
+
def flow_completion_count
|
48
|
+
0
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class Alias < Node
|
53
|
+
check_completion
|
54
|
+
has_child alias: [Sym, Dsym, Gvar, BackRef]
|
55
|
+
has_child original: [Sym, Dsym, Gvar, BackRef]
|
56
|
+
# TODO: test
|
57
|
+
end
|
58
|
+
|
59
|
+
class NeverEvaluated < Node
|
60
|
+
has_extra_children whatever: [:any], remap: {Parser::AST::Node => NeverEvaluated}
|
61
|
+
|
62
|
+
def executable?
|
63
|
+
false
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class Defined < Node
|
68
|
+
has_child code: {Parser::AST::Node => NeverEvaluated}
|
69
|
+
# TODO: test
|
70
|
+
end
|
71
|
+
|
72
|
+
class Undef < Node
|
73
|
+
check_completion
|
74
|
+
has_extra_children arguments: [Sym, Dsym]
|
75
|
+
# TODO: test
|
76
|
+
end
|
77
|
+
|
78
|
+
class Return < Node
|
79
|
+
include ExecutedAfterChildren
|
80
|
+
|
81
|
+
def flow_completion_count
|
82
|
+
0
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'begin'
|
4
|
+
require_relative 'variables'
|
5
|
+
require_relative 'collections'
|
6
|
+
module DeepCover
|
7
|
+
class Node
|
8
|
+
def simple_literal?
|
9
|
+
false
|
10
|
+
end
|
11
|
+
|
12
|
+
class StaticLiteral < Node
|
13
|
+
executed_loc_keys :expression
|
14
|
+
|
15
|
+
def simple_literal?
|
16
|
+
true
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Singletons
|
21
|
+
class SingletonLiteral < StaticLiteral
|
22
|
+
end
|
23
|
+
True = False = Nil = Self = SingletonLiteral
|
24
|
+
|
25
|
+
# Atoms
|
26
|
+
def self.atom(type)
|
27
|
+
::Class.new(StaticLiteral) do
|
28
|
+
has_child value: type
|
29
|
+
end
|
30
|
+
end
|
31
|
+
Sym = atom(::Symbol)
|
32
|
+
Int = atom(::Integer)
|
33
|
+
Float = atom(::Float)
|
34
|
+
Complex = atom(::Complex)
|
35
|
+
Rational = atom(::Rational)
|
36
|
+
class Regopt < StaticLiteral
|
37
|
+
has_extra_children options: [::Symbol]
|
38
|
+
end
|
39
|
+
|
40
|
+
class Str < StaticLiteral
|
41
|
+
has_child value: ::String
|
42
|
+
|
43
|
+
def executed_loc_keys
|
44
|
+
keys = [:expression, :heredoc_body, :heredoc_end]
|
45
|
+
|
46
|
+
exp = expression
|
47
|
+
keys.delete(:expression) if exp && exp.source !~ /\S/
|
48
|
+
|
49
|
+
hd_body = loc_hash[:heredoc_body]
|
50
|
+
keys.delete(:heredoc_body) if hd_body && hd_body.source !~ /\S/
|
51
|
+
|
52
|
+
keys
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# (Potentially) dynamic
|
57
|
+
module SimpleIfItsChildrenAre
|
58
|
+
def simple_literal?
|
59
|
+
children.all?(&:simple_literal?)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Di-atomic
|
64
|
+
class Range < Node
|
65
|
+
include SimpleIfItsChildrenAre
|
66
|
+
|
67
|
+
has_child from: Node
|
68
|
+
has_child to: Node
|
69
|
+
end
|
70
|
+
Erange = Irange = Range
|
71
|
+
|
72
|
+
# Dynamic
|
73
|
+
def self.has_evaluated_segments
|
74
|
+
has_extra_children constituents: [Str, Begin, Ivar, Cvar, Gvar, Dstr, NthRef]
|
75
|
+
end
|
76
|
+
class DynamicLiteral < Node
|
77
|
+
def executed_loc_keys
|
78
|
+
if loc_hash[:heredoc_end]
|
79
|
+
[:expression, :heredoc_end]
|
80
|
+
else
|
81
|
+
[:begin, :end]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
Dsym = Dstr = DynamicLiteral
|
86
|
+
DynamicLiteral.has_evaluated_segments
|
87
|
+
|
88
|
+
class Regexp < Node
|
89
|
+
include SimpleIfItsChildrenAre
|
90
|
+
|
91
|
+
has_evaluated_segments
|
92
|
+
has_child option: Regopt
|
93
|
+
end
|
94
|
+
|
95
|
+
class Xstr < Node
|
96
|
+
check_completion
|
97
|
+
has_evaluated_segments
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DeepCover
|
4
|
+
class Node
|
5
|
+
class For < Node
|
6
|
+
has_tracker :body
|
7
|
+
has_child assignments: [Mlhs, VariableAssignment], flow_entry_count: -> { body.flow_entry_count if body }
|
8
|
+
has_child iterable: [Node], flow_entry_count: -> { flow_entry_count }
|
9
|
+
has_child body: Node,
|
10
|
+
can_be_empty: -> { base_node.loc.end.begin },
|
11
|
+
flow_entry_count: :body_tracker_hits,
|
12
|
+
rewrite: '(%{body_tracker};%{local}=nil;%{node})'
|
13
|
+
check_completion
|
14
|
+
|
15
|
+
def execution_count
|
16
|
+
iterable.flow_completion_count
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Until < Node
|
21
|
+
has_tracker :body
|
22
|
+
has_child condition: Node, rewrite: '((%{node})) || (%{body_tracker};false)'
|
23
|
+
has_child body: Node,
|
24
|
+
can_be_empty: -> { base_node.loc.end.begin },
|
25
|
+
flow_entry_count: :body_tracker_hits
|
26
|
+
check_completion
|
27
|
+
|
28
|
+
def execution_count
|
29
|
+
# TODO: while this distringuishes correctly 0 vs >0, the number return is often too high
|
30
|
+
condition.flow_completion_count
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class UntilPost < Node
|
35
|
+
has_tracker :body
|
36
|
+
has_child condition: Node, rewrite: '((%{node})) || (%{body_tracker};false)'
|
37
|
+
has_child body: Kwbegin, flow_entry_count: -> { body_tracker_hits + parent.flow_entry_count }
|
38
|
+
check_completion
|
39
|
+
|
40
|
+
def execution_count
|
41
|
+
# TODO: while this distringuishes correctly 0 vs >0, the number return is often too high
|
42
|
+
body.flow_completion_count
|
43
|
+
end
|
44
|
+
# TODO: test
|
45
|
+
end
|
46
|
+
|
47
|
+
class While < Node
|
48
|
+
has_tracker :body
|
49
|
+
has_child condition: Node, rewrite: '((%{node})) && %{body_tracker}'
|
50
|
+
has_child body: Node,
|
51
|
+
can_be_empty: -> { base_node.loc.end.begin },
|
52
|
+
flow_entry_count: :body_tracker_hits
|
53
|
+
check_completion
|
54
|
+
|
55
|
+
def execution_count
|
56
|
+
# TODO: while this distringuishes correctly 0 vs >0, the number return is often too high
|
57
|
+
condition.flow_completion_count
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class WhilePost < Node
|
62
|
+
has_tracker :body
|
63
|
+
has_child condition: Node, rewrite: '((%{node})) && %{body_tracker}'
|
64
|
+
has_child body: Kwbegin, flow_entry_count: -> { body_tracker_hits + parent.flow_entry_count }
|
65
|
+
check_completion
|
66
|
+
|
67
|
+
def execution_count
|
68
|
+
# TODO: while this distringuishes correctly 0 vs >0, the number return is often too high
|
69
|
+
body.flow_completion_count
|
70
|
+
end
|
71
|
+
# TODO: test
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DeepCover
|
4
|
+
module Node::Mixin
|
5
|
+
module CanAugmentChildren
|
6
|
+
def self.included(base)
|
7
|
+
base.has_child_handler('remap_%{name}')
|
8
|
+
base.singleton_class.prepend ClassMethods
|
9
|
+
end
|
10
|
+
|
11
|
+
# Augment creates a covered node from the child_base_node.
|
12
|
+
# Caution: receiver is not fully constructed since it is also being augmented.
|
13
|
+
# don't call `children`
|
14
|
+
def augment_children(child_base_nodes)
|
15
|
+
if child_base_nodes.size < (sz = self.class.min_children)
|
16
|
+
child_base_nodes = child_base_nodes.dup
|
17
|
+
child_base_nodes[sz..-1] = [] # Fill with nil
|
18
|
+
end
|
19
|
+
child_base_nodes.map.with_index do |child, child_index|
|
20
|
+
child_name = self.class.child_index_to_name(child_index, child_base_nodes.size)
|
21
|
+
if (klass = remap_child(child, child_name))
|
22
|
+
klass.new(child, parent: self, index: child_index)
|
23
|
+
else
|
24
|
+
child
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
private :augment_children
|
29
|
+
|
30
|
+
def remap_child(child, name = nil)
|
31
|
+
return unless child.is_a?(Parser::AST::Node)
|
32
|
+
class_name = Tools.camelize(child.type)
|
33
|
+
Node.const_defined?(class_name) ? Node.const_get(class_name) : Node
|
34
|
+
end
|
35
|
+
|
36
|
+
module ClassMethods
|
37
|
+
# This handles the following shortcuts:
|
38
|
+
# has_child foo: {type: NodeClass, ...}
|
39
|
+
# same as:
|
40
|
+
# has_child foo: [], remap: {type: NodeClass, ...}
|
41
|
+
# same as:
|
42
|
+
# has_child foo: [NodeClass, ...], remap: {type: NodeClass, ...}
|
43
|
+
#
|
44
|
+
def has_child(remap: nil, **args)
|
45
|
+
name, types = args.first
|
46
|
+
if types.is_a? Hash
|
47
|
+
raise 'Use either remap or a hash as type but not both' if remap
|
48
|
+
remap = types
|
49
|
+
args[name] = types = []
|
50
|
+
end
|
51
|
+
if remap.is_a? Hash
|
52
|
+
type_map = remap
|
53
|
+
remap = ->(child) do
|
54
|
+
klass = type_map[child.type] if child.respond_to? :type
|
55
|
+
klass ||= type_map[child.class]
|
56
|
+
klass
|
57
|
+
end
|
58
|
+
types.concat(type_map.values).uniq!
|
59
|
+
end
|
60
|
+
super(**args, remap: remap)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'executed_after_children'
|
4
|
+
|
5
|
+
module DeepCover
|
6
|
+
module Node::Mixin
|
7
|
+
module CheckCompletion
|
8
|
+
def check_completion(outer: '(%{node})', inner: '(%{node})')
|
9
|
+
has_tracker :completion
|
10
|
+
include ExecutedAfterChildren
|
11
|
+
alias_method :flow_completion_count, :completion_tracker_hits
|
12
|
+
pre, post = outer.split('%{node}')
|
13
|
+
# The local=local is to avoid Ruby warning about "Possible use of value in void context"
|
14
|
+
define_method(:rewrite) { "#{pre}(%{local}=#{inner};%{completion_tracker};%{local}=%{local})#{post}" }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DeepCover
|
4
|
+
module Node::Mixin
|
5
|
+
module ChildCanBeEmpty
|
6
|
+
class << self
|
7
|
+
attr_accessor :last_empty_position # Ugly hack to pass info from Handler to constructor
|
8
|
+
def included(base)
|
9
|
+
base.has_child_handler('%{name}_can_be_empty')
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def remap_child(child, name)
|
14
|
+
if child == nil
|
15
|
+
if (ChildCanBeEmpty.last_empty_position = child_can_be_empty(child, name))
|
16
|
+
return Node::EmptyBody
|
17
|
+
end
|
18
|
+
end
|
19
|
+
super
|
20
|
+
end
|
21
|
+
|
22
|
+
def child_can_be_empty(_child, _name = nil)
|
23
|
+
false
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DeepCover
|
4
|
+
module Node::Mixin
|
5
|
+
# By default, nodes are considered executed if they are entered.
|
6
|
+
# Some are considered executed only if their arguments complete.
|
7
|
+
module ExecutedAfterChildren
|
8
|
+
def execution_count
|
9
|
+
last = children_nodes_in_flow_order.last
|
10
|
+
return last.flow_completion_count if last
|
11
|
+
super
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DeepCover
|
4
|
+
module Node::Mixin
|
5
|
+
module ExecutionLocation
|
6
|
+
def self.included(base)
|
7
|
+
base.extend ClassMethods
|
8
|
+
base.has_child_handler('%{name}_executed_loc_keys')
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
# Macro to define the executed_loc_keys
|
13
|
+
def executed_loc_keys(*loc_keys)
|
14
|
+
# #flatten allows passing an empty array to be explicit
|
15
|
+
loc_keys = loc_keys.flatten
|
16
|
+
define_method :executed_loc_keys do
|
17
|
+
loc_keys
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def executed_loc_keys
|
23
|
+
return [] unless executable?
|
24
|
+
loc_hash.keys - [:expression]
|
25
|
+
end
|
26
|
+
|
27
|
+
def child_executed_loc_keys(_child, _child_name)
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
|
31
|
+
def executed_loc_hash
|
32
|
+
h = Tools.slice(loc_hash, *executed_loc_keys)
|
33
|
+
if (keys = parent.child_executed_loc_keys(self))
|
34
|
+
h.merge!(Tools.slice(parent.loc_hash, *keys))
|
35
|
+
end
|
36
|
+
h.reject { |k, v| v.nil? }
|
37
|
+
end
|
38
|
+
|
39
|
+
def executed_locs
|
40
|
+
executed_loc_hash.values
|
41
|
+
end
|
42
|
+
|
43
|
+
def loc_hash
|
44
|
+
base_node.respond_to?(:location) ? base_node.location.to_hash : {}
|
45
|
+
end
|
46
|
+
|
47
|
+
def expression
|
48
|
+
loc_hash[:expression]
|
49
|
+
end
|
50
|
+
|
51
|
+
def source
|
52
|
+
expression.source if expression
|
53
|
+
end
|
54
|
+
|
55
|
+
def diagnostic_expression
|
56
|
+
expression || parent.diagnostic_expression
|
57
|
+
end
|
58
|
+
|
59
|
+
# Returns an array of character numbers (in the original buffer) that
|
60
|
+
# pertain exclusively to this node (and thus not to any children).
|
61
|
+
def proper_range
|
62
|
+
executed_locs.map(&:to_a).inject([], :+).uniq
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|