Skip to content

fix: report analyzer diagnostics per type at in-source locations - #4

Draft
teneko wants to merge 4 commits into
mainfrom
fix/analyzer-diagnostics
Draft

teneko wants to merge 4 commits into
mainfrom
fix/analyzer-diagnostics

Conversation

@teneko

@teneko teneko commented Oct 9, 2026

Copy link
Copy Markdown
Member

Problem

  • #pragma warning disable MOGxxx, [SuppressMessage("MethodOverloadsGenerator", "MOGxxx")] and per-file .editorconfig severities had no effect on MOG001–MOG019. The analyzer rebuilt locations from stored paths and spans (Location.Create(path, span, lineSpan)), which yields LocationKind.ExternalFile locations without a SyntaxTree. Roslyn only applies in-source suppression to in-source locations.
  • All diagnostics were reported from a CompilationEndAction, so they did not show up live in the IDE (only on build or with full solution analysis). Every run re-parsed all targets and built the complete overload plan just to get diagnostics.

Change

  • MethodOverloadsDiagnosticsAnalyzer registers a SymbolAction per named type. It builds the model for that type only and runs the existing Parser and OverloadPlanBuilder on it, so generator and analyzer keep sharing one set of rules.
  • A type reports only the diagnostics located in its own declaration. They are mapped to the declaring SyntaxTree (Location.Create(tree, span)). If several declarations share a path (e.g. in-memory trees without a path), the old path-based location is used as a fallback.
  • Diagnostics at a matcher are reported when the matcher type itself is analyzed. Previously they were reported once per target that matched it.
  • MOG002 is evaluated per target. It is reported at the typeof(T) reference in Matchers, and the message names the target: Matcher '{0}' has no subsequence match for any target method in '{1}'.

Behavior changes

  • MOG002 changes location, scope and message (snapshot updated). With a matcher type shared by several targets, a matcher method that matches in one target but not in another is now reported for the other target. The acceptance project has three such cases: Class_39_2, Class_40_2 and Bug_Class_1_Bucket (the latter declares no methods at all).
  • Formal diagnostics on matcher attributes (e.g. MOG001 on a matcher) are reported whenever the matcher type is analyzed, not only when a target matches.

Known limitation (follow-up planned)

The matcher role is implicit: a type becomes a matcher only because some other type references it via Matchers = [typeof(T)]. When a matcher type is analyzed on its own, its methods look like direct targets. In the acceptance project this produces two false MOG006 (severity Hidden, not visible in build output or the error list): Class_4_Matcher.Matcher_2 and Bug_Class_1_ICommandRuntimeFactoryMatchers.Matcher.

The same root cause affects the generator: referencing a type as a matcher anywhere disables direct generation for that type's own [GenerateOverloads] methods. A separate PR will make the role explicit with a marker attribute on matcher types (strict: an unmarked type in Matchers is an error).

Test infrastructure

  • AcceptanceFixture runs the analyzer with reportSuppressedDiagnostics: true. The [SuppressMessage] attributes that declare expected diagnostics now really suppress them, so suppressed diagnostics are kept in the actual set (IsSuppressed == true). Analyzer exceptions are captured, and a new test fails on them.
  • AcceptanceTestData.GetRootFromPath is removed. Locations now carry their SyntaxTree.
  • MOG002 expectations moved from the matcher interfaces to the target classes. Before, they sat in interfaces, which the acceptance infrastructure does not map, so MOG002 was effectively untested.
  • New DiagnosticSuppressionTests: #pragma and [SuppressMessage] suppress MOG001, the location is in source and spans the attribute, and MOG002 sits at typeof(IMatcher).

.globalconfig

Verified: without the severity downgrades the acceptance project builds with 0 errors in both modes, because the error-level diagnostics are suppressed by their [SuppressMessage]. The file was removed. The full build (AttributesOnly=false) printed 15 MOG warnings before this PR and prints 0 now. The 59 CS8625 warnings are unchanged.

Verification

  • dotnet test --nologo: 120 passed.
  • Before/after dump of all analyzer diagnostics on the acceptance sources (in-memory):
    • Before: 34 diagnostics, all ExternalFile.
    • After: 39 diagnostics, all SourceFile, 30 of them suppressed.
    • MOG001 and MOG003–MOG019 at direct targets are identical.
    • Differences: 4 MOG002 relocated to typeof, 3 new MOG002 (shared matchers), 2 MOG006 false positives (known limitation).
  • dotnet build ref/...csproj -c Release -nodeReuse:false --no-incremental with TenekonMethodOverloadsSourceGeneratorAttributesOnly=true and =false: 0 errors.

Out of scope

  • Generator models still carry source locations, which weakens incremental caching (separate PR).
  • Explicit matcher role (separate PR, see above).
  • Acceptance infrastructure gaps (regex parsing of extension(...) blocks, interfaces not mapped, comparison per class only, return type missing from the key) and tidy-up items.

🤖 Generated with Claude Code

teneko and others added 4 commits October 9, 2026 07:19
The analyzer collected all targets and reported every diagnostic from a
CompilationEndAction. Locations were rebuilt from stored paths and spans
(LocationKind.ExternalFile), so #pragma, [SuppressMessage] and per-file
.editorconfig severities never applied, and the IDE did not show the
diagnostics while typing.

The analyzer now registers a symbol action per named type, builds the
model for that type only and runs the shared parser and plan builder on
it. Each type reports only the diagnostics located in its own
declaration, mapped to the declaring syntax tree. Diagnostics at a
matcher are reported when the matcher type itself is analyzed, which
also removes duplicates that were reported once per target.

MOG002 is now evaluated per target and reported at the typeof reference
in Matchers. Its message names the target type.

The acceptance fixture reports suppressed diagnostics, because the
[SuppressMessage] attributes that declare expected diagnostics now
suppress them, and it fails on analyzer exceptions. MOG002 expectations
move from the matchers to the targets.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In-memory regression tests for the analyzer: #pragma warning disable and
[SuppressMessage] on the method suppress MOG001, reported locations are
in source and span the attribute, and MOG002 is reported at the typeof
reference of the target.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The error-level MOG diagnostics in the acceptance project are now
suppressed by the [SuppressMessage] attributes that declare them as
expected, so the project builds without downgrading their severity.
Verified with full builds in both attributes-only modes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Document that diagnostics are reported live and can be suppressed with
#pragma, [SuppressMessage] or .editorconfig severities, that MOG002 is
reported per target at the typeof reference, and how expected
diagnostics are declared now that the acceptance project no longer uses
a .globalconfig.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@codecov

codecov Bot commented Oct 9, 2026

Copy link
Copy Markdown

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