Skip to content

Report MA0102 on the event instead of its accessors - #1356

Merged
meziantou merged 1 commit into
mainfrom
fix/ma0102-event-accessors
Aug 27, 2026
Merged

Report MA0102 on the event instead of its accessors#1356
meziantou merged 1 commit into
mainfrom
fix/ma0102-event-accessors

Conversation

@meziantou

Copy link
Copy Markdown
Owner

MA0102 reported event accessors, whose fix does not compile

readonly cannot be applied to an event accessor — readonly add { } is CS1609. But
MakeMemberReadOnlyAnalyzer reported each accessor
separately, and the fixer put the modifier on the accessor whenever the sibling accessor did not already have it,
which for an event is always. So Add readonly on

struct Test
{
    public event System.Action<System.EventArgs> Event1
    {
        add { }      // MA0102
        remove { }   // MA0102
    }
}

produced readonly add { }, which does not compile, and the analyzer kept reporting the accessor, so applying the
fix again appended another readonly. Only Fix all occurrences produced valid code, because it saw both
diagnostics at once and hoisted the modifier onto the event.

There is no fix for a single event accessor: readonly on an event covers both of them. So the analyzer now
collects the accessors that can be readonly per event and reports the event once every one of its accessors
qualifies, the way it already reports the property rather than the accessor for an expression-bodied property. The
fixer adds the modifier to the event declaration, and the incremental fix and the fix all now agree.

An event whose accessors do not all qualify is no longer reported at all — previously the fixable-looking accessor
was reported on its own, and taking that fix would have produced a readonly event with an accessor that writes to
a field. CannotBeReadOnly_EventWithOnlyOneReadOnlyAccessor covers that, and CannotBeReadOnly_ReadOnlyEvent
covers the already-readonly case.

CanBeReadOnly_Event moves from ShouldBatchFixCodeWith to ShouldFixCodeWith: the incremental fix produces the
right code now, and the test harness compiles the fixed code, so it is a regression test for the CS1609.

MA0156 and MA0157 in the same test

The two rules contradict each other — "a method returning IAsyncEnumerable<T> must / must not use the Async
suffix". Both are isEnabledByDefault: false and a project enables one of them, but WithAnalyzer<T>() enables
every rule an analyzer supports, so the two code fixers fought over the same method: the fix for one produced code
the other reported. The two code fix tests now pin the rule they are about with WithDefaultAnalyzerId.

No production code changes for this one — it is a test that was configured in a way no real project can be.

Verification

Meziantou.Analyzer.Test.roslyn5.9: 3842 tests, 0 failures (2 new).
MakeMemberReadOnlyAnalyzerTests + MethodsReturningAnAwaitableTypeMustHaveTheAsyncSuffixAnalyzerTests also run
against roslyn4.8: 59 tests, 0 failures.
dotnet run --project src/DocumentationGenerator reports no change.

The MA0156/MA0157 half is unrelated to the MA0102 fix and can be split into its own PR if you prefer to review
them separately.

@meziantou
meziantou merged commit a1e6552 into main Aug 27, 2026
13 checks passed
@meziantou
meziantou deleted the fix/ma0102-event-accessors branch August 27, 2026 14:29
This was referenced Aug 27, 2026
This was referenced Aug 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.

1 participant