Skip to content

Add: Support fully-registered pre/post images#9

Merged
mkholt merged 1 commit intomainfrom
full-entity-images-628
Apr 22, 2026
Merged

Add: Support fully-registered pre/post images#9
mkholt merged 1 commit intomainfrom
full-entity-images-628

Conversation

@mkholt
Copy link
Copy Markdown
Member

@mkholt mkholt commented Apr 22, 2026

Add: When specifying a Pre or Post image without filtering the attributes, we now generate a Pre or Post image wrapper with all attributes that have a logical name spacified.

Add: Warning to avoid image registrations without attributes

…utes, we now generate a Pre or Post image wrapper with all attributes that have a logical name spacified.

Add: Warning to avoid image registrations without attributes
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 support for generating image wrapper classes when WithPreImage() / WithPostImage() are invoked without specifying attributes (full entity images), and introduces a new analyzer warning (XPC3005) to discourage this pattern due to performance overhead.

Changes:

  • Update registration parsing to treat parameterless WithPreImage() / WithPostImage() as “include all mapped entity attributes”.
  • Add new diagnostic descriptor + documentation for XPC3005 (full entity image registrations).
  • Add a new Roslyn analyzer + unit tests covering wrapper generation and diagnostic reporting for parameterless image registrations.

Reviewed changes

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

Show a summary per file
File Description
XrmPluginCore.SourceGenerator/rules/XPC3005.md Adds rule documentation explaining the performance implications and how to fix/suppress.
XrmPluginCore.SourceGenerator/Parsers/RegistrationParser.cs Populates image attribute metadata from all mapped entity properties when no attributes are specified.
XrmPluginCore.SourceGenerator/DiagnosticDescriptors.cs Introduces XPC3005 diagnostic descriptor and help link.
XrmPluginCore.SourceGenerator/Analyzers/FullEntityImageAnalyzer.cs New analyzer that reports XPC3005 for parameterless WithPreImage() / WithPostImage().
XrmPluginCore.SourceGenerator/AnalyzerReleases.Unshipped.md Registers XPC3005 in analyzer release notes.
XrmPluginCore.SourceGenerator.Tests/Helpers/TestFixtures.cs Adds new plugin fixtures for full-entity pre/post image scenarios.
XrmPluginCore.SourceGenerator.Tests/GenerationTests/WrapperClassGenerationTests.cs Adds generation tests validating wrappers + action wrapper behavior for full-entity images.
XrmPluginCore.SourceGenerator.Tests/DiagnosticTests/DiagnosticReportingTests.cs Adds analyzer tests asserting XPC3005 reports only when no-arg image methods are used.

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

Comment on lines +41 to +46

context.ReportDiagnostic(Diagnostic.Create(
DiagnosticDescriptors.FullEntityImage,
invocation.GetLocation(),
methodName));
}
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

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

FullEntityImageAnalyzer will report XPC3005 for any method named WithPreImage/WithPostImage anywhere in the codebase (including unrelated user-defined APIs), because it only checks the identifier text and does not verify the invocation is part of an XrmPluginCore RegisterStep fluent chain or that the target symbol is the expected method. This can create false-positive warnings in non-plugin code. Consider restricting analysis to invocations chained from a RegisterStep call (similar to ImageWithoutMethodReferenceAnalyzer) and/or using the semantic model to confirm the invoked IMethodSymbol belongs to the intended XrmPluginCore registration API type/namespace.

Suggested change
context.ReportDiagnostic(Diagnostic.Create(
DiagnosticDescriptors.FullEntityImage,
invocation.GetLocation(),
methodName));
}
// Restrict matches to the XrmPluginCore registration fluent chain to avoid
// reporting on unrelated user-defined APIs with the same method name.
if (!IsRegisterStepChain(memberAccess.Expression))
return;
context.ReportDiagnostic(Diagnostic.Create(
DiagnosticDescriptors.FullEntityImage,
invocation.GetLocation(),
methodName));
}
private static bool IsRegisterStepChain(ExpressionSyntax expression)
{
switch (expression)
{
case InvocationExpressionSyntax invocation:
return IsRegisterStepInvocation(invocation) || IsRegisterStepChain(invocation.Expression);
case MemberAccessExpressionSyntax memberAccess:
return IsRegisterStepChain(memberAccess.Expression);
default:
return false;
}
}
private static bool IsRegisterStepInvocation(InvocationExpressionSyntax invocation)
{
return invocation.Expression switch
{
MemberAccessExpressionSyntax memberAccess =>
memberAccess.Name.Identifier.Text == Constants.RegisterStepMethodName,
IdentifierNameSyntax identifier =>
identifier.Identifier.Text == Constants.RegisterStepMethodName,
_ => false
};
}

Copilot uses AI. Check for mistakes.
.OfType<IPropertySymbol>()
.Where(p => p.GetAttributes().Any(a => a.AttributeClass?.Name == Constants.LogicalNameAttributeName))
.Select(p => GetAttributeMetadata(p.Name, entityType))
.Where(a => a != null);
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

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

GetAllEntityAttributes returns entity members in Roslyn’s default order, and later generation iterates image.Attributes as-is. This can make generated source unstable between compilations (and produces noisy diffs) if member enumeration order changes. Consider applying a deterministic ordering (e.g., order properties by Name and/or order the resulting AttributeMetadata by LogicalName) when building the full-entity attribute list.

Suggested change
.Where(a => a != null);
.Where(a => a != null)
.OrderBy(a => a.LogicalName, global::System.StringComparer.Ordinal)
.ThenBy(a => a.PropertyName, global::System.StringComparer.Ordinal);

Copilot uses AI. Check for mistakes.
Comment on lines +247 to +252
// For WithPreImage/WithPostImage with no attributes specified, capture all entity attributes
if (!imageMetadata.Attributes.Any() &&
(methodName == Constants.WithPreImageMethodName || methodName == Constants.WithPostImageMethodName))
{
imageMetadata.Attributes.AddRange(GetAllEntityAttributes(entityType));
}
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

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

The comment says this branch will "capture all entity attributes", but GetAllEntityAttributes only includes properties decorated with AttributeLogicalNameAttribute. To avoid confusion for future maintainers (and align with the actual behavior), consider rewording the comment to reflect that it captures all mapped entity properties/attributes (i.e., those with logical name metadata).

Copilot uses AI. Check for mistakes.
@mkholt mkholt merged commit 94df1c7 into main Apr 22, 2026
5 checks passed
@mkholt mkholt deleted the full-entity-images-628 branch April 22, 2026 19:03
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