feat(hooks): implement customization lifecycle hooks#100
Conversation
- Introduced lifecycle hooks `BeforeToItem`, `AfterToItem`, `BeforeFromItem`, and `AfterFromItem`. - Added validation diagnostics for hook signatures, static declaration, and parameter type checks. - Updated templates to incorporate hooks when present. - Extended `MapperClassInfo` model to support hook-related metadata.
- Added unit tests for all hook combinations: BeforeToItem, AfterToItem, BeforeFromItem, and AfterFromItem. - Verified preservation of expression bodies when no hooks are used. - Added snapshot test cases for all scenarios to ensure generated code accuracy. - Covered edge cases for mappers with partial hooks and varying source fields.
- Added DM0401 to validate hook signature format. - Added DM0402 to ensure hook methods are static. - Added DM0403 to validate parameter types in hooks. - Updated AnalyzerReleases.Unshipped.md with the new diagnostic rules.
- Expanded explanation of zero-cost abstraction for unimplemented hooks. - Clarified behavior for generated code with and without declared hooks. - Updated Phase 2 section to note planned DSL-based hook configuration. - Added examples and diagnostics for common hook signature errors.
…delMapper - Added comprehensive tests for all hook scenarios: BeforeToItem, AfterToItem, BeforeFromItem, AfterFromItem. - Verified round-trip consistency of mapped properties and hook-added keys. - Ensured unmapped properties like NormalizedName are ignored in ToItem and derived in FromItem. - Covered edge cases for entity type validation and lifecycle marker injection.
ncipollina
left a comment
There was a problem hiding this comment.
Code review via sr-net-reviewer. 9 findings below — 1 critical, 3 major, 5 minor.
|
💡 Minor: Custom This is pre-existing, but the hooks feature now routes more through this equality path. The auto-generated record equality would include Worth adding a comment explaining why these fields are excluded. Future maintainers will question why the |
ncipollina
left a comment
There was a problem hiding this comment.
Reviewed via sr-net-reviewer. See inline comments for 1 critical, 3 major, and 5 minor findings. The 3 major issues (silent hook failures on malformed signatures, missing parameter type validation, and dead diagnostics) should be addressed before merge.
- Added `.config/dotnet-tools.json` to configure ReSharper global tools. - Updated `.gitignore` to include `.config` directory exclusion.
|
Addressed in ad0a043. Added an explicit comment above MapperInfo.Equals documenting why Context and HelperRegistry are intentionally excluded from record equality for incremental generator stability. |
- Added `references/hooks.md` to cover hook definitions, rules, and execution order. - Updated `diagnostics.md` to include DM0401, DM0402, and DM0403 as hook-related diagnostics. - Clarified lifecycle hook behavior in `core-usage.md` and `gotchas.md`. - Revised `SKILL.md` to include hooks as an extension point for pre/post mapping logic.
- Updated `Directory.Build.props` to increase `<VersionPrefix>` from 1.3.0 to 1.4.0.
ncipollina
left a comment
There was a problem hiding this comment.
✅ Left one comment, but otherwise looks good!
🚀 Pull Request
📋 Summary
Implements the four customization lifecycle hooks described in the docs (
BeforeToItem,AfterToItem,BeforeFromItem,AfterFromItem) that allow users to inject logic into the mapping pipeline without modifying core mapping code.Hooks are
static partial voidmethods — the generator detects whether each hook is declared in the mapper class and only emits calls when they exist. Mappers with no hooks preserve the existing compact expression-body form with zero overhead.Primary use cases enabled:
AfterToItem)AfterToItem)BeforeFromItem)AfterFromItem)AfterFromItem)✅ Checklist
🧪 Related Issues or PRs
💬 Notes for Reviewers
MapperClassInfo.cs— AddedToItemParameterName+ four hook booleans.IsHookPresent()scans class members forstatic partial voidmethods matching by name and parameter count.Mapper.scriban—ToItemswitches from expression body to block body (var item = ...; item.SetXxx(...); AfterToItem(...); return item;) only when a To-hook is detected. Existing chainable.SetXxx(...)assignments work in block-body form by prependingitemin the template — no changes toPropertyMappingCodeRenderer.FromItemgetsBeforeFromItem/AfterFromItemcalls injected at the right lifecycle points.Snapshot tests (
HooksVerifyTests) — 7 cases: each hook in isolation, all four together, and a regression case confirming expression body is preserved when no hooks are declared.Integration tests (
HooksMapperTests) — 11 runtime tests verifying actual hook execution order (lifecycle phase marker), pk/sk injection,BeforeFromItementity-type validation (including throw-on-mismatch),AfterFromItemcomputed property population, and round-trip correctness.Diagnostics — DM0401–DM0403 added (warnings for invalid hook signatures, non-static hooks, type mismatches). Registered in
AnalyzerReleases.Unshipped.md.