undercover 0.8.4 → 0.9.0

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.
data/lib/undercover.rb CHANGED
@@ -2,25 +2,33 @@
2
2
 
3
3
  $LOAD_PATH << 'lib'
4
4
  require 'json'
5
- require 'imagen'
6
5
  require 'rainbow'
7
- require 'bigdecimal'
8
6
  require 'forwardable'
9
7
  require 'simplecov'
10
8
 
11
9
  require 'undercover/lcov_parser'
10
+ require 'undercover/coverage_index'
12
11
  require 'undercover/result'
13
12
  require 'undercover/cli'
14
13
  require 'undercover/changeset'
15
14
  require 'undercover/formatter'
15
+ require 'undercover/json_formatter'
16
16
  require 'undercover/options'
17
+ require 'undercover/coverage_root'
17
18
  require 'undercover/filter_set'
18
19
  require 'undercover/simplecov_result_adapter'
19
20
  require 'undercover/version'
21
+ require 'undercover/node'
22
+ require 'undercover/node_visitor'
23
+ require 'undercover/erb_node'
24
+ require 'undercover/erb_visitor'
25
+ require 'undercover/erb_extractor'
26
+ require 'undercover/file_loader'
20
27
 
21
28
  module Undercover
22
29
  class Report
23
30
  extend Forwardable
31
+ include FileLoader
24
32
 
25
33
  def_delegators :changeset, :validate
26
34
 
@@ -29,6 +37,7 @@ module Undercover
29
37
  :coverage_adapter,
30
38
  :results,
31
39
  :code_dir,
40
+ :coverage_root,
32
41
  :filter_set,
33
42
  :max_warnings_limit
34
43
 
@@ -39,41 +48,28 @@ module Undercover
39
48
  # @param coverage_adapter [Undercover::SimplecovResultAdapter|Undercover::LcovParser] pre-parsed coverage adapter
40
49
  def initialize(changeset, opts, coverage_adapter)
41
50
  @coverage_adapter = coverage_adapter
42
-
43
- @code_dir = opts.path
44
51
  @changeset = changeset
45
-
46
- ignored_files = coverage_adapter.ignored_files || []
47
- @filter_set = FilterSet.new(opts.glob_allow_filters, opts.glob_reject_filters, ignored_files)
52
+ @code_dir = changeset.repo_workdir
53
+ @coverage_root = resolve_coverage_root
54
+ coverage_adapter.coverage_root = @coverage_root
55
+ @filter_set = build_filter_set(opts)
48
56
  changeset.filter_with(filter_set)
49
57
  @max_warnings_limit = opts.max_warnings_limit
50
58
  @loaded_files = {}
51
59
  @results = {}
52
60
  end
53
61
 
54
- # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
62
+ # rubocop:disable-next Metrics/MethodLength, Metrics/AbcSize
55
63
  def build
56
64
  flag_count = 0
57
65
  changeset.each_changed_line do |filepath, line_no|
58
66
  break if max_warnings_limit && flag_count >= max_warnings_limit
59
67
 
60
- dist_from_line_no = lambda do |res|
61
- return BigDecimal::INFINITY if line_no < res.first_line
62
-
63
- res_lines = res.first_line..res.last_line
64
- return BigDecimal::INFINITY unless res_lines.cover?(line_no)
65
-
66
- line_no - res.first_line
67
- end
68
- dist_from_line_no_sorter = lambda do |res1, res2|
69
- dist_from_line_no[res1] <=> dist_from_line_no[res2]
70
- end
71
-
72
68
  load_and_parse_file(filepath)
73
69
 
74
70
  next unless loaded_files[filepath]
75
71
 
76
- res = loaded_files[filepath].min(&dist_from_line_no_sorter)
72
+ res = result_for_line(filepath, line_no)
77
73
  if res.uncovered?(line_no) && !res.flagged?
78
74
  res.flag
79
75
  flag_count += 1
@@ -83,7 +79,6 @@ module Undercover
83
79
  end
84
80
  self
85
81
  end
86
- # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
87
82
 
88
83
  def build_warnings
89
84
  warn('Undercover::Report#build_warnings is deprecated! ' \
@@ -99,6 +94,29 @@ module Undercover
99
94
  all_results.select(&:flagged?)
100
95
  end
101
96
 
97
+ # Changed files undercover would have reported on, had --include-files /
98
+ # --exclude-files not rejected them. Everything rejected and nothing reported usually
99
+ # means the globs are written against the wrong root.
100
+ def globbed_out_files
101
+ @globbed_out_files ||= changeset.file_paths.select { |path| filter_set.rejected_by_globs?(path) }
102
+ end
103
+
104
+ # Changed ERB templates left unchecked because the coverage report has no ERB coverage
105
+ # at all, meaning SimpleCov ran without enable_coverage(:eval). Checking them anyway
106
+ # would flag every block at 0%.
107
+ def erb_files_without_coverage
108
+ return [] if coverage_adapter.erb_coverage?
109
+
110
+ @erb_files_without_coverage ||= changeset.file_paths.select do |path|
111
+ path.end_with?(FileLoader::ERB_EXTENSION) && filter_set.include?(path)
112
+ end
113
+ end
114
+
115
+ # Changed ERB templates left unchecked because they could not be parsed
116
+ def erb_parse_failures
117
+ @erb_parse_failures ||= []
118
+ end
119
+
102
120
  def inspect
103
121
  "#<Undercover::Report:#{object_id} results: #{results.size}>"
104
122
  end
@@ -108,21 +126,41 @@ module Undercover
108
126
 
109
127
  attr_reader :loaded_files
110
128
 
111
- # rubocop:disable Metrics/AbcSize
112
- def load_and_parse_file(filepath)
113
- key = filepath.gsub(/^\.\//, '')
114
- return if loaded_files[key]
115
-
116
- root_ast = Imagen::Node::Root.new.build_from_file(
117
- File.join(code_dir, filepath)
118
- )
119
- return if root_ast.children.empty?
129
+ # The innermost result whose lines cover line_no: the one starting closest above it,
130
+ # the first one listed on a tie. Lines outside every result fall back to the first.
131
+ def result_for_line(filepath, line_no)
132
+ @line_results ||= {}
133
+ by_line = @line_results[filepath] ||= index_results_by_line(loaded_files[filepath])
134
+ by_line[line_no] || loaded_files[filepath].first
135
+ end
120
136
 
121
- loaded_files[key] = []
122
- root_ast.find_all(->(node) { !node.is_a?(Imagen::Node::Root) }).each do |imagen_node|
123
- loaded_files[key] << Result.new(imagen_node, coverage_adapter, filepath)
137
+ def index_results_by_line(file_results)
138
+ by_line = {}
139
+ file_results.each do |res|
140
+ (res.first_line..res.last_line).each do |line_no|
141
+ current = by_line[line_no]
142
+ by_line[line_no] = res if current.nil? || res.first_line > current.first_line
143
+ end
124
144
  end
145
+ by_line
146
+ end
147
+
148
+ def build_filter_set(opts)
149
+ FilterSet.new(opts.glob_allow_filters, opts.glob_reject_filters, coverage_adapter.ignored_files || [],
150
+ coverage_root: coverage_root)
151
+ end
152
+
153
+ # LCOV reports carry no SimpleCov.root, so fall back to where undercover runs, which
154
+ # is where SimpleCov ran for anyone invoking the CLI by hand. A wrong guess is caught
155
+ # by the covered-file check in CoverageRoot.
156
+ def resolve_coverage_root
157
+ simplecov_root = coverage_adapter.simplecov_root || Dir.pwd
158
+ CoverageRoot.derive(code_dir, simplecov_root, coverage_adapter.coverage_keys, exists: method(:repository_path?))
159
+ end
160
+
161
+ # On disk, or committed at HEAD for files a sparse checkout left out
162
+ def repository_path?(path)
163
+ File.exist?(File.join(code_dir, path)) || changeset.head_path?(path)
125
164
  end
126
- # rubocop:enable Metrics/AbcSize
127
165
  end
128
166
  end
data/undercover.gemspec CHANGED
@@ -4,7 +4,7 @@ lib = File.expand_path('lib', __dir__)
4
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
5
  require 'undercover/version'
6
6
 
7
- Gem::Specification.new do |spec|
7
+ Gem::Specification.new do |spec| # rubocop:disable Metrics/BlockLength
8
8
  spec.name = 'undercover'
9
9
  spec.version = Undercover::VERSION
10
10
  spec.authors = ['Jan Grodowski']
@@ -25,13 +25,16 @@ Gem::Specification.new do |spec|
25
25
  spec.executables = spec.files.grep(/^bin\//) { |f| File.basename(f) }
26
26
  spec.require_paths = ['lib']
27
27
 
28
- spec.required_ruby_version = '>= 3.0.0'
28
+ spec.required_ruby_version = '>= 3.2.0'
29
29
 
30
30
  spec.add_dependency 'base64'
31
- spec.add_dependency 'bigdecimal'
32
- spec.add_dependency 'imagen', '>= 0.2.0'
31
+ spec.add_dependency 'benchmark'
32
+ spec.add_dependency 'erubi', '>= 1.13'
33
+ spec.add_dependency 'prism', '>= 1.8', '< 3'
33
34
  spec.add_dependency 'rainbow', '>= 2.1', '< 4.0'
34
35
  spec.add_dependency 'rugged', '>= 0.27', '< 1.10'
35
- spec.add_dependency 'simplecov'
36
- spec.add_dependency 'simplecov_json_formatter'
36
+ spec.add_dependency 'simplecov', '>= 1.0'
37
+ # Pinned: undercover requires its internal files by path and subclasses
38
+ # ResultHashFormatter, so a file reorganisation would break loading.
39
+ spec.add_dependency 'simplecov_json_formatter', '~> 0.1'
37
40
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: undercover
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.4
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jan Grodowski
@@ -24,7 +24,7 @@ dependencies:
24
24
  - !ruby/object:Gem::Version
25
25
  version: '0'
26
26
  - !ruby/object:Gem::Dependency
27
- name: bigdecimal
27
+ name: benchmark
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
30
  - - ">="
@@ -38,19 +38,39 @@ dependencies:
38
38
  - !ruby/object:Gem::Version
39
39
  version: '0'
40
40
  - !ruby/object:Gem::Dependency
41
- name: imagen
41
+ name: erubi
42
42
  requirement: !ruby/object:Gem::Requirement
43
43
  requirements:
44
44
  - - ">="
45
45
  - !ruby/object:Gem::Version
46
- version: 0.2.0
46
+ version: '1.13'
47
47
  type: :runtime
48
48
  prerelease: false
49
49
  version_requirements: !ruby/object:Gem::Requirement
50
50
  requirements:
51
51
  - - ">="
52
52
  - !ruby/object:Gem::Version
53
- version: 0.2.0
53
+ version: '1.13'
54
+ - !ruby/object:Gem::Dependency
55
+ name: prism
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '1.8'
61
+ - - "<"
62
+ - !ruby/object:Gem::Version
63
+ version: '3'
64
+ type: :runtime
65
+ prerelease: false
66
+ version_requirements: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: '1.8'
71
+ - - "<"
72
+ - !ruby/object:Gem::Version
73
+ version: '3'
54
74
  - !ruby/object:Gem::Dependency
55
75
  name: rainbow
56
76
  requirement: !ruby/object:Gem::Requirement
@@ -97,28 +117,28 @@ dependencies:
97
117
  requirements:
98
118
  - - ">="
99
119
  - !ruby/object:Gem::Version
100
- version: '0'
120
+ version: '1.0'
101
121
  type: :runtime
102
122
  prerelease: false
103
123
  version_requirements: !ruby/object:Gem::Requirement
104
124
  requirements:
105
125
  - - ">="
106
126
  - !ruby/object:Gem::Version
107
- version: '0'
127
+ version: '1.0'
108
128
  - !ruby/object:Gem::Dependency
109
129
  name: simplecov_json_formatter
110
130
  requirement: !ruby/object:Gem::Requirement
111
131
  requirements:
112
- - - ">="
132
+ - - "~>"
113
133
  - !ruby/object:Gem::Version
114
- version: '0'
134
+ version: '0.1'
115
135
  type: :runtime
116
136
  prerelease: false
117
137
  version_requirements: !ruby/object:Gem::Requirement
118
138
  requirements:
119
- - - ">="
139
+ - - "~>"
120
140
  - !ruby/object:Gem::Version
121
- version: '0'
141
+ version: '0.1'
122
142
  email:
123
143
  - jgrodowski@gmail.com
124
144
  executables:
@@ -127,6 +147,7 @@ extensions: []
127
147
  extra_rdoc_files: []
128
148
  files:
129
149
  - ".codebeatsettings"
150
+ - ".github/FUNDING.yml"
130
151
  - ".github/dependabot.yml"
131
152
  - ".github/workflows/ruby.yml"
132
153
  - ".gitignore"
@@ -147,14 +168,23 @@ files:
147
168
  - docs/screenshot_warnings.png
148
169
  - docs/semaphore.yml
149
170
  - lib/undercover.rb
171
+ - lib/undercover/ast_branch_annotator.rb
150
172
  - lib/undercover/changeset.rb
151
173
  - lib/undercover/cli.rb
174
+ - lib/undercover/coverage_index.rb
175
+ - lib/undercover/coverage_root.rb
176
+ - lib/undercover/erb_extractor.rb
177
+ - lib/undercover/erb_node.rb
178
+ - lib/undercover/erb_visitor.rb
179
+ - lib/undercover/file_loader.rb
152
180
  - lib/undercover/filter_set.rb
153
181
  - lib/undercover/formatter.rb
182
+ - lib/undercover/json_formatter.rb
154
183
  - lib/undercover/lcov_parser.rb
184
+ - lib/undercover/node.rb
185
+ - lib/undercover/node_visitor.rb
155
186
  - lib/undercover/options.rb
156
187
  - lib/undercover/result.rb
157
- - lib/undercover/root_to_relative_paths.rb
158
188
  - lib/undercover/simplecov_formatter.rb
159
189
  - lib/undercover/simplecov_result_adapter.rb
160
190
  - lib/undercover/version.rb
@@ -171,7 +201,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
171
201
  requirements:
172
202
  - - ">="
173
203
  - !ruby/object:Gem::Version
174
- version: 3.0.0
204
+ version: 3.2.0
175
205
  required_rubygems_version: !ruby/object:Gem::Requirement
176
206
  requirements:
177
207
  - - ">="
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Undercover
4
- module RootToRelativePaths
5
- # Needed if undercover is running inside nested subdirectories (e.g. in a monorepo app), where
6
- # the git paths are rooted deeper than the paths in the coverage report.
7
- # If that is the case, trim the git filepath to match the local relative path, assumming undercover is
8
- # running in the correct directory (has to be equal to SimpleCov.root for paths to match)
9
- # @param filepath[String]
10
- # @return String
11
- def fix_relative_filepath(filepath)
12
- return filepath unless @code_dir
13
-
14
- code_dir_abs = File.expand_path(@code_dir)
15
-
16
- if Pathname.new(Dir.pwd).ascend.any? { |p| p.to_s == code_dir_abs }
17
- prefix_to_skip = Pathname.new(Dir.pwd).relative_path_from(code_dir_abs).to_s
18
- return filepath.delete_prefix(prefix_to_skip).gsub(/\A\//, '')
19
- end
20
-
21
- filepath
22
- end
23
- end
24
- end