diff --git a/NUnit.Analyzers/Constants/AnalyzerIdentifiers.cs b/NUnit.Analyzers/Constants/AnalyzerIdentifiers.cs index df71138..6cc6329 100644 --- a/NUnit.Analyzers/Constants/AnalyzerIdentifiers.cs +++ b/NUnit.Analyzers/Constants/AnalyzerIdentifiers.cs @@ -3,5 +3,6 @@ internal static class AnalyzerIdentifiers { internal const string TestFieldIsNotReadonly = "NU0001"; + internal const string TestUsesSetupAttributes = "NU0002"; } } \ No newline at end of file diff --git a/NUnit.Analyzers/TestFieldIsNotReadonly/TestFieldIsNotReadonlyCodeFix.cs b/NUnit.Analyzers/TestFieldIsNotReadonly/TestFieldIsNotReadonlyCodeFix.cs index 69b737b..c0e08ab 100644 --- a/NUnit.Analyzers/TestFieldIsNotReadonly/TestFieldIsNotReadonlyCodeFix.cs +++ b/NUnit.Analyzers/TestFieldIsNotReadonly/TestFieldIsNotReadonlyCodeFix.cs @@ -1,8 +1,10 @@ -using System.Collections.Immutable; +using System.Collections.Immutable; +using System.Composition; using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -11,9 +13,12 @@ namespace NUnit.Analyzers.TestFieldIsNotReadonly { + [Shared] [ExportCodeFixProvider(LanguageNames.CSharp)] public class TestFieldIsNotReadonlyCodeFix : CodeFixProvider { + private const string makeTestFieldReadonly = "Make test field readonly"; + public override sealed FixAllProvider GetFixAllProvider() { return WellKnownFixAllProviders.BatchFixer; @@ -37,7 +42,22 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) if (originalExpression.Modifiers.Any(IsReadonlyModifier)) return; - // var newExpression = originalExpression.WithModifiers(originalExpression.Modifiers.Add(Synra)) + var readonlySyntax = SyntaxFactory.Token( + SyntaxTriviaList.Empty, + SyntaxKind.ReadOnlyKeyword, + SyntaxTriviaList.Create(SyntaxFactory.Whitespace(" "))); + + var addedReadonlyModifier = originalExpression.Modifiers.Add(readonlySyntax); + var newExpression = originalExpression.WithModifiers(addedReadonlyModifier); + + var newRoot = root.ReplaceNode(originalExpression, newExpression); + + var codeAction = CodeAction.Create( + makeTestFieldReadonly, + _ => Task.FromResult(context.Document.WithSyntaxRoot(newRoot)), + makeTestFieldReadonly); + + context.RegisterCodeFix(codeAction, context.Diagnostics); } private static bool IsReadonlyModifier(SyntaxToken syntaxToken) => diff --git a/NUnit.Analyzers/TestFieldIsNotReadonly/TestFieldIsNotReadonlyConstants.cs b/NUnit.Analyzers/TestFieldIsNotReadonly/TestFieldIsNotReadonlyConstants.cs index 7c2b2a6..3ddfaf2 100644 --- a/NUnit.Analyzers/TestFieldIsNotReadonly/TestFieldIsNotReadonlyConstants.cs +++ b/NUnit.Analyzers/TestFieldIsNotReadonly/TestFieldIsNotReadonlyConstants.cs @@ -1,6 +1,6 @@ -namespace NUnit.Analyzers.TestFieldIsNotReadonly +namespace NUnit.Analyzers.TestFieldIsNotReadonly { - internal class TestFieldIsNotReadonlyConstants + internal static class TestFieldIsNotReadonlyConstants { internal const string TestFieldIsNotReadonlyTitle = "The field in test class is not readonly"; internal const string TestFieldIsNotReadonlyMessage = "Fields in test classes should be readonly"; diff --git a/NUnit.Analyzers/TestUsesSetupMethods/TestUsesSetupMethodsAnalyzer.cs b/NUnit.Analyzers/TestUsesSetupMethods/TestUsesSetupMethodsAnalyzer.cs new file mode 100644 index 0000000..d1945d4 --- /dev/null +++ b/NUnit.Analyzers/TestUsesSetupMethods/TestUsesSetupMethodsAnalyzer.cs @@ -0,0 +1,50 @@ +using System.Collections.Immutable; +using System.Linq; + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; + +using NUnit.Analyzers.Constants; +using NUnit.Analyzers.Extensions; + +namespace NUnit.Analyzers.TestUsesSetupMethods +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class TestUsesSetupMethodsAnalyzer : DiagnosticAnalyzer + { + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterSymbolAction(AnalyzeMethod, SymbolKind.Method); + } + + private static void AnalyzeMethod(SymbolAnalysisContext context) + { + var methodSymbol = (IMethodSymbol)context.Symbol; + if (IsSetUpTearDownMethod(context.Compilation, methodSymbol)) + { + context.ReportDiagnostic(Diagnostic.Create(testUsesSetupMethods, methodSymbol.Locations[0])); + } + } + + private static bool IsSetUpTearDownMethod(Compilation compilation, IMethodSymbol methodSymbol) + { + return methodSymbol.GetAttributes().Any(a => a.IsSetUpOrTearDownMethodAttribute(compilation)); + } + + public override ImmutableArray SupportedDiagnostics => + ImmutableArray.Create(testUsesSetupMethods); + + private static readonly DiagnosticDescriptor testUsesSetupMethods = new DiagnosticDescriptor( + AnalyzerIdentifiers.TestUsesSetupAttributes, + TestUsesSetupMethodsConstants.TestUsesSetupMethodsTitle, + TestUsesSetupMethodsConstants.TestUsesSetupMethodsMessage, + Categories.ParallelExecution, + DiagnosticSeverity.Warning, + true, + TestUsesSetupMethodsConstants.TestUsesSetupMethodsDescription, + TestUsesSetupMethodsConstants.TestUsesSetupMethodsUri + ); + } +} \ No newline at end of file diff --git a/NUnit.Analyzers/TestUsesSetupMethods/TestUsesSetupMethodsConstants.cs b/NUnit.Analyzers/TestUsesSetupMethods/TestUsesSetupMethodsConstants.cs new file mode 100644 index 0000000..9bd83b8 --- /dev/null +++ b/NUnit.Analyzers/TestUsesSetupMethods/TestUsesSetupMethodsConstants.cs @@ -0,0 +1,10 @@ +namespace NUnit.Analyzers.TestUsesSetupMethods +{ + internal class TestUsesSetupMethodsConstants + { + internal const string TestUsesSetupMethodsTitle = "Test uses setup methods"; + internal const string TestUsesSetupMethodsMessage = "Setup methods are considered harmful"; + internal const string TestUsesSetupMethodsDescription = "If you require a similar object or state for your tests, prefer a helper method than using Setup and Teardown attributes."; + internal const string TestUsesSetupMethodsUri = "https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-best-practices#prefer-helper-methods-to-setup-and-teardown"; + } +} \ No newline at end of file diff --git a/NUnit.Extensions.Tests/Middlewares/ParallelParametrizedTestFixtureTest.cs b/NUnit.Extensions.Tests/Middlewares/ParallelParametrizedTestFixtureTest.cs index 24a677c..06352e0 100644 --- a/NUnit.Extensions.Tests/Middlewares/ParallelParametrizedTestFixtureTest.cs +++ b/NUnit.Extensions.Tests/Middlewares/ParallelParametrizedTestFixtureTest.cs @@ -1,10 +1,8 @@ -using System; -using System.Linq; +using System.Linq; using FluentAssertions; using NUnit.Framework; -using NUnit.Framework.Internal.Execution; using SkbKontur.NUnit.Middlewares; @@ -24,21 +22,13 @@ namespace SkbKontur.NUnit.Extensions.Tests.Middlewares [Parallelizable(ParallelScope.Self)] public class ParallelParametrizedTestFixtureTest : SimpleTestBase { - private event Action E; - private readonly int i1; - private int i2; - private const int i3 = 0; - public ParallelParametrizedTestFixtureTest(int i, int i2) { this.i = i; - this.i1 = 1; - E += () => {}; } protected override void Configure(ISetupBuilder fixture, ISetupBuilder test) { - i2 = 3; fixture .UseSetup() .Use(t => t.GetFromThisOrParentContext().InvocationsCount += i); @@ -47,10 +37,6 @@ protected override void Configure(ISetupBuilder fixture, ISetupBuilder test) [Test] public void Test() { - E(); - Console.WriteLine(i1); - Console.WriteLine(i2); - Console.WriteLine(i3); var counter = SimpleTestContext.Current.Get(); counter.Should().NotBeNull(); diff --git a/NUnit.Extensions.Tests/NUnit.Extensions.Tests.csproj b/NUnit.Extensions.Tests/NUnit.Extensions.Tests.csproj index 08269d5..ade9e9d 100644 --- a/NUnit.Extensions.Tests/NUnit.Extensions.Tests.csproj +++ b/NUnit.Extensions.Tests/NUnit.Extensions.Tests.csproj @@ -10,14 +10,12 @@ - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - + + + + - +