Skip to content

Always generate aliased usings#12

Merged
mkholt merged 8 commits into
mainfrom
always-generate-aliased-usings-118
Jun 22, 2026
Merged

Always generate aliased usings#12
mkholt merged 8 commits into
mainfrom
always-generate-aliased-usings-118

Conversation

@mkholt

@mkholt mkholt commented Jun 19, 2026

Copy link
Copy Markdown
Member

Always generate aliased usings for plugin image registrations

Context

The XrmPluginCore.SourceGenerator ships analyzers + code-fix providers that wire up type-safe Pre/Post image handler signatures. When a RegisterStep<TEntity, TService>(...) registration declares an image via WithPreImage/WithPostImage/AddImage but the referenced handler method's signature does not match, two diagnostics/code-fixes come into play:

  • FixHandlerSignatureCodeFixProvider (XrmPluginCore.SourceGenerator/CodeFixes/FixHandlerSignatureCodeFixProvider.cs) — fixes an existing handler method's parameter list to accept the registered PreImage/PostImage wrapper types. Fires on DiagnosticDescriptors.HandlerSignatureMismatch.Id and DiagnosticDescriptors.HandlerSignatureMismatchError.Id.
  • CreateHandlerMethodCodeFixProvider (XrmPluginCore.SourceGenerator/CodeFixes/CreateHandlerMethodCodeFixProvider.cs) — creates a missing handler method on the service interface with the correct image parameters. Fires on DiagnosticDescriptors.HandlerMethodNotFound.Id.

Each registration's image wrapper classes (PreImage, PostImage) are generated into an isolated namespace produced by RegisterStepHelper.GetExpectedImageNamespace(...) (XrmPluginCore.SourceGenerator/Helpers/RegisterStepHelper.cs:17), with the shape:

{PluginNamespace}.PluginRegistrations.{PluginClassName}.{EntityTypeName}{Operation}{Stage}

e.g. Some.Namespace.Plugins.PluginRegistrations.SomePlugin.LeadUpdatePostOperation. The shared marker for these namespaces is the substring .PluginRegistrations. (SyntaxFactoryHelper.IsImageRegistrationNamespace, SyntaxFactoryHelper.cs:207). The alias used is the last namespace segment (GetLastNamespaceSegment, SyntaxFactoryHelper.cs:212), e.g. LeadUpdatePostOperation. Both PreImage and PostImage are simple type names (Constants.PreImageTypeName = "PreImage", Constants.PostImageTypeName = "PostImage"), so two registrations in the same file both expose a PreImage/PostImage type.

The bug: Today the code fixes try to be clever — they only convert to aliased usings when an ambiguity is detected, otherwise they add a plain (non-aliased) using. This is driven by SyntaxFactoryHelper.DetectImageAmbiguity(...) (SyntaxFactoryHelper.cs:118):

  • In FixHandlerSignatureCodeFixProvider.FixMethodDeclarationsAsync (lines 199–235): ambiguity = DetectImageAmbiguity(...); CreateImageParameterList(hasPreImage, hasPostImage, ambiguity.needsAlias ? ambiguity.alias : null); then if (ambiguity.needsAlias) ConvertToAliasedUsingsAndQualifyRefs(...) else AddUsingDirectiveIfMissing(...).
  • In CreateHandlerMethodCodeFixProvider.CreateMethodAsync (lines 135–152): same DetectImageAmbiguity / conditional pattern via CreateMethodDeclaration(..., needsAlias ? alias : null) and the same if (needsAlias) ConvertToAliasedUsingsAndQualifyRefs else AddUsingDirectiveIfMissing branch.

When multiple plugins use the same service and more than one of those registrations declares Pre/Post images, the automatic generation of usings and modification of parameters fails, because the plain-using path produces bare PreImage/PostImage references that collide across the multiple image namespaces, and the on-demand conversion logic (ImageAmbiguityRewriter, SyntaxFactoryHelper.cs:234) assumes "there should be exactly one existing plain image using at this point" (SyntaxFactoryHelper.cs:309, the break; after taking the first entry of _typeToExistingAlias). That assumption breaks with multiple image usings.

The decision: Stop trying to convert on demand. Always emit aliased usings and always qualify the image parameter types with the alias. This makes every emitted reference unambiguous regardless of how many same-service registrations exist in the file.

Existing tests assert the old behavior and must be updated. See:

  • XrmPluginCore.SourceGenerator.Tests/DiagnosticTests/FixHandlerSignatureCodeFixProviderTests.csShould_Fix_Signature_And_Add_Using_For_PreImage (line ~61/64), ..._For_PostImage (line ~114/117), ..._For_Both_Images (line ~120), and Should_Avoid_Ambiguous_Usings (line 231).
  • XrmPluginCore.SourceGenerator.Tests/DiagnosticTests/CreateHandlerMethodCodeFixProviderTests.cs (same directory).

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR updates the SourceGenerator’s image-handler code fixes to always generate aliased image usings (and always qualify PreImage/PostImage parameter types with that alias), eliminating ambiguity when multiple image-registration namespaces are imported in the same file. It also introduces a custom FixAll provider to apply multiple image-handler fixes in a single consolidated rewrite pass.

Changes:

  • Always emit aliased image usings and alias-qualified image parameter types in both handler signature fixes and handler method creation fixes.
  • Rework image-using rewrite logic to support multiple namespaces and (when available) use a semantic model to correctly re-qualify existing bare PreImage/PostImage references.
  • Add AliasedImageUsingsFixAllProvider and expand tests to cover FixAll and multi-registration scenarios.

Reviewed changes

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

Show a summary per file
File Description
XrmPluginCore.SourceGenerator/Helpers/SyntaxFactoryHelper.cs Adds “always alias” APIs and semantic-model-backed rewrite logic for qualifying image references.
XrmPluginCore.SourceGenerator/Helpers/RegisterStepHelper.cs Adds helper to resolve the RegisterStep service type from a diagnostic span (used by FixAll).
XrmPluginCore.SourceGenerator/CodeFixes/FixHandlerSignatureCodeFixProvider.cs Switches to always alias image params/usings; uses the new FixAll provider.
XrmPluginCore.SourceGenerator/CodeFixes/CreateHandlerMethodCodeFixProvider.cs Switches to always alias method params/usings; uses the new FixAll provider.
XrmPluginCore.SourceGenerator/CodeFixes/AliasedImageUsingsFixAllProvider.cs New consolidated FixAll provider for image-handler code fixes.
XrmPluginCore.SourceGenerator.Tests/DiagnosticTests/FixHandlerSignatureCodeFixProviderTests.cs Updates assertions for alias-qualified output; adds multi-using + FixAll coverage.
XrmPluginCore.SourceGenerator.Tests/DiagnosticTests/CreateHandlerMethodCodeFixProviderTests.cs Updates assertions for alias-qualified output; adds FixAll coverage.
XrmPluginCore.SourceGenerator.Tests/DiagnosticTests/CodeFixTestBase.cs Adds test helper to exercise a provider’s FixAll path.
.gitignore Ignores .conducktor/ local files.

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

Comment thread XrmPluginCore.SourceGenerator/Helpers/SyntaxFactoryHelper.cs
Comment thread XrmPluginCore.SourceGenerator/CodeFixes/CreateHandlerMethodCodeFixProvider.cs Outdated
…ixAll title)

- SyntaxFactoryHelper: only treat a plain (non-aliased) using as "already
  present" for the image namespace. A namespace imported under a different
  alias no longer suppresses the standard alias, which the emitted parameter
  types reference.
- CreateHandlerMethodCodeFixProvider: track the target interface with a
  SyntaxAnnotation (binding via a ReplaceSyntaxTree'd semantic model) instead
  of re-finding by identifier text, so same-named interfaces across namespaces
  or nested types resolve correctly.
- AliasedImageUsingsFixAllProvider: use a descriptive constant title for the
  FixAll code action; keep the equivalence key for identity only.
- Add regression tests for both behavioral fixes (verified failing without
  the fix); full suite 80/80.

Co-Authored-By: Claude <noreply@anthropic.com> via Conducktor <conducktor@contextand.com>
@mkholt mkholt merged commit a4c10b1 into main Jun 22, 2026
1 check passed
@mkholt mkholt deleted the always-generate-aliased-usings-118 branch June 22, 2026 07:31
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