diff --git a/TUnit.Analyzers.Tests/MissingPolyfillAnalyzerTests.cs b/TUnit.Analyzers.Tests/MissingPolyfillAnalyzerTests.cs new file mode 100644 index 0000000000..3ec986a8e5 --- /dev/null +++ b/TUnit.Analyzers.Tests/MissingPolyfillAnalyzerTests.cs @@ -0,0 +1,51 @@ +using Microsoft.CodeAnalysis.Testing; +using Verifier = TUnit.Analyzers.Tests.Verifiers.CSharpAnalyzerVerifier; + +namespace TUnit.Analyzers.Tests; + +public class MissingPolyfillAnalyzerTests +{ + [Test] + public async Task No_Error_On_Modern_Tfm() + { + await Verifier + .VerifyAnalyzerAsync( + """ + public class MyClass + { + } + """, + test => + { + test.TestState.AdditionalReferences.Clear(); + } + ); + } + + [Test] + public async Task Error_When_ModuleInitializerAttribute_Missing() + { + var net48References = new ReferenceAssemblies( + "net48", + new PackageIdentity("Microsoft.NETFramework.ReferenceAssemblies.net48", "1.0.3"), + System.IO.Path.Combine("ref", "net48")); + + await Verifier + .VerifyAnalyzerAsync( + """ + public class MyClass + { + } + """, + test => + { + test.ReferenceAssemblies = net48References; + test.TestState.AdditionalReferences.Clear(); + test.CompilerDiagnostics = CompilerDiagnostics.None; + }, + Verifier + .Diagnostic(Rules.MissingPolyfillPackage) + .WithArguments("System.Runtime.CompilerServices.ModuleInitializerAttribute") + ); + } +} diff --git a/TUnit.Analyzers/AnalyzerReleases.Unshipped.md b/TUnit.Analyzers/AnalyzerReleases.Unshipped.md index f64b591235..7c65f71a62 100644 --- a/TUnit.Analyzers/AnalyzerReleases.Unshipped.md +++ b/TUnit.Analyzers/AnalyzerReleases.Unshipped.md @@ -4,6 +4,7 @@ Rule ID | Category | Severity | Notes --------|----------|----------|------- TUnit0061 | Usage | Error | ClassDataSource type requires parameterless constructor TUnit0062 | Usage | Warning | CancellationToken must be the last parameter +TUnit0073 | Usage | Error | Missing polyfill types required by TUnit ### Removed Rules diff --git a/TUnit.Analyzers/MissingPolyfillAnalyzer.cs b/TUnit.Analyzers/MissingPolyfillAnalyzer.cs new file mode 100644 index 0000000000..3d84eb10c4 --- /dev/null +++ b/TUnit.Analyzers/MissingPolyfillAnalyzer.cs @@ -0,0 +1,32 @@ +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace TUnit.Analyzers; + +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public class MissingPolyfillAnalyzer : ConcurrentDiagnosticAnalyzer +{ + private static readonly ImmutableArray RequiredTypes = + ImmutableArray.Create("System.Runtime.CompilerServices.ModuleInitializerAttribute"); + + public override ImmutableArray SupportedDiagnostics { get; } = + ImmutableArray.Create(Rules.MissingPolyfillPackage); + + protected override void InitializeInternal(AnalysisContext context) + { + context.RegisterCompilationAction(AnalyzeCompilation); + } + + private static void AnalyzeCompilation(CompilationAnalysisContext context) + { + foreach (var typeName in RequiredTypes) + { + if (context.Compilation.GetTypeByMetadataName(typeName) is null) + { + context.ReportDiagnostic( + Diagnostic.Create(Rules.MissingPolyfillPackage, Location.None, typeName)); + } + } + } +} diff --git a/TUnit.Analyzers/Resources.resx b/TUnit.Analyzers/Resources.resx index 7ae4425c17..3462f5c018 100644 --- a/TUnit.Analyzers/Resources.resx +++ b/TUnit.Analyzers/Resources.resx @@ -525,6 +525,15 @@ Multiple constructors found without [TestConstructor] attribute + + TUnit requires certain types (such as ModuleInitializerAttribute) that are not available on older target frameworks. Install the 'Polyfill' NuGet package to provide these types. + + + Type '{0}' is required by TUnit but is not available. Install the 'Polyfill' NuGet package: dotnet add package Polyfill + + + Missing polyfill types required by TUnit + Tuple types require reflection for property/field access which is not AOT-compatible. Consider using concrete types or value deconstruction. diff --git a/TUnit.Analyzers/Rules.cs b/TUnit.Analyzers/Rules.cs index 489ede5331..684b61e18d 100644 --- a/TUnit.Analyzers/Rules.cs +++ b/TUnit.Analyzers/Rules.cs @@ -168,6 +168,11 @@ public static class Rules public static readonly DiagnosticDescriptor NoAccessibleConstructor = CreateDescriptor("TUnit0061", UsageCategory, DiagnosticSeverity.Error); + public static readonly DiagnosticDescriptor MissingPolyfillPackage = + CreateDescriptor("TUnit0073", UsageCategory, DiagnosticSeverity.Error, + customTags: [WellKnownDiagnosticTags.CompilationEnd], + helpLinkUri: "https://www.nuget.org/packages/Polyfill"); + public static readonly DiagnosticDescriptor GenericTypeNotAotCompatible = CreateDescriptor("TUnit0300", UsageCategory, DiagnosticSeverity.Warning); @@ -175,7 +180,8 @@ public static class Rules CreateDescriptor("TUnit0301", UsageCategory, DiagnosticSeverity.Warning); - private static DiagnosticDescriptor CreateDescriptor(string diagnosticId, string category, DiagnosticSeverity severity) + private static DiagnosticDescriptor CreateDescriptor(string diagnosticId, string category, DiagnosticSeverity severity, + string[]? customTags = null, string? helpLinkUri = null) { return new DiagnosticDescriptor( id: diagnosticId, @@ -187,7 +193,9 @@ private static DiagnosticDescriptor CreateDescriptor(string diagnosticId, string defaultSeverity: severity, isEnabledByDefault: true, description: new LocalizableResourceString(diagnosticId + "Description", Resources.ResourceManager, - typeof(Resources)) + typeof(Resources)), + helpLinkUri: helpLinkUri, + customTags: customTags ?? [] ); } }