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,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