Skip to content

Add a shared fix-all provider that fixes a document through one editor - #55533

Merged
tannergooding merged 4 commits into
dotnet:mainfrom
tannergooding:tannergooding-shared-fix-all-provider
Jul 30, 2026
Merged

Add a shared fix-all provider that fixes a document through one editor#55533
tannergooding merged 4 commits into
dotnet:mainfrom
tannergooding:tannergooding-shared-fix-all-provider

Conversation

@tannergooding

Copy link
Copy Markdown
Member

WellKnownFixAllProviders.BatchFixer computes a separate fixed document per diagnostic, each
against the original, then diffs and merges them — a document fork, a semantic model bind, and a
text diff per diagnostic. Where two of those text changes overlap the merge silently drops one, and
the user has to invoke fix-all again.

SyntaxEditorFixAllProvider records every one of a document's fixes into a single SyntaxEditor
instead. It mirrors Roslyn's own SyntaxEditorBasedCodeFixProvider
— same name, same shape, same pipeline: Distinct(), source order, one editor per document, and the
single-fix and fix-all paths sharing one code path so they cannot diverge.


CA1516's OrderedCodeFixProvider is the only caller moved, because it already hand-rolled this exact
loop — over a DocumentEditor, with a hardcoded descending order. It now takes the shared one and is
renamed to match Roslyn.

The order it pinned is no longer declared: Order sorts ascending by span start, as Roslyn does, and
goes innermost-first only when the spans actually nest — read off the spans rather than declared by
the fixer.


SyntaxEditor rather than DocumentEditor, because DocumentEditor.CreateAsync
awaits GetSemanticModelAsync unconditionally, whether or not the fix reads one — and CA1516 never
does. Measured on Roslyn 3.11 over 40 fresh 400-member documents per arm, run in both orders to rule
out an ordering artifact: 1.27 / 1.20 ms per document versus 0.029 / 0.025 — 44x / 48x. That is
the cold case, which is also the fix-all case: Project and Solution scope walk unopened documents.


Out of scope. The repo-wide migration off the batch fixer is a large diff with its own regression
risk, and does not need reviewing in the same sitting as the thing it migrates onto.

ContainingMember / ContainingType fix-all scopes need the Roslyn 4.4
DocumentBasedFixAllProvider(ImmutableArray<FixAllScope>) constructor, since GetSupportedFixAllScopes
is sealed at every version. This PR is scope-neutral — CA1516 advertises the same three before and
after — and leaves that as one constructor call.

No ForkingSyntaxEditorBasedCodeFixProvider<T> counterpart. It has 6 consumers in Roslyn against
~120 on the plain base, and the nesting shape it exists for is covered here two cheaper ways: the
innermost-first ordering, and expressing an enclosing fix through
ReplaceNode(SyntaxNode, Func<SyntaxNode, SyntaxGenerator, SyntaxNode>). It is purely additive if a
fixer ever needs a re-bound SemanticModel mid-batch, which is forking's one irreplaceable
capability.

Builds 0 errors / 0 warnings on Roslyn 3.11; Microsoft.CodeAnalysis.NetAnalyzers.UnitTests at
17,374 / 0, main's 17,364 plus the 10 added here.

Note

Drafted with agent assistance.

Copilot AI review requested due to automatic review settings July 29, 2026 22:50
@tannergooding
tannergooding requested a review from a team as a code owner July 29, 2026 22:50
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
2 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@tannergooding

Copy link
Copy Markdown
Member Author

Namely this more closely unifies on what Roslyn does internally so that it is easier to migrate all the other analyzers to the "better" pattern.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 adds a shared “single-editor” fix-all implementation for NetAnalyzers code fixes, aiming to avoid WellKnownFixAllProviders.BatchFixer’s per-diagnostic document forking/merging behavior and to ensure fixes within a document compose through one SyntaxEditor. It also migrates CA1516’s intrinsics fixer to the new base/provider and adds targeted regression tests for ordering and scale.

Changes:

  • Add SyntaxEditorFixAllProvider to apply all diagnostics for a document through a single SyntaxEditor, plus ordering logic for nested spans.
  • Add SyntaxEditorBasedCodeFixProvider to share the single-fix and fix-all pipelines via the same implementation.
  • Migrate UseCrossPlatformIntrinsicsFixer off the removed OrderedCodeFixProvider, and add fix-all regression tests for deep nesting and higher diagnostic counts.
Show a summary per file
File Description
src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/SyntaxEditorFixAllProvider.cs New shared fix-all provider built around a single SyntaxEditor and diagnostic ordering.
src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/SyntaxEditorBasedCodeFixProvider.cs New base CodeFixProvider that routes single-fix and fix-all through the same editor-based pipeline.
src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/OrderedCodeFixProvider.cs Removed the previous per-fixer ordered DocumentEditor fix-all base.
src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/Maintainability/UseCrossPlatformIntrinsicsFixer.cs Migrated to the new editor-based base class/provider and updated fix application pattern.
src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/SyntaxEditorFixAllProviderTests.cs New unit tests validating ordering rules and semantic-model binding behavior.
src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.CodeQuality.Analyzers/Maintainability/CSharpUseCrossPlatformIntrinsicsTests.cs Added fix-all regression tests for deep nesting and higher diagnostic counts.

Copilot's findings

  • Files reviewed: 6/6 changed files
  • Comments generated: 4

tannergooding and others added 4 commits July 30, 2026 14:38
WellKnownFixAllProviders.BatchFixer computes a separate fixed document per
diagnostic, each against the original, then diffs and merges them. That is a
document fork, a semantic model, and a text diff per diagnostic, and where two
of those text changes overlap the merge silently drops one, so the user has to
invoke fix-all again.

SyntaxEditorFixAllProvider records every one of a document's fixes into a single
SyntaxEditor instead. It has no consumer yet.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CA1516 was the only fixer with a shared base of its own, hand-rolling a fix-all
loop over a DocumentEditor and pinning a descending order. Route it through
SyntaxEditorFixAllProvider and name the base after Roslyn's own
SyntaxEditorBasedCodeFixProvider, which it now mirrors.

The order it pinned is no longer declared: Order sorts ascending by span start,
as Roslyn does, and only goes innermost-first when the spans actually nest.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The remarks said nested spans order innermost-first, which reads as though only
the nested pair moves. One nested pair reverses the whole document, as the
existing test already asserts.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The add-net-analyzer skill and netcore-getting-started.md both link
OrderedCodeFixProvider, which this branch removes. Retarget them at
SyntaxEditorBasedCodeFixProvider, and record what the shared provider now handles
for the author: registration, ordering, and the equivalence-key filtering
DocumentBasedFixAllProvider does not do.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@tannergooding
tannergooding force-pushed the tannergooding-shared-fix-all-provider branch from 6d1964c to f2542b0 Compare July 30, 2026 21:57
@tannergooding
tannergooding enabled auto-merge July 30, 2026 22:13
@tannergooding
tannergooding merged commit 1266dc0 into dotnet:main Jul 30, 2026
22 checks passed
@tannergooding
tannergooding deleted the tannergooding-shared-fix-all-provider branch July 31, 2026 00:17
@dotnet-milestone-bot dotnet-milestone-bot Bot added this to the 11.0-rc1 milestone Jul 31, 2026
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