Skip to content

Rewrite all extension-form calls of a document in one Fix-All pass - #1013

Merged
FreeApophis merged 2 commits into
mainfrom
fix-all-using-insertion
Oct 29, 2026
Merged

FreeApophis merged 2 commits into
mainfrom
fix-all-using-insertion

Conversation

@FreeApophis

Copy link
Copy Markdown
Member

Closes #1009

Problem

The code fixes for λ1201, λ1202, λ1203 and the CS0618 fix on Option.FromBoolean all add using Funcky.Extensions; when the namespace is not imported. With the default BatchFixer, three or more such fixes in one document insert the identical directive, which the batch fixer treats as colliding edits: it drops the colliding actions together with their call rewrite, so "Fix all in document" needed a second pass. Two diagnostics worked, which is why the existing tests stopped there.

Fix

  • New abstract base ExtensionMemberCodeFix for the four providers. It owns a DocumentBasedFixAllProvider that collects the rewrites of all flagged calls in a document (TryCreateRewrite per diagnostic), applies them in one ReplaceNodes and adds the using once per using container (file or namespace declaration with usings). Project and solution scope work document by document through the base class. The single-diagnostic actions keep their titles, equivalence keys and lazy/eager behaviour.
  • FunckyExtensionsImport.ReplaceWithExtensionMemberCalls is the shared plural helper (indexed annotations, binding predicate), the single-call overload delegates to it. A rewrite nested in another flagged call is skipped and left for the next pass, as before.
  • BooleanThen.TryReplaceAllAsync keeps the post-check that every Then call binds to Funcky's extension after the using was added; calls that do not bind (another Then in scope) are left out instead of withholding the whole document. OptionSomeWhereToThenRefactoring keeps using the single-call wrapper.
  • Regression tests: the "adds the using" tests of all four fixes now have three or four diagnostics per document (the harness runs Fix-All in document, project and solution scope with exactly one iteration, so they failed with Expected '1' iterations but found '2' before the change).
  • Changelog bullet under Funcky.Analyzers 2.1.0.

Not touched: PreferOrNoneCodeFix (λ1012, shipped in 2.0.0) adds the same using through its own path and most likely has the same limitation; it could adopt the base class in a follow-up.

🤖 Generated with Claude Code

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.

🟡 Changes recommended

Replacement annotations persist across batches and can cause subsequent fixes to discard valid rewrites or throw during import insertion.

1 open issue
What changed in this PR

This PR addresses #1009 by replacing batch fixing with document-level rewriting for four extension-form code fixes, avoiding collisions when multiple fixes add using Funcky.Extensions;.

Changes:

  • Introduces a shared Fix-All provider and coordinated rewrite/import handling.
  • Preserves binding validation for Then, filtering out unsuccessful rewrites.
  • Expands regression tests to three or more diagnostics and documents the fix.
File Description
Funcky.Analyzers/​Funcky.Analyzers.Test/​PreferXunitAssertTest.cs Tests three assertion rewrites requiring an import.
Funcky.Analyzers/​Funcky.Analyzers.Test/​PreferStaticParseOrNoneTest.cs Expands multi-call parsing regression cases.
Funcky.Analyzers/​Funcky.Analyzers.Test/​PreferEnumerableSequenceConstructorsTest.cs Tests three sequence-constructor rewrites.
Funcky.Analyzers/​Funcky.Analyzers.Test/​OptionFromBooleanCodeFixTest.cs Tests three FromBoolean overloads together.
Funcky.Analyzers/​Funcky.Analyzers.CodeFixes/​OptionFromBooleanCodeFix.cs Adopts shared batching with binding validation.
Funcky.Analyzers/​Funcky.Analyzers.CodeFixes/​ExtensionMemberCodeFix.cs Adds the document-based Fix-All base class.
Funcky.Analyzers/​Funcky.Analyzers.CodeFixes/​ExtensionForms/​PreferXunitAssertCodeFix.cs Migrates assertion fixes to the shared provider.
Funcky.Analyzers/​Funcky.Analyzers.CodeFixes/​ExtensionForms/​PreferStaticParseOrNoneCodeFix.cs Migrates parsing fixes to the shared provider.
Funcky.Analyzers/​Funcky.Analyzers.CodeFixes/​ExtensionForms/​PreferEnumerableSequenceConstructorsCodeFix.cs Migrates sequence fixes to the shared provider.
Funcky.Analyzers/​Funcky.Analyzers.CodeFixes/​CodeAnalysisExtensions/​FunckyExtensionsImport.cs Batches replacements and groups import insertion.
Funcky.Analyzers/​Funcky.Analyzers.CodeFixes/​CodeAnalysisExtensions/​ExtensionMemberRewrite.cs Adds a shared rewrite representation.
Funcky.Analyzers/​Funcky.Analyzers.CodeFixes/​CodeAnalysisExtensions/​BooleanThen.cs Validates batched rewrites and retries valid subsets.
changelog.md Documents the Fix-All improvement.

Review effort: Balanced

FreeApophis and others added 2 commits October 29, 2026 13:45
The code fixes for λ1201, λ1202, λ1203 and the Option.FromBoolean
obsoletion all add `using Funcky.Extensions;` when needed. The batch
fixer treats three or more identical directive insertions in a document
as colliding edits and drops those actions, call rewrite included, so a
second Fix-All pass was needed.

The new ExtensionMemberCodeFix base owns a DocumentBasedFixAllProvider
that applies every rewrite of a document at once through the plural
FunckyExtensionsImport.ReplaceWithExtensionMemberCalls and adds the
using once per using container. BooleanThen keeps its post-check that
every Then call binds to Funcky's extension and leaves out the calls
that do not. The single-diagnostic actions keep their behaviour.

The "adds the using" tests of all four fixes now have three or more
diagnostics per document.

Closes #1009

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The per-rewrite annotations that FunckyExtensionsImport uses to find the
replacements stayed in the returned document. A host that keeps the
syntax trees between two fixes (the test harness re-parses them) then
handed them to the next fix on the same document: BooleanThen took a
leftover parse replacement for one of its own Then calls, saw that it
does not bind to Then and withheld the valid fix; a later fix needing
the import could hit duplicate indices in AddUsings.

ReplaceWithExtensionMemberCalls now strips the annotations before the
document is returned; BooleanThen binds the annotated root and strips
them afterwards. ExtensionMemberCodeFixTest applies a λ1201 fix and then
the Option.FromBoolean fix to the changed solution directly, and
PreferStaticParseOrNoneTest covers two namespace declarations that each
get their own using.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
FreeApophis added a commit that referenced this pull request Oct 29, 2026
…Fix-All

The fix inserted `using Funcky.Extensions;` through its own path with the
default BatchFixer, so three or more diagnostics in one document that all
needed the directive collided and some were only rewritten by a second
Fix-All pass, the same defect as #1009. It now derives from the base of
#1013, which rewrites all flagged calls of a document at once and adds the
directive once. The rewrite replaces the whole `?? fallback` coalesce, which
the base covers unchanged; only the binding probe differs, since the
replacement binds to `Option.GetOrElse` in `Funcky.Monads` rather than to a
member of `Funcky.Extensions`, so the fix overrides `TryApplyAsync` with a
predicate that takes any bound method as proof the directive is in place.

Two tests with three and four fixes per document, with the usings at file
level and inside the namespace, failed before with "Expected '1' iterations
but found '2' iterations".

Closes #1020

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@FreeApophis
FreeApophis force-pushed the fix-all-using-insertion branch from 6c3f2b3 to 567a03d Compare October 29, 2026 12:45
@FreeApophis
FreeApophis merged commit 25b50c4 into main Oct 29, 2026
10 checks passed
@FreeApophis
FreeApophis deleted the fix-all-using-insertion branch October 29, 2026 13:14
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.

Fix-All needs a second pass when three or more extension-form fixes add the same using

3 participants