Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,10 +258,10 @@ Emit attributes only (skip generation and diagnostics):

## Diagnostics

Diagnostics are reported by the analyzer and surfaced during build:
Diagnostics are reported by the analyzer, live in the IDE and during build:

- `MOG001` Invalid window anchor.
- `MOG002` Matcher has no subsequence match.
- `MOG002` Matcher has no subsequence match in a target (reported at the `typeof` reference in `Matchers`).
- `MOG003` Defaults inside window.
- `MOG004` Params outside window.
- `MOG005` Ref/out/in omitted.
Expand All @@ -280,7 +280,20 @@ Diagnostics are reported by the analyzer and surfaced during build:
- `MOG018` ExcludeAny refers to missing/out-of-window parameter.
- `MOG019` ExcludeAny contains invalid entries.

You can downgrade error-level diagnostics in `.globalconfig` if you need the project to compile with intentional violations.
Diagnostics can be suppressed in source like any other analyzer diagnostic:

```csharp
#pragma warning disable MOG001
[GenerateOverloads(Begin = "missing")]
public void Configure(int value) { }
#pragma warning restore MOG001

[SuppressMessage("MethodOverloadsGenerator", "MOG011")]
[GenerateOverloads]
public void Reset() { }
```

Severities can also be changed in `.editorconfig` or `.globalconfig`, e.g. `dotnet_diagnostic.MOG002.severity = none`.

## Generation Rules (Summary)

Expand Down
30 changes: 13 additions & 17 deletions docs/acceptance-criterias.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ Source files:

Support files:
- `.editorconfig` (IDE/inspection suppression + generated code marker)
- `.globalconfig` (diagnostic severity overrides)

## How tests consume it

Expand All @@ -33,6 +32,8 @@ There are two main test paths:
- Tests read the `ref/*.cs` files and build an in-memory compilation.
- Expected overload signatures come from `Class_*_AcceptanceCriterias`.
- Expected diagnostics are inferred from `[SuppressMessage]` attributes (MOG IDs).
- The analyzer runs with `reportSuppressedDiagnostics: true`, so diagnostics suppressed by those attributes
are still compared (`Diagnostic.IsSuppressed == true`).
- See: `tests/Tenekon.MethodOverloads.SourceGenerator.Tests/Infrastructure/AcceptanceTestData.cs`.

2) **Project build validation (real MSBuild)**
Expand All @@ -54,20 +55,11 @@ The tests explicitly build the project with:
- `TenekonMethodOverloadsSourceGeneratorAttributesOnly=false`
to ensure a full generation build also succeeds.

## .globalconfig (diagnostic severities)
## Error-level diagnostics

File:
- `ref/Tenekon.MethodOverloads.AcceptanceCriterias/.globalconfig`

Role:
- Downgrades certain MOG diagnostics from **error** to **warning** so the project can build while still
exercising those diagnostics in acceptance criteria.

Current overrides:
- `MOG007`, `MOG009`, `MOG010`, `MOG012` → warning

These diagnostics are treated as errors by default and are not suppressable in source;
the globalconfig allows the project to compile while still surfacing them for tests.
Some MOG diagnostics are errors by default (e.g. `MOG007`, `MOG012`). The project still compiles because the
`[SuppressMessage]` attributes that declare them as expected also suppress them in the build. No severity
overrides are needed.

## .editorconfig (IDE suppression and generated code)

Expand All @@ -80,9 +72,13 @@ Role:

## How expected diagnostics are declared

Use `[SuppressMessage]` on the target class with a `CheckId` that includes the MOG ID:
Use `[SuppressMessage]` with a `CheckId` that includes the MOG ID:
- Example: `[SuppressMessage("MethodOverloadsGenerator", "MOG012")]`

Tests interpret these attributes as **expected diagnostics** for that class, even if the suppression is only
used for documentation/clarity in the source.
Place it on the class or member that contains the diagnostic location. It does two things:
- Tests interpret it as an **expected diagnostic** for the enclosing class.
- It suppresses the diagnostic in the real build and in the IDE.

`MOG002` is reported at the `typeof` reference in the target's `Matchers`, so its `[SuppressMessage]` belongs on
the target class, not on the matcher.

10 changes: 10 additions & 0 deletions docs/generator.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,16 @@ Diagnostics are reported by the analyzer (not by the generator):
- Analyzer: src/Tenekon.MethodOverloads.SourceGenerator/MethodOverloadsDiagnosticsAnalyzer.cs
- Generator: src/Tenekon.MethodOverloads.SourceGenerator/MethodOverloadsGenerator.cs

The analyzer analyzes each type on its own (symbol action) with the same parsing and planning logic as the
generator:
- Diagnostics are reported live in the IDE, not only at the end of a build.
- Each type reports only diagnostics located in its own declaration; diagnostics at a matcher are reported when the
matcher type itself is analyzed.
- Locations are in source, so `#pragma warning disable`, `[SuppressMessage]` and per-file `.editorconfig`
severities apply.
- MOG002 is evaluated per target: it is reported at the `typeof` reference in `Matchers` when a matcher method has
no subsequence match in that target.

Attributes-only mode suppresses diagnostics the same way it suppresses overload generation.

## 11) Entry point & files
Expand Down
2 changes: 1 addition & 1 deletion docs/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ Each file defines:
Non-test helpers (fixtures, data, models) live under:
- tests/Tenekon.MethodOverloads.SourceGenerator.Tests/Infrastructure

More detail on the acceptance criterias project (including editorconfig/globalconfig behavior) is in:
More detail on the acceptance criterias project (including editorconfig behavior and expected diagnostics) is in:
- docs/acceptance-criterias.md

## Running tests
Expand Down
10 changes: 0 additions & 10 deletions ref/Tenekon.MethodOverloads.AcceptanceCriterias/.globalconfig

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Diagnostics.CodeAnalysis;

namespace Tenekon.MethodOverloads.AcceptanceCriterias.Bug;

public sealed class Bug_Class_1_Constraint_1;
Expand Down Expand Up @@ -39,6 +41,8 @@ Bug_Class_1_ReturnType Create(
Bug_Class_1_Type_4<TConstraint>? param_4);
}

// The bucket declares no methods, so the matcher has no match here (MOG002 at the typeof reference).
[SuppressMessage("MethodOverloadsGenerator", "MOG002")]
[GenerateMethodOverloads(Matchers = [typeof(Bug_Class_1_ICommandRuntimeFactoryMatchers)])]
public static partial class Bug_Class_1_Bucket
{
Expand Down
3 changes: 2 additions & 1 deletion ref/Tenekon.MethodOverloads.AcceptanceCriterias/Class_21.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,12 @@ public static void Parameterless() { }

internal interface Class_21_Matcher
{
[SuppressMessage("MethodOverloadsGenerator", "MOG002")]
[GenerateOverloads(nameof(m))]
void MatcherSignature(int m);
}

// 2) Matcher without subsequence match (reported at the typeof reference of the target).
[SuppressMessage("MethodOverloadsGenerator", "MOG002")]
[GenerateMethodOverloads(Matchers = [typeof(Class_21_Matcher)])]
public abstract class Class_21_Matched
{
Expand Down
6 changes: 3 additions & 3 deletions ref/Tenekon.MethodOverloads.AcceptanceCriterias/Class_25.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ namespace Tenekon.MethodOverloads.AcceptanceCriterias;

internal interface Class_25_MatcherUnused
{
[SuppressMessage("MethodOverloadsGenerator", "MOG002")]
[GenerateOverloads(nameof(param_a))]
void Matcher_1(Guid param_a);
}

/// <summary>
/// Unused matcher method should produce MOG002 once per matcher method.
/// Matcher method without a match in this target produces MOG002 at the typeof reference.
/// </summary>
[SuppressMessage("MethodOverloadsGenerator", "MOG002")]
[GenerateMethodOverloads(Matchers = [typeof(Class_25_MatcherUnused)])]
public sealed class Class_25_Target
{
Expand All @@ -21,6 +21,7 @@ public void Case_1(int param_1, string? param_2, bool param_3) { }
/// <summary>
/// Mixed matcher set: one matcher applies, one does not (MOG002 expected for the unused one).
/// </summary>
[SuppressMessage("MethodOverloadsGenerator", "MOG002")]
[GenerateMethodOverloads(Matchers = [typeof(Class_25_MatcherMixed)])]
public sealed class Class_25_MixedTarget
{
Expand All @@ -32,7 +33,6 @@ internal interface Class_25_MatcherMixed
[GenerateOverloads(nameof(param_b))]
void Matcher_1(int param_a, string? param_b);

[SuppressMessage("MethodOverloadsGenerator", "MOG002")]
[GenerateOverloads(nameof(param_a))]
void Matcher_2(Guid param_a);
}
Expand Down
4 changes: 4 additions & 0 deletions ref/Tenekon.MethodOverloads.AcceptanceCriterias/Class_39.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Diagnostics.CodeAnalysis;

namespace Tenekon.MethodOverloads.AcceptanceCriterias;

public readonly struct Class_39_Case_1_Param_1;
Expand Down Expand Up @@ -41,6 +43,8 @@ public abstract class Class_39_1
public abstract void Case_2(Class_39_Case_2_Param_1 param_1, Class_39_Case_2_Param_2 param_2);
}

// Matcher_2 has no match in Class_39_2 (MOG002 at the typeof reference).
[SuppressMessage("MethodOverloadsGenerator", "MOG002")]
[GenerateMethodOverloads(Matchers = [typeof(Class_39_Matcher)])]
[OverloadGenerationOptions(SubsequenceStrategy = OverloadSubsequenceStrategy.UniqueBySignature)]
public abstract class Class_39_2
Expand Down
4 changes: 4 additions & 0 deletions ref/Tenekon.MethodOverloads.AcceptanceCriterias/Class_40.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Diagnostics.CodeAnalysis;

namespace Tenekon.MethodOverloads.AcceptanceCriterias;

public readonly struct Class_40_Case_1_Param_1;
Expand Down Expand Up @@ -33,6 +35,8 @@ public abstract class Class_40_1
protected internal abstract void Case_2(Class_40_Case_2_Param_1 param_1, Class_40_Case_2_Param_2 param_2);
}

// Matcher_2 has no match in Class_40_2 (MOG002 at the typeof reference).
[SuppressMessage("MethodOverloadsGenerator", "MOG002")]
[GenerateMethodOverloads(Matchers = [typeof(Class_40_Matcher)])]
[OverloadGenerationOptions(OverloadVisibility = OverloadVisibility.Public)]
public abstract class Class_40_2
Expand Down
3 changes: 2 additions & 1 deletion ref/Tenekon.MethodOverloads.AcceptanceCriterias/Class_5.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ internal interface Class_5_Matcher
void Matcher_1(bool param_1, CancellationToken param_2);

[OverloadGenerationOptions(RangeAnchorMatchMode = RangeAnchorMatchMode.TypeAndName)]
[SuppressMessage("MethodOverloadsGenerator", "MOG002")]
[GenerateOverloads(EndExclusive = nameof(param_2))]
void Matcher_2(string? param_1, bool param_2, CancellationToken param_3);
}

// Matcher_2 has no match in Class_5 (MOG002 at the typeof reference).
[SuppressMessage("MethodOverloadsGenerator", "MOG002")]
[GenerateMethodOverloads(Matchers = [typeof(Class_5_Matcher)])]
public abstract class Class_5
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
</ItemGroup>

<ItemGroup>
<None Include=".globalconfig"/>
<EditorConfig Include=".editorconfig"/>
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -359,10 +359,12 @@ private void BuildMethods()
{
if (entry.Value) continue;

Report(
GeneratorDiagnostics.MatcherHasNoSubsequenceMatch,
matcherLocations.TryGetValue(entry.Key, out var location) ? location : null,
entry.Key.MethodName);
_diagnostics.Add(
new EquatableDiagnostic(
GeneratorDiagnostics.MatcherHasNoSubsequenceMatch,
matcherLocations.TryGetValue(entry.Key, out var location) ? location : null,
new EquatableArray<string>([entry.Key.MethodName]),
entry.Key.ContainingTypeDisplay));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ internal static class GeneratorDiagnostics
public static readonly DiagnosticDescriptor MatcherHasNoSubsequenceMatch = new(
"MOG002",
"Matcher has no subsequence match",
"Matcher '{0}' has no subsequence match for any target method",
"Matcher '{0}' has no subsequence match for any target method in '{1}'",
"MethodOverloadsGenerator",
DiagnosticSeverity.Info,
isEnabledByDefault: true);
Expand Down
Loading
Loading