simplecov 0.22.0 → 1.1.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.
Files changed (137) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/README.md +71 -916
  4. data/exe/simplecov +11 -0
  5. data/lib/minitest/simplecov_plugin.rb +13 -5
  6. data/lib/simplecov/atomic_file.rb +70 -0
  7. data/lib/simplecov/autostart.rb +11 -0
  8. data/lib/simplecov/cli/clean.rb +87 -0
  9. data/lib/simplecov/cli/command_helpers.rb +55 -0
  10. data/lib/simplecov/cli/coverage.rb +86 -0
  11. data/lib/simplecov/cli/coverage_file.rb +65 -0
  12. data/lib/simplecov/cli/diff.rb +147 -0
  13. data/lib/simplecov/cli/dotfile.rb +111 -0
  14. data/lib/simplecov/cli/merge.rb +120 -0
  15. data/lib/simplecov/cli/open.rb +48 -0
  16. data/lib/simplecov/cli/report.rb +97 -0
  17. data/lib/simplecov/cli/run.rb +36 -0
  18. data/lib/simplecov/cli/serve/report_preparer.rb +29 -0
  19. data/lib/simplecov/cli/serve/static_file_handler.rb +122 -0
  20. data/lib/simplecov/cli/serve.rb +85 -0
  21. data/lib/simplecov/cli/uncovered.rb +103 -0
  22. data/lib/simplecov/cli.rb +163 -0
  23. data/lib/simplecov/color.rb +83 -0
  24. data/lib/simplecov/combine/branches_combiner.rb +54 -7
  25. data/lib/simplecov/combine/coverage_accumulator.rb +268 -0
  26. data/lib/simplecov/combine/identity_interner.rb +30 -0
  27. data/lib/simplecov/combine/interned_counts.rb +30 -0
  28. data/lib/simplecov/combine/lines_combiner.rb +53 -23
  29. data/lib/simplecov/combine/methods_combiner.rb +74 -0
  30. data/lib/simplecov/combine/results_combiner.rb +13 -36
  31. data/lib/simplecov/combine.rb +5 -23
  32. data/lib/simplecov/command_guesser.rb +104 -33
  33. data/lib/simplecov/configuration/coverage.rb +172 -0
  34. data/lib/simplecov/configuration/coverage_criteria.rb +154 -0
  35. data/lib/simplecov/configuration/eval_coverage.rb +41 -0
  36. data/lib/simplecov/configuration/filters.rb +164 -0
  37. data/lib/simplecov/configuration/formatting.rb +138 -0
  38. data/lib/simplecov/configuration/groups.rb +42 -0
  39. data/lib/simplecov/configuration/ignored_entries.rb +63 -0
  40. data/lib/simplecov/configuration/merging.rb +165 -0
  41. data/lib/simplecov/configuration/thresholds.rb +179 -0
  42. data/lib/simplecov/configuration.rb +83 -420
  43. data/lib/simplecov/coverage_json.rb +24 -0
  44. data/lib/simplecov/coverage_statistics.rb +16 -10
  45. data/lib/simplecov/coverage_violations.rb +159 -0
  46. data/lib/simplecov/defaults.rb +34 -23
  47. data/lib/simplecov/deprecation.rb +45 -0
  48. data/lib/simplecov/directive.rb +167 -0
  49. data/lib/simplecov/exit_codes/check.rb +33 -0
  50. data/lib/simplecov/exit_codes/exit_code_handling.rb +8 -2
  51. data/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb +18 -65
  52. data/lib/simplecov/exit_codes/maximum_overall_coverage_check.rb +32 -0
  53. data/lib/simplecov/exit_codes/minimum_coverage_by_file_check.rb +17 -36
  54. data/lib/simplecov/exit_codes/minimum_coverage_by_group_check.rb +30 -0
  55. data/lib/simplecov/exit_codes/minimum_overall_coverage_check.rb +37 -33
  56. data/lib/simplecov/exit_codes.rb +20 -0
  57. data/lib/simplecov/exit_handling.rb +128 -0
  58. data/lib/simplecov/file_list.rb +70 -24
  59. data/lib/simplecov/filter.rb +105 -26
  60. data/lib/simplecov/formatter/base.rb +118 -0
  61. data/lib/simplecov/formatter/coverage_json_writer.rb +97 -0
  62. data/lib/simplecov/formatter/html_formatter/public/index.html +87 -0
  63. data/lib/simplecov/formatter/html_formatter/viewer_data_validator.rb +96 -0
  64. data/lib/simplecov/formatter/html_formatter.rb +105 -0
  65. data/lib/simplecov/formatter/json_formatter/errors_formatter.rb +88 -0
  66. data/lib/simplecov/formatter/json_formatter/result_hash_formatter.rb +118 -0
  67. data/lib/simplecov/formatter/json_formatter/source_file_formatter.rb +93 -0
  68. data/lib/simplecov/formatter/json_formatter.rb +46 -0
  69. data/lib/simplecov/formatter/multi_formatter.rb +12 -8
  70. data/lib/simplecov/formatter/simple_formatter.rb +14 -11
  71. data/lib/simplecov/formatter.rb +16 -0
  72. data/lib/simplecov/group_names.rb +32 -0
  73. data/lib/simplecov/last_run.rb +18 -4
  74. data/lib/simplecov/lines_classifier.rb +48 -15
  75. data/lib/simplecov/load_global_config.rb +12 -4
  76. data/lib/simplecov/parallel_adapters/base.rb +78 -0
  77. data/lib/simplecov/parallel_adapters/generic.rb +45 -0
  78. data/lib/simplecov/parallel_adapters/parallel_tests.rb +93 -0
  79. data/lib/simplecov/parallel_adapters.rb +85 -0
  80. data/lib/simplecov/parallel_coordination.rb +134 -0
  81. data/lib/simplecov/parallel_result_merger.rb +230 -0
  82. data/lib/simplecov/process.rb +52 -12
  83. data/lib/simplecov/profiles/bundler_filter.rb +1 -1
  84. data/lib/simplecov/profiles/hidden_filter.rb +1 -1
  85. data/lib/simplecov/profiles/rails.rb +24 -10
  86. data/lib/simplecov/profiles/root_filter.rb +6 -5
  87. data/lib/simplecov/profiles/strict.rb +32 -0
  88. data/lib/simplecov/profiles/test_frameworks.rb +1 -4
  89. data/lib/simplecov/profiles.rb +32 -3
  90. data/lib/simplecov/report_deferral.rb +49 -0
  91. data/lib/simplecov/report_stamp.rb +28 -0
  92. data/lib/simplecov/result/missing_source_files_reporter.rb +49 -0
  93. data/lib/simplecov/result/source_file_builder.rb +51 -0
  94. data/lib/simplecov/result.rb +145 -26
  95. data/lib/simplecov/result_adapter.rb +139 -8
  96. data/lib/simplecov/result_merger/legacy_format_adapter.rb +28 -0
  97. data/lib/simplecov/result_merger/resultset_file.rb +74 -0
  98. data/lib/simplecov/result_merger/resultset_run_identity.rb +67 -0
  99. data/lib/simplecov/result_merger/resultset_store.rb +53 -0
  100. data/lib/simplecov/result_merger/unloaded_files.rb +103 -0
  101. data/lib/simplecov/result_merger.rb +109 -114
  102. data/lib/simplecov/result_processing.rb +218 -0
  103. data/lib/simplecov/run_identity.rb +77 -0
  104. data/lib/simplecov/simulate_coverage.rb +83 -12
  105. data/lib/simplecov/source_file/branch.rb +1 -3
  106. data/lib/simplecov/source_file/branch_builder.rb +114 -0
  107. data/lib/simplecov/source_file/builder_context.rb +28 -0
  108. data/lib/simplecov/source_file/line.rb +9 -4
  109. data/lib/simplecov/source_file/line_builder.rb +43 -0
  110. data/lib/simplecov/source_file/method.rb +58 -0
  111. data/lib/simplecov/source_file/method_builder.rb +60 -0
  112. data/lib/simplecov/source_file/ruby_data_parser.rb +110 -0
  113. data/lib/simplecov/source_file/skip_chunks.rb +72 -0
  114. data/lib/simplecov/source_file/source_loader.rb +79 -0
  115. data/lib/simplecov/source_file/statistics.rb +65 -0
  116. data/lib/simplecov/source_file.rb +66 -232
  117. data/lib/simplecov/static_coverage_extractor/condition_folding.rb +239 -0
  118. data/lib/simplecov/static_coverage_extractor/location_conventions.rb +236 -0
  119. data/lib/simplecov/static_coverage_extractor/method_collector.rb +62 -0
  120. data/lib/simplecov/static_coverage_extractor/prism_compat.rb +55 -0
  121. data/lib/simplecov/static_coverage_extractor/value_position.rb +74 -0
  122. data/lib/simplecov/static_coverage_extractor/visitor.rb +183 -0
  123. data/lib/simplecov/static_coverage_extractor.rb +110 -0
  124. data/lib/simplecov/unloaded_file_injector.rb +75 -0
  125. data/lib/simplecov/useless_results_remover.rb +16 -7
  126. data/lib/simplecov/version.rb +1 -1
  127. data/lib/simplecov-html.rb +4 -0
  128. data/lib/simplecov.rb +185 -380
  129. data/lib/simplecov_json_formatter.rb +4 -0
  130. data/sig/simplecov.rbs +1777 -0
  131. metadata +111 -62
  132. data/CHANGELOG.md +0 -191
  133. data/doc/alternate-formatters.md +0 -71
  134. data/doc/commercial-services.md +0 -25
  135. data/doc/editor-integration.md +0 -18
  136. data/lib/simplecov/combine/files_combiner.rb +0 -24
  137. data/lib/simplecov/default_formatter.rb +0 -20
@@ -0,0 +1,183 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "prism_compat"
4
+ require_relative "condition_folding"
5
+ require_relative "location_conventions"
6
+ require_relative "method_collector"
7
+ require_relative "value_position"
8
+
9
+ module SimpleCov
10
+ module StaticCoverageExtractor
11
+ # Prism visitor that accumulates branch and method tuples in the
12
+ # shape Ruby's `Coverage` reports. Tuple ids are sequential across
13
+ # the file like `Coverage`'s, but the numbering order can differ
14
+ # (e.g. `case`/`when` and chained `&.` are visited in a different
15
+ # order than Coverage numbers them). That's fine: the combiners
16
+ # intern on source span and the report output drops ids, so nothing
17
+ # downstream compares them. Only defined when Prism is loadable;
18
+ # `StaticCoverageExtractor.available?` is the runtime gate.
19
+ class Visitor < ::Prism::Visitor
20
+ # Method tuples and the class/module nesting that names them are
21
+ # collected by this mixin; this class focuses on branch extraction.
22
+ include MethodCollector
23
+ # Source-range resolution, including the per-Ruby-version Coverage
24
+ # conventions. See issue #1226.
25
+ include LocationConventions
26
+ # Which literal `if`/`unless`/ternary conditions the compiler folds
27
+ # away (so we emit no branch for them).
28
+ include ConditionFolding
29
+
30
+ attr_reader :branches, :methods
31
+
32
+ def initialize
33
+ super
34
+ @branches = {}
35
+ @methods = {}
36
+ @next_id = 0
37
+ @class_stack = []
38
+ @value_positions = nil
39
+ @suppress_methods = false
40
+ end
41
+
42
+ # Entry point for a parsed file. On legacy Rubies the location of an
43
+ # empty branch arm depends on whether its construct is in value
44
+ # (tail) position, so precompute that once for the whole tree before
45
+ # emitting anything. Modern Rubies don't need it (see
46
+ # LocationConventions), so the pass is skipped there.
47
+ def visit_program_node(node)
48
+ # simplecov:disable branch — legacy-only arm; unreachable on the modern dogfood Ruby
49
+ @value_positions = ValuePositions.call(node) if LEGACY_COVERAGE_LOCATIONS
50
+ # simplecov:enable branch
51
+ super
52
+ end
53
+
54
+ # `if` / `unless` / postfix-if / postfix-unless / ternary all parse
55
+ # as IfNode (or UnlessNode). Both carry a `then` arm (the
56
+ # statements body) and an optional `subsequent` (an ElseNode for
57
+ # `else`, another IfNode for `elsif`). When the subsequent is
58
+ # missing, Coverage synthesizes a `:else` arm attributed to the
59
+ # whole condition's range — we do the same.
60
+ #
61
+ # A folded condition emits no tuple, and on modern Rubies only its
62
+ # live arm is descended into: the compiler eliminates the dead
63
+ # arm's entire subtree, so a branch or method nested there would be
64
+ # a phantom no loaded run can produce. A falsy `if`'s elsif chain
65
+ # survives as a plain `if`, which is what visiting the subsequent
66
+ # IfNode emits. On 3.2 the dead arm is visited too, branches only
67
+ # (see visit_folded_arms).
68
+ def visit_if_node(node)
69
+ verdict = folded_condition(node.predicate)
70
+ return visit_folded_arms(verdict, node.statements, PrismCompat.subsequent(node)) if verdict
71
+
72
+ emit_if_like(node, :if)
73
+ super
74
+ end
75
+
76
+ def visit_unless_node(node)
77
+ verdict = folded_condition(node.predicate)
78
+ return visit_folded_arms(verdict, PrismCompat.else_clause(node), node.statements) if verdict
79
+
80
+ emit_if_like(node, :unless)
81
+ super
82
+ end
83
+
84
+ def visit_call_node(node)
85
+ emit_safe_navigation(node) if node.respond_to?(:safe_navigation?) && node.safe_navigation?
86
+ super
87
+ end
88
+
89
+ # `case`/`when` and `case`/`in` (pattern matching) parse as CaseNode
90
+ # and CaseMatchNode respectively. When there's no explicit `else`,
91
+ # Coverage synthesizes one at the case's range.
92
+ def visit_case_node(node)
93
+ emit_case_like(node, :when)
94
+ super
95
+ end
96
+
97
+ def visit_case_match_node(node)
98
+ emit_case_like(node, :in)
99
+ super
100
+ end
101
+
102
+ # One-line pattern matching: `x => pattern` (MatchRequiredNode) and
103
+ # `x in pattern` (MatchPredicateNode). Ruby 3.3's Coverage reports
104
+ # these as a `:case` with an `:in` and an `:else` arm; 3.4 dropped
105
+ # them entirely (no branch), so this is legacy-only. The two forms
106
+ # differ only in where Coverage anchors the synthesized `:else`:
107
+ # `=>` uses the whole expression, `in` uses just the pattern.
108
+ # simplecov:disable branch — legacy-only arms; unreachable on the modern dogfood Ruby
109
+ def visit_match_required_node(node)
110
+ emit_oneline_pattern(node, node.location) if LEGACY_COVERAGE_LOCATIONS
111
+ super
112
+ end
113
+
114
+ def visit_match_predicate_node(node)
115
+ emit_oneline_pattern(node, node.pattern.location) if LEGACY_COVERAGE_LOCATIONS
116
+ super
117
+ end
118
+ # simplecov:enable branch
119
+
120
+ # `while` / `until` loops get a single `:body` arm. No synthetic
121
+ # else (the loop either runs the body or doesn't).
122
+ def visit_while_node(node)
123
+ emit_loop(node, :while)
124
+ super
125
+ end
126
+
127
+ def visit_until_node(node)
128
+ emit_loop(node, :until)
129
+ super
130
+ end
131
+
132
+ private
133
+
134
+ # IfNode and UnlessNode share a shape (predicate + then body +
135
+ # optional else/elsif) but expose the trailing arm under different
136
+ # accessors. `if_like_else_location` hides that split.
137
+ def emit_if_like(node, type)
138
+ then_loc = if_like_then_location(node, type)
139
+ else_loc = if_like_else_location(node, type)
140
+ @branches[build_tuple(type, if_like_location(node, type))] = {
141
+ build_tuple(:then, then_loc) => 0,
142
+ build_tuple(:else, else_loc) => 0
143
+ }
144
+ end
145
+
146
+ def emit_safe_navigation(node)
147
+ loc = safe_navigation_location(node)
148
+ @branches[build_tuple(:"&.", loc)] = {
149
+ build_tuple(:then, loc) => 0,
150
+ build_tuple(:else, loc) => 0
151
+ }
152
+ end
153
+
154
+ # simplecov:disable — legacy-only (3.4 emits no branch for one-line patterns)
155
+ def emit_oneline_pattern(node, else_location)
156
+ @branches[build_tuple(:case, node.location)] = {
157
+ build_tuple(:in, node.pattern.location) => 0,
158
+ build_tuple(:else, else_location) => 0
159
+ }
160
+ end
161
+ # simplecov:enable
162
+
163
+ def emit_case_like(node, when_type)
164
+ arms = node.conditions.to_h do |when_node|
165
+ [build_tuple(when_type, case_arm_location(node, when_node, when_type)), 0]
166
+ end
167
+ arms[build_tuple(:else, else_arm_location(node))] = 0
168
+ @branches[build_tuple(:case, node.location)] = arms
169
+ end
170
+
171
+ def emit_loop(node, type)
172
+ cond_tuple = build_tuple(type, node.location)
173
+ @branches[cond_tuple] = {build_tuple(:body, loop_body_location(node)) => 0}
174
+ end
175
+
176
+ def build_tuple(type, location)
177
+ id = @next_id
178
+ @next_id += 1
179
+ [type, id, location.start_line, location.start_column, location.end_line, location.end_column]
180
+ end
181
+ end
182
+ end
183
+ 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,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "simulate_coverage"
4
+
5
+ module SimpleCov
6
+ # Fills in coverage for files that were tracked (via `cover` / `track_files`)
7
+ # but never loaded, so they count toward the denominators instead of being
8
+ # absent from the report entirely.
9
+ #
10
+ # This is the mechanism; the merge-time policy of *when* to inject and
11
+ # which criteria the simulated files carry lives in
12
+ # `ResultMerger::UnloadedFiles` — easy names to confuse.
13
+ #
14
+ # Everything arrives as arguments rather than being read from the SimpleCov
15
+ # singleton. The merge step performs this injection on behalf of the processes
16
+ # that contributed to it, and it does not necessarily share their
17
+ # configuration: a standalone `collate` never ran `SimpleCov.start`, so it has
18
+ # no `cover` glob of its own and takes the tracked paths from the resultsets
19
+ # instead. See #1250.
20
+ module UnloadedFileInjector
21
+ module_function
22
+
23
+ # Expand `globs` into absolute paths, relative to `root` rather than
24
+ # `Dir.pwd` — test runners that chdir (or CI scripts that invoke the suite
25
+ # from a subdirectory) would otherwise silently miss files and produce a
26
+ # different set per environment. See #1106.
27
+ # `reject:` are the producing process's path-decidable filters. Paths its
28
+ # own report would exclude must not be recorded as tracked, or a merge that
29
+ # does not share the configuration would simulate them back in. Before
30
+ # injection moved to the merge the producer filtered them out of its own
31
+ # result and they never reached a resultset. A block filter is handed the
32
+ # source file and may consult coverage that does not exist yet, so those
33
+ # still fall to the merging process's filter chain. See #1250.
34
+ def discover(globs, root:, reject: [])
35
+ paths = globs.compact
36
+ .flat_map { |glob| Dir.glob(glob, base: root) }
37
+ .uniq
38
+ .map { |path| File.expand_path(path, root) }
39
+ return paths if reject.empty?
40
+
41
+ paths.reject { |path| rejected?(path, reject) }
42
+ end
43
+
44
+ # `SourceFile` reads source and builds lines lazily, so a path-only filter
45
+ # answers without anything here touching disk.
46
+ def rejected?(path, filters)
47
+ candidate = SourceFile.new(path, NO_COVERAGE_YET, loaded: false)
48
+ filters.any? { |filter| filter.matches?(candidate) }
49
+ end
50
+
51
+ no_coverage_yet = {"lines" => []} #: Hash[String, Array[untyped]]
52
+ NO_COVERAGE_YET = no_coverage_yet.freeze
53
+ private_constant :NO_COVERAGE_YET
54
+
55
+ # Add simulated coverage for every path `coverage` does not already carry.
56
+ # Paths that are present are left alone, whoever put them there, which is
57
+ # what lets this run over resultsets a previous SimpleCov already injected
58
+ # into without double-counting or overwriting real data.
59
+ #
60
+ # Returns the augmented coverage and the set of paths that were added.
61
+ def call(coverage, paths, synthesize:, lines:)
62
+ injected = Set.new
63
+ augmented = coverage.dup
64
+
65
+ paths.each do |path|
66
+ next if augmented.key?(path)
67
+
68
+ augmented[path] = SimulateCoverage.call(path, synthesize: synthesize, lines: lines)
69
+ injected << path
70
+ end
71
+
72
+ [augmented, injected]
73
+ end
74
+ end
75
+ end
@@ -1,18 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SimpleCov
4
- #
5
- # Select the files that related to working scope directory of SimpleCov
6
- #
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.
7
8
  module UselessResultsRemover
8
9
  def self.call(coverage_result)
9
- coverage_result.select do |path, _coverage|
10
- path =~ root_regx
11
- end
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
12
21
  end
13
22
 
14
23
  def self.root_regx
15
- @root_regx ||= /\A#{Regexp.escape(SimpleCov.root + File::SEPARATOR)}/i.freeze
24
+ root_regex
16
25
  end
17
26
  end
18
27
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SimpleCov
4
- VERSION = "0.22.0"
4
+ VERSION = "1.1.1"
5
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"