From b2f4e680137e078ae287ba88887bd9f50942a41e Mon Sep 17 00:00:00 2001 From: Manfred Brands Date: Wed, 13 Nov 2024 01:04:17 +0800 Subject: [PATCH] Detect more Classic Assert (#791) * Add ClassicModel Analyzer/CodeFixes for Postive and Negative * Add ClassicModel Analyzer/CodeFixes for (Not)AssignableFrom * "Fix" markdown long lines * Code review changes --- documentation/NUnit2051.md | 82 ++++ documentation/NUnit2052.md | 82 ++++ documentation/NUnit2053.md | 82 ++++ documentation/NUnit2054.md | 83 ++++ documentation/index.md | 4 + src/nunit.analyzers.sln | 6 +- .../ClassicModelAssertUsageAnalyzerTests.cs | 106 ++++- ...FromClassicModelAssertUsageCodeFixTests.cs | 442 ++++++++++++++++++ ...ceOfClassicModelAssertUsageCodeFixTests.cs | 46 +- ...FromClassicModelAssertUsageCodeFixTests.cs | 442 ++++++++++++++++++ ...ceOfClassicModelAssertUsageCodeFixTests.cs | 51 +- ...tiveClassicModelAssertUsageCodeFixTests.cs | 205 ++++++++ ...tiveClassicModelAssertUsageCodeFixTests.cs | 205 ++++++++ .../Constants/NUnitFrameworkConstantsTests.cs | 6 + .../ClassicModelAssertUsageAnalyzer.cs | 36 ++ .../ClassicModelUsageAnalyzerConstants.cs | 16 + ...nableFromClassicModelAssertUsageCodeFix.cs | 53 +++ ...nableFromClassicModelAssertUsageCodeFix.cs | 59 +++ .../NegativeClassicModelAssertUsageCodeFix.cs | 33 ++ .../PositiveClassicModelAssertUsageCodeFix.cs | 33 ++ .../Constants/AnalyzerIdentifiers.cs | 4 + .../Constants/NUnitFrameworkConstants.cs | 6 + 22 files changed, 2047 insertions(+), 35 deletions(-) create mode 100644 documentation/NUnit2051.md create mode 100644 documentation/NUnit2052.md create mode 100644 documentation/NUnit2053.md create mode 100644 documentation/NUnit2054.md create mode 100644 src/nunit.analyzers.tests/ClassicModelAssertUsage/IsAssignableFromClassicModelAssertUsageCodeFixTests.cs create mode 100644 src/nunit.analyzers.tests/ClassicModelAssertUsage/IsNotAssignableFromClassicModelAssertUsageCodeFixTests.cs create mode 100644 src/nunit.analyzers.tests/ClassicModelAssertUsage/NegativeClassicModelAssertUsageCodeFixTests.cs create mode 100644 src/nunit.analyzers.tests/ClassicModelAssertUsage/PositiveClassicModelAssertUsageCodeFixTests.cs create mode 100644 src/nunit.analyzers/ClassicModelAssertUsage/IsAssignableFromClassicModelAssertUsageCodeFix.cs create mode 100644 src/nunit.analyzers/ClassicModelAssertUsage/IsNotAssignableFromClassicModelAssertUsageCodeFix.cs create mode 100644 src/nunit.analyzers/ClassicModelAssertUsage/NegativeClassicModelAssertUsageCodeFix.cs create mode 100644 src/nunit.analyzers/ClassicModelAssertUsage/PositiveClassicModelAssertUsageCodeFix.cs diff --git a/documentation/NUnit2051.md b/documentation/NUnit2051.md new file mode 100644 index 00000000..ae7eada4 --- /dev/null +++ b/documentation/NUnit2051.md @@ -0,0 +1,82 @@ +# NUnit2051 + +## Consider using Assert.That(expr, Is.Positive) instead of ClassicAssert.Positive(expr) + +| Topic | Value +| :-- | :-- +| Id | NUnit2051 +| Severity | Info +| Enabled | True +| Category | Assertion +| Code | [ClassicModelAssertUsageAnalyzer](https://github.com/nunit/nunit.analyzers/blob/master/src/nunit.analyzers/ClassicModelAssertUsage/ClassicModelAssertUsageAnalyzer.cs) + +## Description + +Consider using the constraint model, `Assert.That(expr, Is.Positive)`, instead of the classic model, +`ClassicAssert.Positive(expr)`. + +## Motivation + +The classic Assert model contains less flexibility than the constraint model, +so this analyzer marks usages of `ClassicAssert.Positive` from the classic Assert model. + +```csharp +[Test] +public void Test() +{ + ClassicAssert.Positive(expression); +} +``` + +## How to fix violations + +The analyzer comes with a code fix that will replace `ClassicAssert.Positive(expression)` with +`Assert.That(expression, Is.Positive)`. So the code block above will be changed into. + +```csharp +[Test] +public void Test() +{ + Assert.That(expression, Is.Positive); +} +``` + + +## Configure severity + +### Via ruleset file + +Configure the severity per project, for more info see +[MSDN](https://learn.microsoft.com/en-us/visualstudio/code-quality/using-rule-sets-to-group-code-analysis-rules?view=vs-2022). + +### Via .editorconfig file + +```ini +# NUnit2051: Consider using Assert.That(expr, Is.Positive) instead of ClassicAssert.Positive(expr) +dotnet_diagnostic.NUnit2051.severity = chosenSeverity +``` + +where `chosenSeverity` can be one of `none`, `silent`, `suggestion`, `warning`, or `error`. + +### Via #pragma directive + +```csharp +#pragma warning disable NUnit2051 // Consider using Assert.That(expr, Is.Positive) instead of ClassicAssert.Positive(expr) +Code violating the rule here +#pragma warning restore NUnit2051 // Consider using Assert.That(expr, Is.Positive) instead of ClassicAssert.Positive(expr) +``` + +Or put this at the top of the file to disable all instances. + +```csharp +#pragma warning disable NUnit2051 // Consider using Assert.That(expr, Is.Positive) instead of ClassicAssert.Positive(expr) +``` + +### Via attribute `[SuppressMessage]` + +```csharp +[System.Diagnostics.CodeAnalysis.SuppressMessage("Assertion", + "NUnit2051:Consider using Assert.That(expr, Is.Positive) instead of ClassicAssert.Positive(expr)", + Justification = "Reason...")] +``` + diff --git a/documentation/NUnit2052.md b/documentation/NUnit2052.md new file mode 100644 index 00000000..c93c9ebf --- /dev/null +++ b/documentation/NUnit2052.md @@ -0,0 +1,82 @@ +# NUnit2052 + +## Consider using Assert.That(expr, Is.Negative) instead of ClassicAssert.Negative(expr) + +| Topic | Value +| :-- | :-- +| Id | NUnit2052 +| Severity | Info +| Enabled | True +| Category | Assertion +| Code | [ClassicModelAssertUsageAnalyzer](https://github.com/nunit/nunit.analyzers/blob/master/src/nunit.analyzers/ClassicModelAssertUsage/ClassicModelAssertUsageAnalyzer.cs) + +## Description + +Consider using the constraint model, `Assert.That(expr, Is.Negative)`, instead of the classic model, +`ClassicAssert.Negative(expr)`. + +## Motivation + +The classic Assert model contains less flexibility than the constraint model, +so this analyzer marks usages of `ClassicAssert.Negative` from the classic Assert model. + +```csharp +[Test] +public void Test() +{ + ClassicAssert.Negative(expression); +} +``` + +## How to fix violations + +The analyzer comes with a code fix that will replace `ClassicAssert.Negative(expression)` with +`Assert.That(expression, Is.Negative)`. So the code block above will be changed into. + +```csharp +[Test] +public void Test() +{ + Assert.That(expression, Is.Negative); +} +``` + + +## Configure severity + +### Via ruleset file + +Configure the severity per project, for more info see +[MSDN](https://learn.microsoft.com/en-us/visualstudio/code-quality/using-rule-sets-to-group-code-analysis-rules?view=vs-2022). + +### Via .editorconfig file + +```ini +# NUnit2052: Consider using Assert.That(expr, Is.Negative) instead of ClassicAssert.Negative(expr) +dotnet_diagnostic.NUnit2052.severity = chosenSeverity +``` + +where `chosenSeverity` can be one of `none`, `silent`, `suggestion`, `warning`, or `error`. + +### Via #pragma directive + +```csharp +#pragma warning disable NUnit2052 // Consider using Assert.That(expr, Is.Negative) instead of ClassicAssert.Negative(expr) +Code violating the rule here +#pragma warning restore NUnit2052 // Consider using Assert.That(expr, Is.Negative) instead of ClassicAssert.Negative(expr) +``` + +Or put this at the top of the file to disable all instances. + +```csharp +#pragma warning disable NUnit2052 // Consider using Assert.That(expr, Is.Negative) instead of ClassicAssert.Negative(expr) +``` + +### Via attribute `[SuppressMessage]` + +```csharp +[System.Diagnostics.CodeAnalysis.SuppressMessage("Assertion", + "NUnit2052:Consider using Assert.That(expr, Is.Negative) instead of ClassicAssert.Negative(expr)", + Justification = "Reason...")] +``` + diff --git a/documentation/NUnit2053.md b/documentation/NUnit2053.md new file mode 100644 index 00000000..f0650750 --- /dev/null +++ b/documentation/NUnit2053.md @@ -0,0 +1,82 @@ +# NUnit2053 + +## Consider using Assert.That(actual, Is.AssignableFrom(expected)) instead of ClassicAssert.IsAssignableFrom(expected, actual) + +| Topic | Value +| :-- | :-- +| Id | NUnit2053 +| Severity | Info +| Enabled | True +| Category | Assertion +| Code | [ClassicModelAssertUsageAnalyzer](https://github.com/nunit/nunit.analyzers/blob/master/src/nunit.analyzers/ClassicModelAssertUsage/ClassicModelAssertUsageAnalyzer.cs) + +## Description + +Consider using the constraint model, `Assert.That(actual, Is.AssignableFrom(expected))`, instead of the classic model, +`ClassicAssert.IsAssignableFrom(expected, actual)`. + +## Motivation + +The assert `ClassicAssert.IsAssignableFrom` from the classic Assert model makes it easy to confuse the `expected` and the +`actual` argument, so this analyzer marks usages of `ClassicAssert.IsAssignableFrom`. + +```csharp +[Test] +public void Test() +{ + ClassicAssert.IsAssignableFrom(expected, actual); +} +``` + +## How to fix violations + +The analyzer comes with a code fix that will replace `ClassicAssert.IsAssignableFrom(expected, actual)` with +`Assert.That(actual, Is.AssignableFrom(expected))`. So the code block above will be changed into. + +```csharp +[Test] +public void Test() +{ + Assert.That(actual, Is.AssignableFrom(expected)); +} +``` + + +## Configure severity + +### Via ruleset file + +Configure the severity per project, for more info see +[MSDN](https://learn.microsoft.com/en-us/visualstudio/code-quality/using-rule-sets-to-group-code-analysis-rules?view=vs-2022). + +### Via .editorconfig file + +```ini +# NUnit2053: Consider using Assert.That(actual, Is.AssignableFrom(expected)) instead of ClassicAssert.IsAssignableFrom(expected, actual) +dotnet_diagnostic.NUnit2053.severity = chosenSeverity +``` + +where `chosenSeverity` can be one of `none`, `silent`, `suggestion`, `warning`, or `error`. + +### Via #pragma directive + +```csharp +#pragma warning disable NUnit2053 // Consider using Assert.That(actual, Is.AssignableFrom(expected)) instead of ClassicAssert.IsAssignableFrom(expected, actual) +Code violating the rule here +#pragma warning restore NUnit2053 // Consider using Assert.That(actual, Is.AssignableFrom(expected)) instead of ClassicAssert.IsAssignableFrom(expected, actual) +``` + +Or put this at the top of the file to disable all instances. + +```csharp +#pragma warning disable NUnit2053 // Consider using Assert.That(actual, Is.AssignableFrom(expected)) instead of ClassicAssert.IsAssignableFrom(expected, actual) +``` + +### Via attribute `[SuppressMessage]` + +```csharp +[System.Diagnostics.CodeAnalysis.SuppressMessage("Assertion", + "NUnit2053:Consider using Assert.That(actual, Is.AssignableFrom(expected)) instead of ClassicAssert.IsAssignableFrom(expected, actual)", + Justification = "Reason...")] +``` + diff --git a/documentation/NUnit2054.md b/documentation/NUnit2054.md new file mode 100644 index 00000000..2efba94d --- /dev/null +++ b/documentation/NUnit2054.md @@ -0,0 +1,83 @@ +# NUnit2054 + + +## Consider using Assert.That(actual, Is.Not.AssignableFrom(expected)) instead of ClassicAssert.IsNotAssignableFrom(expected, actual) + +| Topic | Value +| :-- | :-- +| Id | NUnit2054 +| Severity | Info +| Enabled | True +| Category | Assertion +| Code | [ClassicModelAssertUsageAnalyzer](https://github.com/nunit/nunit.analyzers/blob/master/src/nunit.analyzers/ClassicModelAssertUsage/ClassicModelAssertUsageAnalyzer.cs) + +## Description + +Consider using the constraint model, `Assert.That(actual, Is.Not.AssignableFrom(expected))`, instead of the classic model, +`ClassicAssert.IsNotAssignableFrom(expected, actual)`. + +## Motivation + +The assert `ClassicAssert.IsNotAssignableFrom` from the classic Assert model makes it easy to confuse the `expected` +and the `actual` argument, so this analyzer marks usages of `ClassicAssert.IsNotAssignableFrom`. + +```csharp +[Test] +public void Test() +{ + ClassicAssert.IsNotAssignableFrom(expected, actual); +} +``` + +## How to fix violations + +The analyzer comes with a code fix that will replace `ClassicAssert.IsNotAssignableFrom(expected, actual)` with +`Assert.That(actual, Is.Not.AssignableFrom(expected))`. So the code block above will be changed into. + +```csharp +[Test] +public void Test() +{ + Assert.That(actual, Is.Not.AssignableFrom(expected)); +} +``` + + +## Configure severity + +### Via ruleset file + +Configure the severity per project, for more info see +[MSDN](https://learn.microsoft.com/en-us/visualstudio/code-quality/using-rule-sets-to-group-code-analysis-rules?view=vs-2022). + +### Via .editorconfig file + +```ini +# NUnit2054: Consider using Assert.That(actual, Is.Not.AssignableFrom(expected)) instead of ClassicAssert.IsNotAssignableFrom(expected, actual) +dotnet_diagnostic.NUnit2054.severity = chosenSeverity +``` + +where `chosenSeverity` can be one of `none`, `silent`, `suggestion`, `warning`, or `error`. + +### Via #pragma directive + +```csharp +#pragma warning disable NUnit2054 // Consider using Assert.That(actual, Is.Not.AssignableFrom(expected)) instead of ClassicAssert.IsNotAssignableFrom(expected, actual) +Code violating the rule here +#pragma warning restore NUnit2054 // Consider using Assert.That(actual, Is.Not.AssignableFrom(expected)) instead of ClassicAssert.IsNotAssignableFrom(expected, actual) +``` + +Or put this at the top of the file to disable all instances. + +```csharp +#pragma warning disable NUnit2054 // Consider using Assert.That(actual, Is.Not.AssignableFrom(expected)) instead of ClassicAssert.IsNotAssignableFrom(expected, actual) +``` + +### Via attribute `[SuppressMessage]` + +```csharp +[System.Diagnostics.CodeAnalysis.SuppressMessage("Assertion", + "NUnit2054:Consider using Assert.That(actual, Is.Not.AssignableFrom(expected)) instead of ClassicAssert.IsNotAssignableFrom(expected, actual)", + Justification = "Reason...")] +``` + diff --git a/documentation/index.md b/documentation/index.md index 88e55e3a..d3933f41 100644 --- a/documentation/index.md +++ b/documentation/index.md @@ -109,6 +109,10 @@ Rules which improve assertions in the test code. | [NUnit2048](https://github.com/nunit/nunit.analyzers/tree/master/documentation/NUnit2048.md) | Consider using Assert.That(...) instead of StringAssert(...) | :white_check_mark: | :warning: | :white_check_mark: | | [NUnit2049](https://github.com/nunit/nunit.analyzers/tree/master/documentation/NUnit2049.md) | Consider using Assert.That(...) instead of CollectionAssert(...) | :white_check_mark: | :warning: | :white_check_mark: | | [NUnit2050](https://github.com/nunit/nunit.analyzers/tree/master/documentation/NUnit2050.md) | NUnit 4 no longer supports string.Format specification | :white_check_mark: | :exclamation: | :white_check_mark: | +| [NUnit2051](https://github.com/nunit/nunit.analyzers/tree/master/documentation/NUnit2051.md) | Consider using Assert.That(expr, Is.Positive) instead of ClassicAssert.Positive(expr) | :white_check_mark: | :information_source: | :white_check_mark: | +| [NUnit2052](https://github.com/nunit/nunit.analyzers/tree/master/documentation/NUnit2052.md) | Consider using Assert.That(expr, Is.Negative) instead of ClassicAssert.Negative(expr) | :white_check_mark: | :information_source: | :white_check_mark: | +| [NUnit2053](https://github.com/nunit/nunit.analyzers/tree/master/documentation/NUnit2053.md) | Consider using Assert.That(actual, Is.AssignableFrom(expected)) instead of ClassicAssert.IsAssignableFrom(expected, actual) | :white_check_mark: | :information_source: | :white_check_mark: | +| [NUnit2054](https://github.com/nunit/nunit.analyzers/tree/master/documentation/NUnit2054.md) | Consider using Assert.That(actual, Is.Not.AssignableFrom(expected)) instead of ClassicAssert.IsNotAssignableFrom(expected, actual) | :white_check_mark: | :information_source: | :white_check_mark: | ## Suppressor Rules (NUnit3001 - ) diff --git a/src/nunit.analyzers.sln b/src/nunit.analyzers.sln index 8cd7c0ed..26a7cff0 100644 --- a/src/nunit.analyzers.sln +++ b/src/nunit.analyzers.sln @@ -1,6 +1,6 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 +# 17 VisualStudioVersion = 17.2.32210.308 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "nunit.analyzers", "nunit.analyzers\nunit.analyzers.csproj", "{74151914-3C12-4EAC-8FD5-5766EBEA35A3}" @@ -93,6 +93,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "documentation", "documentat ..\documentation\NUnit2048.md = ..\documentation\NUnit2048.md ..\documentation\NUnit2049.md = ..\documentation\NUnit2049.md ..\documentation\NUnit2050.md = ..\documentation\NUnit2050.md + ..\documentation\NUnit2051.md = ..\documentation\NUnit2051.md + ..\documentation\NUnit2052.md = ..\documentation\NUnit2052.md + ..\documentation\NUnit2053.md = ..\documentation\NUnit2053.md + ..\documentation\NUnit2054.md = ..\documentation\NUnit2054.md ..\documentation\NUnit3001.md = ..\documentation\NUnit3001.md ..\documentation\NUnit3002.md = ..\documentation\NUnit3002.md ..\documentation\NUnit3003.md = ..\documentation\NUnit3003.md diff --git a/src/nunit.analyzers.tests/ClassicModelAssertUsage/ClassicModelAssertUsageAnalyzerTests.cs b/src/nunit.analyzers.tests/ClassicModelAssertUsage/ClassicModelAssertUsageAnalyzerTests.cs index 0acc9f62..2d7e3533 100644 --- a/src/nunit.analyzers.tests/ClassicModelAssertUsage/ClassicModelAssertUsageAnalyzerTests.cs +++ b/src/nunit.analyzers.tests/ClassicModelAssertUsage/ClassicModelAssertUsageAnalyzerTests.cs @@ -21,7 +21,7 @@ public void VerifySupportedDiagnostics() var analyzer = new ClassicModelAssertUsageAnalyzer(); var diagnostics = analyzer.SupportedDiagnostics; - Assert.That(diagnostics, Has.Length.EqualTo(24), nameof(DiagnosticAnalyzer.SupportedDiagnostics)); + Assert.That(diagnostics, Has.Length.EqualTo(28), nameof(DiagnosticAnalyzer.SupportedDiagnostics)); foreach (var diagnostic in diagnostics) { @@ -90,6 +90,14 @@ public void VerifySupportedDiagnostics() $"{AnalyzerIdentifiers.IsInstanceOfUsage} is missing."); Assert.That(diagnosticIds, Contains.Item(AnalyzerIdentifiers.IsNotInstanceOfUsage), $"{AnalyzerIdentifiers.IsNotInstanceOfUsage} is missing."); + Assert.That(diagnosticIds, Contains.Item(AnalyzerIdentifiers.PositiveUsage), + $"{AnalyzerIdentifiers.PositiveUsage} is missing."); + Assert.That(diagnosticIds, Contains.Item(AnalyzerIdentifiers.NegativeUsage), + $"{AnalyzerIdentifiers.NegativeUsage} is missing."); + Assert.That(diagnosticIds, Contains.Item(AnalyzerIdentifiers.IsAssignableFromUsage), + $"{AnalyzerIdentifiers.NegativeUsage} is missing."); + Assert.That(diagnosticIds, Contains.Item(AnalyzerIdentifiers.IsNotAssignableFromUsage), + $"{AnalyzerIdentifiers.NegativeUsage} is missing."); }); } @@ -569,6 +577,102 @@ public void Test() { ↓ClassicAssert.IsNotInstanceOf(2); } + }"); + RoslynAssert.Diagnostics(this.analyzer, expectedDiagnostic, testCode); + } + + [Test] + public void AnalyzeWhenPositiveIsUsed() + { + var expectedDiagnostic = ExpectedDiagnostic.Create(AnalyzerIdentifiers.PositiveUsage); + + var testCode = TestUtility.WrapClassInNamespaceAndAddUsing(@" + public sealed class AnalyzeWhenPositiveIsUsed + { + public void Test() + { + ↓ClassicAssert.Positive(2); + } + }"); + RoslynAssert.Diagnostics(this.analyzer, expectedDiagnostic, testCode); + } + + [Test] + public void AnalyzeWhenNegativeIsUsed() + { + var expectedDiagnostic = ExpectedDiagnostic.Create(AnalyzerIdentifiers.NegativeUsage); + + var testCode = TestUtility.WrapClassInNamespaceAndAddUsing(@" + public sealed class AnalyzeWhenNegativeIsUsed + { + public void Test() + { + ↓ClassicAssert.Negative(2); + } + }"); + RoslynAssert.Diagnostics(this.analyzer, expectedDiagnostic, testCode); + } + + [Test] + public void AnalyzeWhenIsAssignableFromIsUsed() + { + var expectedDiagnostic = ExpectedDiagnostic.Create(AnalyzerIdentifiers.IsAssignableFromUsage); + + var testCode = TestUtility.WrapClassInNamespaceAndAddUsing(@" + public sealed class AnalyzeWhenIsAssignableFromIsUsed + { + public void Test() + { + ↓ClassicAssert.IsAssignableFrom(typeof(int), 2); + } + }"); + RoslynAssert.Diagnostics(this.analyzer, expectedDiagnostic, testCode); + } + + [Test] + public void AnalyzeWhenGenericIsAssignableFromIsUsed() + { + var expectedDiagnostic = ExpectedDiagnostic.Create(AnalyzerIdentifiers.IsAssignableFromUsage); + + var testCode = TestUtility.WrapClassInNamespaceAndAddUsing(@" + public sealed class AnalyzeWhenGenericIsAssignableFromIsUsed + { + public void Test() + { + ↓ClassicAssert.IsAssignableFrom(2); + } + }"); + RoslynAssert.Diagnostics(this.analyzer, expectedDiagnostic, testCode); + } + + [Test] + public void AnalyzeWhenIsNotAssignableFromIsUsed() + { + var expectedDiagnostic = ExpectedDiagnostic.Create(AnalyzerIdentifiers.IsNotAssignableFromUsage); + + var testCode = TestUtility.WrapClassInNamespaceAndAddUsing(@" + public sealed class AnalyzeWhenIsNotAssignableFromIsUsed + { + public void Test() + { + ↓ClassicAssert.IsNotAssignableFrom(typeof(int), 2); + } + }"); + RoslynAssert.Diagnostics(this.analyzer, expectedDiagnostic, testCode); + } + + [Test] + public void AnalyzeWhenGenericIsNotAssignableFromIsUsed() + { + var expectedDiagnostic = ExpectedDiagnostic.Create(AnalyzerIdentifiers.IsNotAssignableFromUsage); + + var testCode = TestUtility.WrapClassInNamespaceAndAddUsing(@" + public sealed class AnalyzeWhenGenericIsNotAssignableFromIsUsed + { + public void Test() + { + ↓ClassicAssert.IsNotAssignableFrom(2); + } }"); RoslynAssert.Diagnostics(this.analyzer, expectedDiagnostic, testCode); } diff --git a/src/nunit.analyzers.tests/ClassicModelAssertUsage/IsAssignableFromClassicModelAssertUsageCodeFixTests.cs b/src/nunit.analyzers.tests/ClassicModelAssertUsage/IsAssignableFromClassicModelAssertUsageCodeFixTests.cs new file mode 100644 index 00000000..06b5e38d --- /dev/null +++ b/src/nunit.analyzers.tests/ClassicModelAssertUsage/IsAssignableFromClassicModelAssertUsageCodeFixTests.cs @@ -0,0 +1,442 @@ +using Gu.Roslyn.Asserts; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; +using NUnit.Analyzers.ClassicModelAssertUsage; +using NUnit.Analyzers.Constants; +using NUnit.Framework; + +namespace NUnit.Analyzers.Tests.ClassicModelAssertUsage +{ + [TestFixture] + public sealed class IsAssignableFromClassicModelAssertUsageCodeFixTests + { + private static readonly DiagnosticAnalyzer analyzer = new ClassicModelAssertUsageAnalyzer(); + private static readonly CodeFixProvider fix = new IsAssignableFromClassicModelAssertUsageCodeFix(); + private static readonly ExpectedDiagnostic expectedDiagnostic = + ExpectedDiagnostic.Create(AnalyzerIdentifiers.IsAssignableFromUsage); + + [Test] + public void VerifyGetFixableDiagnosticIds() + { + var fix = new IsAssignableFromClassicModelAssertUsageCodeFix(); + var ids = fix.FixableDiagnosticIds; + + Assert.That(ids, Is.EquivalentTo(new[] { AnalyzerIdentifiers.IsAssignableFromUsage })); + } + + [Test] + public void VerifyIsAssignableFromFix() + { + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var expected = typeof(int); + var actual = 42; + + ↓ClassicAssert.IsAssignableFrom(expected, actual); + }"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var expected = typeof(int); + var actual = 42; + + Assert.That(actual, Is.AssignableFrom(expected)); + }"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + + [Test] + public void VerifyIsAssignableFromFixWithMessage() + { + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var expected = typeof(int); + var actual = 42; + + ↓ClassicAssert.IsAssignableFrom(expected, actual, ""message""); + }"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var expected = typeof(int); + var actual = 42; + + Assert.That(actual, Is.AssignableFrom(expected), ""message""); + }"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + + [Test] + public void VerifyIsAssignableFromFixWithMessageAndOneArgumentForParams() + { + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var expected = typeof(int); + var actual = 42; + + ↓ClassicAssert.IsAssignableFrom(expected, actual, ""message-id: {0}"", Guid.NewGuid()); + }"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var expected = typeof(int); + var actual = 42; + + Assert.That(actual, Is.AssignableFrom(expected), $""message-id: {Guid.NewGuid()}""); + }"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + + [Test] + public void VerifyIsAssignableFromFixWithMessageAndTwoArgumentsForParams() + { + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var expected = typeof(int); + var actual = 42; + + ↓ClassicAssert.IsAssignableFrom(expected, actual, ""{0}, {1}"", ""first"", ""second""); + }"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var expected = typeof(int); + var actual = 42; + + Assert.That(actual, Is.AssignableFrom(expected), $""{""first""}, {""second""}""); + }"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + + [Test] + public void VerifyIsAssignableFromFixWithMessageAndArrayParamsInNonstandardOrder() + { + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var expected = typeof(int); + var actual = 42; + + ↓ClassicAssert.IsAssignableFrom(args: new[] { ""first"", ""second"" }, message: ""{0}, {1}"", actual: actual, expected: expected); + }"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var expected = typeof(int); + var actual = 42; + + Assert.That(actual, Is.AssignableFrom(expected), $""{""first""}, {""second""}""); + }"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + + [Test] + public void VerifyIsAssignableFromGenericFix() + { + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var actual = 42; + + ↓ClassicAssert.IsAssignableFrom(actual); + }"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var actual = 42; + + Assert.That(actual, Is.AssignableFrom()); + }"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + + [Test] + public void VerifyIsAssignableFromSingleNestedGenericFix() + { + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var wrapped = Create(42); + ↓ClassicAssert.IsAssignableFrom>(wrapped); + } + + private Wrapped Create(T value) => new Wrapped(value); + + private class Wrapped + { + public Wrapped(T value) + { + Value = value; + } + + public T Value { get; } + }"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var wrapped = Create(42); + Assert.That(wrapped, Is.AssignableFrom>()); + } + + private Wrapped Create(T value) => new Wrapped(value); + + private class Wrapped + { + public Wrapped(T value) + { + Value = value; + } + + public T Value { get; } + }"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + + [Test] + public void VerifyIsAssignableFromDoubleNestedGenericFix() + { + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var wrapped = Create(42); + var nested = Create(wrapped); + ↓ClassicAssert.IsAssignableFrom>>(wrapped); + } + + private Wrapped Create(T value) => new Wrapped(value); + + private class Wrapped + { + public Wrapped(T value) + { + Value = value; + } + + public T Value { get; } + }"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var wrapped = Create(42); + var nested = Create(wrapped); + Assert.That(wrapped, Is.AssignableFrom>>()); + } + + private Wrapped Create(T value) => new Wrapped(value); + + private class Wrapped + { + public Wrapped(T value) + { + Value = value; + } + + public T Value { get; } + }"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + + [Test] + public void VerifyIsAssignableFromGenericFixWithMessage() + { + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var actual = 42; + + ↓ClassicAssert.IsAssignableFrom(actual, ""message""); + }"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var actual = 42; + + Assert.That(actual, Is.AssignableFrom(), ""message""); + }"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + + [Test] + public void VerifyIsAssignableFromGenericFixWithMessageAndOneArgumentForParams() + { + var code = TestUtility.WrapInTestMethod(@" + var actual = 42; + + ↓ClassicAssert.IsAssignableFrom(actual, ""message-id: {0}"", Guid.NewGuid());"); + var fixedCode = TestUtility.WrapInTestMethod(@" + var actual = 42; + + Assert.That(actual, Is.AssignableFrom(), $""message-id: {Guid.NewGuid()}"");"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + + [Test] + public void VerifyIsAssignableFromGenericFixWithMessageAndTwoArgumentsForParams() + { + var code = TestUtility.WrapInTestMethod(@" + var actual = 42; + + ↓ClassicAssert.IsAssignableFrom(actual, ""{0}, {1}"", ""first"", ""second"");"); + var fixedCode = TestUtility.WrapInTestMethod(@" + var actual = 42; + + Assert.That(actual, Is.AssignableFrom(), $""{""first""}, {""second""}"");"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + + [Test] + public void VerifyIsAssignableFromGenericFixWithMessageAndArrayParamsInNonstandardOrder() + { + var code = TestUtility.WrapInTestMethod(@" + var actual = 42; + + ↓ClassicAssert.IsAssignableFrom(args: new[] { ""first"", ""second"" }, message: ""{0}, {1}"", actual: actual);"); + var fixedCode = TestUtility.WrapInTestMethod(@" + var actual = 42; + + Assert.That(actual, Is.AssignableFrom(), $""{""first""}, {""second""}"");"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + + [Test] + public void CodeFixMaintainsReasonableTriviaWithEndOfLineClosingParen([Values] bool hasMessage) + { + var commaAndMessage = hasMessage + ? ",\r\n \"message\"" + : string.Empty; + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" + public void TestMethod() + {{ + var expected = typeof(int); + var actual = 42; + + ↓ClassicAssert.IsAssignableFrom( + expected, + actual{commaAndMessage}); + }}"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" + public void TestMethod() + {{ + var expected = typeof(int); + var actual = 42; + + Assert.That( + actual, + Is.AssignableFrom(expected){commaAndMessage}); + }}"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + + [Test] + public void CodeFixMaintainsReasonableTriviaWithNewLineClosingParen([Values] bool hasMessage) + { + var commaAndMessage = hasMessage + ? ",\r\n \"message\"" + : string.Empty; + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" + public void TestMethod() + {{ + var expected = typeof(int); + var actual = 42; + + ↓ClassicAssert.IsAssignableFrom( + expected, + actual{commaAndMessage} + ); + }}"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" + public void TestMethod() + {{ + var expected = typeof(int); + var actual = 42; + + Assert.That( + actual, + Is.AssignableFrom(expected){commaAndMessage} + ); + }}"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + + [Test] + public void CodeFixForGenericMaintainsReasonableTriviaWithEndOfLineClosingParen([Values] bool hasMessage) + { + var commaAndMessage = hasMessage + ? ",\r\n \"message\"" + : string.Empty; + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" + public void TestMethod() + {{ + var actual = 42; + + ↓ClassicAssert.IsAssignableFrom( + actual{commaAndMessage}); + }}"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" + public void TestMethod() + {{ + var actual = 42; + + Assert.That( + actual, + Is.AssignableFrom(){commaAndMessage}); + }}"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + + [Test] + public void CodeFixForGenericMaintainsReasonableTriviaWithNewLineClosingParen([Values] bool hasMessage) + { + var commaAndMessage = hasMessage + ? ",\r\n \"message\"" + : string.Empty; + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" + public void TestMethod() + {{ + var actual = 42; + + ↓ClassicAssert.IsAssignableFrom( + actual{commaAndMessage} + ); + }}"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" + public void TestMethod() + {{ + var actual = 42; + + Assert.That( + actual, + Is.AssignableFrom(){commaAndMessage} + ); + }}"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + + [Test] + public void CodeFixMaintainsReasonableTriviaWithAllArgumentsOnSameLine([Values] bool newlineBeforeClosingParen) + { + var optionalNewline = newlineBeforeClosingParen ? "\r\n " : string.Empty; + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" + public void TestMethod() + {{ + var actual = 42; + + ↓ClassicAssert.IsAssignableFrom( + actual, ""message""{optionalNewline}); + }}"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" + public void TestMethod() + {{ + var actual = 42; + + Assert.That( + actual, Is.AssignableFrom(), ""message""{optionalNewline}); + }}"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + } +} diff --git a/src/nunit.analyzers.tests/ClassicModelAssertUsage/IsInstanceOfClassicModelAssertUsageCodeFixTests.cs b/src/nunit.analyzers.tests/ClassicModelAssertUsage/IsInstanceOfClassicModelAssertUsageCodeFixTests.cs index 3e06e049..8db4cb63 100644 --- a/src/nunit.analyzers.tests/ClassicModelAssertUsage/IsInstanceOfClassicModelAssertUsageCodeFixTests.cs +++ b/src/nunit.analyzers.tests/ClassicModelAssertUsage/IsInstanceOfClassicModelAssertUsageCodeFixTests.cs @@ -303,60 +303,62 @@ public void VerifyIsInstanceOfGenericFixWithMessageAndArrayParamsInNonstandardOr } [Test] - public void CodeFixMaintainsReasonableTriviaWithEndOfLineClosingParen() + public void CodeFixMaintainsReasonableTriviaWithEndOfLineClosingParen([Values] bool hasMessage) { - var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + var commaAndMessage = hasMessage + ? ",\r\n \"message\"" + : string.Empty; + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" public void TestMethod() - { + {{ var expected = typeof(int); var actual = 42; ↓ClassicAssert.IsInstanceOf( expected, - actual, - ""message""); - }"); - var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + actual{commaAndMessage}); + }}"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" public void TestMethod() - { + {{ var expected = typeof(int); var actual = 42; Assert.That( actual, - Is.InstanceOf(expected), - ""message""); - }"); + Is.InstanceOf(expected){commaAndMessage}); + }}"); RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); } [Test] - public void CodeFixMaintainsReasonableTriviaWithNewLineClosingParen() + public void CodeFixMaintainsReasonableTriviaWithNewLineClosingParen([Values] bool hasMessage) { - var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + var commaAndMessage = hasMessage + ? ",\r\n \"message\"" + : string.Empty; + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" public void TestMethod() - { + {{ var expected = typeof(int); var actual = 42; ↓ClassicAssert.IsInstanceOf( expected, - actual, - ""message"" + actual{commaAndMessage} ); - }"); - var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + }}"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" public void TestMethod() - { + {{ var expected = typeof(int); var actual = 42; Assert.That( actual, - Is.InstanceOf(expected), - ""message"" + Is.InstanceOf(expected){commaAndMessage} ); - }"); + }}"); RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); } diff --git a/src/nunit.analyzers.tests/ClassicModelAssertUsage/IsNotAssignableFromClassicModelAssertUsageCodeFixTests.cs b/src/nunit.analyzers.tests/ClassicModelAssertUsage/IsNotAssignableFromClassicModelAssertUsageCodeFixTests.cs new file mode 100644 index 00000000..02513cfd --- /dev/null +++ b/src/nunit.analyzers.tests/ClassicModelAssertUsage/IsNotAssignableFromClassicModelAssertUsageCodeFixTests.cs @@ -0,0 +1,442 @@ +using Gu.Roslyn.Asserts; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; +using NUnit.Analyzers.ClassicModelAssertUsage; +using NUnit.Analyzers.Constants; +using NUnit.Framework; + +namespace NUnit.Analyzers.Tests.ClassicModelAssertUsage +{ + [TestFixture] + public sealed class IsNotAssignableFromClassicModelAssertUsageCodeFixTests + { + private static readonly DiagnosticAnalyzer analyzer = new ClassicModelAssertUsageAnalyzer(); + private static readonly CodeFixProvider fix = new IsNotAssignableFromClassicModelAssertUsageCodeFix(); + private static readonly ExpectedDiagnostic expectedDiagnostic = + ExpectedDiagnostic.Create(AnalyzerIdentifiers.IsNotAssignableFromUsage); + + [Test] + public void VerifyGetFixableDiagnosticIds() + { + var fix = new IsNotAssignableFromClassicModelAssertUsageCodeFix(); + var ids = fix.FixableDiagnosticIds; + + Assert.That(ids, Is.EquivalentTo(new[] { AnalyzerIdentifiers.IsNotAssignableFromUsage })); + } + + [Test] + public void VerifyIsNotAssignableFromFix() + { + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var expected = typeof(int); + var actual = 42; + + ↓ClassicAssert.IsNotAssignableFrom(expected, actual); + }"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var expected = typeof(int); + var actual = 42; + + Assert.That(actual, Is.Not.AssignableFrom(expected)); + }"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + + [Test] + public void VerifyIsNotAssignableFromFixWithMessage() + { + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var expected = typeof(int); + var actual = 42; + + ↓ClassicAssert.IsNotAssignableFrom(expected, actual, ""message""); + }"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var expected = typeof(int); + var actual = 42; + + Assert.That(actual, Is.Not.AssignableFrom(expected), ""message""); + }"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + + [Test] + public void VerifyIsNotAssignableFromFixWithMessageAndOneArgumentForParams() + { + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var expected = typeof(int); + var actual = 42; + + ↓ClassicAssert.IsNotAssignableFrom(expected, actual, ""message-id: {0}"", Guid.NewGuid()); + }"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var expected = typeof(int); + var actual = 42; + + Assert.That(actual, Is.Not.AssignableFrom(expected), $""message-id: {Guid.NewGuid()}""); + }"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + + [Test] + public void VerifyIsNotAssignableFromFixWithMessageAndTwoArgumentsForParams() + { + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var expected = typeof(int); + var actual = 42; + + ↓ClassicAssert.IsNotAssignableFrom(expected, actual, ""{0}, {1}"", ""first"", ""second""); + }"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var expected = typeof(int); + var actual = 42; + + Assert.That(actual, Is.Not.AssignableFrom(expected), $""{""first""}, {""second""}""); + }"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + + [Test] + public void VerifyIsNotAssignableFromFixWithMessageAndArrayParamsInNonstandardOrder() + { + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var expected = typeof(int); + var actual = 42; + + ↓ClassicAssert.IsNotAssignableFrom(args: new[] { ""first"", ""second"" }, message: ""{0}, {1}"", actual: actual, expected: expected); + }"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var expected = typeof(int); + var actual = 42; + + Assert.That(actual, Is.Not.AssignableFrom(expected), $""{""first""}, {""second""}""); + }"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + + [Test] + public void VerifyIsNotAssignableFromGenericFix() + { + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var actual = 42; + + ↓ClassicAssert.IsNotAssignableFrom(actual); + }"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var actual = 42; + + Assert.That(actual, Is.Not.AssignableFrom()); + }"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + + [Test] + public void VerifyIsNotAssignableFromSingleNestedGenericFix() + { + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var wrapped = Create(42); + ↓ClassicAssert.IsNotAssignableFrom>(wrapped); + } + + private Wrapped Create(T value) => new Wrapped(value); + + private class Wrapped + { + public Wrapped(T value) + { + Value = value; + } + + public T Value { get; } + }"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var wrapped = Create(42); + Assert.That(wrapped, Is.Not.AssignableFrom>()); + } + + private Wrapped Create(T value) => new Wrapped(value); + + private class Wrapped + { + public Wrapped(T value) + { + Value = value; + } + + public T Value { get; } + }"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + + [Test] + public void VerifyIsNotAssignableFromDoubleNestedGenericFix() + { + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var wrapped = Create(42); + var nested = Create(wrapped); + ↓ClassicAssert.IsNotAssignableFrom>>(wrapped); + } + + private Wrapped Create(T value) => new Wrapped(value); + + private class Wrapped + { + public Wrapped(T value) + { + Value = value; + } + + public T Value { get; } + }"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var wrapped = Create(42); + var nested = Create(wrapped); + Assert.That(wrapped, Is.Not.AssignableFrom>>()); + } + + private Wrapped Create(T value) => new Wrapped(value); + + private class Wrapped + { + public Wrapped(T value) + { + Value = value; + } + + public T Value { get; } + }"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + + [Test] + public void VerifyIsNotAssignableFromGenericFixWithMessage() + { + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var actual = 42; + + ↓ClassicAssert.IsNotAssignableFrom(actual, ""message""); + }"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var actual = 42; + + Assert.That(actual, Is.Not.AssignableFrom(), ""message""); + }"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + + [Test] + public void VerifyIsNotAssignableFromGenericFixWithMessageAndOneArgumentForParams() + { + var code = TestUtility.WrapInTestMethod(@" + var actual = 42; + + ↓ClassicAssert.IsNotAssignableFrom(actual, ""message-id: {0}"", Guid.NewGuid());"); + var fixedCode = TestUtility.WrapInTestMethod(@" + var actual = 42; + + Assert.That(actual, Is.Not.AssignableFrom(), $""message-id: {Guid.NewGuid()}"");"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + + [Test] + public void VerifyIsNotAssignableFromGenericFixWithMessageAndTwoArgumentsForParams() + { + var code = TestUtility.WrapInTestMethod(@" + var actual = 42; + + ↓ClassicAssert.IsNotAssignableFrom(actual, ""{0}, {1}"", ""first"", ""second"");"); + var fixedCode = TestUtility.WrapInTestMethod(@" + var actual = 42; + + Assert.That(actual, Is.Not.AssignableFrom(), $""{""first""}, {""second""}"");"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + + [Test] + public void VerifyIsNotAssignableFromGenericFixWithMessageAndArrayParamsInNonstandardOrder() + { + var code = TestUtility.WrapInTestMethod(@" + var actual = 42; + + ↓ClassicAssert.IsNotAssignableFrom(args: new[] { ""first"", ""second"" }, message: ""{0}, {1}"", actual: actual);"); + var fixedCode = TestUtility.WrapInTestMethod(@" + var actual = 42; + + Assert.That(actual, Is.Not.AssignableFrom(), $""{""first""}, {""second""}"");"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + + [Test] + public void CodeFixMaintainsReasonableTriviaWithEndOfLineClosingParen([Values] bool hasMessage) + { + var commaAndMessage = hasMessage + ? ",\r\n \"message\"" + : string.Empty; + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" + public void TestMethod() + {{ + var expected = typeof(int); + var actual = 42; + + ↓ClassicAssert.IsNotAssignableFrom( + expected, + actual{commaAndMessage}); + }}"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" + public void TestMethod() + {{ + var expected = typeof(int); + var actual = 42; + + Assert.That( + actual, + Is.Not.AssignableFrom(expected){commaAndMessage}); + }}"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + + [Test] + public void CodeFixMaintainsReasonableTriviaWithNewLineClosingParen([Values] bool hasMessage) + { + var commaAndMessage = hasMessage + ? ",\r\n \"message\"" + : string.Empty; + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" + public void TestMethod() + {{ + var expected = typeof(int); + var actual = 42; + + ↓ClassicAssert.IsNotAssignableFrom( + expected, + actual{commaAndMessage} + ); + }}"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" + public void TestMethod() + {{ + var expected = typeof(int); + var actual = 42; + + Assert.That( + actual, + Is.Not.AssignableFrom(expected){commaAndMessage} + ); + }}"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + + [Test] + public void CodeFixForGenericMaintainsReasonableTriviaWithEndOfLineClosingParen([Values] bool hasMessage) + { + var commaAndMessage = hasMessage + ? ",\r\n \"message\"" + : string.Empty; + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" + public void TestMethod() + {{ + var actual = 42; + + ↓ClassicAssert.IsNotAssignableFrom( + actual{commaAndMessage}); + }}"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" + public void TestMethod() + {{ + var actual = 42; + + Assert.That( + actual, + Is.Not.AssignableFrom(){commaAndMessage}); + }}"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + + [Test] + public void CodeFixForGenericMaintainsReasonableTriviaWithNewLineClosingParen([Values] bool hasMessage) + { + var commaAndMessage = hasMessage + ? ",\r\n \"message\"" + : string.Empty; + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" + public void TestMethod() + {{ + var actual = 42; + + ↓ClassicAssert.IsNotAssignableFrom( + actual{commaAndMessage} + ); + }}"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" + public void TestMethod() + {{ + var actual = 42; + + Assert.That( + actual, + Is.Not.AssignableFrom(){commaAndMessage} + ); + }}"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + + [Test] + public void CodeFixMaintainsReasonableTriviaWithAllArgumentsOnSameLine([Values] bool newlineBeforeClosingParen) + { + var optionalNewline = newlineBeforeClosingParen ? "\r\n " : string.Empty; + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" + public void TestMethod() + {{ + var actual = 42; + + ↓ClassicAssert.IsNotAssignableFrom( + actual, ""message""{optionalNewline}); + }}"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" + public void TestMethod() + {{ + var actual = 42; + + Assert.That( + actual, Is.Not.AssignableFrom(), ""message""{optionalNewline}); + }}"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + } +} diff --git a/src/nunit.analyzers.tests/ClassicModelAssertUsage/IsNotInstanceOfClassicModelAssertUsageCodeFixTests.cs b/src/nunit.analyzers.tests/ClassicModelAssertUsage/IsNotInstanceOfClassicModelAssertUsageCodeFixTests.cs index a53e6927..160dd2fb 100644 --- a/src/nunit.analyzers.tests/ClassicModelAssertUsage/IsNotInstanceOfClassicModelAssertUsageCodeFixTests.cs +++ b/src/nunit.analyzers.tests/ClassicModelAssertUsage/IsNotInstanceOfClassicModelAssertUsageCodeFixTests.cs @@ -303,30 +303,31 @@ public void VerifyIsNotInstanceOfGenericFixWithMessageAndArrayParamsInNonstandar } [Test] - public void CodeFixMaintainsReasonableTriviaWithEndOfLineClosingParen() + public void CodeFixMaintainsReasonableTriviaWithEndOfLineClosingParen([Values] bool hasMessage) { - var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + var commaAndMessage = hasMessage + ? ",\r\n \"message\"" + : string.Empty; + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" public void TestMethod() - { + {{ var expected = typeof(int); var actual = 42; ↓ClassicAssert.IsNotInstanceOf( expected, - actual, - ""message""); - }"); - var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + actual{commaAndMessage}); + }}"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" public void TestMethod() - { + {{ var expected = typeof(int); var actual = 42; Assert.That( actual, - Is.Not.InstanceOf(expected), - ""message""); - }"); + Is.Not.InstanceOf(expected){commaAndMessage}); + }}"); RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); } @@ -387,6 +388,34 @@ public void TestMethod() RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); } + [Test] + public void CodeFixForGenericMaintainsReasonableTriviaWithNewLineClosingParen([Values] bool hasMessage) + { + var commaAndMessage = hasMessage + ? ",\r\n \"message\"" + : string.Empty; + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" + public void TestMethod() + {{ + var actual = 42; + + ↓ClassicAssert.IsNotInstanceOf( + actual{commaAndMessage} + ); + }}"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" + public void TestMethod() + {{ + var actual = 42; + + Assert.That( + actual, + Is.Not.InstanceOf(){commaAndMessage} + ); + }}"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + [Test] public void CodeFixMaintainsReasonableTriviaWithAllArgumentsOnSameLine([Values] bool newlineBeforeClosingParen) { diff --git a/src/nunit.analyzers.tests/ClassicModelAssertUsage/NegativeClassicModelAssertUsageCodeFixTests.cs b/src/nunit.analyzers.tests/ClassicModelAssertUsage/NegativeClassicModelAssertUsageCodeFixTests.cs new file mode 100644 index 00000000..03e7f49d --- /dev/null +++ b/src/nunit.analyzers.tests/ClassicModelAssertUsage/NegativeClassicModelAssertUsageCodeFixTests.cs @@ -0,0 +1,205 @@ +using Gu.Roslyn.Asserts; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; +using NUnit.Analyzers.ClassicModelAssertUsage; +using NUnit.Analyzers.Constants; +using NUnit.Framework; + +namespace NUnit.Analyzers.Tests.ClassicModelAssertUsage +{ + [TestFixture] + public sealed class NegativeClassicModelAssertUsageCodeFixTests + { + private static readonly DiagnosticAnalyzer analyzer = new ClassicModelAssertUsageAnalyzer(); + private static readonly CodeFixProvider fix = new NegativeClassicModelAssertUsageCodeFix(); + private static readonly ExpectedDiagnostic expectedDiagnostic = + ExpectedDiagnostic.Create(AnalyzerIdentifiers.NegativeUsage); + + [Test] + public void VerifyGetFixableDiagnosticIds() + { + var fix = new NegativeClassicModelAssertUsageCodeFix(); + var ids = fix.FixableDiagnosticIds; + + Assert.That(ids, Is.EquivalentTo(new[] { AnalyzerIdentifiers.NegativeUsage })); + } + + [Test] + public void VerifyNegativeFix() + { + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var expr = default(int); + + ↓ClassicAssert.Negative(expr); + }"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var expr = default(int); + + Assert.That(expr, Is.Negative); + }"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + + [Test] + public void VerifyNegativeFixWithMessage() + { + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var expr = default(int); + + ↓ClassicAssert.Negative(expr, ""message""); + }"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var expr = default(int); + + Assert.That(expr, Is.Negative, ""message""); + }"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + + [Test] + public void VerifyNegativeFixWithMessageAndOneArgumentForParams() + { + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var expr = default(int); + + ↓ClassicAssert.Negative(expr, ""message-id: {0}"", Guid.NewGuid()); + }"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var expr = default(int); + + Assert.That(expr, Is.Negative, $""message-id: {Guid.NewGuid()}""); + }"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + + [Test] + public void VerifyNegativeFixWithMessageAndTwoArgumentsForParams() + { + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var expr = default(int); + + ↓ClassicAssert.Negative(expr, ""{0}, {1}"", ""first"", ""second""); + }"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var expr = default(int); + + Assert.That(expr, Is.Negative, $""{""first""}, {""second""}""); + }"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + + [Test] + public void VerifyNegativeFixWithMessageAndArrayParamsInNonstandardOrder() + { + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var expr = default(int); + + ↓ClassicAssert.Negative(args: new[] { ""first"", ""second"" }, message: ""{0}, {1}"", actual: expr); + }"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var expr = default(int); + + Assert.That(expr, Is.Negative, $""{""first""}, {""second""}""); + }"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + + [Test] + public void CodeFixMaintainsReasonableTriviaWithEndOfLineClosingParen([Values] bool hasMessage) + { + var commaAndMessage = hasMessage + ? ",\r\n \"message\"" + : string.Empty; + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" + public void TestMethod() + {{ + var expr = default(int); + + ↓ClassicAssert.Negative( + expr{commaAndMessage}); + }}"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" + public void TestMethod() + {{ + var expr = default(int); + + Assert.That( + expr, + Is.Negative{commaAndMessage}); + }}"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + + [Test] + public void CodeFixMaintainsReasonableTriviaWithNewLineClosingParen([Values] bool hasMessage) + { + var commaAndMessage = hasMessage + ? ",\r\n \"message\"" + : string.Empty; + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" + public void TestMethod() + {{ + var expr = default(int); + + ↓ClassicAssert.Negative( + expr{commaAndMessage} + ); + }}"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" + public void TestMethod() + {{ + var expr = default(int); + + Assert.That( + expr, + Is.Negative{commaAndMessage} + ); + }}"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + + [Test] + public void CodeFixMaintainsReasonableTriviaWithAllArgumentsOnSameLine([Values] bool newlineBeforeClosingParen) + { + var optionalNewline = newlineBeforeClosingParen ? "\r\n " : string.Empty; + + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" + public void TestMethod() + {{ + var expr = default(int); + + ↓ClassicAssert.Negative( + expr, ""message""{optionalNewline}); + }}"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" + public void TestMethod() + {{ + var expr = default(int); + + Assert.That( + expr, Is.Negative, ""message""{optionalNewline}); + }}"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + } +} diff --git a/src/nunit.analyzers.tests/ClassicModelAssertUsage/PositiveClassicModelAssertUsageCodeFixTests.cs b/src/nunit.analyzers.tests/ClassicModelAssertUsage/PositiveClassicModelAssertUsageCodeFixTests.cs new file mode 100644 index 00000000..e25b8751 --- /dev/null +++ b/src/nunit.analyzers.tests/ClassicModelAssertUsage/PositiveClassicModelAssertUsageCodeFixTests.cs @@ -0,0 +1,205 @@ +using Gu.Roslyn.Asserts; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; +using NUnit.Analyzers.ClassicModelAssertUsage; +using NUnit.Analyzers.Constants; +using NUnit.Framework; + +namespace NUnit.Analyzers.Tests.ClassicModelAssertUsage +{ + [TestFixture] + public sealed class PositiveClassicModelAssertUsageCodeFixTests + { + private static readonly DiagnosticAnalyzer analyzer = new ClassicModelAssertUsageAnalyzer(); + private static readonly CodeFixProvider fix = new PositiveClassicModelAssertUsageCodeFix(); + private static readonly ExpectedDiagnostic expectedDiagnostic = + ExpectedDiagnostic.Create(AnalyzerIdentifiers.PositiveUsage); + + [Test] + public void VerifyGetFixableDiagnosticIds() + { + var fix = new PositiveClassicModelAssertUsageCodeFix(); + var ids = fix.FixableDiagnosticIds; + + Assert.That(ids, Is.EquivalentTo(new[] { AnalyzerIdentifiers.PositiveUsage })); + } + + [Test] + public void VerifyPositiveFix() + { + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var expr = default(int); + + ↓ClassicAssert.Positive(expr); + }"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var expr = default(int); + + Assert.That(expr, Is.Positive); + }"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + + [Test] + public void VerifyPositiveFixWithMessage() + { + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var expr = default(int); + + ↓ClassicAssert.Positive(expr, ""message""); + }"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var expr = default(int); + + Assert.That(expr, Is.Positive, ""message""); + }"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + + [Test] + public void VerifyPositiveFixWithMessageAndOneArgumentForParams() + { + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var expr = default(int); + + ↓ClassicAssert.Positive(expr, ""message-id: {0}"", Guid.NewGuid()); + }"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var expr = default(int); + + Assert.That(expr, Is.Positive, $""message-id: {Guid.NewGuid()}""); + }"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + + [Test] + public void VerifyPositiveFixWithMessageAndTwoArgumentsForParams() + { + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var expr = default(int); + + ↓ClassicAssert.Positive(expr, ""{0}, {1}"", ""first"", ""second""); + }"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var expr = default(int); + + Assert.That(expr, Is.Positive, $""{""first""}, {""second""}""); + }"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + + [Test] + public void VerifyPositiveFixWithMessageAndArrayParamsInNonstandardOrder() + { + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var expr = default(int); + + ↓ClassicAssert.Positive(args: new[] { ""first"", ""second"" }, message: ""{0}, {1}"", actual: expr); + }"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" + public void TestMethod() + { + var expr = default(int); + + Assert.That(expr, Is.Positive, $""{""first""}, {""second""}""); + }"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + + [Test] + public void CodeFixMaintainsReasonableTriviaWithEndOfLineClosingParen([Values] bool hasMessage) + { + var commaAndMessage = hasMessage + ? ",\r\n \"message\"" + : string.Empty; + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" + public void TestMethod() + {{ + var expr = default(int); + + ↓ClassicAssert.Positive( + expr{commaAndMessage}); + }}"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" + public void TestMethod() + {{ + var expr = default(int); + + Assert.That( + expr, + Is.Positive{commaAndMessage}); + }}"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + + [Test] + public void CodeFixMaintainsReasonableTriviaWithNewLineClosingParen([Values] bool hasMessage) + { + var commaAndMessage = hasMessage + ? ",\r\n \"message\"" + : string.Empty; + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" + public void TestMethod() + {{ + var expr = default(int); + + ↓ClassicAssert.Positive( + expr{commaAndMessage} + ); + }}"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" + public void TestMethod() + {{ + var expr = default(int); + + Assert.That( + expr, + Is.Positive{commaAndMessage} + ); + }}"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + + [Test] + public void CodeFixMaintainsReasonableTriviaWithAllArgumentsOnSameLine([Values] bool newlineBeforeClosingParen) + { + var optionalNewline = newlineBeforeClosingParen ? "\r\n " : string.Empty; + + var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" + public void TestMethod() + {{ + var expr = default(int); + + ↓ClassicAssert.Positive( + expr, ""message""{optionalNewline}); + }}"); + var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" + public void TestMethod() + {{ + var expr = default(int); + + Assert.That( + expr, Is.Positive, ""message""{optionalNewline}); + }}"); + RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription); + } + } +} diff --git a/src/nunit.analyzers.tests/Constants/NUnitFrameworkConstantsTests.cs b/src/nunit.analyzers.tests/Constants/NUnitFrameworkConstantsTests.cs index 4ea50688..3750ae14 100644 --- a/src/nunit.analyzers.tests/Constants/NUnitFrameworkConstantsTests.cs +++ b/src/nunit.analyzers.tests/Constants/NUnitFrameworkConstantsTests.cs @@ -42,10 +42,12 @@ public sealed class NUnitFrameworkConstantsTests (nameof(NUnitFrameworkConstants.NameOfIsLessThan), nameof(Is.LessThan)), (nameof(NUnitFrameworkConstants.NameOfIsLessThanOrEqualTo), nameof(Is.LessThanOrEqualTo)), (nameof(NUnitFrameworkConstants.NameOfIsPositive), nameof(Is.Positive)), + (nameof(NUnitFrameworkConstants.NameOfIsNegative), nameof(Is.Negative)), (nameof(NUnitFrameworkConstants.NameOfIsZero), nameof(Is.Zero)), (nameof(NUnitFrameworkConstants.NameOfIsNaN), nameof(Is.NaN)), (nameof(NUnitFrameworkConstants.NameOfIsEmpty), nameof(Is.Empty)), (nameof(NUnitFrameworkConstants.NameOfIsInstanceOf), nameof(Is.InstanceOf)), + (nameof(NUnitFrameworkConstants.NameOfIsAssignableFrom), nameof(Is.AssignableFrom)), (nameof(NUnitFrameworkConstants.NameOfIsAll), nameof(Is.All)), (nameof(NUnitFrameworkConstants.NameOfIsUnique), nameof(Is.Unique)), (nameof(NUnitFrameworkConstants.NameOfIsOrdered), nameof(Is.Ordered)), @@ -120,6 +122,10 @@ public sealed class NUnitFrameworkConstantsTests (nameof(NUnitFrameworkConstants.NameOfAssertContains), nameof(ClassicAssert.Contains)), (nameof(NUnitFrameworkConstants.NameOfAssertIsInstanceOf), nameof(ClassicAssert.IsInstanceOf)), (nameof(NUnitFrameworkConstants.NameOfAssertIsNotInstanceOf), nameof(ClassicAssert.IsNotInstanceOf)), + (nameof(NUnitFrameworkConstants.NameOfAssertPositive), nameof(ClassicAssert.Positive)), + (nameof(NUnitFrameworkConstants.NameOfAssertNegative), nameof(ClassicAssert.Negative)), + (nameof(NUnitFrameworkConstants.NameOfAssertIsAssignableFrom), nameof(ClassicAssert.IsAssignableFrom)), + (nameof(NUnitFrameworkConstants.NameOfAssertIsNotAssignableFrom), nameof(ClassicAssert.IsNotAssignableFrom)), (nameof(NUnitFrameworkConstants.NameOfAssertCatch), nameof(Assert.Catch)), (nameof(NUnitFrameworkConstants.NameOfAssertCatchAsync), nameof(Assert.CatchAsync)), diff --git a/src/nunit.analyzers/ClassicModelAssertUsage/ClassicModelAssertUsageAnalyzer.cs b/src/nunit.analyzers/ClassicModelAssertUsage/ClassicModelAssertUsageAnalyzer.cs index 875c2d75..0dbd50f0 100644 --- a/src/nunit.analyzers/ClassicModelAssertUsage/ClassicModelAssertUsageAnalyzer.cs +++ b/src/nunit.analyzers/ClassicModelAssertUsage/ClassicModelAssertUsageAnalyzer.cs @@ -204,6 +204,38 @@ public sealed class ClassicModelAssertUsageAnalyzer : ClassicAssertionAnalyzer defaultSeverity: DiagnosticSeverity.Info, description: ClassicModelUsageAnalyzerConstants.IsNotInstanceOfDescription); + private static readonly DiagnosticDescriptor positiveDescriptor = DiagnosticDescriptorCreator.Create( + id: AnalyzerIdentifiers.PositiveUsage, + title: ClassicModelUsageAnalyzerConstants.PositiveTitle, + messageFormat: ClassicModelUsageAnalyzerConstants.PositiveMessage, + category: Categories.Assertion, + defaultSeverity: DiagnosticSeverity.Info, + description: ClassicModelUsageAnalyzerConstants.PositiveDescription); + + private static readonly DiagnosticDescriptor negativeDescriptor = DiagnosticDescriptorCreator.Create( + id: AnalyzerIdentifiers.NegativeUsage, + title: ClassicModelUsageAnalyzerConstants.NegativeTitle, + messageFormat: ClassicModelUsageAnalyzerConstants.NegativeMessage, + category: Categories.Assertion, + defaultSeverity: DiagnosticSeverity.Info, + description: ClassicModelUsageAnalyzerConstants.NegativeDescription); + + private static readonly DiagnosticDescriptor isAssignableFromDescriptor = DiagnosticDescriptorCreator.Create( + id: AnalyzerIdentifiers.IsAssignableFromUsage, + title: ClassicModelUsageAnalyzerConstants.IsAssignableFromTitle, + messageFormat: ClassicModelUsageAnalyzerConstants.IsAssignableFromMessage, + category: Categories.Assertion, + defaultSeverity: DiagnosticSeverity.Info, + description: ClassicModelUsageAnalyzerConstants.IsAssignableFromDescription); + + private static readonly DiagnosticDescriptor isNotAssignableFromDescriptor = DiagnosticDescriptorCreator.Create( + id: AnalyzerIdentifiers.IsNotAssignableFromUsage, + title: ClassicModelUsageAnalyzerConstants.IsNotAssignableFromTitle, + messageFormat: ClassicModelUsageAnalyzerConstants.IsNotAssignableFromMessage, + category: Categories.Assertion, + defaultSeverity: DiagnosticSeverity.Info, + description: ClassicModelUsageAnalyzerConstants.IsNotAssignableFromDescription); + private static readonly ImmutableDictionary NameToDescriptor = new Dictionary { @@ -231,6 +263,10 @@ public sealed class ClassicModelAssertUsageAnalyzer : ClassicAssertionAnalyzer { NameOfAssertContains, containsDescriptor }, { NameOfAssertIsInstanceOf, isInstanceOfDescriptor }, { NameOfAssertIsNotInstanceOf, isNotInstanceOfDescriptor }, + { NameOfAssertPositive, positiveDescriptor }, + { NameOfAssertNegative, negativeDescriptor }, + { NameOfAssertIsAssignableFrom, isAssignableFromDescriptor }, + { NameOfAssertIsNotAssignableFrom, isNotAssignableFromDescriptor }, }.ToImmutableDictionary(); public override ImmutableArray SupportedDiagnostics { get; } = ClassicModelAssertUsageAnalyzer.NameToDescriptor.Values.ToImmutableArray(); diff --git a/src/nunit.analyzers/ClassicModelAssertUsage/ClassicModelUsageAnalyzerConstants.cs b/src/nunit.analyzers/ClassicModelAssertUsage/ClassicModelUsageAnalyzerConstants.cs index a95eec1f..457fe496 100644 --- a/src/nunit.analyzers/ClassicModelAssertUsage/ClassicModelUsageAnalyzerConstants.cs +++ b/src/nunit.analyzers/ClassicModelAssertUsage/ClassicModelUsageAnalyzerConstants.cs @@ -97,5 +97,21 @@ internal static class ClassicModelUsageAnalyzerConstants internal const string IsNotInstanceOfTitle = "Consider using Assert.That(actual, Is.Not.InstanceOf(expected)) instead of ClassicAssert.IsNotInstanceOf(expected, actual)"; internal const string IsNotInstanceOfMessage = "Consider using the constraint model, Assert.That(actual, Is.Not.InstanceOf(expected)), instead of the classic model, ClassicAssert.IsNotInstanceOf(expected, actual)"; internal const string IsNotInstanceOfDescription = "Consider using the constraint model, Assert.That(actual, Is.Not.InstanceOf(expected)), instead of the classic model, ClassicAssert.IsNotInstanceOf(expected, actual)."; + + internal const string PositiveTitle = "Consider using Assert.That(expr, Is.Positive) instead of ClassicAssert.Positive(expr)"; + internal const string PositiveMessage = "Consider using the constraint model, Assert.That(expr, Is.Positive), instead of the classic model, ClassicAssert.Positive(expr)"; + internal const string PositiveDescription = "Consider using the constraint model, Assert.That(expr, Is.Positive), instead of the classic model, ClassicAssert.Positive(expr)."; + + internal const string NegativeTitle = "Consider using Assert.That(expr, Is.Negative) instead of ClassicAssert.Negative(expr)"; + internal const string NegativeMessage = "Consider using the constraint model, Assert.That(expr, Is.Negative), instead of the classic model, ClassicAssert.Negative(expr)"; + internal const string NegativeDescription = "Consider using the constraint model, Assert.That(expr, Is.Negative), instead of the classic model, ClassicAssert.Negative(expr)."; + + internal const string IsAssignableFromTitle = "Consider using Assert.That(actual, Is.AssignableFrom(expected)) instead of ClassicAssert.IsAssignableFrom(expected, actual)"; + internal const string IsAssignableFromMessage = "Consider using the constraint model, Assert.That(actual, Is.AssignableFrom(expected)), instead of the classic model, ClassicAssert.IsAssignableFrom(expected, actual)"; + internal const string IsAssignableFromDescription = "Consider using the constraint model, Assert.That(actual, Is.AssignableFrom(expected)), instead of the classic model, ClassicAssert.IsAssignableFrom(expected, actual)."; + + internal const string IsNotAssignableFromTitle = "Consider using Assert.That(actual, Is.Not.AssignableFrom(expected)) instead of ClassicAssert.IsNotAssignableFrom(expected, actual)"; + internal const string IsNotAssignableFromMessage = "Consider using the constraint model, Assert.That(actual, Is.Not.AssignableFrom(expected)), instead of the classic model, ClassicAssert.IsNotAssignableFrom(expected, actual)"; + internal const string IsNotAssignableFromDescription = "Consider using the constraint model, Assert.That(actual, Is.Not.AssignableFrom(expected)), instead of the classic model, ClassicAssert.IsNotAssignableFrom(expected, actual)."; } } diff --git a/src/nunit.analyzers/ClassicModelAssertUsage/IsAssignableFromClassicModelAssertUsageCodeFix.cs b/src/nunit.analyzers/ClassicModelAssertUsage/IsAssignableFromClassicModelAssertUsageCodeFix.cs new file mode 100644 index 00000000..a5e6bcaa --- /dev/null +++ b/src/nunit.analyzers/ClassicModelAssertUsage/IsAssignableFromClassicModelAssertUsageCodeFix.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using NUnit.Analyzers.Constants; + +namespace NUnit.Analyzers.ClassicModelAssertUsage +{ + [ExportCodeFixProvider(LanguageNames.CSharp)] + [Shared] + public sealed class IsAssignableFromClassicModelAssertUsageCodeFix + : ClassicModelAssertUsageCodeFix + { + public override ImmutableArray FixableDiagnosticIds { get; } = ImmutableArray.Create(AnalyzerIdentifiers.IsAssignableFromUsage); + + protected override (ArgumentSyntax ActualArgument, ArgumentSyntax ConstraintArgument) ConstructActualAndConstraintArguments( + Diagnostic diagnostic, + IReadOnlyDictionary argumentNamesToArguments, + TypeArgumentListSyntax typeArguments) + { + var constraintArgument = SyntaxFactory.Argument( + SyntaxFactory.InvocationExpression( + SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.IdentifierName(NUnitFrameworkConstants.NameOfIs), + SyntaxFactory.GenericName(NUnitFrameworkConstants.NameOfIsAssignableFrom) + .WithTypeArgumentList(typeArguments)))); + var actualArgument = argumentNamesToArguments[NUnitFrameworkConstants.NameOfActualParameter].WithNameColon(null); + return (actualArgument, constraintArgument); + } + + protected override (ArgumentSyntax ActualArgument, ArgumentSyntax? ConstraintArgument) ConstructActualAndConstraintArguments( + Diagnostic diagnostic, + IReadOnlyDictionary argumentNamesToArguments) + { + var expectedArgument = argumentNamesToArguments[NUnitFrameworkConstants.NameOfExpectedParameter].WithNameColon(null); + var constraintArgument = SyntaxFactory.Argument( + SyntaxFactory.InvocationExpression( + SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.IdentifierName(NUnitFrameworkConstants.NameOfIs), + SyntaxFactory.IdentifierName(NUnitFrameworkConstants.NameOfIsAssignableFrom))) + .WithArgumentList(SyntaxFactory.ArgumentList( + SyntaxFactory.SingletonSeparatedList(expectedArgument)))); + + var actualArgument = argumentNamesToArguments[NUnitFrameworkConstants.NameOfActualParameter].WithNameColon(null); + return (actualArgument, constraintArgument); + } + } +} diff --git a/src/nunit.analyzers/ClassicModelAssertUsage/IsNotAssignableFromClassicModelAssertUsageCodeFix.cs b/src/nunit.analyzers/ClassicModelAssertUsage/IsNotAssignableFromClassicModelAssertUsageCodeFix.cs new file mode 100644 index 00000000..ba533b7a --- /dev/null +++ b/src/nunit.analyzers/ClassicModelAssertUsage/IsNotAssignableFromClassicModelAssertUsageCodeFix.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using NUnit.Analyzers.Constants; + +namespace NUnit.Analyzers.ClassicModelAssertUsage +{ + [ExportCodeFixProvider(LanguageNames.CSharp)] + [Shared] + public sealed class IsNotAssignableFromClassicModelAssertUsageCodeFix + : ClassicModelAssertUsageCodeFix + { + public override ImmutableArray FixableDiagnosticIds { get; } = ImmutableArray.Create(AnalyzerIdentifiers.IsNotAssignableFromUsage); + + protected override (ArgumentSyntax ActualArgument, ArgumentSyntax ConstraintArgument) ConstructActualAndConstraintArguments( + Diagnostic diagnostic, + IReadOnlyDictionary argumentNamesToArguments, + TypeArgumentListSyntax typeArguments) + { + var constraintArgument = SyntaxFactory.Argument( + SyntaxFactory.InvocationExpression( + SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.IdentifierName(NUnitFrameworkConstants.NameOfIs), + SyntaxFactory.IdentifierName(NUnitFrameworkConstants.NameOfIsNot)), + SyntaxFactory.GenericName(NUnitFrameworkConstants.NameOfIsAssignableFrom) + .WithTypeArgumentList(typeArguments)))); + var actualArgument = argumentNamesToArguments[NUnitFrameworkConstants.NameOfActualParameter].WithNameColon(null); + return (actualArgument, constraintArgument); + } + + protected override (ArgumentSyntax ActualArgument, ArgumentSyntax? ConstraintArgument) ConstructActualAndConstraintArguments( + Diagnostic diagnostic, + IReadOnlyDictionary argumentNamesToArguments) + { + var expectedArgument = argumentNamesToArguments[NUnitFrameworkConstants.NameOfExpectedParameter].WithNameColon(null); + var constraintArgument = SyntaxFactory.Argument( + SyntaxFactory.InvocationExpression( + SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.IdentifierName(NUnitFrameworkConstants.NameOfIs), + SyntaxFactory.IdentifierName(NUnitFrameworkConstants.NameOfIsNot)), + SyntaxFactory.IdentifierName(NUnitFrameworkConstants.NameOfIsAssignableFrom))) + .WithArgumentList(SyntaxFactory.ArgumentList( + SyntaxFactory.SingletonSeparatedList(expectedArgument)))); + + var actualArgument = argumentNamesToArguments[NUnitFrameworkConstants.NameOfActualParameter].WithNameColon(null); + return (actualArgument, constraintArgument); + } + } +} diff --git a/src/nunit.analyzers/ClassicModelAssertUsage/NegativeClassicModelAssertUsageCodeFix.cs b/src/nunit.analyzers/ClassicModelAssertUsage/NegativeClassicModelAssertUsageCodeFix.cs new file mode 100644 index 00000000..466e8092 --- /dev/null +++ b/src/nunit.analyzers/ClassicModelAssertUsage/NegativeClassicModelAssertUsageCodeFix.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using NUnit.Analyzers.Constants; + +namespace NUnit.Analyzers.ClassicModelAssertUsage +{ + [ExportCodeFixProvider(LanguageNames.CSharp)] + [Shared] + public sealed class NegativeClassicModelAssertUsageCodeFix + : ClassicModelAssertUsageCodeFix + { + public override ImmutableArray FixableDiagnosticIds { get; } = ImmutableArray.Create( + AnalyzerIdentifiers.NegativeUsage); + + protected override (ArgumentSyntax ActualArgument, ArgumentSyntax? ConstraintArgument) ConstructActualAndConstraintArguments( + Diagnostic diagnostic, + IReadOnlyDictionary argumentNamesToArguments) + { + var actualArgument = argumentNamesToArguments[NUnitFrameworkConstants.NameOfActualParameter].WithNameColon(null); + var constraintArgument = SyntaxFactory.Argument( + SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.IdentifierName(NUnitFrameworkConstants.NameOfIs), + SyntaxFactory.IdentifierName(NUnitFrameworkConstants.NameOfIsNegative))); + return (actualArgument, constraintArgument); + } + } +} diff --git a/src/nunit.analyzers/ClassicModelAssertUsage/PositiveClassicModelAssertUsageCodeFix.cs b/src/nunit.analyzers/ClassicModelAssertUsage/PositiveClassicModelAssertUsageCodeFix.cs new file mode 100644 index 00000000..33fe3ee7 --- /dev/null +++ b/src/nunit.analyzers/ClassicModelAssertUsage/PositiveClassicModelAssertUsageCodeFix.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using NUnit.Analyzers.Constants; + +namespace NUnit.Analyzers.ClassicModelAssertUsage +{ + [ExportCodeFixProvider(LanguageNames.CSharp)] + [Shared] + public sealed class PositiveClassicModelAssertUsageCodeFix + : ClassicModelAssertUsageCodeFix + { + public override ImmutableArray FixableDiagnosticIds { get; } = ImmutableArray.Create( + AnalyzerIdentifiers.PositiveUsage); + + protected override (ArgumentSyntax ActualArgument, ArgumentSyntax? ConstraintArgument) ConstructActualAndConstraintArguments( + Diagnostic diagnostic, + IReadOnlyDictionary argumentNamesToArguments) + { + var actualArgument = argumentNamesToArguments[NUnitFrameworkConstants.NameOfActualParameter].WithNameColon(null); + var constraintArgument = SyntaxFactory.Argument( + SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.IdentifierName(NUnitFrameworkConstants.NameOfIs), + SyntaxFactory.IdentifierName(NUnitFrameworkConstants.NameOfIsPositive))); + return (actualArgument, constraintArgument); + } + } +} diff --git a/src/nunit.analyzers/Constants/AnalyzerIdentifiers.cs b/src/nunit.analyzers/Constants/AnalyzerIdentifiers.cs index 79c92c74..ba808138 100644 --- a/src/nunit.analyzers/Constants/AnalyzerIdentifiers.cs +++ b/src/nunit.analyzers/Constants/AnalyzerIdentifiers.cs @@ -92,6 +92,10 @@ internal static class AnalyzerIdentifiers internal const string StringAssertUsage = "NUnit2048"; internal const string CollectionAssertUsage = "NUnit2049"; internal const string UpdateStringFormatToInterpolatableString = "NUnit2050"; + internal const string PositiveUsage = "NUnit2051"; + internal const string NegativeUsage = "NUnit2052"; + internal const string IsAssignableFromUsage = "NUnit2053"; + internal const string IsNotAssignableFromUsage = "NUnit2054"; #endregion Assertion diff --git a/src/nunit.analyzers/Constants/NUnitFrameworkConstants.cs b/src/nunit.analyzers/Constants/NUnitFrameworkConstants.cs index 7f5f7b2c..abd786e6 100644 --- a/src/nunit.analyzers/Constants/NUnitFrameworkConstants.cs +++ b/src/nunit.analyzers/Constants/NUnitFrameworkConstants.cs @@ -22,10 +22,12 @@ public static class NUnitFrameworkConstants public const string NameOfIsLessThan = "LessThan"; public const string NameOfIsLessThanOrEqualTo = "LessThanOrEqualTo"; public const string NameOfIsPositive = "Positive"; + public const string NameOfIsNegative = "Negative"; public const string NameOfIsZero = "Zero"; public const string NameOfIsNaN = "NaN"; public const string NameOfIsEmpty = "Empty"; public const string NameOfIsInstanceOf = "InstanceOf"; + public const string NameOfIsAssignableFrom = "AssignableFrom"; public const string NameOfIsAll = "All"; public const string NameOfIsUnique = "Unique"; public const string NameOfIsOrdered = "Ordered"; @@ -96,6 +98,10 @@ public static class NUnitFrameworkConstants public const string NameOfAssertContains = "Contains"; public const string NameOfAssertIsInstanceOf = "IsInstanceOf"; public const string NameOfAssertIsNotInstanceOf = "IsNotInstanceOf"; + public const string NameOfAssertIsAssignableFrom = "IsAssignableFrom"; + public const string NameOfAssertIsNotAssignableFrom = "IsNotAssignableFrom"; + public const string NameOfAssertPositive = "Positive"; + public const string NameOfAssertNegative = "Negative"; public const string NameOfAssertCatch = "Catch"; public const string NameOfAssertCatchAsync = "CatchAsync";