-
Notifications
You must be signed in to change notification settings - Fork 33
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #772 from manfred-brands/Issue770_TestContext.Write
Added TestContext.Write Is Obsolete Analyzer
- Loading branch information
Showing
12 changed files
with
358 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
# NUnit1033 | ||
|
||
## The Write methods on TestContext will be marked as Obsolete and eventually removed | ||
|
||
| Topic | Value | ||
| :-- | :-- | ||
| Id | NUnit1033 | ||
| Severity | Warning | ||
| Enabled | True | ||
| Category | Structure | ||
| Code | [TestContextWriteIsObsoleteAnalyzer](https://github.com/nunit/nunit.analyzers/blob/master/src/nunit.analyzers/TestContextWriteIsObsolete/TestContextWriteIsObsoleteAnalyzer.cs) | ||
|
||
## Description | ||
|
||
Direct `Write` calls should be replaced with `Out.Write`. | ||
|
||
Future version of NUnit will first mark the `.Write` methods on `TestContext` | ||
as `Obsolete` and eventually remove them. | ||
|
||
This rule allows updating your code before the methods are removed. | ||
|
||
## Motivation | ||
|
||
The `Write` methods are simple wrappers calling `Out.Write`. | ||
There is no wrapper for `Error` which always required to use `TestContext.Error.Write`. | ||
Besides this being inconsistent, later versions of .NET added new overloads, | ||
e.g. for `ReadOnlySpan<char>` and `async` methods like `WriteAsync`. | ||
Instead of adding more and more dummy wrappers, it was decided that user code should use | ||
the `Out` property and then can use any `Write` overload available on `TextWriter`. | ||
|
||
## How to fix violations | ||
|
||
Simply insert `.Out` between `TestContext` and `.Write`. | ||
|
||
`TestContext.WriteLine("This isn't right");` | ||
|
||
becomes | ||
|
||
`TestContext.Out.WriteLine("This isn't right");` | ||
|
||
<!-- start generated config severity --> | ||
## 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 | ||
# NUnit1033: The Write methods on TestContext will be marked as Obsolete and eventually removed | ||
dotnet_diagnostic.NUnit1033.severity = chosenSeverity | ||
``` | ||
|
||
where `chosenSeverity` can be one of `none`, `silent`, `suggestion`, `warning`, or `error`. | ||
|
||
### Via #pragma directive | ||
|
||
```csharp | ||
#pragma warning disable NUnit1033 // The Write methods on TestContext will be marked as Obsolete and eventually removed | ||
Code violating the rule here | ||
#pragma warning restore NUnit1033 // The Write methods on TestContext will be marked as Obsolete and eventually removed | ||
``` | ||
|
||
Or put this at the top of the file to disable all instances. | ||
|
||
```csharp | ||
#pragma warning disable NUnit1033 // The Write methods on TestContext will be marked as Obsolete and eventually removed | ||
``` | ||
|
||
### Via attribute `[SuppressMessage]` | ||
|
||
```csharp | ||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Structure", | ||
"NUnit1033:The Write methods on TestContext will be marked as Obsolete and eventually removed", | ||
Justification = "Reason...")] | ||
``` | ||
<!-- end generated config severity --> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
39 changes: 39 additions & 0 deletions
39
...nit.analyzers.tests/TestContextWriteIsObsolete/TestContextWriteIsObsoleteAnalyzerTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
using Gu.Roslyn.Asserts; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
using NUnit.Analyzers.Constants; | ||
using NUnit.Analyzers.TestContextWriteIsObsolete; | ||
using NUnit.Framework; | ||
|
||
namespace NUnit.Analyzers.Tests.TestContextWriteIsObsolete | ||
{ | ||
public class TestContextWriteIsObsoleteAnalyzerTests | ||
{ | ||
private static readonly DiagnosticAnalyzer analyzer = new TestContextWriteIsObsoleteAnalyzer(); | ||
private static readonly ExpectedDiagnostic expectedDiagnostic = | ||
ExpectedDiagnostic.Create(AnalyzerIdentifiers.TestContextWriteIsObsolete); | ||
|
||
[TestCaseSource(typeof(TestContextWriteIsObsoleteTestCases), nameof(TestContextWriteIsObsoleteTestCases.WriteInvocations))] | ||
public void AnyDirectWriteMethod(string writeMethodAndParameters) | ||
{ | ||
var testCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" | ||
public void Test() | ||
{{ | ||
↓TestContext.{writeMethodAndParameters}; | ||
}}"); | ||
|
||
RoslynAssert.Diagnostics(analyzer, expectedDiagnostic, testCode); | ||
} | ||
|
||
[TestCaseSource(typeof(TestContextWriteIsObsoleteTestCases), nameof(TestContextWriteIsObsoleteTestCases.WriteInvocations))] | ||
public void AnyIndirectWriteMethod(string writeMethodAndParameters) | ||
{ | ||
var testCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" | ||
public void Test() | ||
{{ | ||
TestContext.Out.{writeMethodAndParameters}; | ||
}}"); | ||
|
||
RoslynAssert.Valid(analyzer, testCode); | ||
} | ||
} | ||
} |
36 changes: 36 additions & 0 deletions
36
...unit.analyzers.tests/TestContextWriteIsObsolete/TestContextWriteIsObsoleteCodeFixTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
using Gu.Roslyn.Asserts; | ||
using Microsoft.CodeAnalysis.CodeFixes; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
using NUnit.Analyzers.Constants; | ||
using NUnit.Analyzers.TestContextWriteIsObsolete; | ||
using NUnit.Framework; | ||
|
||
namespace NUnit.Analyzers.Tests.TestContextWriteIsObsolete | ||
{ | ||
public class TestContextWriteIsObsoleteCodeFixTests | ||
{ | ||
private static readonly DiagnosticAnalyzer analyzer = new TestContextWriteIsObsoleteAnalyzer(); | ||
private static readonly CodeFixProvider fix = new TestContextWriteIsObsoleteCodeFix(); | ||
private static readonly ExpectedDiagnostic expectedDiagnostic = | ||
ExpectedDiagnostic.Create(AnalyzerIdentifiers.TestContextWriteIsObsolete); | ||
|
||
[TestCaseSource(typeof(TestContextWriteIsObsoleteTestCases), nameof(TestContextWriteIsObsoleteTestCases.WriteInvocations))] | ||
public void AnyWriteMethod(string writeMethodAndParameters) | ||
{ | ||
var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" | ||
public void Test() | ||
{{ | ||
↓TestContext.{writeMethodAndParameters}; | ||
}}"); | ||
|
||
var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" | ||
public void Test() | ||
{{ | ||
TestContext.Out.{writeMethodAndParameters}; | ||
}}"); | ||
|
||
RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, | ||
fixTitle: TestContextWriteIsObsoleteCodeFix.InsertOutDescription); | ||
} | ||
} | ||
} |
46 changes: 46 additions & 0 deletions
46
src/nunit.analyzers.tests/TestContextWriteIsObsolete/TestContextWriteIsObsoleteTestCases.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
namespace NUnit.Analyzers.Tests.TestContextWriteIsObsolete | ||
{ | ||
internal static class TestContextWriteIsObsoleteTestCases | ||
{ | ||
public static readonly string[] WriteInvocations = | ||
{ | ||
"Write(true)", | ||
"Write('!')", | ||
"Write(new char[] { '!', '!' })", | ||
"Write(default(char[]))", | ||
"Write(1D)", | ||
"Write(1)", | ||
"Write(1L)", | ||
"Write(1M)", | ||
"Write(default(object))", | ||
"Write(1F)", | ||
"Write(\"NUnit\")", | ||
"Write(default(string))", | ||
"Write(1U)", | ||
"Write(1UL)", | ||
"Write(\"{0}\", 1)", | ||
"Write(\"{0} + {1}\", 1, 2)", | ||
"Write(\"{0} + {1} = {2}\", 1, 2, 3)", | ||
"Write(\"{0} + {1} = {2} + {3}\", 1, 2, 2, 1)", | ||
"WriteLine()", | ||
"WriteLine(true)", | ||
"WriteLine('!')", | ||
"WriteLine(new char[] { '!', '!' })", | ||
"WriteLine(default(char[]))", | ||
"WriteLine(1D)", | ||
"WriteLine(1)", | ||
"WriteLine(1L)", | ||
"WriteLine(1M)", | ||
"WriteLine(default(object))", | ||
"WriteLine(1F)", | ||
"WriteLine(\"NUnit\")", | ||
"Write(default(string))", | ||
"WriteLine(1U)", | ||
"WriteLine(1UL)", | ||
"WriteLine(\"{0}\", 1)", | ||
"WriteLine(\"{0} + {1}\", 1, 2)", | ||
"WriteLine(\"{0} + {1} = {2}\", 1, 2, 3)", | ||
"WriteLine(\"{0} + {1} = {2} + {3}\", 1, 2, 2, 1)", | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
68 changes: 68 additions & 0 deletions
68
src/nunit.analyzers/TestContextWriteIsObsolete/TestContextWriteIsObsoleteAnalyzer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
using System.Collections.Immutable; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
using Microsoft.CodeAnalysis.Operations; | ||
using NUnit.Analyzers.Constants; | ||
|
||
namespace NUnit.Analyzers.TestContextWriteIsObsolete | ||
{ | ||
[DiagnosticAnalyzer(LanguageNames.CSharp)] | ||
public class TestContextWriteIsObsoleteAnalyzer : DiagnosticAnalyzer | ||
{ | ||
private static readonly DiagnosticDescriptor descriptor = DiagnosticDescriptorCreator.Create( | ||
id: AnalyzerIdentifiers.TestContextWriteIsObsolete, | ||
title: TestContextWriteIsObsoleteAnalyzerConstants.Title, | ||
messageFormat: TestContextWriteIsObsoleteAnalyzerConstants.Message, | ||
category: Categories.Structure, | ||
defaultSeverity: DiagnosticSeverity.Warning, | ||
description: TestContextWriteIsObsoleteAnalyzerConstants.Description); | ||
|
||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(descriptor); | ||
|
||
public override void Initialize(AnalysisContext context) | ||
{ | ||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); | ||
context.EnableConcurrentExecution(); | ||
context.RegisterCompilationStartAction(AnalyzeCompilationStart); | ||
} | ||
|
||
private static void AnalyzeCompilationStart(CompilationStartAnalysisContext context) | ||
{ | ||
INamedTypeSymbol? testContextType = context.Compilation.GetTypeByMetadataName(NUnitFrameworkConstants.FullNameOfTypeTestContext); | ||
if (testContextType is null) | ||
{ | ||
return; | ||
} | ||
|
||
context.RegisterOperationAction(context => AnalyzeInvocation(testContextType, context), OperationKind.Invocation); | ||
} | ||
|
||
private static void AnalyzeInvocation(INamedTypeSymbol testContextType, OperationAnalysisContext context) | ||
{ | ||
if (context.Operation is not IInvocationOperation invocationOperation) | ||
return; | ||
|
||
// TestContext.Write methods are static methods | ||
if (invocationOperation.Instance is not null) | ||
return; | ||
|
||
IMethodSymbol targetMethod = invocationOperation.TargetMethod; | ||
|
||
if (!targetMethod.ReturnsVoid) | ||
return; | ||
|
||
context.CancellationToken.ThrowIfCancellationRequested(); | ||
|
||
if (!SymbolEqualityComparer.Default.Equals(targetMethod.ContainingType, testContextType)) | ||
return; | ||
|
||
if (targetMethod.Name is NUnitFrameworkConstants.NameOfWrite or | ||
NUnitFrameworkConstants.NameOfWriteLine) | ||
{ | ||
context.ReportDiagnostic(Diagnostic.Create( | ||
descriptor, | ||
invocationOperation.Syntax.GetLocation())); | ||
} | ||
} | ||
} | ||
} |
9 changes: 9 additions & 0 deletions
9
...nunit.analyzers/TestContextWriteIsObsolete/TestContextWriteIsObsoleteAnalyzerConstants.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
namespace NUnit.Analyzers.TestContextWriteIsObsolete | ||
{ | ||
internal static class TestContextWriteIsObsoleteAnalyzerConstants | ||
{ | ||
public const string Title = "The Write methods on TestContext will be marked as Obsolete and eventually removed"; | ||
public const string Message = "The Write methods are wrappers on TestContext.Out"; | ||
public const string Description = "Direct Write calls should be replaced with Out.Write."; | ||
} | ||
} |
Oops, something went wrong.