diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 1b252254..8f98c700 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -11,7 +11,7 @@ true enable - 10.0 + 11.0 false false diff --git a/src/nunit.analyzers.tests/SetUpFixture.cs b/src/nunit.analyzers.tests/SetUpFixture.cs index 9a248c2d..8b7b02a5 100644 --- a/src/nunit.analyzers.tests/SetUpFixture.cs +++ b/src/nunit.analyzers.tests/SetUpFixture.cs @@ -1,4 +1,5 @@ using Gu.Roslyn.Asserts; +using Microsoft.CodeAnalysis.CSharp; using NUnit.Framework; #if NUNIT4 @@ -16,7 +17,7 @@ public void SetDefaults() Settings.Default = Settings.Default #if NUNIT4 .WithMetadataReferences(MetadataReferences.Transitive(typeof(Assert), typeof(ClassicAssert))) - .WithParseOption(Settings.Default.ParseOptions.WithPreprocessorSymbols("NUNIT4")); + .WithParseOption(new CSharpParseOptions(LanguageVersion.Preview).WithPreprocessorSymbols("NUNIT4")); #else .WithMetadataReferences(MetadataReferences.Transitive(typeof(Assert))); #endif diff --git a/src/nunit.analyzers.tests/TestCaseUsage/TestCaseUsageAnalyzerTests.cs b/src/nunit.analyzers.tests/TestCaseUsage/TestCaseUsageAnalyzerTests.cs index c8dc554a..177ebbd0 100644 --- a/src/nunit.analyzers.tests/TestCaseUsage/TestCaseUsageAnalyzerTests.cs +++ b/src/nunit.analyzers.tests/TestCaseUsage/TestCaseUsageAnalyzerTests.cs @@ -15,6 +15,55 @@ namespace NUnit.Analyzers.Tests.TestCaseUsage [TestFixture] public sealed class TestCaseUsageAnalyzerTests { +#if NET6_0_OR_GREATER + // This can go once NUnit 4.2.0 is released and we update our reference. + private const string GenericTestCaseAttributeSource = """ + using System; + + namespace NUnit.Framework + { + #pragma warning disable CA1019 // Define accessors for attribute arguments + + /// + /// Marks a method as a parameterized test suite and provides arguments for each test case. + /// + /// The type of the argument for the test case. + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)] + public sealed class TestCaseAttribute : TestCaseAttribute + { + /// + /// Initializes a new instance of the class. + /// + /// The argument for the test case. + public TestCaseAttribute(T argument) + : base(new object?[] { argument }) + { + this.TypeArgs = new[] { typeof(T) }; + } + } + + /// + /// Marks a method as a parameterized test suite and provides arguments for each test case. + /// + /// The type of the argument for the test case. + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)] + public sealed class TestCaseAttribute : TestCaseAttribute + { + /// + /// Initializes a new instance of the class. + /// + /// The argument for the test case. + public TestCaseAttribute(T1 argument1, T2 argument2) + : base(new object?[] { argument1, argument2 }) + { + this.TypeArgs = new[] { typeof(T1), typeof(T2) }; + } + } + } + + """; +#endif + private readonly DiagnosticAnalyzer analyzer = new TestCaseUsageAnalyzer(); private static IEnumerable SpecialConversions @@ -757,6 +806,75 @@ public void TestWithGenericParameter(T arg1) { } } #if NUNIT4 +#if NET6_0_OR_GREATER + [Test] + public void AnalyzeWhenArgumentIsCorrectGenericTypeParameter() + { + var testCode = TestUtility.WrapClassInNamespaceAndAddUsing(@" + class AnalyzeWhenArgumentIsGenericTypeParameter + { + [TestCase(2)] + public void Test(byte a) { } + }"); + RoslynAssert.Valid(this.analyzer, GenericTestCaseAttributeSource, testCode); + } + + [Test] + public void AnalyzeWhenArgumentsAreCorrectGenericTypeParameter() + { + var testCode = TestUtility.WrapClassInNamespaceAndAddUsing(@" + class AnalyzeWhenArgumentIsGenericTypeParameter + { + [TestCase(2, 3)] + public void Test(byte a, uint b) { } + }"); + RoslynAssert.Valid(this.analyzer, GenericTestCaseAttributeSource, testCode); + } + + [Test] + public void AnalyzeWhenArgumentIsWrongGenericTypeParameter() + { + var testCode = TestUtility.WrapClassInNamespaceAndAddUsing(@" + class AnalyzeWhenArgumentIsGenericTypeParameter + { + [TestCase(↓2)] + public void Test(int a) { } + }"); + RoslynAssert.Diagnostics(this.analyzer, + ExpectedDiagnostic.Create(AnalyzerIdentifiers.TestCaseParameterTypeMismatchUsage), + GenericTestCaseAttributeSource, testCode); + } + + [Test] + public void AnalyzeWhenArgumentsAreWrongGenericTypeParameter() + { + var testCode = TestUtility.WrapClassInNamespaceAndAddUsing(@" + class AnalyzeWhenArgumentIsGenericTypeParameter + { + [TestCase(↓2, ↓3)] + public void Test(int a, uint b) { } + }"); + RoslynAssert.Diagnostics(this.analyzer, + ExpectedDiagnostic.Create(AnalyzerIdentifiers.TestCaseParameterTypeMismatchUsage), + GenericTestCaseAttributeSource, testCode); + } + + [Test] + public void AnalyzeWhenTestMethodHasTypeParameterArgumentTypeAndGenericTestCase() + { + var testCode = TestUtility.WrapClassInNamespaceAndAddUsing(@" + public sealed class AnalyzeWhenTestMethodHasTypeParameterArgumentType + { + [TestCase(1)] + [TestCase(1)] + [TestCase(1)] + [TestCase(1)] + public void TestWithGenericParameter(T arg1) { } + }"); + RoslynAssert.Valid(this.analyzer, GenericTestCaseAttributeSource, testCode); + } +#endif + [Test] public void AnalyzeWhenTestMethodHasImplicitlySuppliedCancellationTokenParameterDueToCancelAfterOnMethod() { diff --git a/src/nunit.analyzers/TestCaseUsage/TestCaseUsageAnalyzer.cs b/src/nunit.analyzers/TestCaseUsage/TestCaseUsageAnalyzer.cs index 4d8d690b..b0c1d42f 100644 --- a/src/nunit.analyzers/TestCaseUsage/TestCaseUsageAnalyzer.cs +++ b/src/nunit.analyzers/TestCaseUsage/TestCaseUsageAnalyzer.cs @@ -75,7 +75,9 @@ private static void AnalyzeMethod( var testCaseAttributes = methodAttributes .Where(a => a.ApplicationSyntaxReference is not null - && SymbolEqualityComparer.Default.Equals(a.AttributeClass, testCaseType)); + && (SymbolEqualityComparer.Default.Equals(a.AttributeClass, testCaseType) || + (a.AttributeClass is not null && a.AttributeClass.IsGenericType && + SymbolEqualityComparer.Default.Equals(a.AttributeClass.BaseType, testCaseType)))); foreach (var attribute in testCaseAttributes) {