Skip to content

refactor: derived config logic moves to SnapDiff::Config (ADR-008 step 7b) - #230

Merged
pftg merged 1 commit into
masterfrom
refactor/adr8-derived-config-move
Aug 22, 2026
Merged

refactor: derived config logic moves to SnapDiff::Config (ADR-008 step 7b)#230
pftg merged 1 commit into
masterfrom
refactor/adr8-derived-config-move

Conversation

@pftg

@pftg pftg commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator

ADR-008 step 7b. config_legacy.rb was the last file in the v1 trees holding real logic. Step 1 moved config storage to SnapDiff::Config but left the derived values behind, so deleting lib/capybara/ at 3.0 would have lost behaviour instead of being a git rm. This finishes the move.

What moved where

Into SnapDiff::Config as instance methods, next to the storage they read:

Was Now
Capybara::Screenshot.active? SnapDiff::Config#active?
Capybara::Screenshot.screenshot_area SnapDiff::Config#screenshot_area
Capybara::Screenshot.screenshot_area_abs SnapDiff::Config#screenshot_area_abs
Diff.default_options (incl. the vips 0.001 tolerance literal) SnapDiff::Config#default_options

Inverted so the canonical names hold the bodies rather than forwarding into the legacy tree:

  • SnapDiff.compare now builds the Comparison; Diff.compare forwards to it.
  • SnapDiff.start now does the two-arg yield; Diff.configure forwards to it.

The Config::MAPPING accessor generator moved from config_legacy.rb to snap_diff/config.rb, alongside the storage it delegates to — same side of the fence as legacy_shims.rb, which already generates the legacy constants from the snap_diff side, for the same reason: the generator is code, and the v1 trees must stay alias-only.

Everything left in config_legacy.rb is six one-line forwarders plus the AVAILABLE_DRIVERS constant.

Win condition: the gate allowlist is now EMPTY

ALLOWED_WITH_CODE = {}.freeze

config_legacy.rb was the only entry. It is now checked by the general rule like every other legacy file, and the pinned method inventory that narrowed its exemption goes with the exemption (replaced by an assertion that the allowlist stays empty).

Proof the empty allowlist actually guards — a def self.sneaky_logic with a real body added to config_legacy.rb:

1) Failure:
capybara/screenshot/diff/config_legacy.rb: `def self.sneaky_logic` is not a one-line forwarder into SnapDiff

Mutation evidence

(i) break active? precedence — replaced the whole expression with a bare enabled:

529 runs, 0 failures. The precedence rule was completely unguarded.

So a guard was added (snap_diff_config_test.rb) pinning the full truth table through both the canonical method and the legacy forwarder. Re-running the same mutation:

1) Failure:
SnapDiffConfigTest#test_active?_gives_Screenshot.enabled_precedence...
Config#active? with screenshot_enabled=true, enabled=false.
Expected: true
  Actual: false

The other half of the rule (the nil fall-through) was already well covered — mutating to a bare screenshot_enabled reds ~14 tests across the suite, since screenshots go inactive everywhere.

(ii) freeze default_options[:wait] into Config#initialize — the #223 timing guards red on all four entry points:

default_options[:wait] follows Capybara.default_max_wait_time set after require: expected 42.5, got 2
  ... capybara-screenshot-diff / snap_diff / capybara/screenshot/diff / capybara_screenshot_diff/minitest
12 runs, 4 failures

config_default_timing_test.rb is untouched by this PR.

(iii) break screenshot_area path assembly — dropped the os/driver segments:

1) DiffTest#test_screenshot_section_prepends_section_to_path
2) DiffTest#test_writes_screenshot_to_alternate_save_path
530 runs, 2 failures

AVAILABLE_DRIVERS: stays put, and it was never a gate blocker

It is a bare constant assignment, which the gate's ALIAS_SHAPES already accepts as a constant alias — so it never stood between us and the empty allowlist.

Moving the storage to SnapDiff::Drivers was tried anyway, and reverted on two independent blockers:

  1. Eagerly (AVAILABLE = Utils.detect_available_drivers.freeze) it needs Utils, and utils.rb requires drivers.rb back → uninitialized constant SnapDiff::Drivers::Utils (NameError) at load.
  2. Lazily (@available ||= ...) it loads fine but silently breaks the published stubbing point, because Utils reads Drivers.available and would no longer see a stubbed legacy constant:
1) Failure:
ImageCompareTest#test_#initialize_with_:auto_driver_raises_error_when_no_drivers_available
RuntimeError expected but nothing was raised.

#227's reasoning holds. Documented in the allowlist comment so the next reader does not retry it.

Public surface

singleton_methods(false) + parameters diffed before/after (before generated from a pristine git archive HEAD tree). Both legacy modules are byte-identical — zero lines of the diff touch Capybara::Screenshot or Capybara::Screenshot::Diff, including compare[[:req, :baseline_path], [:req, :current_path], [:keyrest, :options]] and configure[].

The only changes are on the canonical side, as a direct consequence of the bodies landing there — both call-compatible:

 == SnapDiff
-  compare[[:rest, :*], [:keyrest, :**], [:block, :&]]
+  compare[[:req, :baseline_path], [:req, :current_path], [:keyrest, :options]]
-  start[[:block, :block]]
+  start[]
 == SnapDiff::Config -- instance:
+  #active?[]
+  #default_options[]
+  #screenshot_area[]
+  #screenshot_area_abs[]

Diff.configure keeps a bare yield rather than an explicit &block specifically to keep its published arity at [].

Tests

baseline (9c53009) this branch
rake test:unit 529 runs, 0 failures 530 runs, 0 failures
rake test 563 runs, 0 failures, 6 skips
standardrb 149 files, no offenses

Net test delta is exactly +1: the active? truth-table guard added, the pinned-inventory test replaced one-for-one by the empty-allowlist assertion. No existing test was modified.

🤖 Generated with Claude Code

Summary by Sourcery

Complete the configuration migration by moving derived behavior and canonical entry points into SnapDiff while leaving the legacy APIs as alias-only compatibility shims.

Enhancements:

  • Move derived configuration behavior into SnapDiff::Config and make SnapDiff the canonical home for comparison and configuration entry points.
  • Reduce the legacy configuration tree to compatibility forwarders and enforce that no legacy files retain executable logic.
  • Preserve legacy public APIs and configuration behavior while keeping dynamic wait-time defaults and driver compatibility intact.

Tests:

  • Add coverage for active? precedence across canonical and legacy APIs and replace the legacy allowlist inventory with an assertion that the allowlist remains empty.

Summary by CodeRabbit

  • Refactor

    • Consolidated screenshot and comparison settings into a single configuration source.
    • Preserved legacy configuration access while routing it through the unified settings.
    • Standardized screenshot path, activity status, and comparison option handling.
    • Updated comparison setup and execution to use the streamlined interface.
  • Tests

    • Added coverage for configuration activity states and legacy compatibility.
    • Verified that legacy configuration code contains no remaining exceptions.

…p 7b)

config_legacy.rb was the last file in the v1 trees holding real logic.
Step 1 moved config STORAGE to SnapDiff::Config but left the DERIVED
values behind, so deleting lib/capybara/ at 3.0 would have lost
behaviour rather than being a `git rm`.

Moved into SnapDiff::Config as instance methods:

- #active?            (was Capybara::Screenshot.active?)
- #screenshot_area    (was Capybara::Screenshot.screenshot_area)
- #screenshot_area_abs
- #default_options    (was Diff.default_options, incl. the vips
                       tolerance 0.001 literal)

Inverted so the canonical names hold the bodies:

- SnapDiff.compare now builds the Comparison; Diff.compare forwards.
- SnapDiff.start now does the two-arg yield; Diff.configure forwards.

The Config::MAPPING accessor generator moved to snap_diff/config.rb
alongside the storage it delegates to -- same reason legacy_shims.rb
generates the legacy constants from the snap_diff side: the generator
is code, and the v1 trees must stay alias-only.

Net effect: the alias-only gate's ALLOWED_WITH_CODE allowlist is now
EMPTY, and config_legacy.rb is checked by the general rule like every
other legacy file. The pinned method inventory that narrowed its
exemption goes with the exemption.

default_options[:wait] stays a call-time read of
Capybara.default_max_wait_time -- freezing it into Config#initialize
reds all four #223 timing guards.

Found while moving: the active? precedence rule had no test. Replacing
the whole expression with a bare `enabled` kept all 529 unit tests
green. Its truth table is now pinned through both the canonical method
and the legacy forwarder.

AVAILABLE_DRIVERS stays in config_legacy.rb (it is a bare constant
assignment, so alias-shaped and never a gate blocker). Moving the
storage to SnapDiff::Drivers was tried and reverted: eagerly it needs
Utils, which requires Drivers back; lazily it breaks
image_compare_test's published stubbing point, because Utils reads
Drivers.available and would no longer see a stubbed legacy constant.

Legacy public surface verified byte-identical: singleton_methods +
parameters for Capybara::Screenshot and Capybara::Screenshot::Diff are
unchanged. On the SnapDiff side, .compare gains an explicit signature
(was a `(...)` pass-through) and .start loses its &block capture (now a
bare yield) -- both a consequence of the bodies landing there, and both
call-compatible.

rake test:unit 530 runs / 0 failures; rake test 563 runs / 0 failures.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @pftg, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@sourcery-ai

sourcery-ai Bot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Derived configuration logic and comparison/configuration entry points are moved from legacy Capybara::Screenshot::Diff modules into SnapDiff::Config and SnapDiff, leaving the v1 tree alias-only and enforcing an empty allowlist, while preserving public APIs and adding tests around previously unguarded behaviour.

Sequence diagram for canonical comparison and configuration flows

sequenceDiagram
    participant Caller
    participant Diff as Legacy Diff
    participant SnapDiff
    participant Config as SnapDiff::Config
    participant Comparison

    Caller->>Diff: compare(baseline_path, current_path, **options)
    Diff->>SnapDiff: compare(baseline_path, current_path, **options)
    SnapDiff->>Config: default_options()
    Config-->>SnapDiff: defaults
    SnapDiff->>Comparison: new(current_path, baseline_path, merged_options)
    Comparison-->>Caller: comparison

    Caller->>Diff: configure()
    Diff->>SnapDiff: start()
    SnapDiff-->>Diff: yield Capybara::Screenshot, Diff
    Diff-->>Caller: configuration block
Loading

File-Level Changes

Change Details Files
Move all derived config behaviour from legacy modules into SnapDiff::Config instance methods and forward legacy accessors to it.
  • Implement Config#active?, #screenshot_area, #screenshot_area_abs, and #default_options using existing storage and live Capybara defaults.
  • Change Capybara::Screenshot.active?, screenshot_area, and screenshot_area_abs to delegate to SnapDiff.config.
  • Update comments to describe derived config living in SnapDiff::Config and clarify default_options timing behaviour.
lib/snap_diff/config.rb
lib/capybara/screenshot/diff/config_legacy.rb
lib/snap_diff.rb
Invert comparison and configuration entry points so SnapDiff is canonical and legacy Diff forwards, without changing public call shapes.
  • Implement SnapDiff.compare(baseline_path, current_path, **options) to build Comparison directly with Config#default_options.
  • Implement SnapDiff.start to yield Capybara::Screenshot and Capybara::Screenshot::Diff, and have Diff.configure forward to it while preserving arity.
  • Have Diff.compare and Diff.default_options delegate to SnapDiff.compare and SnapDiff.config.default_options respectively.
lib/snap_diff.rb
lib/capybara/screenshot/diff/config_legacy.rb
Move the Config::MAPPING-based accessor generator into SnapDiff::Config and keep the legacy tree alias-only, enforcing an empty allowlist gate.
  • Generate legacy mattr-style accessors from Config::MAPPING in snap_diff/config.rb instead of config_legacy.rb, including root’s asymmetric writer.
  • Remove the method-inventory test for config_legacy.rb and replace with an assertion that ALLOWED_WITH_CODE is empty.
  • Set ALLOWED_WITH_CODE to {} and update comments to document AVAILABLE_DRIVERS staying as a bare constant that doesn’t need an exemption.
lib/snap_diff/config.rb
lib/capybara/screenshot/diff/config_legacy.rb
test/unit/legacy_tree_is_alias_only_test.rb
Add targeted tests for Config#active? precedence and legacy forwarding, and keep existing timing tests for default_options behaviour intact.
  • Introduce ACTIVE_TRUTH_TABLE and a test covering all combinations of screenshot_enabled and enabled through Config#active? and Capybara::Screenshot.active?.
  • Preserve config_default_timing_test.rb behaviour by not changing the live wait default and confirming mutations fail as expected.
test/unit/snap_diff_config_test.rb
test/unit/config_default_timing_test.rb

Possibly linked issues

  • #ADR-004: PR implements ADR-004 Phase 1 configuration consolidation by moving derived logic into Config and making SnapDiff canonical.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@pftg
pftg merged commit fa9fc67 into master Aug 22, 2026
5 of 6 checks passed
@pftg
pftg deleted the refactor/adr8-derived-config-move branch August 22, 2026 23:45
@coderabbitai

coderabbitai Bot commented Aug 22, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5e1666e3-6272-40c2-8a5a-2081a64bdcff

📥 Commits

Reviewing files that changed from the base of the PR and between 9c53009 and 805fb31.

📒 Files selected for processing (5)
  • lib/capybara/screenshot/diff/config_legacy.rb
  • lib/snap_diff.rb
  • lib/snap_diff/config.rb
  • test/unit/legacy_tree_is_alias_only_test.rb
  • test/unit/snap_diff_config_test.rb

📝 Walkthrough

Walkthrough

The change moves derived configuration and comparison behavior into SnapDiff::Config and SnapDiff entry points. Legacy Capybara::Screenshot and Diff methods now act as compatibility forwarders. Tests verify activity resolution and the empty legacy logic allowlist.

Changes

Configuration forwarding

Layer / File(s) Summary
Centralized configuration and accessors
lib/snap_diff/config.rb, lib/capybara/screenshot/diff/config_legacy.rb
SnapDiff::Config now computes derived settings and generates legacy accessors that delegate through SnapDiff.config.
Legacy API forwarding
lib/capybara/screenshot/diff/config_legacy.rb, lib/snap_diff.rb
Legacy activity, path, comparison, and configuration methods now delegate to centralized SnapDiff entry points.
Compatibility validation
test/unit/snap_diff_config_test.rb, test/unit/legacy_tree_is_alias_only_test.rb
Tests cover activity-state combinations and require an empty allowlist for retained legacy logic.

Estimated code review effort: 3 (Moderate) | ~20 minutes

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/adr8-derived-config-move

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

pftg added a commit that referenced this pull request Aug 23, 2026
Twenty-nine reads across twelve core files went through
Capybara::Screenshot / Capybara::Screenshot::Diff -- the legacy VIEW of a
storage that has lived in SnapDiff::Config since #230. Same storage,
canonical name; behaviour is unchanged and the legacy delegators stay for
users.

Two follow-ons:
- browser_helpers dropped its respond_to?(:window_size) guard: that
  existed because the mattr_accessor might not be installed yet, and
  Config always has the attribute.
- the fail_if_new error message now tells users
  'SnapDiff.config.fail_if_new = false' -- it named an accessor that 3.0
  deletes.

Tests that STUBBED the legacy accessors now stub SnapDiff.config: a
delegator write still reaches the storage, but a stubbed delegator method
does not, so those stubs were silently no-ops against the new reads. The
legacy accessors keep their own coverage in snap_diff_config_test.rb
(every MAPPING writer) and config_default_timing_test.rb.
pftg added a commit that referenced this pull request Aug 23, 2026
…ess) (#235)

* test: reverse gate -- no core file may depend on the v1 trees

legacy_tree_is_alias_only_test.rb proves the v1 trees hold no logic.
Nothing proved the mirror image, and it was false: 64 core->legacy edges
(6 backward requires, AVAILABLE_DRIVERS, ~55 config reads through the
legacy view) meant `git rm lib/capybara*` would break the gem.

This gate reports every one as file:line and starts with them all
allowlisted, so it lands green and the following commits can only shrink
it. Red on master with an empty allowlist; mutation-checked by adding a
Capybara::Screenshot::Diff reference to region.rb (gate named it).

Comments are ignored (history, not dependency); strings are not.

* refactor: driver detection lives on SnapDiff::Drivers

SnapDiff::Drivers.available is documented canonical API, but the list it
read was defined only in config_legacy.rb, so

  ruby -Ilib -e 'require "snap_diff/drivers"; SnapDiff::Drivers.available'

raised NameError: uninitialized constant Capybara::Screenshot::Diff. It
now answers.

Detection moved to Drivers.detect_available and runs at drivers.rb load
(same load moment in every entry-point path); SnapDiff::Utils
.detect_available_drivers one-lines into it, so the documented Utils name
and its tests are unchanged. config_legacy keeps AVAILABLE_DRIVERS as an
eager same-object alias -- test_helper still reads it at boot, and
namespace_forwarding_test still pins assert_same.

The stubbing point moves with the value: image_compare_test now stubs
SnapDiff::Drivers::AVAILABLE_DRIVERS. Stubbing the legacy alias would
only rebind the alias, which is the whole point of cutting the edge.

utils.rb requires drivers and drivers now needs Utils at call time, so
drivers.rb requires utils at the bottom -- either file can be required
first.

* refactor: core config reads go to SnapDiff.config, not the legacy view

Twenty-nine reads across twelve core files went through
Capybara::Screenshot / Capybara::Screenshot::Diff -- the legacy VIEW of a
storage that has lived in SnapDiff::Config since #230. Same storage,
canonical name; behaviour is unchanged and the legacy delegators stay for
users.

Two follow-ons:
- browser_helpers dropped its respond_to?(:window_size) guard: that
  existed because the mattr_accessor might not be installed yet, and
  Config always has the attribute.
- the fail_if_new error message now tells users
  'SnapDiff.config.fail_if_new = false' -- it named an accessor that 3.0
  deletes.

Tests that STUBBED the legacy accessors now stub SnapDiff.config: a
delegator write still reaches the storage, but a stubbed delegator method
does not, so those stubs were silently no-ops against the new reads. The
legacy accessors keep their own coverage in snap_diff_config_test.rb
(every MAPPING writer) and config_default_timing_test.rb.

* refactor: core requires point at snap_diff/*, not the v1 forwarders

dsl.rb, reporters/html.rb and screenshot_matcher.rb pulled their
dependencies through capybara/screenshot/diff/* and
capybara_screenshot_diff/* forwarders -- files whose only content is a
require of the snap_diff/* unit the core actually wanted. Point at the
units directly.

snap_diff.rb's own two backward requires are the last ones left and go
with the v1-surface consolidation.

* refactor: one file holds the v1 surface, and the core names none of it

Three legacy edges were left in the canonical core:

- lib/snap_diff/config.rb DEFINED the Capybara::Screenshot::Diff module
  skeleton so Config::MAPPING could name it, and generated the legacy
  mattr_accessors. After a 3.0 `git rm` that would have survived: the
  core would still define a phantom v1 namespace, so adopters'
  `defined?(Capybara::Screenshot::Diff)` checks would keep passing.
- lib/snap_diff.rb required config_legacy.rb and image_compare.rb -- the
  only reason being that those forwarders happened to install the v1
  surface for bare `require "snap_diff"` processes.
- SnapDiff.start yields the two legacy holders, so it cannot outlive them.

All three move to lib/snap_diff/legacy_shims.rb, which now holds the whole
v1 surface as code: const_missing forwarders, CONFIG_MAPPING and its
generator, the derived forwarders that were in config_legacy.rb
(Screenshot.active?, Diff.configure/.compare/.default_options),
SnapDiff.start, and the eager AVAILABLE_DRIVERS / Comparison aliases.
config_legacy.rb and image_compare.rb are now requires only.

Config keeps the setting list as Config::SETTINGS and names nothing
legacy; a new test pins CONFIG_MAPPING.keys == SETTINGS so the split
cannot drift into storage with no accessor (or the reverse).

Behaviour is unchanged, including the awkward part: bare
`require "snap_diff"` still answers Capybara::Screenshot.active?,
Diff.default_options, Diff::AVAILABLE_DRIVERS and Diff::Comparison,
exactly as it did when it reached through the v1 forwarders (verified
before/after in a fresh process).

The reverse gate's allowlist is now EMPTY.

* test: alias-only gate demands a real single-expression forwarder

The rule was `body.include?("SnapDiff")` plus a following `end`, which
accepted anything that merely mentioned the canonical namespace:

  def x
    SnapDiff.config.a ? b : c   # a conditional -- real behaviour
  end

  def x; SnapDiff.config.a; File.write(...); end   # two statements

Both are now rejected: a semicolon is never alias-shaped (it is how
several statements, or a whole def, hide inside one "line"), and a
forwarder body must match the whole of FORWARDER_BODY -- one method-call
chain rooted at SnapDiff, at most one argument list and one block.
Mutation-checked with exactly those two shapes plus a genuine forwarder,
which still passes.

* build: gemspec resolves the version from snap_diff/version

The gemspec required capybara/screenshot/diff/version and read
Capybara::Screenshot::Diff::VERSION -- a build-time dependency on a file
3.0 deletes, so `gem build` would have failed the moment it did. Same
value (the legacy constant is an alias of SnapDiff::VERSION), one fewer
3.0 blocker. The legacy constant stays for adopters who read it.

Found by the 3.0 dry run; fixed here because it is one line and in
scope.

* fix: legacy VERSION vanished from six entry points

Capybara::Screenshot::Diff::VERSION raised NameError -- and defined?
returned nil -- under "snap_diff", snap_diff/dsl, both integrations,
snap_diff/static and, worst, the LEGACY capybara_screenshot_diff/dsl.

Cause: the constant was assigned by capybara/screenshot/diff/version.rb,
and the 3.0-readiness pass stopped the core requiring that forwarder.
legacy_shims deliberately omits VERSION from its const_missing map -- it
is one of the documented eager exceptions -- so nothing filled the gap and
adopters' `defined?` feature detection silently went false.

Assign it in legacy_shims next to the other eager aliases: that file is
required by every entry point, canonical and legacy, so it is the only
place the eager exceptions can actually be eager. version.rb drops the
assignment (a second one is a duplicate-constant warning, not a safety
net) and becomes a require.

The suite could not have caught it: EAGER_USER_FACING was only probed for
the four legacy entry points, and VERSION was not in the list at all. New
EAGER_EVERYWHERE probe covers all 15 entry points including
capybara_screenshot_diff/dsl, which is in none of the existing lists --
exactly why it was the legacy entry that lost VERSION unnoticed. Red
without the alias, naming all six.

Also corrects two now-false claims in the legacy_shims header: it said
AVAILABLE_DRIVERS was aliased in config_legacy.rb (it is aliased in this
file) and that VERSION/Comparison were defined by their own forwarder
files (nothing requires those anymore).

* test: reverse gate catches require_relative escapes

LEGACY_REQUIRE anchored straight on the opening quote, so

  require_relative "../capybara/screenshot/diff/version"

passed the gate while genuinely loading the v1 file (confirmed via
$LOADED_FEATURES). Every core file sits one directory below lib/, so that
is a one-line escape, not a hypothetical. Allow an optional (\.{1,2}/)* --
mutation-checked with exactly that line.

Two smaller fixes while here: the header claimed comments are ignored when
only WHOLE-LINE ones are (trailing notes on a live code line are scanned,
which is the behaviour worth keeping -- the header now says so), and the
stale-allowlist check now reports a deleted allowlisted file instead of
raising Errno::ENOENT on it.

* test: forwarder rule takes simple pass-through args only

The tightened rule rejected ternaries and semicolons but left `(.*)` and
`{ .* }` unbounded, so both of these still passed as "forwarders":

  SnapDiff.config.x(File.exist?("/etc/passwd") ? raise("boom") : ENV.fetch("HOME"))
  SnapDiff.config.tap { |c| File.write("/tmp/pwned", c.inspect); exit 1 }

The block case also dodged the semicolon check, because the walk steps
over a def's body line without re-examining it -- that scan now runs over
every line up front.

An argument list is now names, commas, splats and keyword colons, or
Ruby's `...` forwarding. No parens, so no nested call; no `.`, so no bare
receiver call; no `?`/quote/`=`, so no conditional, literal or assignment.
The block form is gone entirely: nothing in these trees has a def left,
and an unbounded block is exactly the hole above.

Mutation-checked with both shapes above (rejected, the block twice) plus
three genuine forwarders that must not false-positive: a bare chain, an
argument pass-through, and CapybaraScreenshotDiff.serve(...) -- which the
first draft of this rule did reject.

* docs: flag the two beta moves that fail silently

Both are read-identical and only break on write, so nothing warns:

- stubbing Capybara::Screenshot::Diff::AVAILABLE_DRIVERS now only rebinds
  an alias -- the gem reads SnapDiff::Drivers::AVAILABLE_DRIVERS, so a
  downstream test stubbing it to [] stops exercising the no-drivers path
  and passes for the wrong reason;
- SnapDiff::Config::MAPPING is gone mid-beta, split into Config::SETTINGS
  and the @api-private LegacyShims::CONFIG_MAPPING.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant