feat(di): DI002 transitive — a singleton weakly holding a transient that drags in a scoped#64
Conversation
…hat drags in a scoped Extends DI002 (shipped direct in #63) to the transitive form: a singleton holding a WeakReference<Transient> whose transient strongly depends on a scoped service. The scoped is still root-resolved and app-lived (a captive), reached one hop below the weak edge. The weak ref keeps the entry edge off the DI001 strong graph, but the captive scoped underneath is the smell -> DI002 (warning) with the full path shown. find_weak_captive_dependencies now runs the same strong-edge DFS DI001 does, rooted at each weak dep instead of the strong deps: a scoped reached directly (WeakReference<Scoped>) or through a weakly-held transient's strong deps is reported; transients are followed, singleton edges are another singleton's own pass, cycles guarded. Core-only — the extractor already emits weak_deps + the transient's strong deps. Pinned by WeakReport (WeakReference<UnitOfWork> -> scoped AppDbContext) in DiCaptiveSample.cs: the wpf-extractor step now asserts exactly 3 DI002 (WeakCache direct, WeakCacheOpt nullable, WeakReport transitive), with WeakReport carrying the path WeakReport -> UnitOfWork -> AppDbContext; the exactly-4-DI001 / exactly-1-DI003 counts and WeakClockHolder silence are unchanged. Unit test asserts the set + the transitive path. Validated: ruff + mypy clean, ownir 98/98, the 3-DI002 bridge with the transitive path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rg8kSk1YT14x7A1vo5zgED
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthrough
ChangesDI002 Transitive Weak Captive Detection
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/ci.yml:
- Around line 335-336: The current CI assertion on line 335 verifies that the
DI002 error exists for WeakReport capturing AppDbContext, but does not verify
the rendered transitive dependency chain path. Add a second dedicated grep
assertion immediately after the existing check that explicitly validates the
transitive chain string is present in the output, ensuring it contains the full
path rendering of WeakReport -> UnitOfWork -> AppDbContext to catch regressions
in path rendering logic.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: ca4d7b7c-ee20-47b2-bb13-f802cd0e02f3
📒 Files selected for processing (5)
.github/workflows/ci.ymldocs/notes/di-captive-extractor.mdfrontend/roslyn/samples/DiCaptiveSample.csownlang/di.pytests/test_ownir.py
The existing assertion verified the WeakReport DI002 finding exists but not the rendered transitive chain. Add a grep for "WeakReport -> UnitOfWork -> AppDbContext" so a path-rendering regression (the whole point of the transitive slice) fails CI. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rg8kSk1YT14x7A1vo5zgED
The transitive form of DI002
Extends DI002 (shipped direct in #63) to the transitive case: a singleton holding a
WeakReference<Transient>whose transient strongly depends on a scoped service.The weak edge enters the transient, but the scoped it drags in is still root-resolved and app-lived — a captive one hop below the weak reference. The weak ref keeps the entry edge off the DI001 strong graph, but the captive scoped underneath is the smell → DI002 (warning), with the full path shown.
How
find_weak_captive_dependenciesnow runs the same strong-edge DFS DI001 does, rooted at each weak dep instead of the strong deps: a scoped reached directly (WeakReference<Scoped>) or through a weakly-held transient's strong deps is reported; transients are followed, singleton edges are another singleton's own pass, cycles guarded. Core-only — the extractor already emitsweak_depsand the transient's strong deps.Pinned in CI
DiCaptiveSample.csaddsWeakReport, so thewpf-extractorjob now asserts exactly 3 DI002:WeakCache→ AppDbContextWeakCacheOpt→ AppDbContextWeakReport→ UnitOfWork → AppDbContextWeakReportcarries the pathWeakReport -> UnitOfWork -> AppDbContext; the existing exactly-4-DI001 / exactly-1-DI003 counts andWeakClockHoldersilence are unchanged. The unit test asserts the set + the transitive path.Validated: ruff + mypy clean, ownir 98/98, the 3-DI002 bridge with the transitive path.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Rg8kSk1YT14x7A1vo5zgED
Generated by Claude Code
Summary by CodeRabbit
Release Notes
Bug Fixes
Documentation
Tests