diff --git a/Directory.Packages.props b/Directory.Packages.props index df3eb7f1..8e1c6513 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,15 +4,15 @@ - + - - + + - + @@ -22,17 +22,17 @@ - - + + - + - - - + + + diff --git a/src/Immediate.Handlers.Analyzers/BehaviorsAnalyzer.cs b/src/Immediate.Handlers.Analyzers/BehaviorsAnalyzer.cs index 3eca3279..c9c96757 100644 --- a/src/Immediate.Handlers.Analyzers/BehaviorsAnalyzer.cs +++ b/src/Immediate.Handlers.Analyzers/BehaviorsAnalyzer.cs @@ -69,12 +69,7 @@ private void AnalyzeOperation(OperationAnalysisContext context) if (context.Operation is not IAttributeOperation { Operation: IObjectCreationOperation attribute }) return; - var behaviorsAttributeSymbol = context.Compilation.GetTypeByMetadataName("Immediate.Handlers.Shared.BehaviorsAttribute"); - if (behaviorsAttributeSymbol is null) - return; - - token.ThrowIfCancellationRequested(); - if (!SymbolEqualityComparer.Default.Equals(attribute.Type?.OriginalDefinition, behaviorsAttributeSymbol)) + if (!attribute.Type.IsBehaviorsAttribute()) return; if (attribute.Arguments.Length != 1) @@ -86,10 +81,22 @@ private void AnalyzeOperation(OperationAnalysisContext context) token.ThrowIfCancellationRequested(); var array = attribute.Arguments[0].Value; - var compilation = context.Compilation; - var arrayTypeSymbol = compilation.CreateArrayTypeSymbol(compilation.GetTypeByMetadataName("System.Type")!, 1); - if (!SymbolEqualityComparer.Default.Equals(array.Type, arrayTypeSymbol) - || array.ChildOperations.Count != 2 + if (array is not + { + Type: IArrayTypeSymbol + { + ElementType: + { + Name: "Type", + ContainingNamespace: + { + Name: "System", + ContainingNamespace.IsGlobalNamespace: true, + }, + }, + }, + ChildOperations.Count: 2 + } || array.ChildOperations.ElementAt(1) is not IArrayInitializerOperation aio) { // note: this will already be a compiler error anyway @@ -117,7 +124,7 @@ private void AnalyzeOperation(OperationAnalysisContext context) var location = toes.Type.GetLocation(); var originalDefinition = behaviorType.OriginalDefinition; - if (!ImplementsBaseClass(originalDefinition, baseBehaviorSymbol)) + if (!originalDefinition.ImplementsBehavior()) { context.ReportDiagnostic( Diagnostic.Create( @@ -148,10 +155,4 @@ private void AnalyzeOperation(OperationAnalysisContext context) } } } - - private static bool ImplementsBaseClass(INamedTypeSymbol typeSymbol, INamedTypeSymbol typeToCheck) => - SymbolEqualityComparer.Default.Equals(typeSymbol, typeToCheck) - || (typeSymbol.BaseType is not null - && ImplementsBaseClass(typeSymbol.BaseType.OriginalDefinition, typeToCheck) - ); } diff --git a/src/Immediate.Handlers.Analyzers/ITypeSymbolExtensions.cs b/src/Immediate.Handlers.Analyzers/ITypeSymbolExtensions.cs new file mode 100644 index 00000000..031ac47a --- /dev/null +++ b/src/Immediate.Handlers.Analyzers/ITypeSymbolExtensions.cs @@ -0,0 +1,86 @@ +using Microsoft.CodeAnalysis; + +namespace Immediate.Handlers.Analyzers; + +internal static class ITypeSymbolExtensions +{ + public static bool IsBehaviorsAttribute(this ITypeSymbol? typeSymbol) => + typeSymbol is + { + Name: "BehaviorsAttribute", + ContainingNamespace: + { + Name: "Shared", + ContainingNamespace: + { + Name: "Handlers", + ContainingNamespace: + { + Name: "Immediate", + ContainingNamespace.IsGlobalNamespace: true, + }, + }, + }, + }; + + public static bool IsRenderModeAttribute(this ITypeSymbol? typeSymbol) => + typeSymbol is + { + Name: "RenderModeAttribute", + ContainingNamespace: + { + Name: "Shared", + ContainingNamespace: + { + Name: "Handlers", + ContainingNamespace: + { + Name: "Immediate", + ContainingNamespace.IsGlobalNamespace: true, + }, + }, + }, + }; + + public static bool IsRenderMode(this ITypeSymbol? typeSymbol) => + typeSymbol is + { + Name: "RenderMode", + ContainingNamespace: + { + Name: "Shared", + ContainingNamespace: + { + Name: "Handlers", + ContainingNamespace: + { + Name: "Immediate", + ContainingNamespace.IsGlobalNamespace: true, + }, + }, + }, + }; + + public static bool IsBehavior2(this ITypeSymbol typeSymbol) => + typeSymbol is + { + MetadataName: "Behavior`2", + ContainingNamespace: + { + Name: "Shared", + ContainingNamespace: + { + Name: "Handlers", + ContainingNamespace: + { + Name: "Immediate", + ContainingNamespace.IsGlobalNamespace: true, + }, + }, + }, + }; + + public static bool ImplementsBehavior(this INamedTypeSymbol typeSymbol) => + typeSymbol.IsBehavior2() + || (typeSymbol.BaseType is not null && ImplementsBehavior(typeSymbol.BaseType.OriginalDefinition)); +} diff --git a/src/Immediate.Handlers.Analyzers/RenderModeAnalyzer.cs b/src/Immediate.Handlers.Analyzers/RenderModeAnalyzer.cs index b421e713..7cb1d22f 100644 --- a/src/Immediate.Handlers.Analyzers/RenderModeAnalyzer.cs +++ b/src/Immediate.Handlers.Analyzers/RenderModeAnalyzer.cs @@ -44,18 +44,14 @@ private void AnalyzeOperation(OperationAnalysisContext context) if (context.Operation is not IAttributeOperation { Operation: IObjectCreationOperation attribute }) return; - var renderModeAttributeSymbol = context.Compilation.GetTypeByMetadataName("Immediate.Handlers.Shared.RenderModeAttribute"); - - token.ThrowIfCancellationRequested(); - if (!SymbolEqualityComparer.Default.Equals(attribute.Type?.OriginalDefinition, renderModeAttributeSymbol)) + if (!attribute.Type.IsRenderModeAttribute()) return; token.ThrowIfCancellationRequested(); - var renderModeSymbol = context.Compilation.GetTypeByMetadataName("Immediate.Handlers.Shared.RenderMode"); if (attribute.Arguments.Length != 1 || attribute.Arguments[0].Value is not IFieldReferenceOperation value - || !SymbolEqualityComparer.Default.Equals(value.Type?.OriginalDefinition, renderModeSymbol) + || !value.Type.IsRenderMode() || value.Member.Name is "None") { context.ReportDiagnostic(Diagnostic.Create( diff --git a/src/Immediate.Handlers.Generators/ITypeSymbolExtensions.cs b/src/Immediate.Handlers.Generators/ITypeSymbolExtensions.cs new file mode 100644 index 00000000..2801a7a0 --- /dev/null +++ b/src/Immediate.Handlers.Generators/ITypeSymbolExtensions.cs @@ -0,0 +1,78 @@ +using Microsoft.CodeAnalysis; + +namespace Immediate.Handlers.Generators; + +internal static class ITypeSymbolExtensions +{ + public static bool IsBehavior2(this ITypeSymbol typeSymbol) => + typeSymbol is + { + MetadataName: "Behavior`2", + ContainingNamespace: + { + Name: "Shared", + ContainingNamespace: + { + Name: "Handlers", + ContainingNamespace: + { + Name: "Immediate", + ContainingNamespace.IsGlobalNamespace: true, + }, + }, + }, + }; + + public static bool ImplementsBehavior(this INamedTypeSymbol typeSymbol) => + typeSymbol.IsBehavior2() + || (typeSymbol.BaseType is not null && ImplementsBehavior(typeSymbol.BaseType.OriginalDefinition)); + + public static bool IsValueTask1(this ITypeSymbol typeSymbol) => + typeSymbol is INamedTypeSymbol + { + MetadataName: "ValueTask`1", + ContainingNamespace: + { + Name: "Tasks", + ContainingNamespace: + { + Name: "Threading", + ContainingNamespace: + { + Name: "System", + ContainingNamespace.IsGlobalNamespace: true + } + } + } + }; + + public static bool IsValueTask(this ITypeSymbol typeSymbol) => + typeSymbol is INamedTypeSymbol + { + Name: "ValueTask", + ContainingNamespace: + { + Name: "Tasks", + ContainingNamespace: + { + Name: "Threading", + ContainingNamespace: + { + Name: "System", + ContainingNamespace.IsGlobalNamespace: true + } + } + } + }; + + public static bool IsIEquatable1(this ITypeSymbol typeSymbol) => + typeSymbol is INamedTypeSymbol + { + MetadataName: "IEquatable`1", + ContainingNamespace: + { + Name: "System", + ContainingNamespace.IsGlobalNamespace: true + }, + }; +} diff --git a/src/Immediate.Handlers.Generators/Immediate.Handlers.Generators.csproj b/src/Immediate.Handlers.Generators/Immediate.Handlers.Generators.csproj index 8909637d..d45335e5 100644 --- a/src/Immediate.Handlers.Generators/Immediate.Handlers.Generators.csproj +++ b/src/Immediate.Handlers.Generators/Immediate.Handlers.Generators.csproj @@ -4,11 +4,16 @@ netstandard2.0 true true + $(NoWarn);CA1716 - - + + + + + + @@ -19,10 +24,6 @@ - - - - $(GetTargetPathDependsOn);GetDependencyTargetPaths @@ -30,7 +31,6 @@ - diff --git a/src/Immediate.Handlers.Generators/ImmediateHandlers/ImmediateHandlersGenerator_Entrypoint.cs b/src/Immediate.Handlers.Generators/ImmediateHandlers/ImmediateHandlersGenerator_Entrypoint.cs index cd838f84..c3111c86 100644 --- a/src/Immediate.Handlers.Generators/ImmediateHandlers/ImmediateHandlersGenerator_Entrypoint.cs +++ b/src/Immediate.Handlers.Generators/ImmediateHandlers/ImmediateHandlersGenerator_Entrypoint.cs @@ -230,8 +230,7 @@ string GetVariableNameSuffix(string typeName) private static bool ValidateType(string? type, GenericType implementedTypes) => type is null - || implementedTypes.Implements - .Contains(type); + || implementedTypes.Implements.Contains(type); private static Template GetTemplate(string name) { diff --git a/src/Immediate.Handlers.Generators/ImmediateHandlers/ImmediateHandlersGenerator_TransformBehaviors.cs b/src/Immediate.Handlers.Generators/ImmediateHandlers/ImmediateHandlersGenerator_TransformBehaviors.cs index 0a32b1d6..600aae3b 100644 --- a/src/Immediate.Handlers.Generators/ImmediateHandlers/ImmediateHandlersGenerator_TransformBehaviors.cs +++ b/src/Immediate.Handlers.Generators/ImmediateHandlers/ImmediateHandlersGenerator_TransformBehaviors.cs @@ -1,4 +1,3 @@ -using Immediate.Handlers.Shared; using Microsoft.CodeAnalysis; namespace Immediate.Handlers.Generators.ImmediateHandlers; @@ -12,32 +11,35 @@ CancellationToken cancellationToken { cancellationToken.ThrowIfCancellationRequested(); - var semanticModel = context.SemanticModel; - var compilation = semanticModel.Compilation; - cancellationToken.ThrowIfCancellationRequested(); - - return ParseBehaviors(context.Attributes[0], compilation, cancellationToken); + return ParseBehaviors(context.Attributes[0], cancellationToken); } private static EquatableReadOnlyList ParseBehaviors( AttributeData attribute, - Compilation compilation, CancellationToken cancellationToken ) { cancellationToken.ThrowIfCancellationRequested(); - var behaviorType = typeof(Behavior<,>); - var behaviorTypeSymbol = compilation.GetTypeByMetadataName(behaviorType.FullName!); - if (behaviorTypeSymbol is null) - return []; if (attribute.ConstructorArguments.Length != 1) return []; var ca = attribute.ConstructorArguments[0]; - var arrayTypeSymbol = compilation.CreateArrayTypeSymbol(compilation.GetTypeByMetadataName("System.Type")!, 1); - if (!SymbolEqualityComparer.Default.Equals(ca.Type, arrayTypeSymbol)) + if (ca.Type is not IArrayTypeSymbol + { + ElementType: + { + Name: "Type", + ContainingNamespace: + { + Name: "System", + ContainingNamespace.IsGlobalNamespace: true, + }, + }, + }) + { return []; + } cancellationToken.ThrowIfCancellationRequested(); return ca.Values @@ -57,7 +59,7 @@ CancellationToken cancellationToken if (originalDefinition.IsAbstract) return null; - if (!originalDefinition.ImplementsBaseClass(behaviorTypeSymbol)) + if (!originalDefinition.ImplementsBehavior()) return null; cancellationToken.ThrowIfCancellationRequested(); diff --git a/src/Immediate.Handlers.Generators/ImmediateHandlers/ImmediateHandlersGenerator_TransformHandler.cs b/src/Immediate.Handlers.Generators/ImmediateHandlers/ImmediateHandlersGenerator_TransformHandler.cs index 054983e6..2189f63a 100644 --- a/src/Immediate.Handlers.Generators/ImmediateHandlers/ImmediateHandlersGenerator_TransformHandler.cs +++ b/src/Immediate.Handlers.Generators/ImmediateHandlers/ImmediateHandlersGenerator_TransformHandler.cs @@ -26,10 +26,7 @@ CancellationToken cancellationToken .GetMembers() .OfType() .Where(m => m.IsStatic) - .Where(m => - m.Name.Equals("Handle", StringComparison.Ordinal) - || m.Name.Equals("HandleAsync", StringComparison.Ordinal) - ) + .Where(m => m.Name is "Handle" or "HandleAsync") .ToList() is not [var handleMethod]) { return null; @@ -45,17 +42,12 @@ CancellationToken cancellationToken cancellationToken.ThrowIfCancellationRequested(); var requestType = BuildGenericType(handleMethod.Parameters[0].Type); - var compilation = context.SemanticModel.Compilation; - var taskSymbol = compilation.GetTypeByMetadataName("System.Threading.Tasks.ValueTask`1")!; - cancellationToken.ThrowIfCancellationRequested(); - var responseTypeSymbol = handleMethod.GetTaskReturnType(taskSymbol); + var responseTypeSymbol = handleMethod.GetTaskReturnType(); if (responseTypeSymbol is null) { - taskSymbol = compilation.GetTypeByMetadataName("System.Threading.Tasks.ValueTask")!; - cancellationToken.ThrowIfCancellationRequested(); - if (!SymbolEqualityComparer.Default.Equals(handleMethod.ReturnType.OriginalDefinition, taskSymbol)) + if (!handleMethod.ReturnType.IsValueTask()) return null; } @@ -75,7 +67,7 @@ CancellationToken cancellationToken var renderMode = GetOverrideRenderMode(symbol); cancellationToken.ThrowIfCancellationRequested(); - var behaviors = GetOverrideBehaviors(symbol, context.SemanticModel.Compilation, cancellationToken); + var behaviors = GetOverrideBehaviors(symbol, cancellationToken); cancellationToken.ThrowIfCancellationRequested(); return new() @@ -104,11 +96,10 @@ CancellationToken cancellationToken private static EquatableReadOnlyList? GetOverrideBehaviors( INamedTypeSymbol symbol, - Compilation compilation, CancellationToken cancellationToken) => symbol.GetAttribute("Immediate.Handlers.Shared.BehaviorsAttribute") is { } ba - ? ParseBehaviors(ba, compilation, cancellationToken) + ? ParseBehaviors(ba, cancellationToken) : null; [return: NotNullIfNotNull(nameof(type))] @@ -131,11 +122,8 @@ CancellationToken cancellationToken private static void AddBaseTypes(ITypeSymbol type, List implements) { - if (type.OriginalDefinition.ToString() is - "object" - or "System.Collections.IEnumerable" - or "System.IEquatable" - ) + if (type.SpecialType is SpecialType.System_Object or SpecialType.System_Collections_IEnumerable + || type.IsIEquatable1()) { return; } diff --git a/src/Immediate.Handlers.Generators/Utility.cs b/src/Immediate.Handlers.Generators/Utility.cs index 3badf438..70a3aea9 100644 --- a/src/Immediate.Handlers.Generators/Utility.cs +++ b/src/Immediate.Handlers.Generators/Utility.cs @@ -4,14 +4,8 @@ namespace Immediate.Handlers.Generators; internal static class Utility { - public static bool ImplementsBaseClass(this INamedTypeSymbol typeSymbol, INamedTypeSymbol typeToCheck) => - SymbolEqualityComparer.Default.Equals(typeSymbol, typeToCheck) - || (typeSymbol.BaseType is not null - && ImplementsBaseClass(typeSymbol.BaseType.OriginalDefinition, typeToCheck) - ); - - public static ITypeSymbol? GetTaskReturnType(this IMethodSymbol method, INamedTypeSymbol taskSymbol) => - SymbolEqualityComparer.Default.Equals(method.ReturnType.OriginalDefinition, taskSymbol) + public static ITypeSymbol? GetTaskReturnType(this IMethodSymbol method) => + method.ReturnType.IsValueTask1() ? ((INamedTypeSymbol)method.ReturnType).TypeArguments.FirstOrDefault() : null; diff --git a/src/Immediate.Handlers.Shared/Immediate.Handlers.Shared.csproj b/src/Immediate.Handlers.Shared/Immediate.Handlers.Shared.csproj index 6fbcaf3d..7b643f7b 100644 --- a/src/Immediate.Handlers.Shared/Immediate.Handlers.Shared.csproj +++ b/src/Immediate.Handlers.Shared/Immediate.Handlers.Shared.csproj @@ -1,7 +1,7 @@ - netstandard2.0 + net8.0 false $(NoWarn);CA1716 diff --git a/src/Immediate.Handlers.Shared/RenderMode.cs b/src/Immediate.Handlers.Shared/RenderMode.cs new file mode 100644 index 00000000..6d5979e4 --- /dev/null +++ b/src/Immediate.Handlers.Shared/RenderMode.cs @@ -0,0 +1,17 @@ +namespace Immediate.Handlers.Shared; + +/// +/// Specifies which type of handler should be rendered +/// +public enum RenderMode +{ + /// + /// Represents an invalid entry, and should not be used. + /// + None, + + /// + /// A common handler should be rendered. + /// + Normal, +} diff --git a/src/Immediate.Handlers.Shared/RenderModeAttribute.cs b/src/Immediate.Handlers.Shared/RenderModeAttribute.cs index fc661de2..02b0caf1 100644 --- a/src/Immediate.Handlers.Shared/RenderModeAttribute.cs +++ b/src/Immediate.Handlers.Shared/RenderModeAttribute.cs @@ -1,21 +1,5 @@ namespace Immediate.Handlers.Shared; -/// -/// Specifies which type of handler should be rendered -/// -public enum RenderMode -{ - /// - /// Represents an invalid entry, and should not be used. - /// - None, - - /// - /// A common handler should be rendered. - /// - Normal, -} - /// /// Allows the specification of which type of handler should be rendered. /// diff --git a/src/Immediate.Handlers/Immediate.Handlers.csproj b/src/Immediate.Handlers/Immediate.Handlers.csproj index a53f3889..6ce4e4ad 100644 --- a/src/Immediate.Handlers/Immediate.Handlers.csproj +++ b/src/Immediate.Handlers/Immediate.Handlers.csproj @@ -1,7 +1,7 @@ - netstandard2.0 + net8.0 true false @@ -25,12 +25,11 @@ - - - - - - + + + + + diff --git a/tests/Immediate.Handlers.Tests/GeneratorTests/GeneratorTestHelper.cs b/tests/Immediate.Handlers.Tests/GeneratorTests/GeneratorTestHelper.cs index 05bce903..c0081e77 100644 --- a/tests/Immediate.Handlers.Tests/GeneratorTests/GeneratorTestHelper.cs +++ b/tests/Immediate.Handlers.Tests/GeneratorTests/GeneratorTestHelper.cs @@ -15,7 +15,7 @@ public static GeneratorDriver GetDriver(string source, DriverReferenceAssemblies // Create a Roslyn compilation for the syntax tree. var compilation = CSharpCompilation.Create( assemblyName: "Tests", - syntaxTrees: new[] { syntaxTree }, + syntaxTrees: [syntaxTree], references: [ .. Basic.Reference.Assemblies.Net80.References.All,