Skip to content

Commit

Permalink
Add Meziantou.Analyzers (#180)
Browse files Browse the repository at this point in the history
  • Loading branch information
viceroypenguin authored Jan 18, 2025
1 parent 4a03fe7 commit fc6295e
Show file tree
Hide file tree
Showing 27 changed files with 122 additions and 88 deletions.
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

0 comments on commit fc6295e

Please sign in to comment.