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
8 changes: 8 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -309,3 +309,11 @@ dotnet_diagnostic.CA2000.severity = error # CA2000: Dispose object
dotnet_diagnostic.CA1515.severity = none # CA1515: Consider making public types internal
dotnet_diagnostic.CA1708.severity = none # CA1708: Identifiers should differ by more than case
dotnet_diagnostic.CA1716.severity = none # CA1716: Identifiers should not match keywords

MA0053.public_class_should_be_sealed = true
MA0053.exceptions_should_be_sealed = true

dotnet_diagnostic.MA0004.severity = none
dotnet_diagnostic.MA0048.severity = none
dotnet_diagnostic.MA0051.severity = none
dotnet_diagnostic.MA0053.severity = warning
4 changes: 4 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,8 @@
<PackageVersion Include="Verify.TUnit" Version="28.9.0" />
<PackageVersion Include="xunit.v3.assert" Version="1.0.1" />
</ItemGroup>

<ItemGroup>
<GlobalPackageReference Include="Meziantou.Analyzer" Version="2.0.186" />
</ItemGroup>
</Project>
2 changes: 1 addition & 1 deletion benchmarks/Benchmark.Behaviors/Benchmark.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public sealed record SomeRequest(Guid Id)

public sealed record SomeResponse(Guid Id);

public class SomeService
public sealed class SomeService
{
private static readonly SomeResponse s_response = new(Guid.NewGuid());

Expand Down
1 change: 1 addition & 0 deletions benchmarks/Benchmark.Large/Types.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Immediate.Handlers.Shared;

#pragma warning disable IDE0060
#pragma warning disable MA0022

namespace Immediate.Handlers.Benchmarks;

Expand Down
2 changes: 1 addition & 1 deletion samples/Normal/Behaviors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

namespace Normal;

public class LoggingBehavior<TRequest, TResponse>(ILogger<LoggingBehavior<TRequest, TResponse>> logger)
public sealed class LoggingBehavior<TRequest, TResponse>(ILogger<LoggingBehavior<TRequest, TResponse>> logger)
: Behavior<TRequest, TResponse>
{
public override async ValueTask<TResponse> HandleAsync(TRequest request, CancellationToken cancellationToken)
Expand Down
4 changes: 2 additions & 2 deletions samples/Normal/GetWeatherForecast.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ public static partial class GetWeatherForecast
"Scorching",
];

public record Query;
public sealed record Query;

public record Response(DateOnly Date, int TemperatureC, string? Summary)
public sealed record Response(DateOnly Date, int TemperatureC, string? Summary)
{
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
Expand Down
19 changes: 19 additions & 0 deletions src/Common/ITypeSymbolExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,25 @@ namespace Immediate.Handlers;

internal static class ITypeSymbolExtensions
{
public static bool IsHandlerAttribute(this ITypeSymbol? typeSymbol) =>
typeSymbol is
{
Name: "HandlerAttribute",
ContainingNamespace:
{
Name: "Shared",
ContainingNamespace:
{
Name: "Handlers",
ContainingNamespace:
{
Name: "Immediate",
ContainingNamespace.IsGlobalNamespace: true,
},
},
},
};

public static bool IsBehavior2(this ITypeSymbol typeSymbol) =>
typeSymbol is
{
Expand Down
8 changes: 3 additions & 5 deletions src/Immediate.Handlers.Analyzers/HandlerClassAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ private static void AnalyzeSymbol(SymbolAnalysisContext context)

if (!namedTypeSymbol
.GetAttributes()
.Any(x => x.AttributeClass?.ToString() == "Immediate.Handlers.Shared.HandlerAttribute")
.Any(x => x.AttributeClass.IsHandlerAttribute())
)
{
return;
Expand Down Expand Up @@ -178,10 +178,8 @@ private static void AnalyzeSymbol(SymbolAnalysisContext context)
case [var methodSymbol]:
{
token.ThrowIfCancellationRequested();
if (methodSymbol.ReturnType is INamedTypeSymbol returnTypeSymbol
&& returnTypeSymbol.ConstructedFrom.ToString() is not (
"System.Threading.Tasks.ValueTask<TResult>"
or "System.Threading.Tasks.ValueTask")
if (methodSymbol.ReturnType is INamedTypeSymbol { ConstructedFrom: { } from }
&& !from.IsValueTask() && !from.IsValueTask1()
)
{
context.ReportDiagnostic(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
namespace Immediate.Handlers.CodeFixes;

[ExportCodeFixProvider(LanguageNames.CSharp)]
public class HandlerMethodMustExistCodeFixProvider : CodeFixProvider
public sealed class HandlerMethodMustExistCodeFixProvider : CodeFixProvider
{
public override ImmutableArray<string> FixableDiagnosticIds { get; } =
ImmutableArray.Create([DiagnosticIds.IHR0001HandlerMethodMustExist]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace Immediate.Handlers.Generators.ImmediateHandlers;

public partial class ImmediateHandlersGenerator
public sealed partial class ImmediateHandlersGenerator
{
[ExcludeFromCodeCoverage]
private sealed record Behavior
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace Immediate.Handlers.Generators.ImmediateHandlers;

public partial class ImmediateHandlersGenerator
public sealed partial class ImmediateHandlersGenerator
{
private static EquatableReadOnlyList<Behavior?> TransformBehaviors(
GeneratorAttributeSyntaxContext context,
Expand Down Expand Up @@ -132,7 +132,7 @@ private static (bool Valid, string? Constraint) GetConstraintType(ITypeParameter
ITypeParameterSymbol s,
]
}
&& s.Name == parameter.Name
&& s.Name.Equals(parameter.Name, StringComparison.OrdinalIgnoreCase)
)
{
displayString = displayString.Replace(parameter.Name, "_TRequest_");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace Immediate.Handlers.Generators.ImmediateHandlers;

public partial class ImmediateHandlersGenerator
public sealed partial class ImmediateHandlersGenerator
{
private static Handler? TransformHandler(
GeneratorAttributeSyntaxContext context,
Expand Down Expand Up @@ -111,7 +111,7 @@ CancellationToken cancellationToken
return new()
{
Name = name,
Implements = implements.Distinct().ToEquatableReadOnlyList(),
Implements = implements.Distinct(StringComparer.Ordinal).ToEquatableReadOnlyList(),
};
}

Expand Down
14 changes: 8 additions & 6 deletions src/Immediate.Handlers.Generators/ImmediateHandlersGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
namespace Immediate.Handlers.Generators.ImmediateHandlers;

[Generator]
public partial class ImmediateHandlersGenerator : IIncrementalGenerator
public sealed partial class ImmediateHandlersGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
Expand Down Expand Up @@ -191,8 +191,10 @@ Template template
GenericType responseType,
IEnumerable<Behavior?> enumerable) =>
enumerable
.Where(b => b is null || ValidateType(b.RequestType, requestType))
.Where(b => b is null || ValidateType(b.ResponseType, responseType))
.Where(b =>
(b is null || ValidateType(b.RequestType, requestType))
&& (b is null || ValidateType(b.ResponseType, responseType))
)
.ToList();

private sealed record RenderBehavior
Expand All @@ -203,7 +205,7 @@ private sealed record RenderBehavior

private static List<RenderBehavior> BuildRenderBehaviors(List<Behavior?> pipelineBehaviors)
{
var typesCount = new Dictionary<string, int>()
var typesCount = new Dictionary<string, int>(StringComparer.Ordinal)
{
["HandleBehavior"] = 1,
};
Expand All @@ -224,7 +226,7 @@ string GetVariableNameSuffix(string typeName)
NonGenericTypeName = b!.NonGenericTypeName,
VariableName = b.Name[0..1].ToLowerInvariant()
+ b.Name[1..]
+ GetVariableNameSuffix(b.Name)
+ GetVariableNameSuffix(b.Name),
})
.ToList();
#pragma warning restore CA1308 // Normalize strings to uppercase
Expand All @@ -234,7 +236,7 @@ string GetVariableNameSuffix(string typeName)

private static bool ValidateType(string? type, GenericType implementedTypes) =>
type is null
|| implementedTypes.Implements.Contains(type.Replace("_TRequest_", implementedTypes.Name));
|| implementedTypes.Implements.Contains(type.Replace("_TRequest_", implementedTypes.Name), StringComparer.Ordinal);

private static Template GetTemplate(string name)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ private static IServiceCollection ConfigureBehaviors(IServiceCollection services
}
}

public class BehaviorWalker
public sealed class BehaviorWalker
{
public IList<string> BehaviorsRan { get; init; } = [];
}

public class BehaviorA<TRequest, TResponse>(BehaviorWalker walker) : Behavior<TRequest, TResponse> where TRequest : A
public sealed class BehaviorA<TRequest, TResponse>(BehaviorWalker walker) : Behavior<TRequest, TResponse> where TRequest : A
{
public override async ValueTask<TResponse> HandleAsync(TRequest request, CancellationToken cancellationToken)
{
Expand All @@ -41,7 +41,7 @@ public override async ValueTask<TResponse> HandleAsync(TRequest request, Cancell
}
}

public class BehaviorB<TRequest, TResponse>(BehaviorWalker walker) : Behavior<TRequest, TResponse> where TRequest : B
public sealed class BehaviorB<TRequest, TResponse>(BehaviorWalker walker) : Behavior<TRequest, TResponse> where TRequest : B
{
public override async ValueTask<TResponse> HandleAsync(TRequest request, CancellationToken cancellationToken)
{
Expand All @@ -50,7 +50,7 @@ public override async ValueTask<TResponse> HandleAsync(TRequest request, Cancell
}
}

public class BehaviorC<TRequest, TResponse>(BehaviorWalker walker) : Behavior<TRequest, TResponse> where TRequest : C
public sealed class BehaviorC<TRequest, TResponse>(BehaviorWalker walker) : Behavior<TRequest, TResponse> where TRequest : C
{
public override async ValueTask<TResponse> HandleAsync(TRequest request, CancellationToken cancellationToken)
{
Expand All @@ -59,7 +59,7 @@ public override async ValueTask<TResponse> HandleAsync(TRequest request, Cancell
}
}

public class BehaviorD<TRequest, TResponse>(BehaviorWalker walker) : Behavior<TRequest, TResponse> where TRequest : D
public sealed class BehaviorD<TRequest, TResponse>(BehaviorWalker walker) : Behavior<TRequest, TResponse> where TRequest : D
{
public override async ValueTask<TResponse> HandleAsync(TRequest request, CancellationToken cancellationToken)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ private static ValueTask<int> HandleAsync(
}
}

public class HandlerAbstractionTests
public sealed class HandlerAbstractionTests
{
[Test]
public async Task NoBehaviorShouldReturnExpectedResponseForAbstraction()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace Immediate.Handlers.FunctionalTests.MultipleBehaviors;

public class Behavior1<TRequest, TResponse> : Behavior<TRequest, TResponse>
public sealed class Behavior1<TRequest, TResponse> : Behavior<TRequest, TResponse>
where TRequest : List<string>
{
public override async ValueTask<TResponse> HandleAsync(TRequest request, CancellationToken cancellationToken)
Expand All @@ -17,7 +17,7 @@ public override async ValueTask<TResponse> HandleAsync(TRequest request, Cancell
}
}

public class Behavior2<TRequest, TResponse> : Behavior<TRequest, TResponse>
public sealed class Behavior2<TRequest, TResponse> : Behavior<TRequest, TResponse>
where TRequest : List<string>
{
public override async ValueTask<TResponse> HandleAsync(TRequest request, CancellationToken cancellationToken)
Expand All @@ -40,7 +40,7 @@ public override async ValueTask<TResponse> HandleAsync(TRequest request, Cancell
)]
public static partial class MultipleBehaviorHandler
{
public class Query : List<string>;
public sealed class Query : List<string>;

private static async ValueTask<int> HandleAsync(Query query, CancellationToken cancellationToken)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ private static ValueTask<int> Handle(

public record AddendProvider(int Addend);

public class ParameterizedTests
public sealed class ParameterizedTests
{
[Test]
public async Task NoBehaviorShouldReturnExpectedResponse()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Query query
}
}

public class ParameterlessTests
public sealed class ParameterlessTests
{
[Test]
public async Task NoBehaviorShouldReturnExpectedResponse()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
namespace Immediate.Handlers.Tests.AnalyzerTests.BehaviorAnalyzerTests;

[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1724:Type names should not match namespaces", Justification = "Not being consumed by other code")]
public partial class Tests
public sealed partial class Tests
{
[Test]
public async Task BehaviorTypeDoesNotUseUnboundedReference_Alerts() =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
namespace Immediate.Handlers.Tests.AnalyzerTests.BehaviorAnalyzerTests;

[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1724:Type names should not match namespaces", Justification = "Not being consumed by other code")]
public partial class Tests
public sealed partial class Tests
{
[Test]
public async Task BehaviorTypeIsValid_DoesNotAlert() =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace Immediate.Handlers.Tests.AnalyzerTests.HandlerClassAnalyzerTests;

public partial class Tests
public sealed partial class Tests
{
[Test]
public async Task HandlerClassNested_DoesAlert() =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
namespace Immediate.Handlers.Tests.CodeFixTests;

[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1724:Type names should not match namespaces", Justification = "Not being consumed by other code")]
public partial class Tests
public sealed partial class Tests
{
[Test]
public async Task HandleMethodDoesNotExist() =>
Expand Down
18 changes: 9 additions & 9 deletions tests/Immediate.Handlers.Tests/GeneratorTests/BehaviorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,14 @@ public interface ILogger<T>;
DriverReferenceAssemblies.Msdi =>
["Immediate.Handlers.Generators/Immediate.Handlers.Generators.ImmediateHandlers.ImmediateHandlersGenerator/IH.ServiceCollectionExtensions.g.cs"],

_ => throw new UnreachableException(),
DriverReferenceAssemblies.None or _ => throw new UnreachableException(),
},
],
result.GeneratedTrees.Select(t => t.FilePath.Replace("\\", "/", StringComparison.Ordinal))
result.GeneratedTrees.Select(t => t.FilePath.Replace('\\', '/'))
);

_ = await Verify(result)
.UseParameters(string.Join("_", assemblies));
.UseParameters(string.Join('_', assemblies));
}

[Test]
Expand Down Expand Up @@ -228,14 +228,14 @@ public interface ILogger<T>;
DriverReferenceAssemblies.Msdi =>
["Immediate.Handlers.Generators/Immediate.Handlers.Generators.ImmediateHandlers.ImmediateHandlersGenerator/IH.ServiceCollectionExtensions.g.cs"],

_ => throw new UnreachableException(),
DriverReferenceAssemblies.None or _ => throw new UnreachableException(),
},
],
result.GeneratedTrees.Select(t => t.FilePath.Replace("\\", "/", StringComparison.Ordinal))
result.GeneratedTrees.Select(t => t.FilePath.Replace('\\', '/'))
);

_ = await Verify(result)
.UseParameters(string.Join("_", assemblies));
.UseParameters(string.Join('_', assemblies));
}

[Test]
Expand Down Expand Up @@ -297,13 +297,13 @@ CancellationToken __
DriverReferenceAssemblies.Msdi =>
["Immediate.Handlers.Generators/Immediate.Handlers.Generators.ImmediateHandlers.ImmediateHandlersGenerator/IH.ServiceCollectionExtensions.g.cs"],

_ => throw new UnreachableException(),
DriverReferenceAssemblies.None or _ => throw new UnreachableException(),
},
],
result.GeneratedTrees.Select(t => t.FilePath.Replace("\\", "/", StringComparison.Ordinal))
result.GeneratedTrees.Select(t => t.FilePath.Replace('\\', '/'))
);

_ = await Verify(result)
.UseParameters(string.Join("_", assemblies));
.UseParameters(string.Join('_', assemblies));
}
}
Loading
Loading