diff --git a/TUnit.Analyzers.Tests/AotCompatibilityAnalyzerTests.cs b/TUnit.Analyzers.Tests/AotCompatibilityAnalyzerTests.cs deleted file mode 100644 index 4524627109..0000000000 --- a/TUnit.Analyzers.Tests/AotCompatibilityAnalyzerTests.cs +++ /dev/null @@ -1,127 +0,0 @@ -using Microsoft.CodeAnalysis.Testing; -using TUnit.Analyzers.Tests.Verifiers; - -namespace TUnit.Analyzers.Tests; - -public class AotCompatibilityAnalyzerTests -{ - [Test] - public async Task Generic_Test_Method_With_DataSource_Triggers_Warning() - { - const string source = """ - using TUnit.Core; - - public class MyTests - { - [Test] - [Arguments(1)] - public void {|TUnit0300:GenericTest|}(T value) - { - } - } - """; - - await CSharpAnalyzerVerifier.VerifyAnalyzerAsync(source); - } - - [Test] - public async Task Tuple_Parameter_Triggers_Warning() - { - const string source = """ - using TUnit.Core; - using System; - - public class MyTests - { - [Test] - [Arguments(1, "test")] - public void TupleTest((int, string) {|TUnit0301:tuple|}) - { - } - } - """; - - await CSharpAnalyzerVerifier.VerifyAnalyzerAsync(source); - } - - [Test] - public async Task Regular_Test_No_Warning() - { - const string source = """ - using TUnit.Core; - - public class MyTests - { - [Test] - [Arguments(1, "test")] - public void RegularTest(int number, string text) - { - } - } - """; - - await CSharpAnalyzerVerifier.VerifyAnalyzerAsync(source); - } - - [Test] - public async Task Generic_Class_With_Test_Triggers_Warning() - { - const string source = """ - using TUnit.Core; - - public class MyTests - { - [Test] - [Arguments(1)] - public void {|TUnit0300:TestMethod|}(int value) - { - } - } - """; - - await CSharpAnalyzerVerifier.VerifyAnalyzerAsync(source); - } - - - - [Test] - public async Task ValueTuple_Parameter_Triggers_Warning() - { - const string source = """ - using TUnit.Core; - using System; - - public class MyTests - { - [Test] - [Arguments(5, 10)] - public void TestMethod(ValueTuple {|TUnit0301:values|}) - { - } - } - """; - - await CSharpAnalyzerVerifier.VerifyAnalyzerAsync(source); - } - - [Test] - public async Task Multiple_Tuple_Parameters_Trigger_Multiple_Warnings() - { - const string source = """ - using TUnit.Core; - using System; - - public class MyTests - { - [Test] - [Arguments(1, "a", 2, "b")] - public void TestMethod((int, string) {|TUnit0301:first|}, (int, string) {|TUnit0301:second|}) - { - } - } - """; - - await CSharpAnalyzerVerifier.VerifyAnalyzerAsync(source); - } - -} \ No newline at end of file diff --git a/TUnit.Analyzers/AnalyzerReleases.Unshipped.md b/TUnit.Analyzers/AnalyzerReleases.Unshipped.md index 1f7d41765a..619f6731ff 100644 --- a/TUnit.Analyzers/AnalyzerReleases.Unshipped.md +++ b/TUnit.Analyzers/AnalyzerReleases.Unshipped.md @@ -12,4 +12,7 @@ TUnit0074 | Usage | Error | Hook attribute is redundant on an override Rule ID | Category | Severity | Notes --------|----------|----------|------- TUnit0015 | Usage | Error | Changed to Warning severity (CancellationToken parameter now optional) -TUnit0043 | Usage | Error | Changed to Info severity (now a suggestion instead of error) \ No newline at end of file +TUnit0043 | Usage | Error | Changed to Info severity (now a suggestion instead of error) +TUnit0300 | Usage | Warning | Removed - rule was inaccurate and produced false positives +TUnit0301 | Usage | Warning | Removed - rule was inaccurate and produced false positives +TUnit0302 | Usage | Warning | Removed - rule was never implemented and the underlying claim was inaccurate \ No newline at end of file diff --git a/TUnit.Analyzers/AotCompatibilityAnalyzer.cs b/TUnit.Analyzers/AotCompatibilityAnalyzer.cs deleted file mode 100644 index 8a3258d4cd..0000000000 --- a/TUnit.Analyzers/AotCompatibilityAnalyzer.cs +++ /dev/null @@ -1,130 +0,0 @@ -using System.Collections.Immutable; -using System.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; -using TUnit.Analyzers.Extensions; - -namespace TUnit.Analyzers; - -[DiagnosticAnalyzer(LanguageNames.CSharp)] -public class AotCompatibilityAnalyzer : ConcurrentDiagnosticAnalyzer -{ - public override ImmutableArray SupportedDiagnostics { get; } = - ImmutableArray.Create( - Rules.GenericTypeNotAotCompatible, - Rules.TupleNotAotCompatible); - - protected override void InitializeInternal(AnalysisContext context) - { - context.RegisterSymbolAction(AnalyzeTestMethod, SymbolKind.Method); - } - - - private void AnalyzeTestMethod(SymbolAnalysisContext context) - { - if (context.Symbol is not IMethodSymbol methodSymbol) - { - return; - } - - if (!methodSymbol.IsTestMethod(context.Compilation)) - { - return; - } - - // Note: We don't skip warnings even in source-generated mode because - // generic test methods still require special handling for AOT - - // Check if test method has generic parameters - if (methodSymbol.IsGenericMethod || methodSymbol.ContainingType.IsGenericType) - { - // Check if the method or class has the AotCompatible attribute - if (HasAotCompatibleAttribute(methodSymbol)) - { - // Method has been marked as AOT-safe - no warning - return; - } - - // Generic test methods with any data source attributes are problematic for AOT - // because the generic type arguments need to be determined at runtime - var hasDataSource = methodSymbol.GetAttributes() - .Any(attr => attr.AttributeClass?.Name == "ArgumentsAttribute" || - attr.AttributeClass?.Name == "MethodDataSourceAttribute" || - attr.AttributeClass?.Name == "ClassDataSourceAttribute" || - IsDataSourceAttribute(attr, context.Compilation)); - - if (hasDataSource || methodSymbol.IsGenericMethod) - { - context.ReportDiagnostic(Diagnostic.Create( - Rules.GenericTypeNotAotCompatible, - methodSymbol.Locations.FirstOrDefault(), - "Generic test method may require runtime type creation")); - } - } - - // Check for tuple parameters only if not using ITuple interface - #if !NET - foreach (var parameter in methodSymbol.Parameters) - { - if (IsTupleType(parameter.Type)) - { - context.ReportDiagnostic(Diagnostic.Create( - Rules.TupleNotAotCompatible, - parameter.Locations.FirstOrDefault(), - $"Tuple parameter '{parameter.Name}' - consider using concrete types for AOT compatibility")); - } - } - #endif - } - - - - - - - private static bool IsTupleType(ITypeSymbol type) - { - // Check if it's a tuple type using the IsTupleType property - if (type is INamedTypeSymbol namedType) - { - return namedType.IsTupleType; - } - return false; - } - - - - private static bool HasAotCompatibleAttribute(IMethodSymbol method) - { - // Check method attributes - if (method.GetAttributes() - .Any(a => a.AttributeClass?.Name == "AotCompatibleAttribute")) - { - return true; - } - - // Check containing type attributes - if (method.ContainingType != null && - method.ContainingType.GetAttributes() - .Any(a => a.AttributeClass?.Name == "AotCompatibleAttribute")) - { - return true; - } - - return false; - } - - private static bool IsDataSourceAttribute(AttributeData attr, Compilation compilation) - { - var dataSourceInterface = compilation.GetTypeByMetadataName("TUnit.Core.IDataSourceAttribute"); - if (dataSourceInterface == null || attr.AttributeClass == null) - { - return false; - } - - return attr.AttributeClass.AllInterfaces.Contains(dataSourceInterface, SymbolEqualityComparer.Default) || - SymbolEqualityComparer.Default.Equals(attr.AttributeClass, dataSourceInterface); - } -} \ No newline at end of file diff --git a/TUnit.Analyzers/Resources.resx b/TUnit.Analyzers/Resources.resx index 8cf0b5223b..850e008bfc 100644 --- a/TUnit.Analyzers/Resources.resx +++ b/TUnit.Analyzers/Resources.resx @@ -507,15 +507,6 @@ CancellationToken must be the last parameter - - Generic types and methods may not be AOT-compatible when using dynamic type creation. Consider using concrete types or ensure all generic combinations are known at compile time. - - - {0} may not be AOT-compatible. All generic type combinations must be known at compile time. - - - Generic type or method may not be AOT-compatible - When a test class has multiple constructors, one should be marked with [TestConstructor] to avoid ambiguity. @@ -543,22 +534,4 @@ Hook attribute is redundant on an override - - Tuple types require reflection for property/field access which is not AOT-compatible. Consider using concrete types or value deconstruction. - - - {0} uses reflection which is not AOT-compatible. Consider using concrete types instead of tuples. - - - Tuple usage may not be AOT-compatible - - - Custom conversion operators (op_Implicit/op_Explicit) require runtime discovery which is not AOT-compatible. Use explicit casting or standard conversions. - - - {0} invocation may not be AOT-compatible. Use explicit casting or standard conversions instead. - - - Custom conversion operator may not be AOT-compatible - \ No newline at end of file diff --git a/TUnit.Analyzers/Rules.cs b/TUnit.Analyzers/Rules.cs index 599087ee4a..681c342aa8 100644 --- a/TUnit.Analyzers/Rules.cs +++ b/TUnit.Analyzers/Rules.cs @@ -176,13 +176,6 @@ public static class Rules public static readonly DiagnosticDescriptor RedundantHookAttributeOnOverride = CreateDescriptor("TUnit0074", UsageCategory, DiagnosticSeverity.Error); - public static readonly DiagnosticDescriptor GenericTypeNotAotCompatible = - CreateDescriptor("TUnit0300", UsageCategory, DiagnosticSeverity.Warning); - - public static readonly DiagnosticDescriptor TupleNotAotCompatible = - CreateDescriptor("TUnit0301", UsageCategory, DiagnosticSeverity.Warning); - - private static DiagnosticDescriptor CreateDescriptor(string diagnosticId, string category, DiagnosticSeverity severity, string[]? customTags = null, string? helpLinkUri = null) {