Skip to content

fix: added benchmarks for AutoMapper, Mapperly, Facet and Mapster#2

Merged
FreakyAli merged 8 commits intomasterfrom
feat/freaky-kit-forge
Mar 24, 2026
Merged

fix: added benchmarks for AutoMapper, Mapperly, Facet and Mapster#2
FreakyAli merged 8 commits intomasterfrom
feat/freaky-kit-forge

Conversation

@FreakyAli
Copy link
Copy Markdown
Owner

@FreakyAli FreakyAli commented Mar 13, 2026

Summary by CodeRabbit

  • Documentation

    • Expanded README/docs: benchmarks, collection projection, init-only/record guidance, null-safe nested/flattening examples, constructor-parameter mapping, converter rules, and Strict Mapping details.
  • New Features

    • Collection-projection methods, broader immutable/read-only collection support, null-safe nested/flattening generation, init-only assignment via object-initializers, ForgeMap on ctor params, opt-in Strict Mapping.
  • Benchmarks

    • New .NET 8 benchmark project with multiple scenario suites and results.
  • Diagnostics

    • New strict-mapping error IDs and invalid-converter warning.
  • Tests

    • Many new/updated generator, analyzer, and integration tests.

@FreakyAli FreakyAli changed the title fix: added benchmarks for AM, Mapperly and Mapster fix: added benchmarks for AutoMapper, Mapperly and Mapster Mar 13, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 13, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

Adds a BenchmarkDotNet project and many benchmarks; extends the source generator with collection-projection, null-safe nested/collection access, init-only handling and collection materialization for immutable/read-only types; introduces StrictMapping diagnostics and converter-signature validation in the analyzer; expands attributes and broad test coverage.

Changes

Cohort / File(s) Summary
Docs & README
README.md, docs/attributes.md, docs/diagnostics.md, docs/generator.md, docs/benchmarks.md, docs/future-plans.md
Expanded feature docs and README: null-safe nested/collection access, collection projection, init-only/record guidance, StrictMapping (FKF110/FKF111), FKF221 converter rule, attribute updates and benchmark results.
Benchmarks project & entry
benchmarks/FreakyKit.Forge.Benchmarks/FreakyKit.Forge.Benchmarks.csproj, benchmarks/FreakyKit.Forge.Benchmarks/Program.cs
Added new net8 benchmark executable project and assembly benchmark runner.
Benchmarks: scenarios & models
benchmarks/.../Benchmarks/*, benchmarks/.../Models/*
Added many benchmark classes (Simple, Medium, Nested, Collection, DeepGraph, Flattening, Update, Throughput, EcommerceOrder, NullableEntity, etc.) and DTO/source model types.
Benchmarks: mappers/configs/facets
benchmarks/.../Forges/BenchmarkForges.cs, benchmarks/.../Forges/HandWrittenMappers.cs, benchmarks/.../Forges/MapperlyMappers.cs, benchmarks/.../Forges/AutoMapperConfig.cs, benchmarks/.../Forges/MapsterConfig.cs, benchmarks/.../Forges/FacetConfig.cs
Added hand-written mappers, mapperly/Mapster/AutoMapper setup, facet DTOs and forge frontier declarations for benchmarks.
Generator core & models
src/FreakyKit.Forge.Generator/.../ForgeGenerator.cs, src/.../Models/ForgeMethodKind.cs, src/.../Models/ForgeMethodModel.cs, src/.../Models/MemberAssignmentModel.cs
Introduced CollectionProject generation, propagated source parameter naming, side-aware member collection, null-safe flattening/nested/collection generation, init-only member handling (object-initializer for create, skip on update), immutable/read-only collection materialization, and new model properties/enum entries.
Analyzer & diagnostics
src/FreakyKit.Forge.Analyzers/ForgeAnalyzer.cs, src/FreakyKit.Forge.Diagnostics/ForgeDiagnostics.cs, src/FreakyKit.Forge.Diagnostics/AnalyzerReleases.Unshipped.md
Added FKF110/FKF111 (strict mapping errors) and FKF221 (invalid converter signature warning); analyzer enforces converter signature rules, supports StrictMapping flag, and honors ForgeIgnore.Side semantics.
Attributes API
src/FreakyKit.Forge/Attributes/ForgeMethodAttribute.cs, src/FreakyKit.Forge/Attributes/ForgeIgnoreAttribute.cs, src/FreakyKit.Forge/Attributes/ForgeMapAttribute.cs
Added StrictMapping on ForgeMethodAttribute, ForgeIgnoreSide enum and Side on ForgeIgnoreAttribute, and allowed ForgeMapAttribute on constructor parameters.
Generator tests
tests/FreakyKit.Forge.Generator.Tests/*
Added/updated tests for collection projection, null-guarded collections, null-safe nested/flattening, init-only/object-initializer and record handling, ForgeMap on ctor params, ForgeIgnore.Side, extended immutable collection expectations, and other strengthened assertions.
Analyzer & integration tests
tests/FreakyKit.Forge.Analyzers.Tests/*, tests/.../IntegrationTests/*
Added StrictMapping, InvalidConverter, ForgeIgnoreSide analyzer tests and updated integration tests to expect null-safe flattening/collection code.
Test harness & references
tests/.../GeneratorTestBase.cs, tests/.../IntegrationTestBase.cs
Updated test compilation references to include System.Collections.Immutable, System.Linq (and optional collections/objectmodel) to support generated LINQ/immutable code.
Misc / manifests & csproj
*.csproj, src/.../AnalyzerReleases.Unshipped.md
Added benchmark project, updated analyzer unshipped rules with new IDs, and many new test/benchmark source files.

Sequence Diagram(s)

sequenceDiagram
  participant Dev as "Developer / CI"
  participant Generator as "ForgeGenerator"
  participant Analyzer as "ForgeAnalyzer"
  participant Diagnostics as "ForgeDiagnostics"
  participant Tests as "Tests / Benchmarks"

  Dev->>Generator: Build / run source generator
  Generator->>Generator: Classify method (Create/Update/CollectionProject), use sourceParamName
  Generator->>Generator: Emit null-safe nested/flattening and collection projections, handle init-only and immutable/read-only materialization
  Generator->>Analyzer: Provide symbol/attribute context for analysis
  Analyzer->>Diagnostics: Validate converters, honor ForgeIgnore.Side, evaluate StrictMapping -> emit FKF100/FKF101 or FKF110/FKF111 and FKF221
  Dev->>Tests: Run unit/integration tests and BenchmarkDotNet
  Tests->>Generator: Assert generated code (null-guards, collection projection, init-only)
  Tests->>Analyzer: Assert diagnostics (strict mapping, converter signatures, ignore-side)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • feat: Forged and ready to use #1 — Overlaps substantially with generator/analyzer/attribute/test surface changes (related design and implementation on mapping generation, diagnostics, and attributes).

Poem

🐇 I hopped through fields and nested trees,

Guarded nulls and mapped each breeze.
Init-only seeds in tidy rows,
Strict eyes catch where drift wind blows.
Benchmarks hum — the little rabbit knows.

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title describes a subset of the changeset (adding benchmarks for specific libraries) but misses the broader scope of the PR, which includes expanded documentation, new diagnostics, and multiple feature enhancements beyond just benchmarks. Consider a more comprehensive title that captures the full scope, such as 'feat: Add benchmarks and enhanced mapping features with strict mode and init-only support' or 'feat: Add benchmarking suite and expand generator capabilities with strict mapping and collection enhancements'.
Docstring Coverage ⚠️ Warning Docstring coverage is 3.80% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 feat/freaky-kit-forge

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 and usage tips.

coderabbitai[bot]

This comment was marked as outdated.

coderabbitai[bot]

This comment was marked as outdated.

@FreakyAli FreakyAli changed the title fix: added benchmarks for AutoMapper, Mapperly and Mapster fix: added benchmarks for AutoMapper, Mapperly, Facet and Mapster Mar 14, 2026
coderabbitai[bot]

This comment was marked as outdated.

coderabbitai[bot]

This comment was marked as outdated.

coderabbitai[bot]

This comment was marked as outdated.

@FreakyAli
Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 22, 2026

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

♻️ Duplicate comments (1)
tests/FreakyKit.Forge.Analyzers.Tests/ForgeIgnoreSideTests.cs (1)

35-59: ⚠️ Potential issue | 🟠 Major

These “does not suppress” tests still don’t exercise ForgeIgnoreSide.

Line [35] and Line [89] scenarios still never apply [ForgeIgnore(Side = ...)] to the member under test, so they can pass without validating side-specific suppression behavior.

✅ Suggested test adjustment
 public void ForgeIgnore_Side_Source_DoesNotSuppressFKF100()
 {
@@
                 public class Dest
                 {
                     public string Name { get; set; } = "";
-                    // Not ignored on dest side — will emit FKF100 since there's no source match
+                    // Source-side ignore must not suppress destination-side FKF100
+                    [ForgeIgnore(Side = ForgeIgnoreSide.Source)]
                     public int Unmatched { get; set; }
                 }
@@
 public void ForgeIgnore_Side_Destination_DoesNotSuppressFKF101()
 {
@@
                 public class Source
                 {
                     public string Name { get; set; } = "";
+                    [ForgeIgnore(Side = ForgeIgnoreSide.Destination)]
                     public string Extra { get; set; } = "";
                 }

Also applies to: 89-112

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/FreakyKit.Forge.Analyzers.Tests/ForgeIgnoreSideTests.cs` around lines
35 - 59, The tests named ForgeIgnore_Side_Source_DoesNotSuppressFKF100 (and the
similar case around lines 89–112) never actually apply the ForgeIgnore attribute
to the member under test, so they don't validate side-specific suppression;
update the test source string for ForgeIgnore_Side_Source_DoesNotSuppressFKF100
to add [ForgeIgnore(Side = Side.Source)] on the Dest.Unmatched member (and in
the other test add the opposite side, e.g., [ForgeIgnore(Side = Side.Dest)] on
the Source member) so the member under test carries the side-specific attribute,
then keep the AssertContainsDiagnostic(...) expectations (FKF100 should still
fire when the attribute is on the opposite side). Ensure you reference the test
methods ForgeIgnore_Side_Source_DoesNotSuppressFKF100 (and the corresponding
test around lines 89–112) when making the changes.
🧹 Nitpick comments (9)
src/FreakyKit.Forge/Attributes/ForgeMapAttribute.cs (1)

5-10: XML documentation should mention parameter applicability.

The AttributeUsage now includes AttributeTargets.Parameter, but the XML summary at lines 5-9 still only mentions "property or field." Consider updating the documentation to reflect that the attribute can also be applied to constructor parameters.

📝 Suggested documentation update
 /// <summary>
-/// Maps a property or field to a differently-named member on the counterpart type.
+/// Maps a property, field, or constructor parameter to a differently-named member on the counterpart type.
 /// When applied to a source member, the value specifies the destination member name.
 /// When applied to a destination member, the value specifies the source member name.
+/// When applied to a constructor parameter, the value specifies the source member name to use for that parameter.
 /// </summary>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/FreakyKit.Forge/Attributes/ForgeMapAttribute.cs` around lines 5 - 10,
Update the XML documentation on ForgeMapAttribute to reflect that it applies to
parameters as well as properties and fields: modify the summary for the
ForgeMapAttribute class (the attribute with AttributeUsage including
AttributeTargets.Parameter) to mention "property, field, or
constructor/parameter" and/or state that when applied to a constructor parameter
the value specifies the counterpart member name, mirroring the existing
descriptions for source/destination usage.
tests/FreakyKit.Forge.Analyzers.Tests/StrictMappingTests.cs (1)

91-94: Assert warning severity explicitly in non-strict mode tests.

These tests validate IDs but not that FKF100/FKF101 remain warnings. Add severity checks to guard against accidental severity regressions.

Proposed assertions
         AssertContainsDiagnostic(source, "FKF100");
+        AssertDiagnosticSeverity(source, "FKF100", DiagnosticSeverity.Warning);
         AssertNotContainsDiagnostic(source, "FKF110");
         AssertContainsDiagnostic(source, "FKF101");
+        AssertDiagnosticSeverity(source, "FKF101", DiagnosticSeverity.Warning);
         AssertNotContainsDiagnostic(source, "FKF111");

Also applies to: 113-116

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/FreakyKit.Forge.Analyzers.Tests/StrictMappingTests.cs` around lines 91
- 94, The test currently asserts presence/absence of diagnostic IDs but not the
severity; update the non-strict-mode assertions in StrictMappingTests.cs to
explicitly assert diagnostic severity for the relevant IDs (use
AssertContainsDiagnostic/AssertDiagnosticSeverity or the project's helper to
verify FKF100 and FKF101 are reported as Warning and that FKF110 is not reported
as Error), and add equivalent severity assertions in the other affected test
block (the one that currently checks FKF100/FKF110) so the tests fail if
severity changes accidentally.
tests/FreakyKit.Forge.Generator.Tests/ForgeIgnoreSideGeneratorTests.cs (1)

140-142: Tighten the assertion to the concrete assignment.

Assert.Contains("InternalId", generated) is too broad and can pass on unrelated occurrences. Assert the exact mapping line instead.

Proposed test assertion hardening
-        Assert.Contains("source.AltId", generated);
-        Assert.Contains("InternalId", generated);
+        Assert.Contains("__result.InternalId = source.AltId", generated);
+        Assert.DoesNotContain("__result.InternalId = source.InternalId", generated);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/FreakyKit.Forge.Generator.Tests/ForgeIgnoreSideGeneratorTests.cs`
around lines 140 - 142, The test currently uses a broad
Assert.Contains("InternalId", generated) which can match unrelated occurrences;
update the assertion in ForgeIgnoreSideGeneratorTests to check the concrete
mapping line in the generated string (use the `generated` variable) — assert the
exact assignment text for InternalId (e.g., the exact mapping "InternalId =
source.InternalId") so the test only passes when the proper mapping is emitted.
tests/FreakyKit.Forge.Generator.Tests/CollectionProjectGeneratorTests.cs (1)

57-59: Use a stricter assertion than return source.

This check can false-pass on unrelated methods. Assert the projected/materialized form you actually require.

Example tightening
-        // The projection method body should be a single return — no construction
-        Assert.Contains("return source", generated);
+        // Projection should be direct and materialized, without extra object construction
+        Assert.Contains("return source.Select(x => ToDto(x)).ToList();", generated);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/FreakyKit.Forge.Generator.Tests/CollectionProjectGeneratorTests.cs`
around lines 57 - 59, Replace the weak substring check Assert.Contains("return
source", generated) in CollectionProjectGeneratorTests with a stricter assertion
that matches the exact projection/materialization syntax you expect (e.g., the
precise Select(...) and ToList()/ToArray() form). Update the assertion to
inspect the 'generated' string for the full projection expression (or use
Assert.Equal/Assert.Contains with the full expected snippet) so it only passes
when the method body returns the exact projected result rather than any
unrelated "return source".
benchmarks/FreakyKit.Forge.Benchmarks/Benchmarks/ThroughputBenchmark.cs (1)

99-107: Facet benchmark returns different type than other benchmarks.

The FacetBench method returns MediumFacetDto[] while all other benchmarks return MediumDestination[]. This is likely intentional due to Facet's type generation requirements, but it may affect comparison fairness if MediumFacetDto has a different structure. Consider adding a comment explaining this discrepancy for clarity.

💡 Suggested documentation
     [Benchmark(Description = "Facet")]
     [BenchmarkCategory("Throughput")]
+    // Facet requires its own DTO type (MediumFacetDto) due to its facet generation approach
     public MediumFacetDto[] FacetBench()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@benchmarks/FreakyKit.Forge.Benchmarks/Benchmarks/ThroughputBenchmark.cs`
around lines 99 - 107, The FacetBench method returns MediumFacetDto[] while
other benchmarks return MediumDestination[]; add a brief inline comment above
the FacetBench method (or inside the method) explaining why it returns
MediumFacetDto (e.g., Facet generates a different DTO type required for the
facet mapping and is intentionally different for fair/expected comparisons) so
readers and reviewers understand the discrepancy and its intent; reference the
FacetBench method and the types MediumFacetDto and MediumDestination in the
comment for clarity.
benchmarks/FreakyKit.Forge.Benchmarks/Forges/HandWrittenMappers.cs (1)

90-99: Consider adding null checks for nested property access in MapFlattening.

The MapFlattening method directly accesses source.HomeAddress.Street etc. without null guards. If source.HomeAddress is null, this will throw a NullReferenceException. The Forge-generated code uses null-conditional access (source.HomeAddress?.Street), so the hand-written baseline may behave differently when source contains nulls.

If the benchmark fixtures always provide non-null HomeAddress, this is fine. Otherwise, consider aligning null-handling with the generated code.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@benchmarks/FreakyKit.Forge.Benchmarks/Forges/HandWrittenMappers.cs` around
lines 90 - 99, MapFlattening currently dereferences source.HomeAddress directly
which can throw if HomeAddress is null; update MapFlattening to guard nested
access (e.g., use null checks or null-conditional semantics when reading
HomeAddress.Street/City/State/ZipCode) and ensure it assigns nulls to
FlatteningDestination fields when HomeAddress is null so behavior matches the
Forge-generated mapping; check FlatteningSource, HomeAddress, and
FlatteningDestination property names when applying the guards.
src/FreakyKit.Forge.Generator/ForgeGenerator.cs (1)

990-994: Init-only properties with IgnoreIfNull are silently skipped.

Lines 990-993 filter out init-only assignments that have IgnoreIfNull set, as object initializers cannot use conditional assignment. This is correct behavior, but it may surprise users who expect the property to be conditionally set.

Consider emitting a diagnostic (info-level) when an init-only property with IgnoreIfNull=true is encountered, so users understand why their conditional mapping isn't being applied.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/FreakyKit.Forge.Generator/ForgeGenerator.cs` around lines 990 - 994, When
computing init-only assignments you currently filter out those with IgnoreIfNull
into skippedInitOnly; instead, after building skippedInitOnly (from
method.Assignments where a.IsInitOnly && a.IgnoreIfNull) emit an informational
diagnostic for each skipped assignment so users know the init-only property was
intentionally not mapped; use the generator's diagnostic/logging API (same place
other diagnostics are produced) and include the target member name and that
IgnoreIfNull prevented object-initializer assignment, referencing the
variables/members initOnlyAssignments, skippedInitOnly, method.Assignments and
the IgnoreIfNull flag to locate where to add the diagnostic emission.
src/FreakyKit.Forge.Analyzers/ForgeAnalyzer.cs (2)

958-959: Default KeyValuePair check may not work as expected.

FirstOrDefault on NamedArguments returns a default KeyValuePair<string, TypedConstant> when not found, where Key is null (not an empty string). However, the logic is inverted—when Side isn't specified, you want side = 0 (both sides). If Key is null, the ternary defaults to 0, which is correct.

That said, checking sideArg.Key != null is fragile. Consider using a more explicit pattern:

♻️ Suggested refinement for clarity
-        var sideArg = attr.NamedArguments.FirstOrDefault(a => a.Key == "Side");
-        int side = sideArg.Key != null && sideArg.Value.Value is int sv ? sv : 0;
+        int side = 0;
+        foreach (var arg in attr.NamedArguments)
+        {
+            if (arg.Key == "Side" && arg.Value.Value is int sv)
+            {
+                side = sv;
+                break;
+            }
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/FreakyKit.Forge.Analyzers/ForgeAnalyzer.cs` around lines 958 - 959, The
current check uses FirstOrDefault and then tests sideArg.Key != null which is
fragile; instead find the "Side" entry explicitly and only accept its value if
the found Key equals "Side". Update the attr.NamedArguments lookup so that you
get sideArg via attr.NamedArguments.FirstOrDefault(a => a.Key == "Side") and
then set int side = (sideArg.Key == "Side" && sideArg.Value.Value is int sv) ?
sv : 0; ensuring you reference the variables sideArg, attr.NamedArguments and
side to locate and fix the logic.

162-168: Consider validating the attribute's containing namespace.

The check a.AttributeClass?.Name == "ForgeConverterAttribute" matches by name only. If another library defines a ForgeConverterAttribute, this could produce false positives. Consider also verifying the containing namespace matches FreakyKit.Forge.

♻️ Suggested refinement
-            bool hasConverterAttr = member.GetAttributes()
-                .Any(a => a.AttributeClass?.Name == "ForgeConverterAttribute" ||
-                           a.AttributeClass?.Name == "ForgeConverter");
+            bool hasConverterAttr = member.GetAttributes()
+                .Any(a => (a.AttributeClass?.Name == "ForgeConverterAttribute" ||
+                           a.AttributeClass?.Name == "ForgeConverter") &&
+                          a.AttributeClass?.ContainingNamespace?.ToDisplayString() == "FreakyKit.Forge");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/FreakyKit.Forge.Analyzers/ForgeAnalyzer.cs` around lines 162 - 168, The
current attribute check in the loop over
type.GetMembers().OfType<IMethodSymbol>() uses only a.Name ==
"ForgeConverterAttribute" or "ForgeConverter", which can collide with similarly
named attributes; update the predicate on member.GetAttributes() to also verify
a.AttributeClass?.ContainingNamespace?.ToDisplayString() (or walk
ContainingNamespace) equals "FreakyKit.Forge" (or starts with that namespace if
sub-namespaces are allowed), and keep the null-safety checks; ensure the code
that uses hasConverterAttr still references the same symbol names
(ForgeConverterAttribute / ForgeConverter) but only sets hasConverterAttr true
when both name and namespace match.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@benchmarks/FreakyKit.Forge.Benchmarks/Benchmarks/FlatteningMapBenchmark.cs`:
- Around line 13-14: The benchmark suite incorrectly groups Facet (the
non-flattening mapper) under the "Flattening" category; update the
BenchmarkCategory attributes so that any benchmark methods or the
FlatteningMapBenchmark class referencing Facet are moved out of "Flattening"
into a new category (e.g., "NonFlattening" or "Facet") and ensure the methods
that actually perform flattened mappings remain under "Flattening"; locate
references to Facet in FlatteningMapBenchmark (and the other occurrences around
the same area noted near lines 64-67) and change their BenchmarkCategory
annotation accordingly so rankings don't mix non-equivalent categories.

In `@benchmarks/FreakyKit.Forge.Benchmarks/Forges/MapsterConfig.cs`:
- Around line 7-12: The Configure() method reads/writes the static _configured
flag without synchronization; make it thread-safe by implementing double-checked
locking: declare a private static readonly object (e.g., _configureLock) and
mark _configured as volatile, then in Configure() first if (_configured) return;
lock(_configureLock) { if (_configured) return; perform the
TypeAdapterConfig.GlobalSettings setup and call
TypeAdapterConfig.GlobalSettings.Compile(); set _configured = true; } so the
configuration block runs exactly once even under concurrent BenchmarkDotNet
GlobalSetup calls.

In `@docs/future-plans.md`:
- Line 336: Change the sentence "Update reverse: Should reverse also support
update methods (void return, 2 params)?" to the more natural wording "Should
reverse support update methods as well?" — locate the line containing the
"Update reverse" bullet/question and replace the phrasing accordingly (preserve
the parenthetical detail "(void return, 2 params)" if desired).

In `@docs/generator.md`:
- Around line 41-43: The documentation example shows a signature "public static
partial List<PersonDto> ToDtos(List<Person> source)" but the generated example
returns null, causing a mismatch; update the example to use a nullable return
and/or parameter to align nullability (e.g., make the return type
List<PersonDto>? and/or parameter List<Person>? as appropriate) so the signature
of ToDtos matches the illustrated behavior of returning null when source is
null; ensure the unique symbols ToDtos, List<PersonDto> and List<Person> are
updated consistently in the doc example.

In `@README.md`:
- Line 89: Update the diagnostics count in the README for the Analyzers package:
change the text "34 diagnostics" to "35 diagnostics" so it matches the rest of
the document and the diagnostics table (which now includes FKF221); locate the
occurrence of the string "34 diagnostics" in the README and replace it with "35
diagnostics" to resolve the inconsistency.

In `@tests/FreakyKit.Forge.Analyzers.Tests/ForgeIgnoreSideTests.cs`:
- Around line 115-139: The test ForgeIgnore_Side_Both_SuppressesBothWarnings
currently only exercises a source-side unmatched member and thus never triggers
FKF100; update the test's source string to add an unmatched destination member
on class Dest decorated with [ForgeIgnore(Side = ForgeIgnoreSide.Both)] (so
both-source-and-dest ignore is exercised), keeping the existing
AssertNotContainsDiagnostic checks for "FKF101" and "FKF100" to verify both
warnings are suppressed; refer to the ForgeIgnore attribute and
ForgeIgnoreSide.Both, the Dest class and the
ForgeIgnore_Side_Both_SuppressesBothWarnings test to locate where to add the
unmatched destination property.

In `@tests/FreakyKit.Forge.Generator.Tests/ForgeMapCtorGeneratorTests.cs`:
- Around line 63-89: The test ForgeMap_OnCtorParam_NullableSourceMember
currently asserts the generator emitted a direct .Value access
("source.Score.Value") which forces a null-unsafe mapping; update the assertion
to expect a null-safe access (e.g. "source.Score.GetValueOrDefault()" or the
equivalent null-coalescing expression) for the generated output held in variable
generated, and remove the negative assertion about diagnostic "FKF501" (or
alternatively replace both assertions with an AssertContains for the diagnostic
"FKF501" if you choose to require a diagnostic instead); reference the test
method name ForgeMap_OnCtorParam_NullableSourceMember and the generated variable
to locate where to change the assertions.

---

Duplicate comments:
In `@tests/FreakyKit.Forge.Analyzers.Tests/ForgeIgnoreSideTests.cs`:
- Around line 35-59: The tests named
ForgeIgnore_Side_Source_DoesNotSuppressFKF100 (and the similar case around lines
89–112) never actually apply the ForgeIgnore attribute to the member under test,
so they don't validate side-specific suppression; update the test source string
for ForgeIgnore_Side_Source_DoesNotSuppressFKF100 to add [ForgeIgnore(Side =
Side.Source)] on the Dest.Unmatched member (and in the other test add the
opposite side, e.g., [ForgeIgnore(Side = Side.Dest)] on the Source member) so
the member under test carries the side-specific attribute, then keep the
AssertContainsDiagnostic(...) expectations (FKF100 should still fire when the
attribute is on the opposite side). Ensure you reference the test methods
ForgeIgnore_Side_Source_DoesNotSuppressFKF100 (and the corresponding test around
lines 89–112) when making the changes.

---

Nitpick comments:
In `@benchmarks/FreakyKit.Forge.Benchmarks/Benchmarks/ThroughputBenchmark.cs`:
- Around line 99-107: The FacetBench method returns MediumFacetDto[] while other
benchmarks return MediumDestination[]; add a brief inline comment above the
FacetBench method (or inside the method) explaining why it returns
MediumFacetDto (e.g., Facet generates a different DTO type required for the
facet mapping and is intentionally different for fair/expected comparisons) so
readers and reviewers understand the discrepancy and its intent; reference the
FacetBench method and the types MediumFacetDto and MediumDestination in the
comment for clarity.

In `@benchmarks/FreakyKit.Forge.Benchmarks/Forges/HandWrittenMappers.cs`:
- Around line 90-99: MapFlattening currently dereferences source.HomeAddress
directly which can throw if HomeAddress is null; update MapFlattening to guard
nested access (e.g., use null checks or null-conditional semantics when reading
HomeAddress.Street/City/State/ZipCode) and ensure it assigns nulls to
FlatteningDestination fields when HomeAddress is null so behavior matches the
Forge-generated mapping; check FlatteningSource, HomeAddress, and
FlatteningDestination property names when applying the guards.

In `@src/FreakyKit.Forge.Analyzers/ForgeAnalyzer.cs`:
- Around line 958-959: The current check uses FirstOrDefault and then tests
sideArg.Key != null which is fragile; instead find the "Side" entry explicitly
and only accept its value if the found Key equals "Side". Update the
attr.NamedArguments lookup so that you get sideArg via
attr.NamedArguments.FirstOrDefault(a => a.Key == "Side") and then set int side =
(sideArg.Key == "Side" && sideArg.Value.Value is int sv) ? sv : 0; ensuring you
reference the variables sideArg, attr.NamedArguments and side to locate and fix
the logic.
- Around line 162-168: The current attribute check in the loop over
type.GetMembers().OfType<IMethodSymbol>() uses only a.Name ==
"ForgeConverterAttribute" or "ForgeConverter", which can collide with similarly
named attributes; update the predicate on member.GetAttributes() to also verify
a.AttributeClass?.ContainingNamespace?.ToDisplayString() (or walk
ContainingNamespace) equals "FreakyKit.Forge" (or starts with that namespace if
sub-namespaces are allowed), and keep the null-safety checks; ensure the code
that uses hasConverterAttr still references the same symbol names
(ForgeConverterAttribute / ForgeConverter) but only sets hasConverterAttr true
when both name and namespace match.

In `@src/FreakyKit.Forge.Generator/ForgeGenerator.cs`:
- Around line 990-994: When computing init-only assignments you currently filter
out those with IgnoreIfNull into skippedInitOnly; instead, after building
skippedInitOnly (from method.Assignments where a.IsInitOnly && a.IgnoreIfNull)
emit an informational diagnostic for each skipped assignment so users know the
init-only property was intentionally not mapped; use the generator's
diagnostic/logging API (same place other diagnostics are produced) and include
the target member name and that IgnoreIfNull prevented object-initializer
assignment, referencing the variables/members initOnlyAssignments,
skippedInitOnly, method.Assignments and the IgnoreIfNull flag to locate where to
add the diagnostic emission.

In `@src/FreakyKit.Forge/Attributes/ForgeMapAttribute.cs`:
- Around line 5-10: Update the XML documentation on ForgeMapAttribute to reflect
that it applies to parameters as well as properties and fields: modify the
summary for the ForgeMapAttribute class (the attribute with AttributeUsage
including AttributeTargets.Parameter) to mention "property, field, or
constructor/parameter" and/or state that when applied to a constructor parameter
the value specifies the counterpart member name, mirroring the existing
descriptions for source/destination usage.

In `@tests/FreakyKit.Forge.Analyzers.Tests/StrictMappingTests.cs`:
- Around line 91-94: The test currently asserts presence/absence of diagnostic
IDs but not the severity; update the non-strict-mode assertions in
StrictMappingTests.cs to explicitly assert diagnostic severity for the relevant
IDs (use AssertContainsDiagnostic/AssertDiagnosticSeverity or the project's
helper to verify FKF100 and FKF101 are reported as Warning and that FKF110 is
not reported as Error), and add equivalent severity assertions in the other
affected test block (the one that currently checks FKF100/FKF110) so the tests
fail if severity changes accidentally.

In `@tests/FreakyKit.Forge.Generator.Tests/CollectionProjectGeneratorTests.cs`:
- Around line 57-59: Replace the weak substring check Assert.Contains("return
source", generated) in CollectionProjectGeneratorTests with a stricter assertion
that matches the exact projection/materialization syntax you expect (e.g., the
precise Select(...) and ToList()/ToArray() form). Update the assertion to
inspect the 'generated' string for the full projection expression (or use
Assert.Equal/Assert.Contains with the full expected snippet) so it only passes
when the method body returns the exact projected result rather than any
unrelated "return source".

In `@tests/FreakyKit.Forge.Generator.Tests/ForgeIgnoreSideGeneratorTests.cs`:
- Around line 140-142: The test currently uses a broad
Assert.Contains("InternalId", generated) which can match unrelated occurrences;
update the assertion in ForgeIgnoreSideGeneratorTests to check the concrete
mapping line in the generated string (use the `generated` variable) — assert the
exact assignment text for InternalId (e.g., the exact mapping "InternalId =
source.InternalId") so the test only passes when the proper mapping is emitted.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e24c3912-a2b2-4ba0-964b-de24239d8bb0

📥 Commits

Reviewing files that changed from the base of the PR and between 39d5133 and f942d10.

📒 Files selected for processing (59)
  • README.md
  • benchmarks/FreakyKit.Forge.Benchmarks/Benchmarks/CollectionMapBenchmark.cs
  • benchmarks/FreakyKit.Forge.Benchmarks/Benchmarks/DeepGraphMapBenchmark.cs
  • benchmarks/FreakyKit.Forge.Benchmarks/Benchmarks/EcommerceOrderBenchmark.cs
  • benchmarks/FreakyKit.Forge.Benchmarks/Benchmarks/FlatteningMapBenchmark.cs
  • benchmarks/FreakyKit.Forge.Benchmarks/Benchmarks/MediumMapBenchmark.cs
  • benchmarks/FreakyKit.Forge.Benchmarks/Benchmarks/NestedMapBenchmark.cs
  • benchmarks/FreakyKit.Forge.Benchmarks/Benchmarks/NullableEntityBenchmark.cs
  • benchmarks/FreakyKit.Forge.Benchmarks/Benchmarks/SimpleMapBenchmark.cs
  • benchmarks/FreakyKit.Forge.Benchmarks/Benchmarks/ThroughputBenchmark.cs
  • benchmarks/FreakyKit.Forge.Benchmarks/Benchmarks/UpdateMapBenchmark.cs
  • benchmarks/FreakyKit.Forge.Benchmarks/Forges/AutoMapperConfig.cs
  • benchmarks/FreakyKit.Forge.Benchmarks/Forges/BenchmarkForges.cs
  • benchmarks/FreakyKit.Forge.Benchmarks/Forges/FacetConfig.cs
  • benchmarks/FreakyKit.Forge.Benchmarks/Forges/HandWrittenMappers.cs
  • benchmarks/FreakyKit.Forge.Benchmarks/Forges/MapperlyMappers.cs
  • benchmarks/FreakyKit.Forge.Benchmarks/Forges/MapsterConfig.cs
  • benchmarks/FreakyKit.Forge.Benchmarks/FreakyKit.Forge.Benchmarks.csproj
  • benchmarks/FreakyKit.Forge.Benchmarks/Models/Destination.cs
  • benchmarks/FreakyKit.Forge.Benchmarks/Models/Source.cs
  • benchmarks/FreakyKit.Forge.Benchmarks/Program.cs
  • docs/attributes.md
  • docs/benchmarks.md
  • docs/diagnostics.md
  • docs/future-plans.md
  • docs/generator.md
  • src/FreakyKit.Forge.Analyzers/ForgeAnalyzer.cs
  • src/FreakyKit.Forge.Diagnostics/AnalyzerReleases.Unshipped.md
  • src/FreakyKit.Forge.Diagnostics/ForgeDiagnostics.cs
  • src/FreakyKit.Forge.Generator/ForgeGenerator.cs
  • src/FreakyKit.Forge.Generator/Models/ForgeMethodKind.cs
  • src/FreakyKit.Forge.Generator/Models/ForgeMethodModel.cs
  • src/FreakyKit.Forge.Generator/Models/MemberAssignmentModel.cs
  • src/FreakyKit.Forge/Attributes/ForgeIgnoreAttribute.cs
  • src/FreakyKit.Forge/Attributes/ForgeMapAttribute.cs
  • src/FreakyKit.Forge/Attributes/ForgeMethodAttribute.cs
  • tests/FreakyKit.Forge.Analyzers.Tests/ForgeIgnoreSideTests.cs
  • tests/FreakyKit.Forge.Analyzers.Tests/InvalidConverterTests.cs
  • tests/FreakyKit.Forge.Analyzers.Tests/StrictMappingTests.cs
  • tests/FreakyKit.Forge.Generator.Tests/CollectionGeneratorTests.cs
  • tests/FreakyKit.Forge.Generator.Tests/CollectionProjectGeneratorTests.cs
  • tests/FreakyKit.Forge.Generator.Tests/ConverterGeneratorTests.cs
  • tests/FreakyKit.Forge.Generator.Tests/CrossFeatureGeneratorTests.cs
  • tests/FreakyKit.Forge.Generator.Tests/EnumGeneratorTests.cs
  • tests/FreakyKit.Forge.Generator.Tests/ExplicitModeGeneratorTests.cs
  • tests/FreakyKit.Forge.Generator.Tests/ExtendedCollectionGeneratorTests.cs
  • tests/FreakyKit.Forge.Generator.Tests/FlatteningGeneratorTests.cs
  • tests/FreakyKit.Forge.Generator.Tests/ForgeIgnoreSideGeneratorTests.cs
  • tests/FreakyKit.Forge.Generator.Tests/ForgeMapCtorGeneratorTests.cs
  • tests/FreakyKit.Forge.Generator.Tests/ForgeMapGeneratorTests.cs
  • tests/FreakyKit.Forge.Generator.Tests/GeneratorSuccessTests.cs
  • tests/FreakyKit.Forge.Generator.Tests/GeneratorTestBase.cs
  • tests/FreakyKit.Forge.Generator.Tests/HooksGeneratorTests.cs
  • tests/FreakyKit.Forge.Generator.Tests/InitOnlyGeneratorTests.cs
  • tests/FreakyKit.Forge.Generator.Tests/NullSafeNestedGeneratorTests.cs
  • tests/FreakyKit.Forge.Generator.Tests/NullableGeneratorTests.cs
  • tests/FreakyKit.Forge.Integration.Tests/CollectionIntegrationTests.cs
  • tests/FreakyKit.Forge.Integration.Tests/FlatteningIntegrationTests.cs
  • tests/FreakyKit.Forge.Integration.Tests/IntegrationTestBase.cs

Comment thread benchmarks/FreakyKit.Forge.Benchmarks/Benchmarks/FlatteningMapBenchmark.cs Outdated
Comment thread benchmarks/FreakyKit.Forge.Benchmarks/Forges/MapsterConfig.cs
Comment thread docs/future-plans.md
Comment thread docs/generator.md Outdated
Comment thread README.md
Comment thread tests/FreakyKit.Forge.Analyzers.Tests/ForgeIgnoreSideTests.cs
Comment thread tests/FreakyKit.Forge.Generator.Tests/ForgeMapCtorGeneratorTests.cs
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (2)
tests/FreakyKit.Forge.Analyzers.Tests/ForgeIgnoreSideTests.cs (2)

34-59: ⚠️ Potential issue | 🟠 Major

Test does not exercise ForgeIgnoreSide — passes even if side-filter logic is broken.

Unmatched has no [ForgeIgnore] attribute, so this test merely confirms that an unignored member emits FKF100. To actually verify that ForgeIgnoreSide.Source does not suppress FKF100, apply [ForgeIgnore(Side = ForgeIgnoreSide.Source)] to Unmatched and assert FKF100 still appears.

✅ Suggested fix
             public class Dest
             {
                 public string Name { get; set; } = "";
-                // Not ignored on dest side — will emit FKF100 since there's no source match
+                // Source-side ignore must NOT suppress destination-side FKF100
+                [ForgeIgnore(Side = ForgeIgnoreSide.Source)]
                 public int Unmatched { get; set; }
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/FreakyKit.Forge.Analyzers.Tests/ForgeIgnoreSideTests.cs` around lines
34 - 59, The test ForgeIgnore_Side_Source_DoesNotSuppressFKF100 currently
doesn't exercise ForgeIgnoreSide because the Unmatched property lacks a
ForgeIgnore attribute; update the test source so the Unmatched property is
annotated with [ForgeIgnore(Side = ForgeIgnoreSide.Source)] and keep the
assertion AssertContainsDiagnostic(source, "FKF100") so the test verifies that a
Source-side ignore does not suppress the FKF100 diagnostic on the
destination-only member.

88-112: ⚠️ Potential issue | 🟠 Major

Test does not exercise ForgeIgnoreSide — passes even if side-filter logic is broken.

Extra has no [ForgeIgnore] attribute, so this test only confirms that an unignored source member emits FKF101. To verify that ForgeIgnoreSide.Destination does not suppress FKF101, apply [ForgeIgnore(Side = ForgeIgnoreSide.Destination)] to Extra and assert FKF101 still appears.

✅ Suggested fix
             public class Source
             {
                 public string Name { get; set; } = "";
+                [ForgeIgnore(Side = ForgeIgnoreSide.Destination)]
                 public string Extra { get; set; } = "";
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/FreakyKit.Forge.Analyzers.Tests/ForgeIgnoreSideTests.cs` around lines
88 - 112, Update the test ForgeIgnore_Side_Destination_DoesNotSuppressFKF101 so
it actually exercises ForgeIgnoreSide by annotating the Source.Extra property
with [ForgeIgnore(Side = ForgeIgnoreSide.Destination)]; keep the rest of the
test the same and continue to assert FKF101 via AssertContainsDiagnostic(source,
"FKF101") to confirm Destination-side ignores do not suppress the
missing-destination diagnostic.
🧹 Nitpick comments (3)
tests/FreakyKit.Forge.Analyzers.Tests/ForgeIgnoreSideTests.cs (2)

61-86: Test only verifies suppression, not that the other diagnostic is unaffected.

Similar to the first test — the name claims NotFKF101 but only asserts FKF100 absence. Consider adding a source member without a destination match to verify FKF101 still fires.

🧪 Suggested enhancement
-            public class Source { public string Name { get; set; } = ""; }
+            public class Source
+            {
+                public string Name { get; set; } = "";
+                public string Extra { get; set; } = "";  // No dest match → FKF101 should still fire
+            }
             public class Dest
             {
                 public string Name { get; set; } = "";
                 [ForgeIgnore(Side = ForgeIgnoreSide.Destination)]
                 public int ComputedScore { get; set; }
             }
             """;

         // Dest side ignored → no FKF100 for ComputedScore
         AssertNotContainsDiagnostic(source, "FKF100");
+        // FKF101 should still fire for unmatched source member
+        AssertContainsDiagnostic(source, "FKF101");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/FreakyKit.Forge.Analyzers.Tests/ForgeIgnoreSideTests.cs` around lines
61 - 86, The test ForgeIgnore_Side_Destination_SuppressesFKF100_NotFKF101
currently only asserts FKF100 is absent; to validate FKF101 is unaffected add an
unmapped source member (e.g., in class Source add a property that has no
matching property in Dest) and then assert the analyzer emits FKF101 using
AssertContainsDiagnostic(source, "FKF101") while keeping the existing
AssertNotContainsDiagnostic for FKF100; this ensures the test still references
the same types (Source, Dest, ComputedScore) and the same test method name
ForgeIgnore_Side_Destination_SuppressesFKF100_NotFKF101.

7-32: Test only verifies suppression, not that the other diagnostic is unaffected.

The test name claims NotFKF100 but only asserts that FKF101 is not present. Consider adding a positive assertion that FKF100 is still emitted for a different unmatched destination member to fully validate the side-filtering behavior.

🧪 Suggested enhancement
             public class Source
             {
                 public string Name { get; set; } = "";
                 [ForgeIgnore(Side = ForgeIgnoreSide.Source)]
                 public string InternalId { get; set; } = "";
             }
-            public class Dest { public string Name { get; set; } = ""; }
+            public class Dest
+            {
+                public string Name { get; set; } = "";
+                public int Unmatched { get; set; }  // No source match → FKF100 should still fire
+            }
             """;

         // Source side ignored → no FKF101 for InternalId
         AssertNotContainsDiagnostic(source, "FKF101");
+        // FKF100 should still fire for unmatched dest member
+        AssertContainsDiagnostic(source, "FKF100");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/FreakyKit.Forge.Analyzers.Tests/ForgeIgnoreSideTests.cs` around lines 7
- 32, The test ForgeIgnore_Side_Source_SuppressesFKF101_NotFKF100 currently only
asserts FKF101 is absent; update it to also assert FKF100 is still emitted for
an unmatched destination member to validate side-filtering. After the existing
AssertNotContainsDiagnostic(source, "FKF101"), add a positive assertion using
AssertContainsDiagnostic or the test helper used elsewhere to check that
"FKF100" is present for the mapping (e.g., targeting the Dest member mismatch),
referencing the test's source string and keeping the test method name
ForgeIgnore_Side_Source_SuppressesFKF101_NotFKF100 and existing class names
(Source, Dest) so the test ensures FKF101 is suppressed while FKF100 still
occurs.
README.md (1)

280-280: Minor grammar: sentence fragment missing subject.

"Can be placed on properties, fields, or constructor parameters" is a sentence fragment. Consider adding a subject for completeness.

📝 Suggested fix
-Use `[ForgeMap]` to map members with different names. Can be placed on properties, fields, or constructor parameters:
+Use `[ForgeMap]` to map members with different names. It can be placed on properties, fields, or constructor parameters:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` at line 280, The sentence fragment "Can be placed on properties,
fields, or constructor parameters" should include a subject; update the README
text near the [ForgeMap] description so it reads something like "It can be
placed on properties, fields, or constructor parameters" or "The [ForgeMap]
attribute can be placed on properties, fields, or constructor parameters" to
make a complete sentence and preserve the existing meaning.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@tests/FreakyKit.Forge.Analyzers.Tests/ForgeIgnoreSideTests.cs`:
- Around line 34-59: The test ForgeIgnore_Side_Source_DoesNotSuppressFKF100
currently doesn't exercise ForgeIgnoreSide because the Unmatched property lacks
a ForgeIgnore attribute; update the test source so the Unmatched property is
annotated with [ForgeIgnore(Side = ForgeIgnoreSide.Source)] and keep the
assertion AssertContainsDiagnostic(source, "FKF100") so the test verifies that a
Source-side ignore does not suppress the FKF100 diagnostic on the
destination-only member.
- Around line 88-112: Update the test
ForgeIgnore_Side_Destination_DoesNotSuppressFKF101 so it actually exercises
ForgeIgnoreSide by annotating the Source.Extra property with [ForgeIgnore(Side =
ForgeIgnoreSide.Destination)]; keep the rest of the test the same and continue
to assert FKF101 via AssertContainsDiagnostic(source, "FKF101") to confirm
Destination-side ignores do not suppress the missing-destination diagnostic.

---

Nitpick comments:
In `@README.md`:
- Line 280: The sentence fragment "Can be placed on properties, fields, or
constructor parameters" should include a subject; update the README text near
the [ForgeMap] description so it reads something like "It can be placed on
properties, fields, or constructor parameters" or "The [ForgeMap] attribute can
be placed on properties, fields, or constructor parameters" to make a complete
sentence and preserve the existing meaning.

In `@tests/FreakyKit.Forge.Analyzers.Tests/ForgeIgnoreSideTests.cs`:
- Around line 61-86: The test
ForgeIgnore_Side_Destination_SuppressesFKF100_NotFKF101 currently only asserts
FKF100 is absent; to validate FKF101 is unaffected add an unmapped source member
(e.g., in class Source add a property that has no matching property in Dest) and
then assert the analyzer emits FKF101 using AssertContainsDiagnostic(source,
"FKF101") while keeping the existing AssertNotContainsDiagnostic for FKF100;
this ensures the test still references the same types (Source, Dest,
ComputedScore) and the same test method name
ForgeIgnore_Side_Destination_SuppressesFKF100_NotFKF101.
- Around line 7-32: The test ForgeIgnore_Side_Source_SuppressesFKF101_NotFKF100
currently only asserts FKF101 is absent; update it to also assert FKF100 is
still emitted for an unmatched destination member to validate side-filtering.
After the existing AssertNotContainsDiagnostic(source, "FKF101"), add a positive
assertion using AssertContainsDiagnostic or the test helper used elsewhere to
check that "FKF100" is present for the mapping (e.g., targeting the Dest member
mismatch), referencing the test's source string and keeping the test method name
ForgeIgnore_Side_Source_SuppressesFKF101_NotFKF100 and existing class names
(Source, Dest) so the test ensures FKF101 is suppressed while FKF100 still
occurs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b830c68d-f0e0-402b-97bf-bcf084e1a54a

📥 Commits

Reviewing files that changed from the base of the PR and between f942d10 and 6132259.

📒 Files selected for processing (4)
  • README.md
  • benchmarks/FreakyKit.Forge.Benchmarks/Benchmarks/FlatteningMapBenchmark.cs
  • docs/generator.md
  • tests/FreakyKit.Forge.Analyzers.Tests/ForgeIgnoreSideTests.cs
🚧 Files skipped from review as they are similar to previous changes (2)
  • benchmarks/FreakyKit.Forge.Benchmarks/Benchmarks/FlatteningMapBenchmark.cs
  • docs/generator.md

@FreakyAli
Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 24, 2026

✅ Actions performed

Full review triggered.

@FreakyAli FreakyAli merged commit 4b8c5a8 into master Mar 24, 2026
4 checks passed
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.

2 participants