Skip to content
Merged
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: 19 additions & 0 deletions src/Immediate.Handlers.Analyzers/AnalyzerReleases.Shipped.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,22 @@ IHR0012 | ImmediateHandler | Warning | HandlerClassAnalyzer
Rule ID | Category | Severity | Notes
--------|----------|----------|--------------------
IHR0013 | ImmediateHandler | Warning | InvalidIHandlerAnalyzer

## Release 3.0

### New Rules

Rule ID | Category | Severity | Notes
--------|----------|----------|-------
IHR0014 | ImmediateHandler | Error | HandlerClassAnalyzer
IHR0015 | ImmediateHandler | Error | HandlerClassAnalyzer
IHR0016 | ImmediateHandler | Error | HandlerClassAnalyzer
IHR0017 | ImmediateHandler | Error | HandlerClassAnalyzer
IHR0018 | ImmediateHandler | Error | HandlerClassAnalyzer
IHR0019 | ImmediateHandler | Hidden | HandlerClassAnalyzer

### Removed Rules

Rule ID | Category | Severity | Notes
--------|----------|----------|-------
IHR0009 | ImmediateHandler | Error | HandlerClassAnalyzer
16 changes: 0 additions & 16 deletions src/Immediate.Handlers.Analyzers/AnalyzerReleases.Unshipped.md
Original file line number Diff line number Diff line change
@@ -1,16 +0,0 @@
### New Rules

Rule ID | Category | Severity | Notes
--------|----------|----------|-------
IHR0014 | ImmediateHandler | Error | HandlerClassAnalyzer
IHR0015 | ImmediateHandler | Error | HandlerClassAnalyzer
IHR0016 | ImmediateHandler | Error | HandlerClassAnalyzer
IHR0017 | ImmediateHandler | Error | HandlerClassAnalyzer
IHR0018 | ImmediateHandler | Error | HandlerClassAnalyzer

### Removed Rules

Rule ID | Category | Severity | Notes
--------|----------|----------|-------
IHR0009 | ImmediateHandler | Error | HandlerClassAnalyzer

1 change: 1 addition & 0 deletions src/Immediate.Handlers.Analyzers/DiagnosticIds.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ internal static class DiagnosticIds
public const string IHR0016ContainingClassMustBeSealed = "IHR0016";
public const string IHR0017ContainingClassInstanceMembersMustBePrivate = "IHR0017";
public const string IHR0018ContainingClassMustBeStatic = "IHR0018";
public const string IHR0019StaticHandlerCouldBeSealed = "IHR0019";
}
21 changes: 21 additions & 0 deletions src/Immediate.Handlers.Analyzers/HandlerClassAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,18 @@ public sealed class HandlerClassAnalyzer : DiagnosticAnalyzer
description: "Containing classes must be static to prevent incorrect usage."
);

public static readonly DiagnosticDescriptor StaticHandlerCouldBeSealed =
new(
id: DiagnosticIds.IHR0019StaticHandlerCouldBeSealed,
title: "Static handler may be converted to a sealed handler",
messageFormat: "Class '{0}' can be converted to be a `sealed` handler",
category: "ImmediateHandler",
defaultSeverity: DiagnosticSeverity.Hidden,
isEnabledByDefault: true,
description: "Static handler may be converted to a sealed handler.",
customTags: [WellKnownDiagnosticTags.NotConfigurable]
);

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } =
ImmutableArray.Create(
[
Expand All @@ -142,6 +154,7 @@ public sealed class HandlerClassAnalyzer : DiagnosticAnalyzer
ContainingClassMustBeSealed,
ContainingClassInstanceMembersMustBePrivate,
ContainingClassMustBeStatic,
StaticHandlerCouldBeSealed,
]);

public override void Initialize(AnalysisContext context)
Expand Down Expand Up @@ -294,6 +307,14 @@ private static void AnalyzeStaticHandler(SymbolAnalysisContext context, INamedTy
AnalyzeReturnType(context, method);
AnalyzeCancellationToken(context, method);

context.ReportDiagnostic(
Diagnostic.Create(
StaticHandlerCouldBeSealed,
containerSymbol.Locations[0],
containerSymbol.Name
)
);

if (method.Parameters.Length == 0
|| (method.Parameters.Length == 1 && method.Parameters[0].Type.IsCancellationToken()))
{
Expand Down
11 changes: 9 additions & 2 deletions src/Immediate.Handlers.CodeFixes/RefactoringExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Text;

namespace Immediate.Handlers.CodeFixes;

[ExcludeFromCodeCoverage]
internal static class RefactoringExtensions
{
internal static void Deconstruct(this CodeRefactoringContext context, out Document document, out TextSpan span, out CancellationToken cancellationToken)
internal static void Deconstruct(
this CodeFixContext context,
out Document document,
out TextSpan span,
out ImmutableArray<Diagnostic> diagnostics,
out CancellationToken cancellationToken)
{
document = context.Document;
span = context.Span;
diagnostics = context.Diagnostics;
cancellationToken = context.CancellationToken;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,104 +1,69 @@
using System.Collections.Immutable;
using Immediate.Handlers.Analyzers;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;

namespace Immediate.Handlers.CodeFixes;

[ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = "Convert to instance handler")]
public sealed class StaticToSealedHandlerRefactoringProvider : CodeRefactoringProvider
[ExportCodeFixProvider(LanguageNames.CSharp)]
public sealed class StaticToSealedHandlerCodeFixProvider : CodeFixProvider
{
public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
public override ImmutableArray<string> FixableDiagnosticIds { get; } =
ImmutableArray.Create([DiagnosticIds.IHR0019StaticHandlerCouldBeSealed]);

public override FixAllProvider? GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;

public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var (document, span, token) = context;
var (document, span, diagnostics, token) = context;
token.ThrowIfCancellationRequested();

if (await document.GetRequiredSyntaxRootAsync(token) is not CompilationUnitSyntax root)
return;

var model = await document.GetRequiredSemanticModelAsync(token);

switch (root.FindNode(span))
{
case ClassDeclarationSyntax cds:
{
if (model.GetDeclaredSymbol(cds, token) is not INamedTypeSymbol { IsStatic: true } container)
return;

if (!container.GetAttributes().Any(a => a.AttributeClass.IsHandlerAttribute()))
return;

var method = container.GetMembers()
.OfType<IMethodSymbol>()
.FirstOrDefault(m => m is { IsStatic: true, Name: "Handle" or "HandleAsync" });

if (method is null)
return;

var mds = (MethodDeclarationSyntax)await method
.DeclaringSyntaxReferences[0]
.GetSyntaxAsync(token);

var service = new RefactoringService(
document,
model,
root,
cds,
mds
);

context.RegisterRefactoring(
CodeAction.Create(
title: "Convert to instance handler",
createChangedDocument: service.ConvertToInstanceHandler,
equivalenceKey: nameof(StaticToSealedHandlerRefactoringProvider)
)
);

break;
}

case MethodDeclarationSyntax mds:
{
if (model.GetDeclaredSymbol(mds, token) is not IMethodSymbol
{
IsStatic: true,
Name: "Handle" or "HandleAsync",
ContainingType: INamedTypeSymbol { IsStatic: true } container,
} method)
{
return;
}

if (!container.GetAttributes().Any(a => a.AttributeClass.IsHandlerAttribute()))
return;

var service = new RefactoringService(
document,
model,
root,
(ClassDeclarationSyntax)mds.Parent!,
mds
);

context.RegisterRefactoring(
CodeAction.Create(
title: "Convert to instance handler",
createChangedDocument: service.ConvertToInstanceHandler,
equivalenceKey: nameof(StaticToSealedHandlerRefactoringProvider)
)
);
if (root.FindNode(span) is not ClassDeclarationSyntax cds)
return;

if (model.GetDeclaredSymbol(cds, token) is not INamedTypeSymbol { IsStatic: true } container)
return;

break;
}
if (!container.GetAttributes().Any(a => a.AttributeClass.IsHandlerAttribute()))
return;

default:
break;
}
}
var method = container.GetMembers()
.OfType<IMethodSymbol>()
.FirstOrDefault(m => m is { IsStatic: true, Name: "Handle" or "HandleAsync" });

if (method is null)
return;

var mds = (MethodDeclarationSyntax)await method
.DeclaringSyntaxReferences[0]
.GetSyntaxAsync(token);

var service = new RefactoringService(
document,
model,
root,
cds,
mds
);

context.RegisterCodeFix(
CodeAction.Create(
title: "Convert to instance handler",
createChangedDocument: service.ConvertToInstanceHandler,
equivalenceKey: nameof(StaticToSealedHandlerCodeFixProvider)
),
diagnostics[0]
);
}
}

file sealed class RefactoringService(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ await AnalyzerTestHelpers.CreateAnalyzerTest<HandlerClassAnalyzer>(
using Immediate.Handlers.Shared;

[Handler]
public static partial class GetUsersQuery
public static partial class {|IHR0019:GetUsersQuery|}
{
public record Query;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ await AnalyzerTestHelpers.CreateAnalyzerTest<HandlerClassAnalyzer>(
using Immediate.Handlers.Shared;

[Handler]
public static partial class GetUsersQuery
public static partial class {|IHR0019:GetUsersQuery|}
{
public record Query;

private static ValueTask<int> HandleAsync(
private static ValueTask<int> {|IHR0019:HandleAsync|}(
Query _,
CancellationToken token)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ await AnalyzerTestHelpers.CreateAnalyzerTest<HandlerClassAnalyzer>(
using Immediate.Handlers.Shared;

[Handler]
public static partial class GetUsersQuery
public static partial class {|IHR0019:GetUsersQuery|}
{
public record Query;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ await AnalyzerTestHelpers.CreateAnalyzerTest<HandlerClassAnalyzer>(
using Immediate.Handlers.Shared;

[Handler]
public static partial class GetUsersQuery
public static partial class {|IHR0019:GetUsersQuery|}
{
public record Query;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ await AnalyzerTestHelpers.CreateAnalyzerTest<HandlerClassAnalyzer>(
using Immediate.Handlers.Shared;

[Handler]
public static partial class GetUsersQuery
public static partial class {|IHR0019:GetUsersQuery|}
{
public record Query;

Expand All @@ -47,7 +47,7 @@ await AnalyzerTestHelpers.CreateAnalyzerTest<HandlerClassAnalyzer>(
using Immediate.Handlers.Shared;

[Handler]
public static partial class GetUsersQuery
public static partial class {|IHR0019:GetUsersQuery|}
{
public record Query;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ await AnalyzerTestHelpers.CreateAnalyzerTest<HandlerClassAnalyzer>(
using Immediate.Handlers.Shared;

[Handler]
public static partial class GetUsersQuery
public static partial class {|IHR0019:GetUsersQuery|}
{
public record Query;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ await AnalyzerTestHelpers.CreateAnalyzerTest<HandlerClassAnalyzer>(
public static partial class Wrapper
{
[Handler]
public static class {|IHR0005:GetUsersQuery|}
public static class {|IHR0019:{|IHR0005:GetUsersQuery|}|}
{
public record Query;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ await AnalyzerTestHelpers.CreateAnalyzerTest<HandlerClassAnalyzer>(
using Immediate.Handlers.Shared;

[Handler]
public partial class {|IHR0018:GetUsersQuery|}
public partial class {|IHR0019:{|IHR0018:GetUsersQuery|}|}
{
public record Query;

Expand Down
Loading