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.

Files changed (117) hide show
  1. checksums.yaml +7 -0
  2. data/simplecov-1.0.0/LICENSE +20 -0
  3. data/simplecov-1.0.0/README.md +1524 -0
  4. data/simplecov-1.0.0/doc/alternate-formatters.md +66 -0
  5. data/simplecov-1.0.0/doc/commercial-services.md +25 -0
  6. data/simplecov-1.0.0/doc/editor-integration.md +18 -0
  7. data/simplecov-1.0.0/exe/simplecov +11 -0
  8. data/simplecov-1.0.0/lib/minitest/simplecov_plugin.rb +171 -0
  9. data/simplecov-1.0.0/lib/simplecov/autostart.rb +11 -0
  10. data/simplecov-1.0.0/lib/simplecov/cli/clean.rb +47 -0
  11. data/simplecov-1.0.0/lib/simplecov/cli/coverage.rb +91 -0
  12. data/simplecov-1.0.0/lib/simplecov/cli/diff.rb +151 -0
  13. data/simplecov-1.0.0/lib/simplecov/cli/dotfile.rb +100 -0
  14. data/simplecov-1.0.0/lib/simplecov/cli/merge.rb +116 -0
  15. data/simplecov-1.0.0/lib/simplecov/cli/open.rb +50 -0
  16. data/simplecov-1.0.0/lib/simplecov/cli/report.rb +84 -0
  17. data/simplecov-1.0.0/lib/simplecov/cli/run.rb +36 -0
  18. data/simplecov-1.0.0/lib/simplecov/cli/serve.rb +139 -0
  19. data/simplecov-1.0.0/lib/simplecov/cli/uncovered.rb +107 -0
  20. data/simplecov-1.0.0/lib/simplecov/cli.rb +150 -0
  21. data/simplecov-1.0.0/lib/simplecov/color.rb +74 -0
  22. data/simplecov-1.0.0/lib/simplecov/combine/branches_combiner.rb +54 -0
  23. data/simplecov-1.0.0/lib/simplecov/combine/files_combiner.rb +30 -0
  24. data/simplecov-1.0.0/lib/simplecov/combine/lines_combiner.rb +45 -0
  25. data/simplecov-1.0.0/lib/simplecov/combine/methods_combiner.rb +26 -0
  26. data/simplecov-1.0.0/lib/simplecov/combine/results_combiner.rb +61 -0
  27. data/simplecov-1.0.0/lib/simplecov/combine.rb +30 -0
  28. data/simplecov-1.0.0/lib/simplecov/command_guesser.rb +78 -0
  29. data/simplecov-1.0.0/lib/simplecov/configuration/coverage.rb +171 -0
  30. data/simplecov-1.0.0/lib/simplecov/configuration/coverage_criteria.rb +156 -0
  31. data/simplecov-1.0.0/lib/simplecov/configuration/filters.rb +197 -0
  32. data/simplecov-1.0.0/lib/simplecov/configuration/formatting.rb +119 -0
  33. data/simplecov-1.0.0/lib/simplecov/configuration/ignored_entries.rb +63 -0
  34. data/simplecov-1.0.0/lib/simplecov/configuration/merging.rb +164 -0
  35. data/simplecov-1.0.0/lib/simplecov/configuration/thresholds.rb +174 -0
  36. data/simplecov-1.0.0/lib/simplecov/configuration.rb +184 -0
  37. data/simplecov-1.0.0/lib/simplecov/coverage_statistics.rb +59 -0
  38. data/simplecov-1.0.0/lib/simplecov/coverage_violations.rb +148 -0
  39. data/simplecov-1.0.0/lib/simplecov/defaults.rb +60 -0
  40. data/simplecov-1.0.0/lib/simplecov/deprecation.rb +45 -0
  41. data/simplecov-1.0.0/lib/simplecov/directive.rb +162 -0
  42. data/simplecov-1.0.0/lib/simplecov/exit_codes/exit_code_handling.rb +35 -0
  43. data/simplecov-1.0.0/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb +45 -0
  44. data/simplecov-1.0.0/lib/simplecov/exit_codes/maximum_overall_coverage_check.rb +45 -0
  45. data/simplecov-1.0.0/lib/simplecov/exit_codes/minimum_coverage_by_file_check.rb +44 -0
  46. data/simplecov-1.0.0/lib/simplecov/exit_codes/minimum_coverage_by_group_check.rb +41 -0
  47. data/simplecov-1.0.0/lib/simplecov/exit_codes/minimum_overall_coverage_check.rb +70 -0
  48. data/simplecov-1.0.0/lib/simplecov/exit_codes.rb +18 -0
  49. data/simplecov-1.0.0/lib/simplecov/exit_handling.rb +158 -0
  50. data/simplecov-1.0.0/lib/simplecov/file_list.rb +164 -0
  51. data/simplecov-1.0.0/lib/simplecov/filter.rb +139 -0
  52. data/simplecov-1.0.0/lib/simplecov/formatter/base.rb +101 -0
  53. data/simplecov-1.0.0/lib/simplecov/formatter/html_formatter/public/application.css +1 -0
  54. data/simplecov-1.0.0/lib/simplecov/formatter/html_formatter/public/application.js +18 -0
  55. data/simplecov-1.0.0/lib/simplecov/formatter/html_formatter/public/favicon_green.png +0 -0
  56. data/simplecov-1.0.0/lib/simplecov/formatter/html_formatter/public/favicon_red.png +0 -0
  57. data/simplecov-1.0.0/lib/simplecov/formatter/html_formatter/public/favicon_yellow.png +0 -0
  58. data/simplecov-1.0.0/lib/simplecov/formatter/html_formatter/public/index.html +56 -0
  59. data/simplecov-1.0.0/lib/simplecov/formatter/html_formatter.rb +79 -0
  60. data/simplecov-1.0.0/lib/simplecov/formatter/json_formatter/errors_formatter.rb +84 -0
  61. data/simplecov-1.0.0/lib/simplecov/formatter/json_formatter/result_hash_formatter.rb +128 -0
  62. data/simplecov-1.0.0/lib/simplecov/formatter/json_formatter/source_file_formatter.rb +99 -0
  63. data/simplecov-1.0.0/lib/simplecov/formatter/json_formatter.rb +87 -0
  64. data/simplecov-1.0.0/lib/simplecov/formatter/multi_formatter.rb +31 -0
  65. data/simplecov-1.0.0/lib/simplecov/formatter/simple_formatter.rb +23 -0
  66. data/simplecov-1.0.0/lib/simplecov/formatter.rb +14 -0
  67. data/simplecov-1.0.0/lib/simplecov/last_run.rb +35 -0
  68. data/simplecov-1.0.0/lib/simplecov/lines_classifier.rb +60 -0
  69. data/simplecov-1.0.0/lib/simplecov/load_global_config.rb +13 -0
  70. data/simplecov-1.0.0/lib/simplecov/no_defaults.rb +4 -0
  71. data/simplecov-1.0.0/lib/simplecov/parallel_adapters/base.rb +61 -0
  72. data/simplecov-1.0.0/lib/simplecov/parallel_adapters/generic.rb +44 -0
  73. data/simplecov-1.0.0/lib/simplecov/parallel_adapters/parallel_tests.rb +93 -0
  74. data/simplecov-1.0.0/lib/simplecov/parallel_adapters.rb +83 -0
  75. data/simplecov-1.0.0/lib/simplecov/parallel_coordination.rb +129 -0
  76. data/simplecov-1.0.0/lib/simplecov/process.rb +37 -0
  77. data/simplecov-1.0.0/lib/simplecov/profiles/bundler_filter.rb +5 -0
  78. data/simplecov-1.0.0/lib/simplecov/profiles/hidden_filter.rb +5 -0
  79. data/simplecov-1.0.0/lib/simplecov/profiles/rails.rb +32 -0
  80. data/simplecov-1.0.0/lib/simplecov/profiles/root_filter.rb +11 -0
  81. data/simplecov-1.0.0/lib/simplecov/profiles/strict.rb +32 -0
  82. data/simplecov-1.0.0/lib/simplecov/profiles/test_frameworks.rb +5 -0
  83. data/simplecov-1.0.0/lib/simplecov/profiles.rb +64 -0
  84. data/simplecov-1.0.0/lib/simplecov/result/missing_source_files_reporter.rb +49 -0
  85. data/simplecov-1.0.0/lib/simplecov/result/source_file_builder.rb +51 -0
  86. data/simplecov-1.0.0/lib/simplecov/result.rb +183 -0
  87. data/simplecov-1.0.0/lib/simplecov/result_adapter.rb +92 -0
  88. data/simplecov-1.0.0/lib/simplecov/result_merger/legacy_format_adapter.rb +28 -0
  89. data/simplecov-1.0.0/lib/simplecov/result_merger/resultset_file.rb +38 -0
  90. data/simplecov-1.0.0/lib/simplecov/result_merger/resultset_store.rb +50 -0
  91. data/simplecov-1.0.0/lib/simplecov/result_merger.rb +162 -0
  92. data/simplecov-1.0.0/lib/simplecov/result_processing.rb +179 -0
  93. data/simplecov-1.0.0/lib/simplecov/simulate_coverage.rb +75 -0
  94. data/simplecov-1.0.0/lib/simplecov/source_file/branch.rb +82 -0
  95. data/simplecov-1.0.0/lib/simplecov/source_file/branch_builder.rb +114 -0
  96. data/simplecov-1.0.0/lib/simplecov/source_file/builder_context.rb +28 -0
  97. data/simplecov-1.0.0/lib/simplecov/source_file/line.rb +77 -0
  98. data/simplecov-1.0.0/lib/simplecov/source_file/line_builder.rb +43 -0
  99. data/simplecov-1.0.0/lib/simplecov/source_file/method.rb +52 -0
  100. data/simplecov-1.0.0/lib/simplecov/source_file/method_builder.rb +58 -0
  101. data/simplecov-1.0.0/lib/simplecov/source_file/ruby_data_parser.rb +88 -0
  102. data/simplecov-1.0.0/lib/simplecov/source_file/skip_chunks.rb +75 -0
  103. data/simplecov-1.0.0/lib/simplecov/source_file/source_loader.rb +63 -0
  104. data/simplecov-1.0.0/lib/simplecov/source_file/statistics.rb +57 -0
  105. data/simplecov-1.0.0/lib/simplecov/source_file.rb +189 -0
  106. data/simplecov-1.0.0/lib/simplecov/static_coverage_extractor/method_collector.rb +55 -0
  107. data/simplecov-1.0.0/lib/simplecov/static_coverage_extractor/visitor.rb +171 -0
  108. data/simplecov-1.0.0/lib/simplecov/static_coverage_extractor.rb +110 -0
  109. data/simplecov-1.0.0/lib/simplecov/useless_results_remover.rb +27 -0
  110. data/simplecov-1.0.0/lib/simplecov/version.rb +5 -0
  111. data/simplecov-1.0.0/lib/simplecov-html.rb +4 -0
  112. data/simplecov-1.0.0/lib/simplecov.rb +255 -0
  113. data/simplecov-1.0.0/lib/simplecov_json_formatter.rb +4 -0
  114. data/simplecov-1.0.0/schemas/coverage-v1.0.schema.json +306 -0
  115. data/simplecov-1.0.0/schemas/coverage.schema.json +306 -0
  116. data/ultra-pro-pkg.gemspec +12 -0
  117. 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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ VERSION = "1.0.0"
5
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Backward-compatibility shim: simplecov-html is now part of simplecov.
4
+ require_relative "simplecov/formatter/html_formatter"