branchproof 0.6.0 → 0.7.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b5d48afceb9c6608abf5ebdb2aa6f402dd11b75f43421b49686cc49fd95641c3
4
- data.tar.gz: 647789d869c3a01f1809eacc871df99e4e0f81c7d8e70ffdb27228f4fd5c797e
3
+ metadata.gz: c4c0c87db7babd3785baf2ea439a90ec852253e7ade2837becec235ee9ac7055
4
+ data.tar.gz: 29b5cd02d6ab9419343af3c9c1d49fd07a77c2487eaf2631aafbc90bafdf4296
5
5
  SHA512:
6
- metadata.gz: b5f87cd4aee8588f7a86ba6a91730b7b4a787c256a0485f6dc278acbd948a5a1399669d0e2903371b28f1b7058e5339efb85fdb13d31110944e6462bdfe55b6a
7
- data.tar.gz: fb81f01ba2e42014364959afcdce9c8cd218611299d8bbd27a0a9fd5ed1ce1f2d311993f834b9f960934deadab659848257010717c2bdcc6814ea01870f985e2
6
+ metadata.gz: bffba10f09dc43d851a79373b1e3ec8b2457179a2f69da8b8076d1f48c002f3a8f6a88e7a30f8c970c5f9960a73abbec4829dfcbee2581da61c5ff3cd1e233f4
7
+ data.tar.gz: c808687e23a8b27ed794d723d4a390f8296d5ade907fb6255660ac025e657530eff850d578d7d600b1177076dcf2376d577b15a4310e620cd618bf4598f626fc
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.7.0] - 2026-09-10
4
+
5
+ - Discover Boolean loop predicates, subjectless case candidates, standalone
6
+ short-circuit expressions, and pattern predicates through Prism.
7
+ - Analyze unary NOT and keyword `and`/`or` with Ruby's parsed precedence.
8
+ - Attribute selected paths for ordinary case, unguarded case/in, safe navigation,
9
+ and conditional assignments without adding them to MC/DC denominators.
10
+ - Expose decision kinds, contexts, alternative evidence, and explicit unsupported
11
+ constructs in schema 1.2 reports while retaining older saved-report support.
12
+
3
13
  ## [0.6.0] - 2026-09-10
4
14
 
5
15
  - Report Decision, Condition, and Condition/Decision Coverage alongside
data/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # Branchproof
2
2
 
3
- Branchproof measures modified condition/decision coverage (MC/DC) from one
4
- serial Minitest run. It inventories supported `if`, `unless`, `elsif`,
5
- modifier, and ordinary ternary (`?:`) decisions, records observed vectors,
6
- and reports independence evidence, missing counterpart constraints, and
7
- smaller supporting test sets.
3
+ Branchproof measures decision, condition, and modified condition/decision
4
+ coverage (MC/DC) from one serial Minitest run. It discovers Ruby decisions
5
+ through Prism, records their runtime paths, and attributes evidence to tests.
6
+ Boolean decisions receive the coverage ladder; `case`, pattern alternatives,
7
+ safe navigation, and conditional assignments receive alternative coverage.
8
8
 
9
9
  The gem and primary command are named `branchproof`. The `mcdc` command and
10
10
  `MCDC` namespace remain compatibility aliases with the same behavior.
@@ -321,26 +321,77 @@ short-circuited or masked rather than fixed to the same observed values. For exa
321
321
  short-circuits it once, but does not prove `right`; `[TF]` is also required.
322
322
  The condition and test views preserve that distinction.
323
323
 
324
- ### Supported conditional forms
324
+ ### Supported decision forms
325
325
 
326
- Ordinary Ruby ternaries use the same predicate instrumentation and `&&`/`||`
327
- condition trees as supported `if` decisions. For example:
326
+ Every decision has a stable `kind` and `context` in JSON. The Boolean ladder
327
+ applies to the following forms:
328
328
 
329
- ```ruby
330
- value = ready ? false : true
331
- ```
332
-
333
- Branchproof records `ready` as the ternary predicate. The decision outcome is
334
- therefore the truth value of `ready`, even though the selected branch returns
335
- `false` or `true`; branch selection, returned values, object identity, and
336
- evaluation order are unchanged. Ternaries nested inside other predicates are
337
- also inventoried at their own level, including all executed nested levels.
338
- Expanding the supported syntax increases the eligible-condition denominator,
339
- so percentages should be compared with that changed scope in mind.
340
-
341
- The existing predicate exclusions and analysis limits still apply. Keyword
342
- `and`/`or` expressions, contextual syntax, unsafe or ambiguous predicates,
343
- and limit overflows remain diagnostics rather than eligible coverage.
329
+ | Construct | Kind | Context |
330
+ | --- | --- | --- |
331
+ | `if`, modifier `if`, `elsif`, `unless`, ternary | `boolean` | `if`, `elsif`, `unless`, `ternary` |
332
+ | `while`, `until`, including modifier and post-test loops | `boolean` | `while`, `until` |
333
+ | Each subjectless `case` candidate | `boolean` | `case_when` |
334
+ | Standalone `value in pattern` | `boolean` | `pattern_in` |
335
+ | Evaluated pattern guard predicate | `boolean` | `pattern_guard` |
336
+ | Standalone `&&`, `||`, `and`, `or` | `boolean` | `short_circuit` |
337
+
338
+ Prism determines precedence. `!` and `not` appear as NOT nodes in the Boolean
339
+ tree; their operands remain the conditions. Short-circuited operands remain
340
+ not evaluated. A Boolean subtree already decomposed in a decision is not
341
+ inventoried again as a standalone decision.
342
+
343
+ Loop outcomes describe the predicate as written: an `until` predicate that
344
+ returns true ends the loop. Every predicate evaluation receives an execution
345
+ ID. Repeated equivalent executions aggregate into a vector's `count`, retaining
346
+ the supporting tests. Ternary outcomes likewise describe the predicate, not
347
+ the value returned by the chosen branch.
348
+
349
+ Other constructs use alternative coverage, separate from MC/DC:
350
+
351
+ | Construct | Kind | Context | Required alternatives |
352
+ | --- | --- | --- | --- |
353
+ | `case subject` | `multiway` | `case` | Each `when` candidate and `else` (or implicit no-match path) |
354
+ | `case/in` | `pattern` | `case_in` | Each pattern clause and explicit `else`, if present |
355
+ | `receiver&.method` | `implicit` | `safe_navigation` | Receiver nil / non-nil |
356
+ | `lhs ||= rhs` | `implicit` | `or_assignment` | RHS skipped / executed |
357
+ | `lhs &&= rhs` | `implicit` | `and_assignment` | RHS skipped / executed |
358
+
359
+ Each safe-navigation operation in a chain is a distinct decision. Assignment
360
+ instrumentation preserves Ruby's native local, instance, class, global,
361
+ constant, method, and indexed assignment operations, including receiver and
362
+ index evaluation order. Safe navigation distinguishes nil from false.
363
+
364
+ For multiway decisions, vector values mean selected (`true`), evaluated but
365
+ not selected (`false`), and skipped (`null`). Later alternatives remain skipped
366
+ when an earlier candidate matches. Implicit vectors record the selected path
367
+ and its unselected complement. Their `outcome` is a selection marker, not the
368
+ truthiness of the application's return value. Reports label these as paths,
369
+ not Boolean outcomes. Each alternative exposes selected, not-selected, and
370
+ skipped evidence with test and vector IDs. The alternative denominator is the
371
+ number of supported selectable alternatives; these decisions do not enter
372
+ Boolean-ladder or MC/DC denominators.
373
+
374
+ A `case/in` without `else` retains Ruby's native no-match exception. An execution
375
+ that fails before choosing a branch is aborted, not counted as a selected
376
+ alternative. Selected branches and assignment paths remain observed even when
377
+ their bodies or right-hand sides subsequently raise or return.
378
+
379
+ Unsupported syntax stays visible and outside coverage denominators. Current
380
+ exclusions include guarded `case/in` (`unsupported_pattern_guard`), dynamic
381
+ `when` splats (`unsupported_case_splat`), safe-navigation compound assignment
382
+ (`unsupported_assignment_target`), and rescue alternatives
383
+ (`unsupported_rescue_control_flow`). A guard predicate can still supply Boolean
384
+ evidence when Ruby evaluates it; an unsupported guarded case does not claim
385
+ pattern-match coverage from that evidence. Flip-flops remain
386
+ `unsupported_flip_flop`. Decisions inside `defined?`, contextual regular
387
+ expressions, heredocs, unsafe predicates, and limit overflows retain explicit
388
+ exclusions. Ruby-defined custom `!` methods keep their runtime behavior;
389
+ evidence that contradicts Boolean negation is rejected instead of proving
390
+ coverage with an invalid logical model.
391
+
392
+ New reports use schema `1.2`; saved schema `1.0` and `1.1` reports remain
393
+ readable. Expanded discovery changes coverage denominators, so compare reports
394
+ with their supported syntax scope in mind.
344
395
 
345
396
  ### Limits
346
397
 
@@ -8,6 +8,9 @@
8
8
  Derives condition- and test-oriented rows from one report document.
9
9
 
10
10
  ## Attributes
11
+ ### `alternatives` [R] <a id="attribute-i-alternatives"></a> <a id="alternatives-instance_method"></a>
12
+ Returns the value of attribute alternatives.
13
+
11
14
  ### `conditions` [R] <a id="attribute-i-conditions"></a> <a id="conditions-instance_method"></a>
12
15
  Returns the value of attribute conditions.
13
16
 
@@ -0,0 +1,20 @@
1
+ # Module Branchproof::DecisionSyntax <a id="module-Branchproof-DecisionSyntax"></a>
2
+
3
+ | | |
4
+ | --- | --- |
5
+ | **Defined in** | lib/branchproof/decision_syntax.rb |
6
+
7
+ Discovers control-flow expressions whose truth is not represented by an
8
+ ordinary Prism IfNode. The records intentionally contain byte ranges and
9
+ scalar metadata only; Prism nodes must not escape the source pass.
10
+
11
+ ## Constants
12
+ ### `AND_WRITE_NODE_NAMES` <a id="constant-AND_WRITE_NODE_NAMES"></a> <a id="AND_WRITE_NODE_NAMES-constant"></a>
13
+ Not documented.
14
+
15
+ ### `OR_WRITE_NODE_NAMES` <a id="constant-OR_WRITE_NODE_NAMES"></a> <a id="OR_WRITE_NODE_NAMES-constant"></a>
16
+ Not documented.
17
+
18
+ ## Public Instance Methods
19
+ ### `flow_decisions_for(program, bytes, source_id, file_reasons = [], encoding = "UTF-8")` <a id="method-i-flow_decisions_for"></a> <a id="flow_decisions_for-instance_method"></a>
20
+ Not documented.
@@ -0,0 +1,7 @@
1
+ # Module Branchproof::FlowInstrumentation <a id="module-Branchproof-FlowInstrumentation"></a>
2
+
3
+ | | |
4
+ | --- | --- |
5
+ | **Defined in** | lib/branchproof/flow_instrumentation.rb |
6
+
7
+ Source-location edits around Ruby's native matching and assignment operations.
@@ -3,6 +3,7 @@
3
3
  | | |
4
4
  | --- | --- |
5
5
  | **Inherits** | Object |
6
+ | **Includes** | [Branchproof::FlowInstrumentation](FlowInstrumentation.md) |
6
7
  | **Defined in** | lib/branchproof/instrumenter.rb |
7
8
 
8
9
  Applies the smallest possible source edits around inventoried expressions. The
@@ -2,6 +2,7 @@
2
2
 
3
3
  | | |
4
4
  | --- | --- |
5
+ | **Extended by** | [Branchproof::RuntimeFlow](RuntimeFlow.md) |
5
6
  | **Defined in** | lib/branchproof/runtime.rb |
6
7
 
7
8
  Process-local execution recorder. It deliberately never coerces or stores
@@ -27,6 +28,24 @@ Not documented.
27
28
  ### `finish(decision_id, value)` <a id="method-c-finish"></a> <a id="finish-class_method"></a>
28
29
  Not documented.
29
30
 
31
+ ### `flow_candidate(decision_id, index)` <a id="method-c-flow_candidate"></a> <a id="flow_candidate-class_method"></a>
32
+ Not documented.
33
+
34
+ ### `flow_finish(decision_id, value, default_path = nil)` <a id="method-c-flow_finish"></a> <a id="flow_finish-class_method"></a>
35
+ Not documented.
36
+
37
+ ### `flow_path(decision_id, index)` <a id="method-c-flow_path"></a> <a id="flow_path-class_method"></a>
38
+ Not documented.
39
+
40
+ ### `flow_receiver(decision_id, receiver)` <a id="method-c-flow_receiver"></a> <a id="flow_receiver-class_method"></a>
41
+ Not documented.
42
+
43
+ ### `flow_select(decision_id, index)` <a id="method-c-flow_select"></a> <a id="flow_select-class_method"></a>
44
+ Not documented.
45
+
46
+ ### `flow_selected(decision_id)` <a id="method-c-flow_selected"></a> <a id="flow_selected-class_method"></a>
47
+ Not documented.
48
+
30
49
  ### `leave(decision_id)` <a id="method-c-leave"></a> <a id="leave-class_method"></a>
31
50
  Not documented.
32
51
 
@@ -0,0 +1,27 @@
1
+ # Module Branchproof::RuntimeFlow <a id="module-Branchproof-RuntimeFlow"></a>
2
+
3
+ | | |
4
+ | --- | --- |
5
+ | **Defined in** | lib/branchproof/runtime_flow.rb |
6
+
7
+ Records native Ruby path selection without evaluating application values
8
+ twice.
9
+
10
+ ## Public Instance Methods
11
+ ### `flow_candidate(decision_id, index)` <a id="method-i-flow_candidate"></a> <a id="flow_candidate-instance_method"></a>
12
+ Not documented.
13
+
14
+ ### `flow_finish(decision_id, value, default_path = nil)` <a id="method-i-flow_finish"></a> <a id="flow_finish-instance_method"></a>
15
+ Not documented.
16
+
17
+ ### `flow_path(decision_id, index)` <a id="method-i-flow_path"></a> <a id="flow_path-instance_method"></a>
18
+ Not documented.
19
+
20
+ ### `flow_receiver(decision_id, receiver)` <a id="method-i-flow_receiver"></a> <a id="flow_receiver-instance_method"></a>
21
+ Not documented.
22
+
23
+ ### `flow_select(decision_id, index)` <a id="method-i-flow_select"></a> <a id="flow_select-instance_method"></a>
24
+ Not documented.
25
+
26
+ ### `flow_selected(decision_id)` <a id="method-i-flow_selected"></a> <a id="flow_selected-instance_method"></a>
27
+ Not documented.
@@ -14,6 +14,12 @@ Not documented.
14
14
  ### `CRITERION_VERSION` <a id="constant-CRITERION_VERSION"></a> <a id="CRITERION_VERSION-constant"></a>
15
15
  Not documented.
16
16
 
17
+ ### `DECISION_KINDS` <a id="constant-DECISION_KINDS"></a> <a id="DECISION_KINDS-constant"></a>
18
+ Not documented.
19
+
20
+ ### `NONBOOLEAN_KINDS` <a id="constant-NONBOOLEAN_KINDS"></a> <a id="NONBOOLEAN_KINDS-constant"></a>
21
+ Not documented.
22
+
17
23
  ### `REQUIRED_FIELDS` <a id="constant-REQUIRED_FIELDS"></a> <a id="REQUIRED_FIELDS-constant"></a>
18
24
  Not documented.
19
25
 
@@ -3,6 +3,7 @@
3
3
  | | |
4
4
  | --- | --- |
5
5
  | **Inherits** | Object |
6
+ | **Includes** | [Branchproof::DecisionSyntax](DecisionSyntax.md) |
6
7
  | **Defined in** | lib/branchproof/source.rb |
7
8
 
8
9
  Inventories supported condition and decision occurrences from Ruby files.
data/doc/Branchproof.md CHANGED
@@ -2,9 +2,10 @@
2
2
 
3
3
  | | |
4
4
  | --- | --- |
5
- | **Defined in** | lib/branchproof.rb, lib/branchproof/cli.rb, lib/branchproof/limits.rb, lib/branchproof/loader.rb, lib/branchproof/report.rb, lib/branchproof/source.rb, lib/branchproof/worker.rb, lib/branchproof/project.rb, lib/branchproof/records.rb, lib/branchproof/runtime.rb, lib/branchproof/version.rb, lib/branchproof/analyzer.rb, lib/branchproof/evidence.rb, lib/branchproof/minimizer.rb, lib/branchproof/comparison.rb, lib/branchproof/instrumenter.rb, lib/branchproof/saved_report.rb, lib/branchproof/rails_support.rb, lib/branchproof/coverage_index.rb, lib/branchproof/focused_report.rb, lib/branchproof/minitest_adapter.rb, lib/branchproof/comparison_report.rb |
5
+ | **Defined in** | lib/branchproof.rb, lib/branchproof/cli.rb, lib/branchproof/limits.rb, lib/branchproof/loader.rb, lib/branchproof/report.rb, lib/branchproof/source.rb, lib/branchproof/worker.rb, lib/branchproof/project.rb, lib/branchproof/records.rb, lib/branchproof/runtime.rb, lib/branchproof/version.rb, lib/branchproof/analyzer.rb, lib/branchproof/evidence.rb, lib/branchproof/minimizer.rb, lib/branchproof/comparison.rb, lib/branchproof/instrumenter.rb, lib/branchproof/runtime_flow.rb, lib/branchproof/saved_report.rb, lib/branchproof/rails_support.rb, lib/branchproof/coverage_index.rb, lib/branchproof/focused_report.rb, lib/branchproof/decision_syntax.rb, lib/branchproof/minitest_adapter.rb, lib/branchproof/comparison_report.rb, lib/branchproof/flow_instrumentation.rb |
6
6
 
7
- Public namespace for source inventory and one-run MC/DC reporting.
7
+ Keep each bounded source rewrite together so its evaluation order can be
8
+ audited. rubocop:disable Metrics/AbcSize, Metrics/MethodLength
8
9
 
9
10
  ## Constants
10
11
  ### `VERSION` <a id="constant-VERSION"></a> <a id="VERSION-constant"></a>
@@ -17,8 +18,10 @@ Not documented.
17
18
  - [Branchproof/Comparison.md](Branchproof/Comparison.md)
18
19
  - [Branchproof/ComparisonReport.md](Branchproof/ComparisonReport.md)
19
20
  - [Branchproof/CoverageIndex.md](Branchproof/CoverageIndex.md)
21
+ - [Branchproof/DecisionSyntax.md](Branchproof/DecisionSyntax.md)
20
22
  - [Branchproof/Error.md](Branchproof/Error.md)
21
23
  - [Branchproof/Evidence.md](Branchproof/Evidence.md)
24
+ - [Branchproof/FlowInstrumentation.md](Branchproof/FlowInstrumentation.md)
22
25
  - [Branchproof/FocusedReport.md](Branchproof/FocusedReport.md)
23
26
  - [Branchproof/Instrumenter.md](Branchproof/Instrumenter.md)
24
27
  - [Branchproof/Limits.md](Branchproof/Limits.md)
@@ -31,6 +34,7 @@ Not documented.
31
34
  - [Branchproof/Records.md](Branchproof/Records.md)
32
35
  - [Branchproof/Report.md](Branchproof/Report.md)
33
36
  - [Branchproof/Runtime.md](Branchproof/Runtime.md)
37
+ - [Branchproof/RuntimeFlow.md](Branchproof/RuntimeFlow.md)
34
38
  - [Branchproof/SavedReport.md](Branchproof/SavedReport.md)
35
39
  - [Branchproof/Source.md](Branchproof/Source.md)
36
40
  - [Branchproof/Worker.md](Branchproof/Worker.md)
data/doc/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.7.0] - 2026-09-10
4
+
5
+ - Discover Boolean loop predicates, subjectless case candidates, standalone
6
+ short-circuit expressions, and pattern predicates through Prism.
7
+ - Analyze unary NOT and keyword `and`/`or` with Ruby's parsed precedence.
8
+ - Attribute selected paths for ordinary case, unguarded case/in, safe navigation,
9
+ and conditional assignments without adding them to MC/DC denominators.
10
+ - Expose decision kinds, contexts, alternative evidence, and explicit unsupported
11
+ constructs in schema 1.2 reports while retaining older saved-report support.
12
+
3
13
  ## [0.6.0] - 2026-09-10
4
14
 
5
15
  - Report Decision, Condition, and Condition/Decision Coverage alongside
data/doc/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # Branchproof
2
2
 
3
- Branchproof measures modified condition/decision coverage (MC/DC) from one
4
- serial Minitest run. It inventories supported `if`, `unless`, `elsif`,
5
- modifier, and ordinary ternary (`?:`) decisions, records observed vectors,
6
- and reports independence evidence, missing counterpart constraints, and
7
- smaller supporting test sets.
3
+ Branchproof measures decision, condition, and modified condition/decision
4
+ coverage (MC/DC) from one serial Minitest run. It discovers Ruby decisions
5
+ through Prism, records their runtime paths, and attributes evidence to tests.
6
+ Boolean decisions receive the coverage ladder; `case`, pattern alternatives,
7
+ safe navigation, and conditional assignments receive alternative coverage.
8
8
 
9
9
  The gem and primary command are named `branchproof`. The `mcdc` command and
10
10
  `MCDC` namespace remain compatibility aliases with the same behavior.
@@ -321,26 +321,77 @@ short-circuited or masked rather than fixed to the same observed values. For exa
321
321
  short-circuits it once, but does not prove `right`; `[TF]` is also required.
322
322
  The condition and test views preserve that distinction.
323
323
 
324
- ### Supported conditional forms
324
+ ### Supported decision forms
325
325
 
326
- Ordinary Ruby ternaries use the same predicate instrumentation and `&&`/`||`
327
- condition trees as supported `if` decisions. For example:
326
+ Every decision has a stable `kind` and `context` in JSON. The Boolean ladder
327
+ applies to the following forms:
328
328
 
329
- ```ruby
330
- value = ready ? false : true
331
- ```
332
-
333
- Branchproof records `ready` as the ternary predicate. The decision outcome is
334
- therefore the truth value of `ready`, even though the selected branch returns
335
- `false` or `true`; branch selection, returned values, object identity, and
336
- evaluation order are unchanged. Ternaries nested inside other predicates are
337
- also inventoried at their own level, including all executed nested levels.
338
- Expanding the supported syntax increases the eligible-condition denominator,
339
- so percentages should be compared with that changed scope in mind.
340
-
341
- The existing predicate exclusions and analysis limits still apply. Keyword
342
- `and`/`or` expressions, contextual syntax, unsafe or ambiguous predicates,
343
- and limit overflows remain diagnostics rather than eligible coverage.
329
+ | Construct | Kind | Context |
330
+ | --- | --- | --- |
331
+ | `if`, modifier `if`, `elsif`, `unless`, ternary | `boolean` | `if`, `elsif`, `unless`, `ternary` |
332
+ | `while`, `until`, including modifier and post-test loops | `boolean` | `while`, `until` |
333
+ | Each subjectless `case` candidate | `boolean` | `case_when` |
334
+ | Standalone `value in pattern` | `boolean` | `pattern_in` |
335
+ | Evaluated pattern guard predicate | `boolean` | `pattern_guard` |
336
+ | Standalone `&&`, `||`, `and`, `or` | `boolean` | `short_circuit` |
337
+
338
+ Prism determines precedence. `!` and `not` appear as NOT nodes in the Boolean
339
+ tree; their operands remain the conditions. Short-circuited operands remain
340
+ not evaluated. A Boolean subtree already decomposed in a decision is not
341
+ inventoried again as a standalone decision.
342
+
343
+ Loop outcomes describe the predicate as written: an `until` predicate that
344
+ returns true ends the loop. Every predicate evaluation receives an execution
345
+ ID. Repeated equivalent executions aggregate into a vector's `count`, retaining
346
+ the supporting tests. Ternary outcomes likewise describe the predicate, not
347
+ the value returned by the chosen branch.
348
+
349
+ Other constructs use alternative coverage, separate from MC/DC:
350
+
351
+ | Construct | Kind | Context | Required alternatives |
352
+ | --- | --- | --- | --- |
353
+ | `case subject` | `multiway` | `case` | Each `when` candidate and `else` (or implicit no-match path) |
354
+ | `case/in` | `pattern` | `case_in` | Each pattern clause and explicit `else`, if present |
355
+ | `receiver&.method` | `implicit` | `safe_navigation` | Receiver nil / non-nil |
356
+ | `lhs ||= rhs` | `implicit` | `or_assignment` | RHS skipped / executed |
357
+ | `lhs &&= rhs` | `implicit` | `and_assignment` | RHS skipped / executed |
358
+
359
+ Each safe-navigation operation in a chain is a distinct decision. Assignment
360
+ instrumentation preserves Ruby's native local, instance, class, global,
361
+ constant, method, and indexed assignment operations, including receiver and
362
+ index evaluation order. Safe navigation distinguishes nil from false.
363
+
364
+ For multiway decisions, vector values mean selected (`true`), evaluated but
365
+ not selected (`false`), and skipped (`null`). Later alternatives remain skipped
366
+ when an earlier candidate matches. Implicit vectors record the selected path
367
+ and its unselected complement. Their `outcome` is a selection marker, not the
368
+ truthiness of the application's return value. Reports label these as paths,
369
+ not Boolean outcomes. Each alternative exposes selected, not-selected, and
370
+ skipped evidence with test and vector IDs. The alternative denominator is the
371
+ number of supported selectable alternatives; these decisions do not enter
372
+ Boolean-ladder or MC/DC denominators.
373
+
374
+ A `case/in` without `else` retains Ruby's native no-match exception. An execution
375
+ that fails before choosing a branch is aborted, not counted as a selected
376
+ alternative. Selected branches and assignment paths remain observed even when
377
+ their bodies or right-hand sides subsequently raise or return.
378
+
379
+ Unsupported syntax stays visible and outside coverage denominators. Current
380
+ exclusions include guarded `case/in` (`unsupported_pattern_guard`), dynamic
381
+ `when` splats (`unsupported_case_splat`), safe-navigation compound assignment
382
+ (`unsupported_assignment_target`), and rescue alternatives
383
+ (`unsupported_rescue_control_flow`). A guard predicate can still supply Boolean
384
+ evidence when Ruby evaluates it; an unsupported guarded case does not claim
385
+ pattern-match coverage from that evidence. Flip-flops remain
386
+ `unsupported_flip_flop`. Decisions inside `defined?`, contextual regular
387
+ expressions, heredocs, unsafe predicates, and limit overflows retain explicit
388
+ exclusions. Ruby-defined custom `!` methods keep their runtime behavior;
389
+ evidence that contradicts Boolean negation is rejected instead of proving
390
+ coverage with an invalid logical model.
391
+
392
+ New reports use schema `1.2`; saved schema `1.0` and `1.1` reports remain
393
+ readable. Expanded discovery changes coverage denominators, so compare reports
394
+ with their supported syntax scope in mind.
344
395
 
345
396
  ### Limits
346
397