Skip to content

Add DiagnosticAnalyzer for Arbiter.Mapping.Generators to validate mapper classes#382

Open
pwelter34 wants to merge 5 commits intomainfrom
claude/mapping-diagnostic-analyzer-F7SLG
Open

Add DiagnosticAnalyzer for Arbiter.Mapping.Generators to validate mapper classes#382
pwelter34 wants to merge 5 commits intomainfrom
claude/mapping-diagnostic-analyzer-F7SLG

Conversation

@pwelter34
Copy link
Copy Markdown
Member

Introduces a Roslyn DiagnosticAnalyzer (separate from the source generator pipeline)
that validates [GenerateMapper] classes at compile time, preserving generator cache:

  • ARB0001: Mapper class must be partial
  • ARB0002: Mapper class must inherit MapperProfile<TSource, TDestination>
  • ARB0003: ConfigureMapping contains unsupported statements (not mapping calls)
  • ARB0004: Destination property in Property() does not exist on destination type
  • ARB0005: Duplicate destination property mapping
  • ARB0006: Unrecognized mapping call pattern (not From/Value/Ignore)
  • ARB0007: Source property in From() does not exist on source type

https://claude.ai/code/session_01SCR7bdSkrCSmNazN911Y2e

claude added 4 commits April 3, 2026 03:10
…per classes

Introduces a Roslyn DiagnosticAnalyzer (separate from the source generator pipeline)
that validates [GenerateMapper] classes at compile time, preserving generator cache:

- ARB0001: Mapper class must be partial
- ARB0002: Mapper class must inherit MapperProfile<TSource, TDestination>
- ARB0003: ConfigureMapping contains unsupported statements (not mapping calls)
- ARB0004: Destination property in Property() does not exist on destination type
- ARB0005: Duplicate destination property mapping
- ARB0006: Unrecognized mapping call pattern (not From/Value/Ignore)
- ARB0007: Source property in From() does not exist on source type

https://claude.ai/code/session_01SCR7bdSkrCSmNazN911Y2e
…roperty existence

The Property() and From() lambdas are strongly-typed expressions, so the C# compiler
itself will error if a referenced property doesn't exist on the type. These diagnostics
were redundant.

https://claude.ai/code/session_01SCR7bdSkrCSmNazN911Y2e
Warns when the generator auto-matches properties by name but the source type
cannot be implicitly converted to the destination type. Without this, the
generated assignment (destination.X = source.X) fails to compile with a
confusing error in generated code.

Uses Compilation.ClassifyConversion to check implicit convertibility. Skips
properties with explicit custom mappings (From/Value/Ignore) and handles
Nullable<T> unwrapping for same-underlying-type cases.

https://claude.ai/code/session_01SCR7bdSkrCSmNazN911Y2e
Tests cover all diagnostic rules using CSharpCompilation with inline source:
- ARB0001: class must be partial (positive and negative)
- ARB0002: class must inherit MapperProfile (positive and negative)
- ARB0003: unsupported statements in ConfigureMapping (variable declaration,
  if, foreach, return, non-mapping method calls, mixed valid/invalid)
- ARB0004: auto-matched property type mismatch (incompatible types, implicit
  numeric conversion, nullable same-type, custom-mapped skip, multiple)
- ARB0005: duplicate destination property mapping
- ARB0006: unrecognized method on property chain
- Valid mapper and no-attribute scenarios produce zero diagnostics

https://claude.ai/code/session_01SCR7bdSkrCSmNazN911Y2e
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a standalone Roslyn DiagnosticAnalyzer to validate [GenerateMapper] mapper profiles at compile time (separate from the incremental generator pipeline), along with unit tests to exercise the analyzer diagnostics.

Changes:

  • Introduce MapperDiagnosticAnalyzer to report ARB0001–ARB0006 diagnostics for mapper shape and ConfigureMapping contents.
  • Add MapperDiagnostics descriptor definitions backing those analyzer rules.
  • Add analyzer-focused tests that compile in-memory sources and run the analyzer, plus a test dependency on Microsoft.CodeAnalysis.CSharp.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
test/Arbiter.Mapping.Tests/MapperDiagnosticAnalyzerTests.cs Adds in-memory compilation harness and tests validating analyzer diagnostics.
test/Arbiter.Mapping.Tests/Arbiter.Mapping.Tests.csproj Adds Roslyn C# package reference needed to compile/run the analyzer in tests.
src/Arbiter.Mapping.Generators/MapperDiagnostics.cs Defines diagnostic descriptors ARB0001–ARB0006.
src/Arbiter.Mapping.Generators/MapperDiagnosticAnalyzer.cs Implements analyzer logic to validate [GenerateMapper] mapper classes and ConfigureMapping.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +389 to +395
// Handle nullable-to-nullable and nullable-to-non-nullable of the same underlying type
var sourceUnderlying = GetUnderlyingType(sourceTypeSymbol);
var destUnderlying = GetUnderlyingType(destTypeSymbol);

if (SymbolEqualityComparer.Default.Equals(sourceUnderlying, destUnderlying))
continue;

Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

The ARB0004 type-compatibility check currently skips cases where the source is nullable and the destination is non-nullable but has the same underlying type (e.g., int? -> int). That conversion is explicit in C# and the generator emits direct assignments for auto-mapped properties (see MapperWriter.WriteSourceExpression path-length==1 branch), so the generated code will not compile. Consider removing this underlying-type equality short-circuit for nullable->non-nullable (or only skipping when the destination is nullable / an implicit conversion exists).

Suggested change
// Handle nullable-to-nullable and nullable-to-non-nullable of the same underlying type
var sourceUnderlying = GetUnderlyingType(sourceTypeSymbol);
var destUnderlying = GetUnderlyingType(destTypeSymbol);
if (SymbolEqualityComparer.Default.Equals(sourceUnderlying, destUnderlying))
continue;

Copilot uses AI. Check for mistakes.
Comment on lines +424 to +442
[Test]
public async Task ARB0004_NullableToNonNullableSameType_NoDiagnostic()
{
var source = """
using Arbiter.Mapping;

namespace TestApp;

[GenerateMapper]
public partial class MyMapper : MapperProfile<Source, Dest> { }

public class Source { public int? Value { get; set; } }
public class Dest { public int Value { get; set; } }
""";

var diagnostics = await RunAnalyzerAsync(source);

diagnostics.Should().NotContain(d => d.Id == "ARB0004");
}
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

This test asserts that ARB0004 should NOT be reported for nullable->non-nullable auto-matched properties (int? -> int). The generator currently emits direct property access for single-segment paths, so that assignment will not compile without a coalesce/cast. Once ARB0004 is fixed to flag explicit-only conversions, this expectation should be updated (or the generator updated to emit a safe conversion).

Copilot uses AI. Check for mistakes.
Comment on lines +54 to +68
/// <summary>
/// ARB0004: An auto-matched property has incompatible types between source and destination.
/// The generated assignment will not compile.
/// </summary>
public static readonly DiagnosticDescriptor PropertyTypeMismatch = new(
id: "ARB0004",
title: "Mapped property type mismatch",
messageFormat: "Property '{0}' cannot be auto-mapped: source type '{1}' is not implicitly convertible to destination type '{2}'",
category: Category,
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true,
description: "An auto-matched property (matched by name) has incompatible types between source and destination. " +
"The generated code will produce a compile error. Use mapping.Property(d => d.Prop).From(...) to provide " +
"an explicit conversion, or .Ignore() to skip the property.");

Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

The PR description lists diagnostics ARB0004 (destination property does not exist) and ARB0007 (source property in From() does not exist), but this PR’s implementation defines ARB0004 as a type-mismatch warning for auto-matched properties and does not implement any ARB0007 descriptor or source/destination existence checks. Please either update the PR description to match the shipped diagnostics, or implement the missing diagnostics so the analyzer behavior aligns with the stated contract.

Copilot uses AI. Check for mistakes.
- Add AnalyzerReleases/Shipped.md and Unshipped.md to satisfy RS2008
  (release tracking required by EnforceExtendedAnalyzerRules)
- Register release tracking files as AdditionalFiles in csproj
- Add MA0051 pragma suppression to MapperDiagnosticAnalyzer.cs
- Change test helper return type to List<Diagnostic> for assertion compat

https://claude.ai/code/session_01SCR7bdSkrCSmNazN911Y2e
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.

3 participants