deep-cover 0.1.14 → 0.1.15

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 (101) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +227 -0
  3. data/Gemfile +5 -2
  4. data/Rakefile +9 -6
  5. data/bin/console +3 -3
  6. data/bin/cov +8 -8
  7. data/bin/gemcov +2 -2
  8. data/bin/selfcov +5 -5
  9. data/bin/test_gems +11 -10
  10. data/bin/testall +6 -6
  11. data/deep_cover.gemspec +26 -21
  12. data/exe/deep-cover +1 -0
  13. data/lib/deep-cover.rb +2 -0
  14. data/lib/deep_cover.rb +3 -0
  15. data/lib/deep_cover/analyser.rb +2 -0
  16. data/lib/deep_cover/analyser/base.rb +2 -0
  17. data/lib/deep_cover/analyser/branch.rb +4 -2
  18. data/lib/deep_cover/analyser/covered_code_source.rb +3 -1
  19. data/lib/deep_cover/analyser/function.rb +3 -1
  20. data/lib/deep_cover/analyser/ignore_uncovered.rb +6 -4
  21. data/lib/deep_cover/analyser/node.rb +3 -0
  22. data/lib/deep_cover/analyser/optionally_covered.rb +12 -7
  23. data/lib/deep_cover/analyser/per_char.rb +7 -6
  24. data/lib/deep_cover/analyser/per_line.rb +9 -8
  25. data/lib/deep_cover/analyser/statement.rb +2 -0
  26. data/lib/deep_cover/analyser/subset.rb +4 -1
  27. data/lib/deep_cover/auto_run.rb +3 -0
  28. data/lib/deep_cover/autoload_tracker.rb +6 -3
  29. data/lib/deep_cover/backports.rb +2 -0
  30. data/lib/deep_cover/base.rb +11 -2
  31. data/lib/deep_cover/builtin_takeover.rb +2 -0
  32. data/lib/deep_cover/cli/debugger.rb +55 -30
  33. data/lib/deep_cover/cli/deep_cover.rb +17 -11
  34. data/lib/deep_cover/cli/instrumented_clone_reporter.rb +16 -14
  35. data/lib/deep_cover/config.rb +29 -16
  36. data/lib/deep_cover/core_ext/autoload_overrides.rb +2 -0
  37. data/lib/deep_cover/core_ext/coverage_replacement.rb +2 -0
  38. data/lib/deep_cover/core_ext/load_overrides.rb +5 -6
  39. data/lib/deep_cover/core_ext/require_overrides.rb +6 -7
  40. data/lib/deep_cover/coverage.rb +21 -18
  41. data/lib/deep_cover/covered_code.rb +22 -12
  42. data/lib/deep_cover/custom_requirer.rb +82 -35
  43. data/lib/deep_cover/memoize.rb +48 -0
  44. data/lib/deep_cover/module_override.rb +2 -0
  45. data/lib/deep_cover/node.rb +14 -1
  46. data/lib/deep_cover/node/arguments.rb +2 -0
  47. data/lib/deep_cover/node/assignments.rb +32 -30
  48. data/lib/deep_cover/node/base.rb +30 -29
  49. data/lib/deep_cover/node/begin.rb +3 -1
  50. data/lib/deep_cover/node/block.rb +5 -2
  51. data/lib/deep_cover/node/branch.rb +2 -1
  52. data/lib/deep_cover/node/case.rb +15 -13
  53. data/lib/deep_cover/node/collections.rb +2 -0
  54. data/lib/deep_cover/node/const.rb +2 -0
  55. data/lib/deep_cover/node/def.rb +10 -8
  56. data/lib/deep_cover/node/empty_body.rb +2 -0
  57. data/lib/deep_cover/node/exceptions.rb +3 -1
  58. data/lib/deep_cover/node/if.rb +3 -1
  59. data/lib/deep_cover/node/keywords.rb +4 -2
  60. data/lib/deep_cover/node/literals.rb +2 -0
  61. data/lib/deep_cover/node/loops.rb +5 -3
  62. data/lib/deep_cover/node/mixin/can_augment_children.rb +8 -7
  63. data/lib/deep_cover/node/mixin/check_completion.rb +3 -1
  64. data/lib/deep_cover/node/mixin/child_can_be_empty.rb +4 -2
  65. data/lib/deep_cover/node/mixin/executed_after_children.rb +2 -0
  66. data/lib/deep_cover/node/mixin/execution_location.rb +4 -2
  67. data/lib/deep_cover/node/mixin/flow_accounting.rb +2 -0
  68. data/lib/deep_cover/node/mixin/has_child.rb +22 -18
  69. data/lib/deep_cover/node/mixin/has_child_handler.rb +10 -8
  70. data/lib/deep_cover/node/mixin/has_tracker.rb +4 -2
  71. data/lib/deep_cover/node/mixin/is_statement.rb +3 -1
  72. data/lib/deep_cover/node/mixin/rewriting.rb +5 -3
  73. data/lib/deep_cover/node/mixin/wrapper.rb +2 -0
  74. data/lib/deep_cover/node/module.rb +11 -9
  75. data/lib/deep_cover/node/root.rb +2 -0
  76. data/lib/deep_cover/node/send.rb +4 -2
  77. data/lib/deep_cover/node/short_circuit.rb +4 -2
  78. data/lib/deep_cover/node/splat.rb +2 -0
  79. data/lib/deep_cover/node/variables.rb +2 -0
  80. data/lib/deep_cover/parser_ext/range.rb +3 -1
  81. data/lib/deep_cover/problem_with_diagnostic.rb +11 -9
  82. data/lib/deep_cover/reporter.rb +2 -0
  83. data/lib/deep_cover/reporter/istanbul.rb +28 -24
  84. data/lib/deep_cover/tools.rb +2 -0
  85. data/lib/deep_cover/tools/builtin_coverage.rb +6 -4
  86. data/lib/deep_cover/tools/camelize.rb +3 -1
  87. data/lib/deep_cover/tools/dasherize.rb +3 -1
  88. data/lib/deep_cover/tools/dump_covered_code.rb +7 -6
  89. data/lib/deep_cover/tools/execute_sample.rb +13 -13
  90. data/lib/deep_cover/tools/format.rb +3 -1
  91. data/lib/deep_cover/tools/format_char_cover.rb +4 -2
  92. data/lib/deep_cover/tools/format_generated_code.rb +3 -1
  93. data/lib/deep_cover/tools/number_lines.rb +2 -0
  94. data/lib/deep_cover/tools/our_coverage.rb +5 -3
  95. data/lib/deep_cover/tools/profiling.rb +66 -0
  96. data/lib/deep_cover/tools/require_relative_dir.rb +3 -1
  97. data/lib/deep_cover/tools/silence_warnings.rb +4 -1
  98. data/lib/deep_cover/tools/slice.rb +3 -1
  99. data/lib/deep_cover/tools/truncate_backtrace.rb +2 -0
  100. data/lib/deep_cover/version.rb +3 -1
  101. metadata +47 -30
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DeepCover
2
4
  require 'bundler/setup'
3
5
  require 'slop'
@@ -9,7 +11,7 @@ module DeepCover
9
11
  extend self
10
12
 
11
13
  def show_version
12
- puts "deep-cover v#{DeepCover::VERSION}; parser v#{Parser::Version}"
14
+ puts "deep-cover v#{::DeepCover::VERSION}; parser v#{::Parser::VERSION}"
13
15
  end
14
16
 
15
17
  def show_help
@@ -17,7 +19,7 @@ module DeepCover
17
19
  end
18
20
 
19
21
  class Parser < Struct.new(:delegate)
20
- def method_missing(method, *args, &block)
22
+ def method_missing(method, *args, &block) # rubocop:disable Style/MethodMissing
21
23
  options = args.last
22
24
  if options.is_a?(Hash) && options.has_key?(:default)
23
25
  args[-2] += " [#{options[:default]}]"
@@ -34,7 +36,7 @@ module DeepCover
34
36
 
35
37
  def menu
36
38
  @menu ||= parse do |o|
37
- o.banner = "usage: deep-cover [options] [path/to/app/or/gem]"
39
+ o.banner = 'usage: deep-cover [options] [path/to/app/or/gem]'
38
40
  o.separator ''
39
41
  o.string '-o', '--output', 'output folder', default: './coverage'
40
42
  o.string '-c', '--command', 'command to run tests', default: 'bundle exec rake'
@@ -43,18 +45,20 @@ module DeepCover
43
45
  o.separator 'Coverage options'
44
46
  @ignore_uncovered_map = Analyser.optionally_covered.map do |option|
45
47
  default = Config::DEFAULTS[:ignore_uncovered].include?(option)
46
- o.bool "--ignore-#{Tools.dasherize(option)}", "", default: default
48
+ o.bool "--ignore-#{Tools.dasherize(option)}", '', default: default
47
49
  [:"ignore_#{option}", option]
48
50
  end.to_h
49
- o.separator ''
50
- o.separator 'For testing purposes:'
51
+ o.separator "\nFor testing purposes:"
52
+ o.bool '--profile', 'use profiler' unless RUBY_PLATFORM == 'java'
51
53
  o.string '-e', '--expression', 'test ruby expression instead of a covering a path'
52
54
  o.bool '-d', '--debug', 'enter debugging after cover'
53
55
 
54
- o.separator ''
55
- o.separator 'Other available commands:'
56
- o.on('--version', 'print the version') { version; exit }
57
- o.on('-h', '--help') { help; exit }
56
+ o.separator "\nOther available commands:"
57
+ o.on('--version', 'print the version') do
58
+ show_version
59
+ exit
60
+ end
61
+ o.boolean('-h', '--help')
58
62
  end
59
63
  end
60
64
 
@@ -68,7 +72,9 @@ module DeepCover
68
72
 
69
73
  def go
70
74
  options = convert_options(menu.to_h)
71
- if options[:expression]
75
+ if options[:help]
76
+ show_help
77
+ elsif options[:expression]
72
78
  Debugger.new(options[:expression], **options).show
73
79
  elsif (path = menu.arguments.first)
74
80
  InstrumentedCloneReporter.new(path, **options).run
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'yaml'
2
4
  require 'tmpdir'
3
5
 
@@ -18,7 +20,7 @@ module DeepCover
18
20
  raise "Can't find Gemfile" unless @root_path.join('Gemfile').exist?
19
21
  end
20
22
  @dest_root = Pathname('~/test_deep_cover').expand_path
21
- @dest_root = Pathname.new(Dir.mktmpdir("deep_cover_test")) unless @dest_root.exist?
23
+ @dest_root = Pathname.new(Dir.mktmpdir('deep_cover_test')) unless @dest_root.exist?
22
24
 
23
25
  gem_relative_path = @source_path.relative_path_from(@root_path)
24
26
  @main_path = @dest_root.join(gem_relative_path)
@@ -31,7 +33,7 @@ module DeepCover
31
33
 
32
34
  def copy
33
35
  return true if @copied
34
- puts "Cloning..."
36
+ puts 'Cloning...'
35
37
  FileUtils.cp_r(Dir.glob("#{@root_path}/#{GLOB_ALL_CONTENT}"), @dest_root)
36
38
  @copied = true
37
39
  end
@@ -39,7 +41,7 @@ module DeepCover
39
41
  def patch_ruby_file(ruby_file)
40
42
  content = ruby_file.read
41
43
  # Insert our code after leading comments:
42
- content.sub!(/^((#.*\n+)*)/, "#{$1}require 'deep_cover/auto_run';DeepCover::AutoRun.run! '#{@dest_root}';")
44
+ content.sub!(/^(#.*\n+)*/) { |header| "#{header}require 'deep_cover/auto_run';DeepCover::AutoRun.run! '#{@dest_root}';" }
43
45
  ruby_file.write(content)
44
46
  end
45
47
 
@@ -79,7 +81,7 @@ module DeepCover
79
81
  module GemCollection
80
82
  include Gem
81
83
  def each_gem_path
82
- Pathname.glob(@main_path.join('*/lib')).each{|p| yield p.dirname}
84
+ Pathname.glob(@main_path.join('*/lib')).each { |p| yield p.dirname }
83
85
  end
84
86
  end
85
87
 
@@ -108,19 +110,19 @@ module DeepCover
108
110
  unless content =~ /gem 'deep-cover'/
109
111
  puts "Patching Gemfile #{gemfile}"
110
112
  File.write(gemfile, [
111
- "# This file was modified by DeepCover",
112
- content,
113
- "gem 'deep-cover', path: '#{File.expand_path(__dir__ + '/../../../')}'",
114
- '',
115
- ].join("\n"))
113
+ '# This file was modified by DeepCover',
114
+ content,
115
+ "gem 'deep-cover', path: '#{File.expand_path(__dir__ + '/../../../')}'",
116
+ '',
117
+ ].join("\n"))
116
118
  end
117
119
  end
118
120
 
119
121
  def patch_rubocop
120
122
  path = @dest_root.join('.rubocop.yml')
121
123
  return unless path.exist?
122
- puts "Patching .rubocop.yml"
123
- config = YAML.load(path.read.gsub(/(?<!\w)lib(?!\w)/, 'lib_original'))
124
+ puts 'Patching .rubocop.yml'
125
+ config = YAML.safe_load(path.read.gsub(/(?<!\w)lib(?!\w)/, 'lib_original'))
124
126
  ((config['AllCops'] ||= {})['Exclude'] ||= []) << 'lib/**/*' << 'app/**/*'
125
127
  path.write("# This file was modified by DeepCover\n" + YAML.dump(config))
126
128
  end
@@ -137,8 +139,8 @@ module DeepCover
137
139
  original = to_cover.sub_ext('_original')
138
140
  FileUtils.cp_r(to_cover, original)
139
141
  Tools.dump_covered_code(original,
140
- coverage: coverage, root_path: @dest_root.to_s,
141
- dest_path: to_cover)
142
+ coverage: coverage, root_path: @dest_root.to_s,
143
+ dest_path: to_cover)
142
144
  end
143
145
  coverage.save(@dest_root.to_s)
144
146
  end
@@ -155,7 +157,7 @@ module DeepCover
155
157
  end
156
158
 
157
159
  def bundle
158
- puts "Running `bundle install`"
160
+ puts 'Running `bundle install`'
159
161
  Bundler.with_clean_env do
160
162
  `cd #{@dest_root} && bundle`
161
163
  end
@@ -1,12 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DeepCover
2
4
  class Config
3
5
  DEFAULTS = {
4
- ignore_uncovered: [],
5
- paths: %w[./app ./lib],
6
- allow_partial: false,
7
- }
6
+ ignore_uncovered: [],
7
+ paths: %w[./app ./lib],
8
+ allow_partial: false,
9
+ }.freeze
8
10
 
9
- def initialize(**options)
11
+ def initialize(notify = nil, **options)
12
+ @notify = notify
10
13
  @options = copy(DEFAULTS.merge(options))
11
14
  end
12
15
 
@@ -17,38 +20,48 @@ module DeepCover
17
20
 
18
21
  def ignore_uncovered(*keywords)
19
22
  check_uncovered(keywords)
20
- @options[:ignore_uncovered] += keywords
21
- self
23
+ change(:ignore_uncovered, @options[:ignore_uncovered] | keywords)
22
24
  end
23
25
 
24
26
  def detect_uncovered(*keywords)
25
27
  check_uncovered(keywords)
26
- @options[:ignore_uncovered] -= keywords
27
- self
28
+ change(:ignore_uncovered, @options[:ignore_uncovered] - keywords)
28
29
  end
29
30
 
30
- def paths(paths)
31
- @options[:paths] = paths
32
- self
31
+ def paths(paths = nil)
32
+ if paths
33
+ change(:paths, Array(paths).dup.freeze)
34
+ else
35
+ @options[:paths]
36
+ end
33
37
  end
34
38
 
35
39
  private
40
+
36
41
  def check_uncovered(keywords)
37
42
  unknown = keywords - Analyser.optionally_covered
38
43
  raise ArgumentError, "unknown options: #{unknown.join(', ')}" unless unknown.empty?
39
44
  end
40
45
 
46
+ def change(option, value)
47
+ if @options[option] != value
48
+ @options[option] = value
49
+ @notify.config_changed(option) if @notify.respond_to? :config_changed
50
+ end
51
+ self
52
+ end
53
+
41
54
  def copy(h)
42
- h.dup.transform_values(&:dup)
55
+ h.dup.transform_values(&:dup).transform_values(&:freeze)
43
56
  end
44
57
 
45
58
  module Setter
46
- def config
47
- @config ||= Config.new
59
+ def config(notify = self)
60
+ @config ||= Config.new(notify)
48
61
  end
49
62
 
50
63
  def configure(&block)
51
- raise "Must provide a block" unless block
64
+ raise 'Must provide a block' unless block
52
65
  case block.arity
53
66
  when 0
54
67
  config.instance_eval(&block)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # We need to override autoload, because MRI has special behaviors associated with it
2
4
  # that we can't reuse, hence we need to do workarounds.
3
5
  #
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # This is a complete replacement for the builtin Coverage module of Ruby
2
4
 
3
5
  require 'coverage'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # These are the monkeypatches to replace the default #load in order
2
4
  # to instrument the code before it gets run.
3
5
  # For now, this is not used, and may never be. The tracking and reporting for things can might be
@@ -8,12 +10,9 @@ module DeepCover
8
10
  def load(path, wrap = false)
9
11
  return load_without_deep_cover(path, wrap) if wrap
10
12
 
11
- result = DeepCover.custom_requirer.load(path)
12
- if [:not_found, :cover_failed, :not_supported].include?(result)
13
- load_without_deep_cover(path)
14
- else
15
- result
16
- end
13
+ result = catch(:use_fallback) { DeepCover.custom_requirer.load(path) }
14
+ result = load_without_deep_cover(path) if result.is_a? Symbol
15
+ result
17
16
  end
18
17
  end
19
18
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # These are the monkeypatches to replace the default #require and
2
4
  # #require_relative in order to instrument the code before it gets run.
3
5
  # Kernel.require and Kernel#require must both have their version because
@@ -7,17 +9,14 @@
7
9
  module DeepCover
8
10
  module RequireOverride
9
11
  def require(path)
10
- result = DeepCover.custom_requirer.require(path)
11
- if [:not_found, :cover_failed, :not_supported].include?(result)
12
- require_without_deep_cover(path)
13
- else
14
- result
15
- end
12
+ result = catch(:use_fallback) { DeepCover.custom_requirer.require(path) }
13
+ result = require_without_deep_cover(path) if result.is_a? Symbol
14
+ result
16
15
  end
17
16
 
18
17
  def require_relative(path)
19
18
  base = caller(1..1).first[/[^:]+/]
20
- raise LoadError, "cannot infer basepath" unless base
19
+ raise LoadError, 'cannot infer basepath' unless base
21
20
  base = File.dirname(base)
22
21
 
23
22
  require(File.absolute_path(path, base))
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DeepCover
2
4
  require 'parser'
3
5
  silence_warnings do
@@ -34,7 +36,7 @@ module DeepCover
34
36
 
35
37
  def each
36
38
  return to_enum unless block_given?
37
- @covered_codes.each{|_path, covered_code| yield covered_code}
39
+ @covered_codes.each_value { |covered_code| yield covered_code }
38
40
  self
39
41
  end
40
42
 
@@ -45,7 +47,7 @@ module DeepCover
45
47
  end.inject(:merge)
46
48
  end
47
49
 
48
- def output_istanbul(dir: '.', name: ".nyc_output", **options)
50
+ def output_istanbul(dir: '.', name: '.nyc_output', **options)
49
51
  path = Pathname.new(dir).expand_path.join(name)
50
52
  path.mkpath
51
53
  path.each_child(&:delete)
@@ -82,7 +84,7 @@ module DeepCover
82
84
  if Reporter::Istanbul.available?
83
85
  report_istanbul(**options)
84
86
  else
85
- warn "nyc not available. Please install `nyc` using `yarn global add nyc` or `npm i nyc -g`"
87
+ warn 'nyc not available. Please install `nyc` using `yarn global add nyc` or `npm i nyc -g`'
86
88
  basic_report
87
89
  end
88
90
  end
@@ -110,6 +112,7 @@ module DeepCover
110
112
  end
111
113
 
112
114
  class Persistence
115
+ # rubocop:disable Security/MarshalLoad
113
116
  BASENAME = 'coverage.dc'
114
117
  TRACKER_TEMPLATE = 'trackers%{unique}.dct'
115
118
 
@@ -132,16 +135,16 @@ module DeepCover
132
135
 
133
136
  def save_trackers(global)
134
137
  saved?
135
- trackers = eval(global)
138
+ trackers = eval(global) # rubocop:disable Security/Eval
136
139
  # Some testing involves more than one process, some of which don't run any of our covered code.
137
140
  # Don't save anything if that's the case
138
141
  return if trackers.nil?
139
- basename = TRACKER_TEMPLATE % {unique: SecureRandom.urlsafe_base64}
140
- dir_path.join(basename).binwrite(Marshal.dump({
141
- version: DeepCover::VERSION,
142
- global: global,
143
- trackers: trackers,
144
- }))
142
+ basename = format(TRACKER_TEMPLATE, unique: SecureRandom.urlsafe_base64)
143
+ dir_path.join(basename).binwrite(Marshal.dump(
144
+ version: DeepCover::VERSION,
145
+ global: global,
146
+ trackers: trackers,
147
+ ))
145
148
  end
146
149
 
147
150
  def saved?
@@ -156,10 +159,10 @@ module DeepCover
156
159
  end
157
160
 
158
161
  def save_coverage(coverage)
159
- dir_path.join(BASENAME).binwrite(Marshal.dump({
160
- version: DeepCover::VERSION,
161
- coverage: coverage,
162
- }))
162
+ dir_path.join(BASENAME).binwrite(Marshal.dump(
163
+ version: DeepCover::VERSION,
164
+ coverage: coverage,
165
+ ))
163
166
  end
164
167
 
165
168
  def load_coverage
@@ -173,22 +176,22 @@ module DeepCover
173
176
  tracker_files.each do |full_path|
174
177
  Marshal.load(full_path.binread).tap do |version: raise, global: raise, trackers: raise|
175
178
  raise "dump version mismatch: #{version}, currently #{DeepCover::VERSION}" unless version == DeepCover::VERSION
176
- merge_trackers(eval("#{global} ||= {}"), trackers)
179
+ merge_trackers(eval("#{global} ||= {}"), trackers) # rubocop:disable Security/Eval
177
180
  end
178
181
  end
179
182
  end
180
183
 
181
184
  def merge_trackers(hash, to_merge)
182
185
  hash.merge!(to_merge) do |_key, current, to_add|
183
- unless current.size == 0 || current.size == to_add.size
186
+ unless current.empty? || current.size == to_add.size
184
187
  warn "Merging trackers of different sizes: #{current.size} vs #{to_add.size}"
185
188
  end
186
- to_add.zip(current).map{|a, b| a+b}
189
+ to_add.zip(current).map { |a, b| a + b }
187
190
  end
188
191
  end
189
192
 
190
193
  def tracker_files
191
- basename = TRACKER_TEMPLATE % { unique: '*' }
194
+ basename = format(TRACKER_TEMPLATE, unique: '*')
192
195
  Pathname.glob(dir_path.join(basename))
193
196
  end
194
197
 
@@ -1,13 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DeepCover
2
4
  class CoveredCode
3
5
  DEFAULT_TRACKER_GLOBAL = '$_cov'
4
6
 
5
7
  attr_accessor :covered_source, :buffer, :tracker_global, :local_var, :name
6
8
  @@counter = 0
7
- @@globals = Hash.new{|h, global| h[global] = eval("#{global} ||= {}") }
9
+ @@globals = Hash.new { |h, global| h[global] = eval("#{global} ||= {}") } # rubocop:disable Security/Eval
8
10
 
9
11
  def initialize(path: nil, source: nil, lineno: 1, tracker_global: DEFAULT_TRACKER_GLOBAL, local_var: '_temp', name: nil)
10
- raise "Must provide either path or source" unless path || source
12
+ raise 'Must provide either path or source' unless path || source
11
13
 
12
14
  @buffer = ::Parser::Source::Buffer.new(path, lineno)
13
15
  @buffer.source = source ||= File.read(path)
@@ -27,20 +29,18 @@ module DeepCover
27
29
  end
28
30
 
29
31
  def nb_lines
30
- @nb_lines ||= begin
31
- lines = buffer.source_lines
32
- if lines.size == 0
33
- 0
34
- else
35
- lines.size - (lines.last.empty? ? 1 : 0)
36
- end
32
+ lines = buffer.source_lines
33
+ if lines.empty?
34
+ 0
35
+ else
36
+ lines.size - (lines.last.empty? ? 1 : 0)
37
37
  end
38
38
  end
39
39
 
40
40
  def execute_code(binding: DeepCover::GLOBAL_BINDING.dup)
41
41
  return if has_executed?
42
42
  global[nb] = Array.new(@tracker_count, 0)
43
- eval(@covered_source, binding, @buffer.name || '<raw_code>', lineno)
43
+ eval(@covered_source, binding, @buffer.name || '<raw_code>', lineno) # rubocop:disable Security/Eval
44
44
  self
45
45
  end
46
46
 
@@ -71,7 +71,7 @@ module DeepCover
71
71
  # Returns a range of tracker ids
72
72
  def allocate_trackers(nb_needed)
73
73
  prev = @tracker_count
74
- @tracker_count += nb_needed
74
+ @tracker_count += nb_needed if nb_needed > 0 # Avoid error if frozen and called with 0.
75
75
  prev...@tracker_count
76
76
  end
77
77
 
@@ -109,7 +109,7 @@ module DeepCover
109
109
  prefix, _node, suffix = rule.partition('%{node}')
110
110
  unless prefix.empty?
111
111
  prefix = yield prefix, node, range.begin, :prefix if block_given?
112
- rewriter.insert_before_multi range, prefix rescue binding.pry
112
+ rewriter.insert_before_multi range, prefix
113
113
  end
114
114
  unless suffix.empty?
115
115
  suffix = yield suffix, node, range.end, :suffix if block_given?
@@ -124,7 +124,17 @@ module DeepCover
124
124
  global[nb] != nil
125
125
  end
126
126
 
127
+ def freeze
128
+ unless frozen? # Guard against reentrance
129
+ must_have_executed
130
+ super
131
+ root.each_node(&:freeze)
132
+ end
133
+ self
134
+ end
135
+
127
136
  protected
137
+
128
138
  def global
129
139
  @@globals[tracker_global]
130
140
  end