ultra-pro-pkg 0.0.1
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.
Potentially problematic release.
This version of ultra-pro-pkg might be problematic. Click here for more details.
- checksums.yaml +7 -0
- data/simplecov-1.0.0/LICENSE +20 -0
- data/simplecov-1.0.0/README.md +1524 -0
- data/simplecov-1.0.0/doc/alternate-formatters.md +66 -0
- data/simplecov-1.0.0/doc/commercial-services.md +25 -0
- data/simplecov-1.0.0/doc/editor-integration.md +18 -0
- data/simplecov-1.0.0/exe/simplecov +11 -0
- data/simplecov-1.0.0/lib/minitest/simplecov_plugin.rb +171 -0
- data/simplecov-1.0.0/lib/simplecov/autostart.rb +11 -0
- data/simplecov-1.0.0/lib/simplecov/cli/clean.rb +47 -0
- data/simplecov-1.0.0/lib/simplecov/cli/coverage.rb +91 -0
- data/simplecov-1.0.0/lib/simplecov/cli/diff.rb +151 -0
- data/simplecov-1.0.0/lib/simplecov/cli/dotfile.rb +100 -0
- data/simplecov-1.0.0/lib/simplecov/cli/merge.rb +116 -0
- data/simplecov-1.0.0/lib/simplecov/cli/open.rb +50 -0
- data/simplecov-1.0.0/lib/simplecov/cli/report.rb +84 -0
- data/simplecov-1.0.0/lib/simplecov/cli/run.rb +36 -0
- data/simplecov-1.0.0/lib/simplecov/cli/serve.rb +139 -0
- data/simplecov-1.0.0/lib/simplecov/cli/uncovered.rb +107 -0
- data/simplecov-1.0.0/lib/simplecov/cli.rb +150 -0
- data/simplecov-1.0.0/lib/simplecov/color.rb +74 -0
- data/simplecov-1.0.0/lib/simplecov/combine/branches_combiner.rb +54 -0
- data/simplecov-1.0.0/lib/simplecov/combine/files_combiner.rb +30 -0
- data/simplecov-1.0.0/lib/simplecov/combine/lines_combiner.rb +45 -0
- data/simplecov-1.0.0/lib/simplecov/combine/methods_combiner.rb +26 -0
- data/simplecov-1.0.0/lib/simplecov/combine/results_combiner.rb +61 -0
- data/simplecov-1.0.0/lib/simplecov/combine.rb +30 -0
- data/simplecov-1.0.0/lib/simplecov/command_guesser.rb +78 -0
- data/simplecov-1.0.0/lib/simplecov/configuration/coverage.rb +171 -0
- data/simplecov-1.0.0/lib/simplecov/configuration/coverage_criteria.rb +156 -0
- data/simplecov-1.0.0/lib/simplecov/configuration/filters.rb +197 -0
- data/simplecov-1.0.0/lib/simplecov/configuration/formatting.rb +119 -0
- data/simplecov-1.0.0/lib/simplecov/configuration/ignored_entries.rb +63 -0
- data/simplecov-1.0.0/lib/simplecov/configuration/merging.rb +164 -0
- data/simplecov-1.0.0/lib/simplecov/configuration/thresholds.rb +174 -0
- data/simplecov-1.0.0/lib/simplecov/configuration.rb +184 -0
- data/simplecov-1.0.0/lib/simplecov/coverage_statistics.rb +59 -0
- data/simplecov-1.0.0/lib/simplecov/coverage_violations.rb +148 -0
- data/simplecov-1.0.0/lib/simplecov/defaults.rb +60 -0
- data/simplecov-1.0.0/lib/simplecov/deprecation.rb +45 -0
- data/simplecov-1.0.0/lib/simplecov/directive.rb +162 -0
- data/simplecov-1.0.0/lib/simplecov/exit_codes/exit_code_handling.rb +35 -0
- data/simplecov-1.0.0/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb +45 -0
- data/simplecov-1.0.0/lib/simplecov/exit_codes/maximum_overall_coverage_check.rb +45 -0
- data/simplecov-1.0.0/lib/simplecov/exit_codes/minimum_coverage_by_file_check.rb +44 -0
- data/simplecov-1.0.0/lib/simplecov/exit_codes/minimum_coverage_by_group_check.rb +41 -0
- data/simplecov-1.0.0/lib/simplecov/exit_codes/minimum_overall_coverage_check.rb +70 -0
- data/simplecov-1.0.0/lib/simplecov/exit_codes.rb +18 -0
- data/simplecov-1.0.0/lib/simplecov/exit_handling.rb +158 -0
- data/simplecov-1.0.0/lib/simplecov/file_list.rb +164 -0
- data/simplecov-1.0.0/lib/simplecov/filter.rb +139 -0
- data/simplecov-1.0.0/lib/simplecov/formatter/base.rb +101 -0
- data/simplecov-1.0.0/lib/simplecov/formatter/html_formatter/public/application.css +1 -0
- data/simplecov-1.0.0/lib/simplecov/formatter/html_formatter/public/application.js +18 -0
- data/simplecov-1.0.0/lib/simplecov/formatter/html_formatter/public/favicon_green.png +0 -0
- data/simplecov-1.0.0/lib/simplecov/formatter/html_formatter/public/favicon_red.png +0 -0
- data/simplecov-1.0.0/lib/simplecov/formatter/html_formatter/public/favicon_yellow.png +0 -0
- data/simplecov-1.0.0/lib/simplecov/formatter/html_formatter/public/index.html +56 -0
- data/simplecov-1.0.0/lib/simplecov/formatter/html_formatter.rb +79 -0
- data/simplecov-1.0.0/lib/simplecov/formatter/json_formatter/errors_formatter.rb +84 -0
- data/simplecov-1.0.0/lib/simplecov/formatter/json_formatter/result_hash_formatter.rb +128 -0
- data/simplecov-1.0.0/lib/simplecov/formatter/json_formatter/source_file_formatter.rb +99 -0
- data/simplecov-1.0.0/lib/simplecov/formatter/json_formatter.rb +87 -0
- data/simplecov-1.0.0/lib/simplecov/formatter/multi_formatter.rb +31 -0
- data/simplecov-1.0.0/lib/simplecov/formatter/simple_formatter.rb +23 -0
- data/simplecov-1.0.0/lib/simplecov/formatter.rb +14 -0
- data/simplecov-1.0.0/lib/simplecov/last_run.rb +35 -0
- data/simplecov-1.0.0/lib/simplecov/lines_classifier.rb +60 -0
- data/simplecov-1.0.0/lib/simplecov/load_global_config.rb +13 -0
- data/simplecov-1.0.0/lib/simplecov/no_defaults.rb +4 -0
- data/simplecov-1.0.0/lib/simplecov/parallel_adapters/base.rb +61 -0
- data/simplecov-1.0.0/lib/simplecov/parallel_adapters/generic.rb +44 -0
- data/simplecov-1.0.0/lib/simplecov/parallel_adapters/parallel_tests.rb +93 -0
- data/simplecov-1.0.0/lib/simplecov/parallel_adapters.rb +83 -0
- data/simplecov-1.0.0/lib/simplecov/parallel_coordination.rb +129 -0
- data/simplecov-1.0.0/lib/simplecov/process.rb +37 -0
- data/simplecov-1.0.0/lib/simplecov/profiles/bundler_filter.rb +5 -0
- data/simplecov-1.0.0/lib/simplecov/profiles/hidden_filter.rb +5 -0
- data/simplecov-1.0.0/lib/simplecov/profiles/rails.rb +32 -0
- data/simplecov-1.0.0/lib/simplecov/profiles/root_filter.rb +11 -0
- data/simplecov-1.0.0/lib/simplecov/profiles/strict.rb +32 -0
- data/simplecov-1.0.0/lib/simplecov/profiles/test_frameworks.rb +5 -0
- data/simplecov-1.0.0/lib/simplecov/profiles.rb +64 -0
- data/simplecov-1.0.0/lib/simplecov/result/missing_source_files_reporter.rb +49 -0
- data/simplecov-1.0.0/lib/simplecov/result/source_file_builder.rb +51 -0
- data/simplecov-1.0.0/lib/simplecov/result.rb +183 -0
- data/simplecov-1.0.0/lib/simplecov/result_adapter.rb +92 -0
- data/simplecov-1.0.0/lib/simplecov/result_merger/legacy_format_adapter.rb +28 -0
- data/simplecov-1.0.0/lib/simplecov/result_merger/resultset_file.rb +38 -0
- data/simplecov-1.0.0/lib/simplecov/result_merger/resultset_store.rb +50 -0
- data/simplecov-1.0.0/lib/simplecov/result_merger.rb +162 -0
- data/simplecov-1.0.0/lib/simplecov/result_processing.rb +179 -0
- data/simplecov-1.0.0/lib/simplecov/simulate_coverage.rb +75 -0
- data/simplecov-1.0.0/lib/simplecov/source_file/branch.rb +82 -0
- data/simplecov-1.0.0/lib/simplecov/source_file/branch_builder.rb +114 -0
- data/simplecov-1.0.0/lib/simplecov/source_file/builder_context.rb +28 -0
- data/simplecov-1.0.0/lib/simplecov/source_file/line.rb +77 -0
- data/simplecov-1.0.0/lib/simplecov/source_file/line_builder.rb +43 -0
- data/simplecov-1.0.0/lib/simplecov/source_file/method.rb +52 -0
- data/simplecov-1.0.0/lib/simplecov/source_file/method_builder.rb +58 -0
- data/simplecov-1.0.0/lib/simplecov/source_file/ruby_data_parser.rb +88 -0
- data/simplecov-1.0.0/lib/simplecov/source_file/skip_chunks.rb +75 -0
- data/simplecov-1.0.0/lib/simplecov/source_file/source_loader.rb +63 -0
- data/simplecov-1.0.0/lib/simplecov/source_file/statistics.rb +57 -0
- data/simplecov-1.0.0/lib/simplecov/source_file.rb +189 -0
- data/simplecov-1.0.0/lib/simplecov/static_coverage_extractor/method_collector.rb +55 -0
- data/simplecov-1.0.0/lib/simplecov/static_coverage_extractor/visitor.rb +171 -0
- data/simplecov-1.0.0/lib/simplecov/static_coverage_extractor.rb +110 -0
- data/simplecov-1.0.0/lib/simplecov/useless_results_remover.rb +27 -0
- data/simplecov-1.0.0/lib/simplecov/version.rb +5 -0
- data/simplecov-1.0.0/lib/simplecov-html.rb +4 -0
- data/simplecov-1.0.0/lib/simplecov.rb +255 -0
- data/simplecov-1.0.0/lib/simplecov_json_formatter.rb +4 -0
- data/simplecov-1.0.0/schemas/coverage-v1.0.schema.json +306 -0
- data/simplecov-1.0.0/schemas/coverage.schema.json +306 -0
- data/ultra-pro-pkg.gemspec +12 -0
- metadata +156 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCov
|
|
4
|
+
class SourceFile
|
|
5
|
+
# Builds the `CoverageStatistics` triple (`:line`, `:branch`, `:method`)
|
|
6
|
+
# for a SourceFile, regardless of which criteria were actually enabled
|
|
7
|
+
# during the run — disabled or empty criteria collapse to 0/0/0 so
|
|
8
|
+
# downstream consumers don't have to special-case enable-state.
|
|
9
|
+
class Statistics
|
|
10
|
+
def initialize(source_file)
|
|
11
|
+
@source_file = source_file
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call
|
|
15
|
+
{
|
|
16
|
+
**line_statistics,
|
|
17
|
+
**branch_statistics,
|
|
18
|
+
**method_statistics
|
|
19
|
+
}
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def line_statistics
|
|
25
|
+
sf = @source_file
|
|
26
|
+
{
|
|
27
|
+
line: CoverageStatistics.new(
|
|
28
|
+
total_strength: sf.lines.sum { |line| line.coverage.to_i },
|
|
29
|
+
covered: sf.covered_lines.size,
|
|
30
|
+
missed: sf.missed_lines.size,
|
|
31
|
+
omitted: sf.never_lines.size
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def branch_statistics
|
|
37
|
+
sf = @source_file
|
|
38
|
+
# Files added via track_files but never loaded/required have no
|
|
39
|
+
# branch data. Report 0% instead of misleading 100% (see #902).
|
|
40
|
+
if sf.not_loaded? && sf.covered_branches.empty? && sf.missed_branches.empty?
|
|
41
|
+
return {branch: CoverageStatistics.new(covered: 0, missed: 0, percent: 0.0)}
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
{branch: CoverageStatistics.new(covered: sf.covered_branches.size, missed: sf.missed_branches.size)}
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def method_statistics
|
|
48
|
+
sf = @source_file
|
|
49
|
+
if sf.not_loaded? && sf.covered_methods.empty? && sf.missed_methods.empty?
|
|
50
|
+
return {method: CoverageStatistics.new(covered: 0, missed: 0, percent: 0.0)}
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
{method: CoverageStatistics.new(covered: sf.covered_methods.size, missed: sf.missed_methods.size)}
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "directive"
|
|
4
|
+
require_relative "static_coverage_extractor"
|
|
5
|
+
require_relative "source_file/ruby_data_parser"
|
|
6
|
+
require_relative "source_file/source_loader"
|
|
7
|
+
require_relative "source_file/skip_chunks"
|
|
8
|
+
require_relative "source_file/builder_context"
|
|
9
|
+
require_relative "source_file/line_builder"
|
|
10
|
+
require_relative "source_file/branch_builder"
|
|
11
|
+
require_relative "source_file/method_builder"
|
|
12
|
+
require_relative "source_file/statistics"
|
|
13
|
+
|
|
14
|
+
module SimpleCov
|
|
15
|
+
#
|
|
16
|
+
# Representation of a source file including it's coverage data, source code,
|
|
17
|
+
# source lines and featuring helpers to interpret that data.
|
|
18
|
+
#
|
|
19
|
+
class SourceFile
|
|
20
|
+
include BuilderContext
|
|
21
|
+
|
|
22
|
+
# The full path to this source file (e.g. /User/colszowka/projects/simplecov/lib/simplecov/source_file.rb)
|
|
23
|
+
attr_reader :filename
|
|
24
|
+
# The array of coverage data received from the Coverage.result
|
|
25
|
+
attr_reader :coverage_data
|
|
26
|
+
|
|
27
|
+
def initialize(filename, coverage_data, loaded: true)
|
|
28
|
+
@filename = filename
|
|
29
|
+
@coverage_data = coverage_data
|
|
30
|
+
@loaded = loaded
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# The path to this source file relative to the projects directory
|
|
34
|
+
def project_filename
|
|
35
|
+
@filename.delete_prefix(SimpleCov.root).sub(%r{\A[/\\]}, "")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# The source code for this file. Aliased as :source.
|
|
39
|
+
# Intentionally read lazily to suppress reading unused source code.
|
|
40
|
+
def src
|
|
41
|
+
@src ||= SourceLoader.call(filename)
|
|
42
|
+
end
|
|
43
|
+
alias source src
|
|
44
|
+
|
|
45
|
+
# Returns a hash keyed by every supported coverage criterion. Each
|
|
46
|
+
# value is a CoverageStatistics, even for criteria that weren't
|
|
47
|
+
# enabled during the run — those collapse to 0/0/0. Consumers
|
|
48
|
+
# (FileList, formatters) decide which keys to surface based on
|
|
49
|
+
# `SimpleCov.coverage_criterion_enabled?`.
|
|
50
|
+
# The per-criterion coverage statistics for this file. With no argument
|
|
51
|
+
# returns the `{line:, branch:, method:}` Hash; pass a criterion symbol
|
|
52
|
+
# (`:line` / `:branch` / `:method`) to get that one CoverageStatistics.
|
|
53
|
+
def coverage_statistics(criterion = nil)
|
|
54
|
+
@coverage_statistics ||= Statistics.new(self).call
|
|
55
|
+
criterion ? @coverage_statistics[criterion] : @coverage_statistics
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Returns all source lines for this file as instances of SimpleCov::SourceFile::Line,
|
|
59
|
+
# and thus including coverage data. Aliased as :source_lines
|
|
60
|
+
def lines
|
|
61
|
+
@lines ||= LineBuilder.new(self).call
|
|
62
|
+
end
|
|
63
|
+
alias source_lines lines
|
|
64
|
+
|
|
65
|
+
# Returns all covered lines as SimpleCov::SourceFile::Line
|
|
66
|
+
def covered_lines
|
|
67
|
+
@covered_lines ||= lines.select(&:covered?)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Returns all lines that should have been, but were not covered
|
|
71
|
+
# as instances of SimpleCov::SourceFile::Line
|
|
72
|
+
def missed_lines
|
|
73
|
+
@missed_lines ||= lines.select(&:missed?)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Returns all lines that are not relevant for coverage as
|
|
77
|
+
# SimpleCov::SourceFile::Line instances
|
|
78
|
+
def never_lines
|
|
79
|
+
@never_lines ||= lines.select(&:never?)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Returns all lines that were skipped as SimpleCov::SourceFile::Line instances
|
|
83
|
+
def skipped_lines
|
|
84
|
+
@skipped_lines ||= lines.select(&:skipped?)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Returns the number of relevant lines (covered + missed)
|
|
88
|
+
def lines_of_code
|
|
89
|
+
coverage_statistics[:line]&.total || 0
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Access SimpleCov::SourceFile::Line source lines by line number
|
|
93
|
+
def line(number)
|
|
94
|
+
lines[number - 1]
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# The coverage for this file in percent, for the given criterion (line by
|
|
98
|
+
# default). Returns nil if the criterion was not measured.
|
|
99
|
+
def covered_percent(criterion = :line)
|
|
100
|
+
coverage_statistics(criterion)&.percent
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def covered_strength(criterion = :line)
|
|
104
|
+
coverage_statistics(criterion)&.strength
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def no_lines?
|
|
108
|
+
lines.empty? || (lines.length == never_lines.size)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def relevant_lines
|
|
112
|
+
lines.size - never_lines.size - skipped_lines.size
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Return all the branches inside current source file
|
|
116
|
+
def branches
|
|
117
|
+
@branches ||= BranchBuilder.new(self).call
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def no_branches?
|
|
121
|
+
total_branches.empty?
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# DEPRECATED: use `covered_percent(:branch)`.
|
|
125
|
+
def branches_coverage_percent
|
|
126
|
+
SimpleCov::Deprecation.warn("`SimpleCov::SourceFile#branches_coverage_percent` is deprecated. " \
|
|
127
|
+
"Use `covered_percent(:branch)`.")
|
|
128
|
+
covered_percent(:branch)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Return the relevant branches to source file
|
|
132
|
+
def total_branches
|
|
133
|
+
@total_branches ||= covered_branches + missed_branches
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Return hash with key of line number and branch coverage count as value
|
|
137
|
+
def branches_report
|
|
138
|
+
@branches_report ||=
|
|
139
|
+
branches.reject(&:skipped?).group_by(&:report_line).transform_values { |bs| bs.map(&:report) }
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Select the covered branches. We use a tree schema here because
|
|
143
|
+
# some conditions like `case` may have an additional `else` that
|
|
144
|
+
# isn't declared in code but is given by default by the coverage
|
|
145
|
+
# report.
|
|
146
|
+
def covered_branches
|
|
147
|
+
@covered_branches ||= branches.select(&:covered?)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Select the missed branches with coverage equal to zero
|
|
151
|
+
def missed_branches
|
|
152
|
+
@missed_branches ||= branches.select(&:missed?)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def branches_for_line(line_number)
|
|
156
|
+
branches_report.fetch(line_number, [])
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Check if any branches missing on given line number
|
|
160
|
+
def line_with_missed_branch?(line_number)
|
|
161
|
+
branches_for_line(line_number).any? { |_type, count| count.zero? }
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Return all methods detected in this source file
|
|
165
|
+
def methods
|
|
166
|
+
@methods ||= MethodBuilder.new(self).call
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def covered_methods
|
|
170
|
+
@covered_methods ||= methods.select(&:covered?)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def missed_methods
|
|
174
|
+
@missed_methods ||= methods.select(&:missed?)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# DEPRECATED: use `covered_percent(:method)`.
|
|
178
|
+
def methods_coverage_percent
|
|
179
|
+
SimpleCov::Deprecation.warn("`SimpleCov::SourceFile#methods_coverage_percent` is deprecated. " \
|
|
180
|
+
"Use `covered_percent(:method)`.")
|
|
181
|
+
covered_percent(:method)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Whether this file was added via track_files but never loaded/required.
|
|
185
|
+
def not_loaded?
|
|
186
|
+
!@loaded
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCov
|
|
4
|
+
module StaticCoverageExtractor
|
|
5
|
+
# Visitor mixin that collects method tuples and tracks the lexical
|
|
6
|
+
# class / module nesting that names them, in the shape Ruby's
|
|
7
|
+
# `Coverage` reports methods. Mixed into `Visitor`, it shares that
|
|
8
|
+
# visitor's `@methods` / `@class_stack` state and keeps the
|
|
9
|
+
# method-collection concern separate from branch extraction.
|
|
10
|
+
module MethodCollector
|
|
11
|
+
# Track class/module nesting so method tuples carry the lexical
|
|
12
|
+
# class name. Module + Class are both treated as namespaces here
|
|
13
|
+
# since `Coverage` reports both as the constant.
|
|
14
|
+
def visit_class_node(node)
|
|
15
|
+
with_class(constant_name(node.constant_path)) { super }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def visit_module_node(node)
|
|
19
|
+
with_class(constant_name(node.constant_path)) { super }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# `def name(...)` and `def self.name(...)` both produce DefNode.
|
|
23
|
+
# The class context is the surrounding lexical class/module (or
|
|
24
|
+
# `Object` at the top level, matching `Coverage`'s convention).
|
|
25
|
+
def visit_def_node(node)
|
|
26
|
+
loc = node.location
|
|
27
|
+
class_name = @class_stack.last || "Object"
|
|
28
|
+
key = [class_name, node.name, loc.start_line, loc.start_column, loc.end_line, loc.end_column]
|
|
29
|
+
@methods[key] = 0
|
|
30
|
+
super
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
# Render a constant path (e.g., `Foo::Bar`) as its source-form
|
|
36
|
+
# string. Defensive nil / to_s fallbacks: ClassNode and ModuleNode
|
|
37
|
+
# always carry a constant_path in practice.
|
|
38
|
+
# simplecov:disable
|
|
39
|
+
def constant_name(node)
|
|
40
|
+
return "<anonymous>" if node.nil?
|
|
41
|
+
return node.slice if node.respond_to?(:slice)
|
|
42
|
+
|
|
43
|
+
node.to_s
|
|
44
|
+
end
|
|
45
|
+
# simplecov:enable
|
|
46
|
+
|
|
47
|
+
def with_class(name)
|
|
48
|
+
@class_stack.push(name)
|
|
49
|
+
yield
|
|
50
|
+
ensure
|
|
51
|
+
@class_stack.pop
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "method_collector"
|
|
4
|
+
|
|
5
|
+
module SimpleCov
|
|
6
|
+
module StaticCoverageExtractor
|
|
7
|
+
# `Prism::IfNode#subsequent` was renamed from `consequent` in Prism
|
|
8
|
+
# 1.3 (Dec 2024). Ruby 3.3's stdlib still ships an older Prism that
|
|
9
|
+
# only exposes `consequent`; 3.4+ and any project that's done
|
|
10
|
+
# `gem install prism` exposes `subsequent`. Resolve the method name
|
|
11
|
+
# ONCE here so the per-node hot path stays branch-free. The
|
|
12
|
+
# not-taken arm on whichever Prism version we're on can't be
|
|
13
|
+
# exercised by our own dogfood (we only run on one Prism at a time).
|
|
14
|
+
# simplecov:disable
|
|
15
|
+
IF_NODE_SUBSEQUENT_METHOD =
|
|
16
|
+
if ::Prism::IfNode.method_defined?(:subsequent)
|
|
17
|
+
:subsequent
|
|
18
|
+
else
|
|
19
|
+
:consequent
|
|
20
|
+
end
|
|
21
|
+
# simplecov:enable
|
|
22
|
+
|
|
23
|
+
# Prism visitor that accumulates branch and method tuples in the
|
|
24
|
+
# shape Ruby's `Coverage` reports. Tuple ids are sequential across
|
|
25
|
+
# the file — `Coverage` uses sequential ids too, so this matches the
|
|
26
|
+
# conventional shape. Only defined when Prism is loadable;
|
|
27
|
+
# `StaticCoverageExtractor.available?` is the runtime gate.
|
|
28
|
+
class Visitor < ::Prism::Visitor
|
|
29
|
+
# Method tuples and the class/module nesting that names them are
|
|
30
|
+
# collected by this mixin; this class focuses on branch extraction.
|
|
31
|
+
include MethodCollector
|
|
32
|
+
|
|
33
|
+
attr_reader :branches, :methods
|
|
34
|
+
|
|
35
|
+
def initialize
|
|
36
|
+
super
|
|
37
|
+
@branches = {}
|
|
38
|
+
@methods = {}
|
|
39
|
+
@next_id = 0
|
|
40
|
+
@class_stack = []
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# `if` / `unless` / postfix-if / postfix-unless / ternary all parse
|
|
44
|
+
# as IfNode (or UnlessNode). Both carry a `then` arm (the
|
|
45
|
+
# statements body) and an optional `subsequent` (an ElseNode for
|
|
46
|
+
# `else`, another IfNode for `elsif`). When the subsequent is
|
|
47
|
+
# missing, Coverage synthesizes a `:else` arm attributed to the
|
|
48
|
+
# whole condition's range — we do the same.
|
|
49
|
+
def visit_if_node(node)
|
|
50
|
+
emit_if_like(node, :if)
|
|
51
|
+
super
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def visit_unless_node(node)
|
|
55
|
+
emit_if_like(node, :unless)
|
|
56
|
+
super
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def visit_call_node(node)
|
|
60
|
+
emit_safe_navigation(node) if node.respond_to?(:safe_navigation?) && node.safe_navigation?
|
|
61
|
+
super
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# `case`/`when` and `case`/`in` (pattern matching) parse as CaseNode
|
|
65
|
+
# and CaseMatchNode respectively. When there's no explicit `else`,
|
|
66
|
+
# Coverage synthesizes one at the case's range.
|
|
67
|
+
def visit_case_node(node)
|
|
68
|
+
emit_case_like(node, :when)
|
|
69
|
+
super
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def visit_case_match_node(node)
|
|
73
|
+
emit_case_like(node, :in)
|
|
74
|
+
super
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# `while` / `until` loops get a single `:body` arm. No synthetic
|
|
78
|
+
# else (the loop either runs the body or doesn't).
|
|
79
|
+
def visit_while_node(node)
|
|
80
|
+
emit_loop(node, :while)
|
|
81
|
+
super
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def visit_until_node(node)
|
|
85
|
+
emit_loop(node, :until)
|
|
86
|
+
super
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
|
|
91
|
+
# IfNode and UnlessNode share a shape (predicate + then body +
|
|
92
|
+
# optional else/elsif) but expose the trailing arm under different
|
|
93
|
+
# accessors. `if_like_else_location` hides that split.
|
|
94
|
+
def emit_if_like(node, type)
|
|
95
|
+
then_loc = arm_location(node.statements, node.location)
|
|
96
|
+
else_loc = if_like_else_location(node)
|
|
97
|
+
@branches[build_tuple(type, node.location)] = {
|
|
98
|
+
build_tuple(:then, then_loc) => 0,
|
|
99
|
+
build_tuple(:else, else_loc) => 0
|
|
100
|
+
}
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def emit_safe_navigation(node)
|
|
104
|
+
loc = node.location
|
|
105
|
+
@branches[build_tuple(:"&.", loc)] = {
|
|
106
|
+
build_tuple(:then, loc) => 0,
|
|
107
|
+
build_tuple(:else, loc) => 0
|
|
108
|
+
}
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Resolve the source range Coverage attributes to a real-or-synthetic
|
|
112
|
+
# `:else` arm of an if-like construct. IfNode uses
|
|
113
|
+
# `subsequent` / `consequent` depending on Prism version (resolved
|
|
114
|
+
# to `IF_NODE_SUBSEQUENT_METHOD` at load time); UnlessNode uses
|
|
115
|
+
# `else_clause`. When neither is present, the synthesized else
|
|
116
|
+
# inherits the whole condition's range (matches Coverage's
|
|
117
|
+
# convention).
|
|
118
|
+
def if_like_else_location(node)
|
|
119
|
+
sub = node.is_a?(::Prism::IfNode) ? node.public_send(IF_NODE_SUBSEQUENT_METHOD) : node.else_clause
|
|
120
|
+
return node.location unless sub
|
|
121
|
+
|
|
122
|
+
arm_location(else_body_of(sub), sub.location)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def emit_case_like(node, when_type)
|
|
126
|
+
arms = node.conditions.to_h do |when_node|
|
|
127
|
+
loc = arm_location(when_node.statements, when_node.location)
|
|
128
|
+
[build_tuple(when_type, loc), 0]
|
|
129
|
+
end
|
|
130
|
+
arms[build_tuple(:else, else_arm_location(node))] = 0
|
|
131
|
+
@branches[build_tuple(:case, node.location)] = arms
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Resolve the source range Coverage attributes to a synthetic-or-real
|
|
135
|
+
# `:else` arm of a case construct: the body of an explicit else,
|
|
136
|
+
# or the case's full range when no else is present.
|
|
137
|
+
def else_arm_location(node)
|
|
138
|
+
return node.location unless node.else_clause
|
|
139
|
+
|
|
140
|
+
arm_location(else_body_of(node.else_clause), node.else_clause.location)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def emit_loop(node, type)
|
|
144
|
+
cond_tuple = build_tuple(type, node.location)
|
|
145
|
+
body_loc = arm_location(node.statements, node.location)
|
|
146
|
+
@branches[cond_tuple] = {build_tuple(:body, body_loc) => 0}
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Body location for an arm. Prism's `statements` is a StatementsNode
|
|
150
|
+
# whose span covers the contained expressions; fall back to the
|
|
151
|
+
# parent when the arm body is empty (e.g., `if cond then end`).
|
|
152
|
+
def arm_location(statements, fallback_location)
|
|
153
|
+
statements&.location || fallback_location
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# simplecov:disable branch
|
|
157
|
+
# The `else_node` fallback is defensive: every Prism node passed
|
|
158
|
+
# in here in practice responds to `:statements`.
|
|
159
|
+
def else_body_of(else_node)
|
|
160
|
+
else_node.respond_to?(:statements) ? else_node.statements : else_node
|
|
161
|
+
end
|
|
162
|
+
# simplecov:enable branch
|
|
163
|
+
|
|
164
|
+
def build_tuple(type, location)
|
|
165
|
+
id = @next_id
|
|
166
|
+
@next_id += 1
|
|
167
|
+
[type, id, location.start_line, location.start_column, location.end_line, location.end_column]
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
begin
|
|
4
|
+
require "prism"
|
|
5
|
+
rescue LoadError
|
|
6
|
+
# Prism isn't available on this Ruby (older than 3.3 without the gem).
|
|
7
|
+
# `StaticCoverageExtractor.available?` will return false and callers
|
|
8
|
+
# fall back to the previous "empty hashes" behavior.
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
module SimpleCov
|
|
12
|
+
# Static enumeration of the branches and methods Ruby's `Coverage` library
|
|
13
|
+
# WOULD have reported if a file had been loaded with `branches: true` /
|
|
14
|
+
# `methods: true`. Used by `SimulateCoverage` to backfill data for files
|
|
15
|
+
# added via `cover` / `track_files` that were never `require`'d during the
|
|
16
|
+
# run — so unloaded files contribute to the branch/method denominators
|
|
17
|
+
# symmetrically with their line coverage, instead of vanishing from the
|
|
18
|
+
# totals (see #1059).
|
|
19
|
+
#
|
|
20
|
+
# Implementation uses Prism (stdlib in Ruby 3.3+, gem on older Rubies).
|
|
21
|
+
# When Prism isn't available, `available?` returns false and SimulateCoverage
|
|
22
|
+
# falls back to the previous behavior — older Rubies keep working, just
|
|
23
|
+
# without the synthesized data.
|
|
24
|
+
#
|
|
25
|
+
# The emitted shape mirrors `Coverage.result[path]` for the same file:
|
|
26
|
+
# branches are nested as `{condition_tuple => {arm_tuple => 0, ...}}` and
|
|
27
|
+
# methods as `{["ClassName", :name, lines/cols] => 0}`. Position info
|
|
28
|
+
# comes from Prism's reported source locations; it doesn't always match
|
|
29
|
+
# `Coverage`'s byte-for-byte (the two parsers report slightly different
|
|
30
|
+
# column conventions for some constructs), but lines are reliable and
|
|
31
|
+
# downstream consumers that key off line numbers (the HTML formatter,
|
|
32
|
+
# SonarQube, etc.) see the data they expect.
|
|
33
|
+
module StaticCoverageExtractor
|
|
34
|
+
module_function
|
|
35
|
+
|
|
36
|
+
# simplecov:disable branch
|
|
37
|
+
# The Prism-unavailable arm of this ternary is unreachable when Prism
|
|
38
|
+
# itself IS loadable — i.e., on every engine that exercises the dogfood
|
|
39
|
+
# report. Asserted-on by callers; tested indirectly via the
|
|
40
|
+
# `available?`-returns-false fallback path in SimulateCoverage's spec.
|
|
41
|
+
def available?
|
|
42
|
+
defined?(::Prism) ? true : false
|
|
43
|
+
end
|
|
44
|
+
# simplecov:enable branch
|
|
45
|
+
|
|
46
|
+
# Parse `source` (a string of Ruby) and return a hash of the form
|
|
47
|
+
# `{"branches" => {...}, "methods" => {...}}` matching the shape that
|
|
48
|
+
# `Coverage.result[path]` produces. Returns nil on parse failure or
|
|
49
|
+
# when Prism isn't available; callers should treat that as "couldn't
|
|
50
|
+
# extract — fall back to empty hashes."
|
|
51
|
+
def call(source)
|
|
52
|
+
# simplecov:disable branch — `then` arm unreachable when Prism IS loadable
|
|
53
|
+
return nil unless available?
|
|
54
|
+
|
|
55
|
+
# simplecov:enable branch
|
|
56
|
+
|
|
57
|
+
result = ::Prism.parse(source)
|
|
58
|
+
return nil if result.failure?
|
|
59
|
+
|
|
60
|
+
visitor = Visitor.new
|
|
61
|
+
visitor.visit(result.value)
|
|
62
|
+
{"branches" => visitor.branches, "methods" => visitor.methods}
|
|
63
|
+
rescue StandardError
|
|
64
|
+
# simplecov:disable line
|
|
65
|
+
# Parser errors beyond the .failure? check, unsupported AST shapes,
|
|
66
|
+
# or anything else: fall back to empty hashes rather than crashing
|
|
67
|
+
# the whole report. Defensive; hard to trigger from a real source
|
|
68
|
+
# input that Prism accepts at parse time.
|
|
69
|
+
nil
|
|
70
|
+
# simplecov:enable line
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Summarize a source file's REAL branch and method positions, for the
|
|
74
|
+
# `:eval_generated` filter (SimpleCov.ignore_branches /
|
|
75
|
+
# SimpleCov.ignore_methods, #1046). Returns a hash:
|
|
76
|
+
#
|
|
77
|
+
# {
|
|
78
|
+
# branches: Set[start_line, ...], # e.g., [3, 12, 20]
|
|
79
|
+
# methods: Set[[name, start_line], ...] # e.g., [[:foo, 7], [:bar, 13]]
|
|
80
|
+
# }
|
|
81
|
+
#
|
|
82
|
+
# Branch matching is start_line-only rather than by the full tuple.
|
|
83
|
+
# Static extraction and Coverage can still disagree on a branch's exact
|
|
84
|
+
# column positions (and, for some constructs, its type), so matching on
|
|
85
|
+
# start_line alone is the conservative choice that tolerates those
|
|
86
|
+
# differences. Coincidental line-sharing between a real branch and an
|
|
87
|
+
# eval-generated one will keep both, which is an acceptable
|
|
88
|
+
# false-negative for an opt-in filter. Method matching uses
|
|
89
|
+
# (name, start_line) since a method name is unique at any line.
|
|
90
|
+
#
|
|
91
|
+
# Returns nil when Prism is unavailable or parsing fails, signaling
|
|
92
|
+
# callers to keep every Coverage entry (no false drops).
|
|
93
|
+
def real_source_positions(source)
|
|
94
|
+
extracted = call(source)
|
|
95
|
+
return nil unless extracted
|
|
96
|
+
|
|
97
|
+
{
|
|
98
|
+
branches: extracted["branches"].keys.to_set { |tuple| tuple[2] },
|
|
99
|
+
methods: extracted["methods"].keys.to_set { |tuple| [tuple[1], tuple[2]] }
|
|
100
|
+
}
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# simplecov:disable branch
|
|
106
|
+
# The `else` arm (Prism missing) is unreachable on engines where the
|
|
107
|
+
# dogfood report runs; the Visitor class only matters when Prism is
|
|
108
|
+
# loadable.
|
|
109
|
+
require_relative "static_coverage_extractor/visitor" if SimpleCov::StaticCoverageExtractor.available?
|
|
110
|
+
# simplecov:enable branch
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCov
|
|
4
|
+
# Drop coverage entries whose paths live outside `SimpleCov.root` so the
|
|
5
|
+
# report only reflects the project's own source. Vendored gems, stdlib
|
|
6
|
+
# files, and anything else that happens to have been touched during the
|
|
7
|
+
# run never make it into the formatted result.
|
|
8
|
+
module UselessResultsRemover
|
|
9
|
+
def self.call(coverage_result)
|
|
10
|
+
coverage_result.select { |path, _coverage| path.match?(root_regex) }
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# The `/i` flag covers case-insensitive matches on Windows / macOS-HFS+
|
|
14
|
+
# where the on-disk path's case can differ from `SimpleCov.root`'s.
|
|
15
|
+
def self.root_regex
|
|
16
|
+
root = SimpleCov.root
|
|
17
|
+
return @root_regex if root == @root_regex_root
|
|
18
|
+
|
|
19
|
+
@root_regex_root = root
|
|
20
|
+
@root_regex = /\A#{Regexp.escape(root.chomp(File::SEPARATOR) + File::SEPARATOR)}/i
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.root_regx
|
|
24
|
+
root_regex
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|