simplecov 1.2.0 → 1.3.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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/lib/simplecov/cli/annotations.rb +166 -0
  4. data/lib/simplecov/cli/patch/output.rb +16 -0
  5. data/lib/simplecov/cli/patch.rb +8 -2
  6. data/lib/simplecov/cli/uncovered/misses.rb +5 -9
  7. data/lib/simplecov/cli/uncovered.rb +6 -20
  8. data/lib/simplecov/cli/usage.rb +2 -1
  9. data/lib/simplecov/configuration/eval_coverage.rb +1 -1
  10. data/lib/simplecov/configuration/view_coverage.rb +2 -3
  11. data/lib/simplecov/coverage_violations.rb +4 -2
  12. data/lib/simplecov/directive/erb.rb +44 -0
  13. data/lib/simplecov/directive/haml.rb +33 -0
  14. data/lib/simplecov/directive/indented_template.rb +37 -0
  15. data/lib/simplecov/directive/slim.rb +35 -0
  16. data/lib/simplecov/directive/template.rb +29 -0
  17. data/lib/simplecov/directive.rb +1 -0
  18. data/lib/simplecov/formatter/html_formatter/public/index.html +12 -12
  19. data/lib/simplecov/production.rb +1 -1
  20. data/lib/simplecov/profiles/rails.rb +4 -4
  21. data/lib/simplecov/profiles/strict.rb +3 -3
  22. data/lib/simplecov/result.rb +6 -0
  23. data/lib/simplecov/result_processing.rb +20 -5
  24. data/lib/simplecov/simulate_coverage.rb +2 -2
  25. data/lib/simplecov/source_file/builder_context.rb +7 -0
  26. data/lib/simplecov/source_file/skip_chunks.rb +11 -2
  27. data/lib/simplecov/source_file/statistics.rb +16 -6
  28. data/lib/simplecov/static_coverage_extractor/condition_folding.rb +9 -42
  29. data/lib/simplecov/static_coverage_extractor/method_collector.rb +1 -5
  30. data/lib/simplecov/static_coverage_extractor/visitor.rb +4 -7
  31. data/lib/simplecov/static_coverage_extractor.rb +6 -22
  32. data/lib/simplecov/version.rb +1 -1
  33. data/man/simplecov.1 +8 -3
  34. data/sig/simplecov.rbs +5 -0
  35. metadata +10 -4
@@ -8,14 +8,9 @@ module SimpleCov
8
8
  # must not synthesize one either: the arm would be a phantom no loaded run
9
9
  # can hit, the same unmergeable-tuple failure as #1226 / #1233.
10
10
  module ConditionFolding
11
- # CRuby 3.4 rebuilt the fold on the Prism compiler, and the parse.y fold it
12
- # replaced differed in three observable ways: `__FILE__` folded on
13
- # 3.2/3.3; parentheses were transparent for every literal on 3.2; and on
14
- # 3.2 the dead arm's branch table entries survive the fold while its
15
- # `def`s still never register.
11
+ # CRuby 3.4 rebuilt the fold on the Prism compiler, and the parse.y fold
12
+ # it replaced still differs on 3.3, where `__FILE__` folds.
16
13
  FOLDS_SOURCE_FILE = Gem::Version.new(RUBY_VERSION) < Gem::Version.new("3.4")
17
- PARENS_ALWAYS_TRANSPARENT = Gem::Version.new(RUBY_VERSION) < Gem::Version.new("3.3")
18
- DEAD_ARM_BRANCHES_SURVIVE = PARENS_ALWAYS_TRANSPARENT
19
14
  # Container literals in discarded position are eliminated from 3.3 on, but
20
15
  # 3.3's compile.c elides a container whose contents are merely
21
16
  # effect-free (`[x]`), while the Prism compiler demands fully static
@@ -42,8 +37,7 @@ module SimpleCov
42
37
 
43
38
  # CRuby folds `if nil`, `if "x"`, and `if -> {}` but keeps a real branch
44
39
  # for `if (nil)`, `if ("x")`, and `if (-> {})`, while every other literal
45
- # folds parenthesized or not. Consulted only when parentheses are not
46
- # always transparent.
40
+ # folds parenthesized or not.
47
41
  PAREN_OPAQUE_TYPES = [
48
42
  ::Prism::NilNode, ::Prism::StringNode, ::Prism::LambdaNode, ::Prism::SourceFileNode
49
43
  ].freeze
@@ -58,42 +52,18 @@ module SimpleCov
58
52
  ::Prism::SourceLineNode, ::Prism::SourceFileNode, ::Prism::SourceEncodingNode, ::Prism::RegularExpressionNode
59
53
  ].freeze
60
54
 
61
- # `self` is eliminated by every supported compiler; local/ivar/defined?
62
- # elimination arrived with the Prism-era compilers. Anything that can
63
- # raise or run hooks is never eliminated and keeps the branch real.
64
- PRISM_ERA_ELIMINABLE_READS = [
65
- ::Prism::LocalVariableReadNode, ::Prism::InstanceVariableReadNode, ::Prism::DefinedNode
66
- ].freeze
67
-
68
- # simplecov:disable branch — which arm runs is fixed by the running Ruby's version
55
+ # Non-literal reads that are also eliminated when discarded. Anything that
56
+ # can raise or run hooks is never eliminated and keeps the branch real.
69
57
  ELIMINABLE_READ_TYPES = [
70
- ::Prism::SelfNode,
71
- *(PRISM_ERA_ELIMINABLE_READS unless PARENS_ALWAYS_TRANSPARENT)
58
+ ::Prism::SelfNode, ::Prism::LocalVariableReadNode,
59
+ ::Prism::InstanceVariableReadNode, ::Prism::DefinedNode
72
60
  ].freeze
73
- # simplecov:enable branch
74
61
 
75
62
  private
76
63
 
77
- # On 3.2 the dead arm's branch table entries survive the fold (parse.y
78
- # instrumented branches before eliminating dead code) while its methods
79
- # never register, so the dead arm is visited with method collection
80
- # suppressed.
64
+ # `visit` is nil-safe, so a missing arm just visits nothing.
81
65
  def visit_folded_arms(verdict, truthy_arm, falsy_arm)
82
- live, dead = verdict.equal?(:truthy) ? [truthy_arm, falsy_arm] : [falsy_arm, truthy_arm]
83
- visit(live)
84
- visit_dead_arm(dead) if DEAD_ARM_BRANCHES_SURVIVE
85
- end
86
-
87
- def visit_dead_arm(arm)
88
- # Save/restore rather than set/clear: a fold nested inside this dead arm
89
- # re-enters here and must not clear suppression for the rest of it.
90
- previous = @suppress_methods
91
- begin
92
- @suppress_methods = true
93
- visit(arm)
94
- ensure
95
- @suppress_methods = previous
96
- end
66
+ visit(verdict.equal?(:truthy) ? truthy_arm : falsy_arm)
97
67
  end
98
68
 
99
69
  # `:truthy` or `:falsy` when the compiler folds, nil when it doesn't. The
@@ -115,8 +85,6 @@ module SimpleCov
115
85
  def foldable?(node, unwrapped)
116
86
  return false unless STATIC_CONDITION_TYPES.any? { |type| unwrapped.instance_of?(type) }
117
87
 
118
- return true if PARENS_ALWAYS_TRANSPARENT
119
-
120
88
  unwrapped.equal?(node) || PAREN_OPAQUE_TYPES.none? { |type| unwrapped.instance_of?(type) }
121
89
  end
122
90
 
@@ -151,7 +119,6 @@ module SimpleCov
151
119
 
152
120
  def static_container_literal?(node)
153
121
  return true if STATIC_LITERAL_LEAF_TYPES.any? { |type| node.instance_of?(type) }
154
- return false if PARENS_ALWAYS_TRANSPARENT
155
122
 
156
123
  static_container?(node)
157
124
  end
@@ -17,12 +17,8 @@ module SimpleCov
17
17
 
18
18
  # `def name(...)` and `def self.name(...)` both produce DefNode. The class
19
19
  # context is the surrounding lexical class or module, or `Object` at the top
20
- # level, matching `Coverage`'s convention. Suppression covers 3.2's folded
21
- # dead arms, where nested branches stay instrumented but a `def` never
22
- # registers.
20
+ # level, matching `Coverage`'s convention.
23
21
  def visit_def_node(node)
24
- return super if @suppress_methods
25
-
26
22
  class_name = @class_stack.last || "Object"
27
23
  key = [class_name, node.name, node.start_line, node.start_column, node.end_line, node.end_column]
28
24
  @methods[key] = 0
@@ -12,7 +12,7 @@ module SimpleCov
12
12
  # Ruby's `Coverage` reports. Tuple ids are sequential across the file like
13
13
  # `Coverage`'s, but the numbering order can differ. That's fine: the
14
14
  # combiners intern on source span and the report output drops ids, so
15
- # nothing downstream compares them. Only defined when Prism is loadable.
15
+ # nothing downstream compares them.
16
16
  class Visitor < Prism::Visitor
17
17
  include MethodCollector
18
18
  include LocationConventions
@@ -31,7 +31,6 @@ module SimpleCov
31
31
  @next_id = 0
32
32
  @class_stack = []
33
33
  @value_positions = nil
34
- @suppress_methods = false
35
34
  end
36
35
 
37
36
  # On legacy Rubies the location of an empty branch arm depends on whether
@@ -48,11 +47,9 @@ module SimpleCov
48
47
  # Coverage synthesizes a `:else` arm attributed to the whole condition's
49
48
  # range, and so do we.
50
49
  #
51
- # A folded condition emits no tuple, and on modern Rubies only its live
52
- # arm is descended into: the compiler eliminates the dead arm's entire
53
- # subtree, so a branch or method nested there would be a phantom no
54
- # loaded run can produce. On 3.2 the dead arm is visited too, branches
55
- # only.
50
+ # A folded condition emits no tuple, and only its live arm is descended
51
+ # into: the compiler eliminates the dead arm's entire subtree, so a branch
52
+ # or method nested there would be a phantom no loaded run can produce.
56
53
  def visit_if_node(node)
57
54
  verdict = folded_condition(node.predicate)
58
55
  return visit_folded_arms(verdict, node.statements, PrismCompat.subsequent(node)) if verdict
@@ -1,12 +1,6 @@
1
1
  # frozen_string_literal: true
2
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 fall
8
- # back to the previous "empty hashes" behavior.
9
- end
3
+ require "prism"
10
4
 
11
5
  module SimpleCov
12
6
  # Static enumeration of the branches and methods Ruby's `Coverage` library
@@ -23,17 +17,11 @@ module SimpleCov
23
17
  module StaticCoverageExtractor
24
18
  extend self
25
19
 
26
- def available?
27
- defined?(Prism) ? true : false
28
- end
29
-
30
20
  # Parse `source` and return `{"branches" => {...}, "methods" => {...}}`
31
21
  # matching the shape `Coverage.result[path]` produces. Returns nil on parse
32
- # failure or when Prism isn't available, which callers treat as "couldn't
33
- # extract, fall back to empty hashes".
22
+ # failure, which callers treat as "couldn't extract, fall back to empty
23
+ # hashes".
34
24
  def call(source)
35
- return nil unless available?
36
-
37
25
  result = Prism.parse(source)
38
26
  return nil if result.failure?
39
27
 
@@ -61,8 +49,8 @@ module SimpleCov
61
49
  # Coincidental line-sharing between a real branch and an eval-generated one
62
50
  # keeps both, an acceptable false-negative for an opt-in filter.
63
51
  #
64
- # Returns nil when Prism is unavailable or parsing fails, signaling callers
65
- # to keep every Coverage entry.
52
+ # Returns nil when parsing fails, signaling callers to keep every Coverage
53
+ # entry.
66
54
  def real_source_positions(source)
67
55
  extracted = call(source)
68
56
  return nil unless extracted
@@ -87,8 +75,4 @@ module SimpleCov
87
75
  end
88
76
  end
89
77
 
90
- # simplecov:disable branch
91
- # The `else` arm (Prism missing) is unreachable on engines where the dogfood
92
- # report runs; the Visitor class only matters when Prism is loadable.
93
- require_relative "static_coverage_extractor/visitor" if SimpleCov::StaticCoverageExtractor.available?
94
- # simplecov:enable branch
78
+ require_relative "static_coverage_extractor/visitor"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SimpleCov
4
- VERSION = "1.2.0"
4
+ VERSION = "1.3.0"
5
5
  end
data/man/simplecov.1 CHANGED
@@ -1,4 +1,4 @@
1
- .TH SIMPLECOV 1 "2026-08-24" "simplecov 1.2.0" "User Commands"
1
+ .TH SIMPLECOV 1 "2026-09-12" "simplecov 1.3.0" "User Commands"
2
2
  .SH NAME
3
3
  simplecov \- coverage reports and questions from the terminal
4
4
  .SH SYNOPSIS
@@ -168,8 +168,9 @@ line, branch, or method (default: line)
168
168
  .B \-\-missing
169
169
  Append the missed line ranges to each row
170
170
  .TP
171
- .B \-\-annotate github
172
- Emit ::warning workflow commands instead of rows
171
+ .B \-\-annotate KIND
172
+ Emit CI annotations instead of rows: github, gitlab, rdjson, azure, teamcity,
173
+ or buildkite
173
174
  .TP
174
175
  .B \-\-json
175
176
  Emit results as a JSON array (for CI)
@@ -252,6 +253,10 @@ Exit non\-zero when patch coverage on any measured criterion is below N%
252
253
  .B \-\-find\-renames
253
254
  Follow a renamed file instead of counting the moved file as all\-new
254
255
  .TP
256
+ .B \-\-annotate KIND
257
+ Emit CI annotations instead of rows: github, gitlab, rdjson, azure, teamcity,
258
+ or buildkite
259
+ .TP
255
260
  .B \-\-json
256
261
  Emit results as a JSON array (for CI)
257
262
  .SS ratchet
data/sig/simplecov.rbs CHANGED
@@ -123,6 +123,8 @@ module SimpleCov
123
123
 
124
124
  def self.tracked_file_paths: () -> Array[String]
125
125
 
126
+ def self.warn_templates_tracked: (Array[String] templates) -> void
127
+
126
128
  def self.inject_unloaded_files: (Hash[String, untyped] result, Array[String] candidate_paths, ?synthesize: bool?, ?lines: bool?) -> [Hash[String, untyped], Set[String]]
127
129
 
128
130
  def self.collect_own_coverage: (standalone: bool) -> void
@@ -669,6 +671,7 @@ module SimpleCov
669
671
  def source_file_for: (String path) -> SourceFile?
670
672
  def coverage_for: (String path) -> Hash[criterion, CoverageStatistics]?
671
673
  def groups: () -> Hash[String, FileList]
674
+ def configured_group?: (String group_name) -> bool
672
675
  def format!: () -> untyped
673
676
  def created_at: () -> Time
674
677
  def command_name: () -> String
@@ -953,6 +956,8 @@ module SimpleCov
953
956
 
954
957
  def method_statistics: () -> Hash[criterion, CoverageStatistics]
955
958
 
959
+ def unaccounted?: (SourceFile source_file, String table) -> bool
960
+
956
961
  def coverage_statistics: (Array[Line | Branch | Method] covered, Array[Line | Branch | Method] missed, ?omitted: Integer, ?percent: Float?) -> CoverageStatistics
957
962
  end
958
963
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simplecov
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Erik Berlin
@@ -44,6 +44,7 @@ files:
44
44
  - lib/simplecov/cli/affected.rb
45
45
  - lib/simplecov/cli/affected/changed_files.rb
46
46
  - lib/simplecov/cli/affected/selection.rb
47
+ - lib/simplecov/cli/annotations.rb
47
48
  - lib/simplecov/cli/badge.rb
48
49
  - lib/simplecov/cli/badge/svg.rb
49
50
  - lib/simplecov/cli/clean.rb
@@ -125,6 +126,11 @@ files:
125
126
  - lib/simplecov/defaults.rb
126
127
  - lib/simplecov/deprecation.rb
127
128
  - lib/simplecov/directive.rb
129
+ - lib/simplecov/directive/erb.rb
130
+ - lib/simplecov/directive/haml.rb
131
+ - lib/simplecov/directive/indented_template.rb
132
+ - lib/simplecov/directive/slim.rb
133
+ - lib/simplecov/directive/template.rb
128
134
  - lib/simplecov/exit_codes.rb
129
135
  - lib/simplecov/exit_codes/baseline_check.rb
130
136
  - lib/simplecov/exit_codes/check.rb
@@ -233,9 +239,9 @@ licenses:
233
239
  metadata:
234
240
  bug_tracker_uri: https://github.com/simplecov-ruby/simplecov/issues
235
241
  changelog_uri: https://github.com/simplecov-ruby/simplecov/blob/main/CHANGELOG.md
236
- documentation_uri: https://www.rubydoc.info/gems/simplecov/1.2.0
242
+ documentation_uri: https://www.rubydoc.info/gems/simplecov/1.3.0
237
243
  mailing_list_uri: https://groups.google.com/forum/#!forum/simplecov
238
- source_code_uri: https://github.com/simplecov-ruby/simplecov/tree/v1.2.0
244
+ source_code_uri: https://github.com/simplecov-ruby/simplecov/tree/v1.3.0
239
245
  rubygems_mfa_required: 'true'
240
246
  rdoc_options: []
241
247
  require_paths:
@@ -244,7 +250,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
244
250
  requirements:
245
251
  - - ">="
246
252
  - !ruby/object:Gem::Version
247
- version: '3.2'
253
+ version: '3.3'
248
254
  required_rubygems_version: !ruby/object:Gem::Requirement
249
255
  requirements:
250
256
  - - ">="