From e49d6c995625de2073b88441b7cd343a90f38a02 Mon Sep 17 00:00:00 2001 From: tomasz-podolak Date: Fri, 22 Jul 2022 03:08:36 +0200 Subject: [PATCH] GH-186 - SyncOverAsyncThrowsCodeFixProvider to provide ThrowsAsync fixes for new versions of NSubstitute --- .../SyncOverAsyncThrowsCodeFixProvider.cs | 11 + ...tractSyncOverAsyncThrowsCodeFixProvider.cs | 70 ++++-- .../Extensions/SubstituteSymbolExtensions.cs | 2 +- .../SyncOverAsyncThrowsCodeFixProvider.cs | 5 + .../SyncOverAsyncThrowsCodeFixActionsTests.cs | 21 +- .../SyncOverAsyncThrowsCodeFixVerifier.cs | 21 ++ .../ThrowsAsExtensionMethodTests.cs | 182 +++++++++++++++- ...ionsMethodWithGenericTypeSpecifiedTests.cs | 152 ++++++++++++- .../ThrowsAsOrdinaryMethodTests.cs | 200 +++++++++++++++++- ...naryMethodWithGenericTypeSpecifiedTests.cs | 158 +++++++++++++- .../CodeFixCodeActionsVerifier.cs | 50 +++-- .../CodeFixProviders/CodeFixVerifier.cs | 99 ++++----- ...ncOverAsyncThrowsCodeFixActionsVerifier.cs | 4 +- .../ISyncOverAsyncThrowsCodeFixVerifier.cs | 6 + .../CodeVerifier.cs | 19 ++ .../SyncOverAsyncThrowsCodeFixActionsTests.cs | 21 +- .../SyncOverAsyncThrowsCodeFixVerifier.cs | 21 ++ .../ThrowsAsExtensionMethodTests.cs | 15 ++ ...ionsMethodWithGenericTypeSpecifiedTests.cs | 15 ++ .../ThrowsAsOrdinaryMethodTests.cs | 15 ++ ...naryMethodWithGenericTypeSpecifiedTests.cs | 15 ++ 21 files changed, 980 insertions(+), 122 deletions(-) diff --git a/src/NSubstitute.Analyzers.CSharp/CodeFixProviders/SyncOverAsyncThrowsCodeFixProvider.cs b/src/NSubstitute.Analyzers.CSharp/CodeFixProviders/SyncOverAsyncThrowsCodeFixProvider.cs index fc33719c..b2977685 100644 --- a/src/NSubstitute.Analyzers.CSharp/CodeFixProviders/SyncOverAsyncThrowsCodeFixProvider.cs +++ b/src/NSubstitute.Analyzers.CSharp/CodeFixProviders/SyncOverAsyncThrowsCodeFixProvider.cs @@ -1,7 +1,9 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using NSubstitute.Analyzers.Shared.CodeFixProviders; +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace NSubstitute.Analyzers.CSharp.CodeFixProviders; @@ -9,4 +11,13 @@ namespace NSubstitute.Analyzers.CSharp.CodeFixProviders; internal sealed class SyncOverAsyncThrowsCodeFixProvider : AbstractSyncOverAsyncThrowsCodeFixProvider { protected override SyntaxNode GetExpression(InvocationExpressionSyntax invocationExpressionSyntax) => ((MemberAccessExpressionSyntax)invocationExpressionSyntax.Expression).Expression; + + protected override SyntaxNode UpdateMemberExpression(InvocationExpressionSyntax invocationExpressionSyntax, string methodName) + { + var expressionSyntax = invocationExpressionSyntax.Expression; + return invocationExpressionSyntax.WithExpression(MemberAccessExpression( + expressionSyntax.Kind(), + ((MemberAccessExpressionSyntax)expressionSyntax).Expression, + IdentifierName(methodName))); + } } \ No newline at end of file diff --git a/src/NSubstitute.Analyzers.Shared/CodeFixProviders/AbstractSyncOverAsyncThrowsCodeFixProvider.cs b/src/NSubstitute.Analyzers.Shared/CodeFixProviders/AbstractSyncOverAsyncThrowsCodeFixProvider.cs index de2d1713..0a576f7e 100644 --- a/src/NSubstitute.Analyzers.Shared/CodeFixProviders/AbstractSyncOverAsyncThrowsCodeFixProvider.cs +++ b/src/NSubstitute.Analyzers.Shared/CodeFixProviders/AbstractSyncOverAsyncThrowsCodeFixProvider.cs @@ -39,19 +39,18 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken); var methodSymbol = (IMethodSymbol)semanticModel.GetSymbolInfo(invocation).Symbol; + var supportsThrowsAsync = SupportsThrowsAsync(semanticModel.Compilation); - if (methodSymbol.Parameters.Any(param => param.Type.IsCallInfoDelegate(semanticModel))) + if (!supportsThrowsAsync && methodSymbol.Parameters.Any(param => param.Type.IsCallInfoDelegate(semanticModel))) { return; } - var replacementMethod = methodSymbol.IsThrowsSyncForAnyArgsMethod() - ? "ReturnsForAnyArgs" - : "Returns"; + var replacementMethod = GetReplacementMethodName(methodSymbol, useModernSyntax: supportsThrowsAsync); var codeAction = CodeAction.Create( $"Replace with {replacementMethod}", - ct => CreateChangedDocument(context, semanticModel, invocation, methodSymbol, ct), + ct => CreateChangedDocument(context, semanticModel, invocation, methodSymbol, supportsThrowsAsync, ct), nameof(AbstractSyncOverAsyncThrowsCodeFixProvider)); context.RegisterCodeFix(codeAction, diagnostic); @@ -59,28 +58,47 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) protected abstract SyntaxNode GetExpression(TInvocationExpressionSyntax invocationExpressionSyntax); + protected abstract SyntaxNode UpdateMemberExpression(TInvocationExpressionSyntax invocationExpressionSyntax, string methodName); + private async Task CreateChangedDocument( CodeFixContext context, SemanticModel semanticModel, TInvocationExpressionSyntax currentInvocationExpression, IMethodSymbol invocationSymbol, + bool useModernSyntax, CancellationToken cancellationToken) { var documentEditor = await DocumentEditor.CreateAsync(context.Document, cancellationToken); var invocationOperation = (IInvocationOperation)semanticModel.GetOperation(currentInvocationExpression); - var updatedInvocationExpression = await CreateUpdatedInvocationExpression( - currentInvocationExpression, - invocationOperation, - invocationSymbol, - context); + var updatedInvocationExpression = useModernSyntax + ? CreateThrowsAsyncInvocationExpression( + currentInvocationExpression, + invocationSymbol) + : await CreateReturnInvocationExpression( + currentInvocationExpression, + invocationOperation, + invocationSymbol, + context); documentEditor.ReplaceNode(currentInvocationExpression, updatedInvocationExpression); return documentEditor.GetChangedDocument(); } - private async Task CreateUpdatedInvocationExpression( + private SyntaxNode CreateThrowsAsyncInvocationExpression( + TInvocationExpressionSyntax currentInvocationExpression, + IMethodSymbol invocationSymbol) + { + var updatedMethodName = + invocationSymbol.IsThrowsSyncForAnyArgsMethod() + ? MetadataNames.NSubstituteThrowsAsyncMethod + : MetadataNames.NSubstituteThrowsAsyncForAnyArgsMethod; + + return UpdateMemberExpression(currentInvocationExpression, updatedMethodName); + } + + private async Task CreateReturnInvocationExpression( TInvocationExpressionSyntax currentInvocationExpression, IInvocationOperation invocationOperation, IMethodSymbol invocationSymbol, @@ -97,7 +115,7 @@ private async Task CreateUpdatedInvocationExpression( if (invocationSymbol.MethodKind == MethodKind.Ordinary) { - return CreateUpdatedOrdinalInvocationExpression( + return CreateReturnOrdinalInvocationExpression( currentInvocationExpression, invocationOperation, syntaxGenerator, @@ -105,14 +123,14 @@ private async Task CreateUpdatedInvocationExpression( returnsMethodName); } - return CreateUpdatedExtensionInvocationExpression( + return CreateReturnExtensionInvocationExpression( currentInvocationExpression, syntaxGenerator, fromExceptionInvocationExpression, returnsMethodName); } - private static SyntaxNode CreateUpdatedOrdinalInvocationExpression( + private static SyntaxNode CreateReturnOrdinalInvocationExpression( TInvocationExpressionSyntax currentInvocationExpression, IInvocationOperation invocationOperation, SyntaxGenerator syntaxGenerator, @@ -126,7 +144,7 @@ private static SyntaxNode CreateUpdatedOrdinalInvocationExpression( fromExceptionInvocationExpression).WithTriviaFrom(currentInvocationExpression); } - private SyntaxNode CreateUpdatedExtensionInvocationExpression( + private SyntaxNode CreateReturnExtensionInvocationExpression( TInvocationExpressionSyntax currentInvocationExpression, SyntaxGenerator syntaxGenerator, SyntaxNode fromExceptionInvocationExpression, @@ -173,4 +191,26 @@ private static SyntaxNode GetExceptionCreationExpression( return invocationOperation.Arguments.OrderBy(arg => arg.Parameter.Ordinal) .First(arg => arg.Parameter.Ordinal > 0).Value.Syntax; } + + private static bool SupportsThrowsAsync(Compilation compilation) + { + var exceptionExtensionsTypeSymbol = compilation.GetTypeByMetadataName("NSubstitute.ExceptionExtensions.ExceptionExtensions"); + + return exceptionExtensionsTypeSymbol != null && + exceptionExtensionsTypeSymbol.GetMembers(MetadataNames.NSubstituteThrowsAsyncMethod).IsEmpty == false; + } + + private static string GetReplacementMethodName(IMethodSymbol methodSymbol, bool useModernSyntax) + { + if (useModernSyntax) + { + return methodSymbol.IsThrowsSyncForAnyArgsMethod() + ? MetadataNames.NSubstituteThrowsAsyncMethod + : MetadataNames.NSubstituteThrowsAsyncForAnyArgsMethod; + } + + return methodSymbol.IsThrowsSyncForAnyArgsMethod() + ? MetadataNames.NSubstituteReturnsMethod + : MetadataNames.NSubstituteReturnsForAnyArgsMethod; + } } \ No newline at end of file diff --git a/src/NSubstitute.Analyzers.Shared/Extensions/SubstituteSymbolExtensions.cs b/src/NSubstitute.Analyzers.Shared/Extensions/SubstituteSymbolExtensions.cs index b535a8cd..7bbfd1f5 100644 --- a/src/NSubstitute.Analyzers.Shared/Extensions/SubstituteSymbolExtensions.cs +++ b/src/NSubstitute.Analyzers.Shared/Extensions/SubstituteSymbolExtensions.cs @@ -51,7 +51,7 @@ public static bool IsThrowsSyncForAnyArgsMethod(this ISymbol symbol) { return IsMember( symbol, - MetadataNames.NSubstituteThrowsForAnyArgsMethod, + MetadataNames.NSubstituteThrowsMethod, MetadataNames.NSubstituteExceptionExtensionsFullTypeName); } diff --git a/src/NSubstitute.Analyzers.VisualBasic/CodeFixProviders/SyncOverAsyncThrowsCodeFixProvider.cs b/src/NSubstitute.Analyzers.VisualBasic/CodeFixProviders/SyncOverAsyncThrowsCodeFixProvider.cs index 8e50f23f..db3cb65e 100644 --- a/src/NSubstitute.Analyzers.VisualBasic/CodeFixProviders/SyncOverAsyncThrowsCodeFixProvider.cs +++ b/src/NSubstitute.Analyzers.VisualBasic/CodeFixProviders/SyncOverAsyncThrowsCodeFixProvider.cs @@ -9,4 +9,9 @@ namespace NSubstitute.Analyzers.VisualBasic.CodeFixProviders; internal sealed class SyncOverAsyncThrowsCodeFixProvider : AbstractSyncOverAsyncThrowsCodeFixProvider { protected override SyntaxNode GetExpression(InvocationExpressionSyntax invocationExpressionSyntax) => ((MemberAccessExpressionSyntax)invocationExpressionSyntax.Expression).Expression; + + protected override SyntaxNode UpdateMemberExpression(InvocationExpressionSyntax invocationExpressionSyntax, string methodName) + { + return invocationExpressionSyntax; + } } \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/SyncOverAsyncThrowsCodeFixProviderTests/SyncOverAsyncThrowsCodeFixActionsTests.cs b/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/SyncOverAsyncThrowsCodeFixProviderTests/SyncOverAsyncThrowsCodeFixActionsTests.cs index 6b6601db..4f055e88 100644 --- a/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/SyncOverAsyncThrowsCodeFixProviderTests/SyncOverAsyncThrowsCodeFixActionsTests.cs +++ b/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/SyncOverAsyncThrowsCodeFixProviderTests/SyncOverAsyncThrowsCodeFixActionsTests.cs @@ -3,6 +3,7 @@ using Microsoft.CodeAnalysis.Diagnostics; using NSubstitute.Analyzers.CSharp.CodeFixProviders; using NSubstitute.Analyzers.CSharp.DiagnosticAnalyzers; +using NSubstitute.Analyzers.Tests.Shared; using NSubstitute.Analyzers.Tests.Shared.CodeFixProviders; using Xunit; @@ -15,9 +16,11 @@ public class SyncOverAsyncThrowsCodeFixActionsTests : CSharpCodeFixActionsVerifi protected override DiagnosticAnalyzer DiagnosticAnalyzer { get; } = new SyncOverAsyncThrowsAnalyzer(); [Theory] - [InlineData("Throws", "Replace with Returns")] - [InlineData("ThrowsForAnyArgs", "Replace with ReturnsForAnyArgs")] - public async Task CreatesCodeAction_WhenOverloadSupported(string method, string expectedCodeActionTitle) + [InlineData("Throws", NSubstituteVersion.NSubstitute4_2_2, "Replace with Returns")] + [InlineData("ThrowsForAnyArgs", NSubstituteVersion.NSubstitute4_2_2, "Replace with ReturnsForAnyArgs")] + [InlineData("Throws", NSubstituteVersion.Latest, "Replace with ThrowsAsync")] + [InlineData("ThrowsForAnyArgs", NSubstituteVersion.Latest, "Replace with ThrowsAsyncForAnyArgs")] + public async Task CreatesCodeAction_WhenOverloadSupported(string method, NSubstituteVersion version, string expectedCodeActionTitle) { var source = $@"using System; using System.Threading.Tasks; @@ -40,13 +43,15 @@ public void Test() }} }} }}"; - await VerifyCodeActions(source, expectedCodeActionTitle); + await VerifyCodeActions(source, version, expectedCodeActionTitle); } [Theory] - [InlineData("Throws")] - [InlineData("ThrowsForAnyArgs")] - public async Task DoesNotCreateCodeAction_WhenOverloadNotSupported(string method) + [InlineData("Throws", NSubstituteVersion.NSubstitute4_2_2)] + [InlineData("ThrowsForAnyArgs", NSubstituteVersion.NSubstitute4_2_2)] + [InlineData("Throws", NSubstituteVersion.Latest)] + [InlineData("ThrowsForAnyArgs", NSubstituteVersion.Latest)] + public async Task DoesNotCreateCodeAction_WhenOverloadNotSupported(string method, NSubstituteVersion version) { var source = $@"using System; using System.Threading.Tasks; @@ -70,6 +75,6 @@ public void Test() }} }} }}"; - await VerifyCodeActions(source); + await VerifyCodeActions(source, version); } } \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/SyncOverAsyncThrowsCodeFixProviderTests/SyncOverAsyncThrowsCodeFixVerifier.cs b/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/SyncOverAsyncThrowsCodeFixProviderTests/SyncOverAsyncThrowsCodeFixVerifier.cs index 9027c553..c742c694 100644 --- a/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/SyncOverAsyncThrowsCodeFixProviderTests/SyncOverAsyncThrowsCodeFixVerifier.cs +++ b/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/SyncOverAsyncThrowsCodeFixProviderTests/SyncOverAsyncThrowsCodeFixVerifier.cs @@ -20,6 +20,15 @@ public static IEnumerable ThrowsTestCases } } + public static IEnumerable ThrowsAsyncTestCases + { + get + { + yield return new object[] { "Throws", "ThrowsAsync" }; + yield return new object[] { "ThrowsForAnyArgs", "ThrowsAsyncForAnyArgs" }; + } + } + protected override CodeFixProvider CodeFixProvider { get; } = new SyncOverAsyncThrowsCodeFixProvider(); protected override DiagnosticAnalyzer DiagnosticAnalyzer { get; } = new SyncOverAsyncThrowsAnalyzer(); @@ -35,4 +44,16 @@ public static IEnumerable ThrowsTestCases [Theory] [MemberData(nameof(ThrowsTestCases))] public abstract Task ReplacesThrowsWithReturns_WhenUsedInIndexer(string method, string updatedMethod); + + [Theory] + [MemberData(nameof(ThrowsAsyncTestCases))] + public abstract Task ReplacesThrowsWithThrowsAsync_WhenUsedInMethod(string method, string updatedMethod); + + [Theory] + [MemberData(nameof(ThrowsAsyncTestCases))] + public abstract Task ReplacesThrowsWithThrowsAsync_WhenUsedInProperty(string method, string updatedMethod); + + [Theory] + [MemberData(nameof(ThrowsAsyncTestCases))] + public abstract Task ReplacesThrowsWithThrowsAsync_WhenUsedInIndexer(string method, string updatedMethod); } \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/SyncOverAsyncThrowsCodeFixProviderTests/ThrowsAsExtensionMethodTests.cs b/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/SyncOverAsyncThrowsCodeFixProviderTests/ThrowsAsExtensionMethodTests.cs index 0d06b40c..7b8e27da 100644 --- a/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/SyncOverAsyncThrowsCodeFixProviderTests/ThrowsAsExtensionMethodTests.cs +++ b/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/SyncOverAsyncThrowsCodeFixProviderTests/ThrowsAsExtensionMethodTests.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using NSubstitute.Analyzers.Tests.Shared; namespace NSubstitute.Analyzers.Tests.CSharp.CodeFixProviderTests.SyncOverAsyncThrowsCodeFixProviderTests; @@ -52,7 +53,7 @@ public void Test() }} }}"; - await VerifyFix(source, newSource); + await VerifyFix(source, newSource, NSubstituteVersion.NSubstitute4_2_2); } public override async Task ReplacesThrowsWithReturns_WhenUsedInProperty(string method, string updatedMethod) @@ -103,7 +104,7 @@ public void Test() }} }}"; - await VerifyFix(source, newSource); + await VerifyFix(source, newSource, NSubstituteVersion.NSubstitute4_2_2); } public override async Task ReplacesThrowsWithReturns_WhenUsedInIndexer(string method, string updatedMethod) @@ -154,6 +155,183 @@ public void Test() }} }}"; + await VerifyFix(source, newSource, NSubstituteVersion.NSubstitute4_2_2); + } + + public override async Task ReplacesThrowsWithThrowsAsync_WhenUsedInMethod(string method, string updatedMethod) + { + var source = $@"using System; +using System.Threading.Tasks; +using NSubstitute; +using NSubstitute.ExceptionExtensions; + +namespace MyNamespace +{{ + public interface IFoo + {{ + Task Bar(); + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = NSubstitute.Substitute.For(); + substitute.Bar().{method}(new Exception()); + substitute.Bar().{method}(ex: new Exception()); + substitute.Bar().{method}(callInfo => new Exception()); + substitute.Bar().{method}(createException: callInfo => new Exception()); + substitute.Bar().{method}(callInfo => {{ return new Exception(); }}); + substitute.Bar().{method}(createException: callInfo => {{ return new Exception(); }}); + }} + }} +}}"; + + var newSource = $@"using System; +using System.Threading.Tasks; +using NSubstitute; +using NSubstitute.ExceptionExtensions; + +namespace MyNamespace +{{ + public interface IFoo + {{ + Task Bar(); + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = NSubstitute.Substitute.For(); + substitute.Bar().{updatedMethod}(new Exception()); + substitute.Bar().{updatedMethod}(ex: new Exception()); + substitute.Bar().{updatedMethod}(callInfo => new Exception()); + substitute.Bar().{updatedMethod}(createException: callInfo => new Exception()); + substitute.Bar().{updatedMethod}(callInfo => {{ return new Exception(); }}); + substitute.Bar().{updatedMethod}(createException: callInfo => {{ return new Exception(); }}); + }} + }} +}}"; + + await VerifyFix(source, newSource); + } + + public override async Task ReplacesThrowsWithThrowsAsync_WhenUsedInProperty(string method, string updatedMethod) + { + var source = $@"using System; +using System.Threading.Tasks; +using NSubstitute; +using NSubstitute.ExceptionExtensions; + +namespace MyNamespace +{{ + public interface IFoo + {{ + Task Bar {{ get; set; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = NSubstitute.Substitute.For(); + substitute.Bar.{method}(new Exception()); + substitute.Bar.{method}(ex: new Exception()); + substitute.Bar.{method}(callInfo => new Exception()); + substitute.Bar.{method}(createException: callInfo => new Exception()); + substitute.Bar.{method}(callInfo => {{ return new Exception(); }}); + substitute.Bar.{method}(createException: callInfo => {{ return new Exception(); }}); + }} + }} +}}"; + + var newSource = $@"using System; +using System.Threading.Tasks; +using NSubstitute; +using NSubstitute.ExceptionExtensions; + +namespace MyNamespace +{{ + public interface IFoo + {{ + Task Bar {{ get; set; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = NSubstitute.Substitute.For(); + substitute.Bar.{updatedMethod}(new Exception()); + substitute.Bar.{updatedMethod}(ex: new Exception()); + substitute.Bar.{updatedMethod}(callInfo => new Exception()); + substitute.Bar.{updatedMethod}(createException: callInfo => new Exception()); + substitute.Bar.{updatedMethod}(callInfo => {{ return new Exception(); }}); + substitute.Bar.{updatedMethod}(createException: callInfo => {{ return new Exception(); }}); + }} + }} +}}"; + + await VerifyFix(source, newSource); + } + + public override async Task ReplacesThrowsWithThrowsAsync_WhenUsedInIndexer(string method, string updatedMethod) + { + var source = $@"using System; +using System.Threading.Tasks; +using NSubstitute; +using NSubstitute.ExceptionExtensions; + +namespace MyNamespace +{{ + public interface IFoo + {{ + Task this[int x] {{ get; set; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = NSubstitute.Substitute.For(); + substitute[0].{method}(new Exception()); + substitute[0].{method}(ex: new Exception()); + substitute[0].{method}(callInfo => new Exception()); + substitute[0].{method}(createException: callInfo => new Exception()); + substitute[0].{method}(callInfo => {{ return new Exception(); }}); + substitute[0].{method}(createException: callInfo => {{ return new Exception(); }}); + }} + }} +}}"; + + var newSource = $@"using System; +using System.Threading.Tasks; +using NSubstitute; +using NSubstitute.ExceptionExtensions; + +namespace MyNamespace +{{ + public interface IFoo + {{ + Task this[int x] {{ get; set; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = NSubstitute.Substitute.For(); + substitute[0].{updatedMethod}(new Exception()); + substitute[0].{updatedMethod}(ex: new Exception()); + substitute[0].{updatedMethod}(callInfo => new Exception()); + substitute[0].{updatedMethod}(createException: callInfo => new Exception()); + substitute[0].{updatedMethod}(callInfo => {{ return new Exception(); }}); + substitute[0].{updatedMethod}(createException: callInfo => {{ return new Exception(); }}); + }} + }} +}}"; + await VerifyFix(source, newSource); } } \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/SyncOverAsyncThrowsCodeFixProviderTests/ThrowsAsExtensionsMethodWithGenericTypeSpecifiedTests.cs b/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/SyncOverAsyncThrowsCodeFixProviderTests/ThrowsAsExtensionsMethodWithGenericTypeSpecifiedTests.cs index 26a55428..62e96d9f 100644 --- a/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/SyncOverAsyncThrowsCodeFixProviderTests/ThrowsAsExtensionsMethodWithGenericTypeSpecifiedTests.cs +++ b/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/SyncOverAsyncThrowsCodeFixProviderTests/ThrowsAsExtensionsMethodWithGenericTypeSpecifiedTests.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using NSubstitute.Analyzers.Tests.Shared; namespace NSubstitute.Analyzers.Tests.CSharp.CodeFixProviderTests.SyncOverAsyncThrowsCodeFixProviderTests; @@ -50,7 +51,7 @@ public void Test() }} }}"; - await VerifyFix(source, newSource); + await VerifyFix(source, newSource, NSubstituteVersion.NSubstitute4_2_2); } public override async Task ReplacesThrowsWithReturns_WhenUsedInProperty(string method, string updatedMethod) @@ -99,7 +100,7 @@ public void Test() }} }}"; - await VerifyFix(source, newSource); + await VerifyFix(source, newSource, NSubstituteVersion.NSubstitute4_2_2); } public override async Task ReplacesThrowsWithReturns_WhenUsedInIndexer(string method, string updatedMethod) @@ -148,6 +149,153 @@ public void Test() }} }}"; + await VerifyFix(source, newSource, NSubstituteVersion.NSubstitute4_2_2); + } + + public override async Task ReplacesThrowsWithThrowsAsync_WhenUsedInMethod(string method, string updatedMethod) + { + var source = $@"using System; +using System.Threading.Tasks; +using NSubstitute; +using NSubstitute.ExceptionExtensions; + +namespace MyNamespace +{{ + public interface IFoo + {{ + Task Bar(); + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = NSubstitute.Substitute.For(); + substitute.Bar().{method}(); + }} + }} +}}"; + + var newSource = $@"using System; +using System.Threading.Tasks; +using NSubstitute; +using NSubstitute.ExceptionExtensions; + +namespace MyNamespace +{{ + public interface IFoo + {{ + Task Bar(); + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = NSubstitute.Substitute.For(); + substitute.Bar().{updatedMethod}(); + }} + }} +}}"; + + await VerifyFix(source, newSource); + } + + public override async Task ReplacesThrowsWithThrowsAsync_WhenUsedInProperty(string method, string updatedMethod) + { + var source = $@"using System; +using System.Threading.Tasks; +using NSubstitute; +using NSubstitute.ExceptionExtensions; + +namespace MyNamespace +{{ + public interface IFoo + {{ + Task Bar {{ get; set; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = NSubstitute.Substitute.For(); + substitute.Bar.{method}(); + }} + }} +}}"; + + var newSource = $@"using System; +using System.Threading.Tasks; +using NSubstitute; +using NSubstitute.ExceptionExtensions; + +namespace MyNamespace +{{ + public interface IFoo + {{ + Task Bar {{ get; set; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = NSubstitute.Substitute.For(); + substitute.Bar.{updatedMethod}(); + }} + }} +}}"; + + await VerifyFix(source, newSource); + } + + public override async Task ReplacesThrowsWithThrowsAsync_WhenUsedInIndexer(string method, string updatedMethod) + { + var source = $@"using System; +using System.Threading.Tasks; +using NSubstitute; +using NSubstitute.ExceptionExtensions; + +namespace MyNamespace +{{ + public interface IFoo + {{ + Task this[int x] {{ get; set; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = NSubstitute.Substitute.For(); + substitute[0].{method}(); + }} + }} +}}"; + + var newSource = $@"using System; +using System.Threading.Tasks; +using NSubstitute; +using NSubstitute.ExceptionExtensions; + +namespace MyNamespace +{{ + public interface IFoo + {{ + Task this[int x] {{ get; set; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = NSubstitute.Substitute.For(); + substitute[0].{updatedMethod}(); + }} + }} +}}"; + await VerifyFix(source, newSource); } } \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/SyncOverAsyncThrowsCodeFixProviderTests/ThrowsAsOrdinaryMethodTests.cs b/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/SyncOverAsyncThrowsCodeFixProviderTests/ThrowsAsOrdinaryMethodTests.cs index 1e3039e3..43733a51 100644 --- a/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/SyncOverAsyncThrowsCodeFixProviderTests/ThrowsAsOrdinaryMethodTests.cs +++ b/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/SyncOverAsyncThrowsCodeFixProviderTests/ThrowsAsOrdinaryMethodTests.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using NSubstitute.Analyzers.Tests.Shared; namespace NSubstitute.Analyzers.Tests.CSharp.CodeFixProviderTests.SyncOverAsyncThrowsCodeFixProviderTests; @@ -54,7 +55,7 @@ public void Test() }} }}"; - await VerifyFix(source, newSource); + await VerifyFix(source, newSource, NSubstituteVersion.NSubstitute4_2_2); } public override async Task ReplacesThrowsWithReturns_WhenUsedInProperty(string method, string updatedMethod) @@ -107,7 +108,7 @@ public void Test() }} }}"; - await VerifyFix(source, newSource); + await VerifyFix(source, newSource, NSubstituteVersion.NSubstitute4_2_2); } public override async Task ReplacesThrowsWithReturns_WhenUsedInIndexer(string method, string updatedMethod) @@ -160,6 +161,201 @@ public void Test() }} }}"; + await VerifyFix(source, newSource, NSubstituteVersion.NSubstitute4_2_2); + } + + public override async Task ReplacesThrowsWithThrowsAsync_WhenUsedInMethod(string method, string updatedMethod) + { + var source = $@"using System; +using System.Threading.Tasks; +using NSubstitute; +using NSubstitute.ExceptionExtensions; + +namespace MyNamespace +{{ + public interface IFoo + {{ + Task Bar(); + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = NSubstitute.Substitute.For(); + ExceptionExtensions.{method}(substitute.Bar(), new Exception()); + ExceptionExtensions.{method}(value: substitute.Bar(), ex: new Exception()); + ExceptionExtensions.{method}(ex: new Exception(), value: substitute.Bar()); + ExceptionExtensions.{method}(substitute.Bar(), callInfo => new Exception()); + ExceptionExtensions.{method}(value: substitute.Bar(), createException: callInfo => new Exception()); + ExceptionExtensions.{method}(createException: callInfo => new Exception(), value: substitute.Bar()); + ExceptionExtensions.{method}(substitute.Bar(), callInfo => {{ return new Exception(); }}); + ExceptionExtensions.{method}(value: substitute.Bar(), createException: callInfo => {{ return new Exception(); }}); + ExceptionExtensions.{method}(createException: callInfo => {{ return new Exception(); }}, value: substitute.Bar()); + }} + }} +}}"; + + var newSource = $@"using System; +using System.Threading.Tasks; +using NSubstitute; +using NSubstitute.ExceptionExtensions; + +namespace MyNamespace +{{ + public interface IFoo + {{ + Task Bar(); + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = NSubstitute.Substitute.For(); + ExceptionExtensions.{updatedMethod}(substitute.Bar(), new Exception()); + ExceptionExtensions.{updatedMethod}(value: substitute.Bar(), ex: new Exception()); + ExceptionExtensions.{updatedMethod}(ex: new Exception(), value: substitute.Bar()); + ExceptionExtensions.{updatedMethod}(substitute.Bar(), callInfo => new Exception()); + ExceptionExtensions.{updatedMethod}(value: substitute.Bar(), createException: callInfo => new Exception()); + ExceptionExtensions.{updatedMethod}(createException: callInfo => new Exception(), value: substitute.Bar()); + ExceptionExtensions.{updatedMethod}(substitute.Bar(), callInfo => {{ return new Exception(); }}); + ExceptionExtensions.{updatedMethod}(value: substitute.Bar(), createException: callInfo => {{ return new Exception(); }}); + ExceptionExtensions.{updatedMethod}(createException: callInfo => {{ return new Exception(); }}, value: substitute.Bar()); + }} + }} +}}"; + + await VerifyFix(source, newSource); + } + + public override async Task ReplacesThrowsWithThrowsAsync_WhenUsedInProperty(string method, string updatedMethod) + { + var source = $@"using System; +using System.Threading.Tasks; +using NSubstitute; +using NSubstitute.ExceptionExtensions; + +namespace MyNamespace +{{ + public interface IFoo + {{ + Task Bar {{ get; set; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = NSubstitute.Substitute.For(); + ExceptionExtensions.{method}(substitute.Bar, new Exception()); + ExceptionExtensions.{method}(value: substitute.Bar, ex: new Exception()); + ExceptionExtensions.{method}(ex: new Exception(), value: substitute.Bar); + ExceptionExtensions.{method}(substitute.Bar, callInfo => new Exception()); + ExceptionExtensions.{method}(value: substitute.Bar, createException: callInfo => new Exception()); + ExceptionExtensions.{method}(createException: callInfo => new Exception(), value: substitute.Bar); + ExceptionExtensions.{method}(substitute.Bar, callInfo => {{ return new Exception(); }}); + ExceptionExtensions.{method}(value: substitute.Bar, createException: callInfo => {{ return new Exception(); }}); + ExceptionExtensions.{method}(createException: callInfo => {{ return new Exception(); }}, value: substitute.Bar); + }} + }} +}}"; + + var newSource = $@"using System; +using System.Threading.Tasks; +using NSubstitute; +using NSubstitute.ExceptionExtensions; + +namespace MyNamespace +{{ + public interface IFoo + {{ + Task Bar {{ get; set; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = NSubstitute.Substitute.For(); + ExceptionExtensions.{updatedMethod}(substitute.Bar, new Exception()); + ExceptionExtensions.{updatedMethod}(value: substitute.Bar, ex: new Exception()); + ExceptionExtensions.{updatedMethod}(ex: new Exception(), value: substitute.Bar); + ExceptionExtensions.{updatedMethod}(substitute.Bar, callInfo => new Exception()); + ExceptionExtensions.{updatedMethod}(value: substitute.Bar, createException: callInfo => new Exception()); + ExceptionExtensions.{updatedMethod}(createException: callInfo => new Exception(), value: substitute.Bar); + ExceptionExtensions.{updatedMethod}(substitute.Bar, callInfo => {{ return new Exception(); }}); + ExceptionExtensions.{updatedMethod}(value: substitute.Bar, createException: callInfo => {{ return new Exception(); }}); + ExceptionExtensions.{updatedMethod}(createException: callInfo => {{ return new Exception(); }}, value: substitute.Bar); + }} + }} +}}"; + + await VerifyFix(source, newSource); + } + + public override async Task ReplacesThrowsWithThrowsAsync_WhenUsedInIndexer(string method, string updatedMethod) + { + var source = $@"using System; +using System.Threading.Tasks; +using NSubstitute; +using NSubstitute.ExceptionExtensions; + +namespace MyNamespace +{{ + public interface IFoo + {{ + Task this[int x] {{ get; set; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = NSubstitute.Substitute.For(); + ExceptionExtensions.{method}(substitute[0], new Exception()); + ExceptionExtensions.{method}(value: substitute[0], ex: new Exception()); + ExceptionExtensions.{method}(ex: new Exception(), value: substitute[0]); + ExceptionExtensions.{method}(substitute[0], callInfo => new Exception()); + ExceptionExtensions.{method}(value: substitute[0], createException: callInfo => new Exception()); + ExceptionExtensions.{method}(createException: callInfo => new Exception(), value: substitute[0]); + ExceptionExtensions.{method}(substitute[0], callInfo => {{ return new Exception(); }}); + ExceptionExtensions.{method}(value: substitute[0], createException: callInfo => {{ return new Exception(); }}); + ExceptionExtensions.{method}(createException: callInfo => {{ return new Exception(); }}, value: substitute[0]); + }} + }} +}}"; + + var newSource = $@"using System; +using System.Threading.Tasks; +using NSubstitute; +using NSubstitute.ExceptionExtensions; + +namespace MyNamespace +{{ + public interface IFoo + {{ + Task this[int x] {{ get; set; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = NSubstitute.Substitute.For(); + ExceptionExtensions.{updatedMethod}(substitute[0], new Exception()); + ExceptionExtensions.{updatedMethod}(value: substitute[0], ex: new Exception()); + ExceptionExtensions.{updatedMethod}(ex: new Exception(), value: substitute[0]); + ExceptionExtensions.{updatedMethod}(substitute[0], callInfo => new Exception()); + ExceptionExtensions.{updatedMethod}(value: substitute[0], createException: callInfo => new Exception()); + ExceptionExtensions.{updatedMethod}(createException: callInfo => new Exception(), value: substitute[0]); + ExceptionExtensions.{updatedMethod}(substitute[0], callInfo => {{ return new Exception(); }}); + ExceptionExtensions.{updatedMethod}(value: substitute[0], createException: callInfo => {{ return new Exception(); }}); + ExceptionExtensions.{updatedMethod}(createException: callInfo => {{ return new Exception(); }}, value: substitute[0]); + }} + }} +}}"; + await VerifyFix(source, newSource); } } \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/SyncOverAsyncThrowsCodeFixProviderTests/ThrowsAsOrdinaryMethodWithGenericTypeSpecifiedTests.cs b/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/SyncOverAsyncThrowsCodeFixProviderTests/ThrowsAsOrdinaryMethodWithGenericTypeSpecifiedTests.cs index 1f82bed5..17a7d2d3 100644 --- a/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/SyncOverAsyncThrowsCodeFixProviderTests/ThrowsAsOrdinaryMethodWithGenericTypeSpecifiedTests.cs +++ b/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/SyncOverAsyncThrowsCodeFixProviderTests/ThrowsAsOrdinaryMethodWithGenericTypeSpecifiedTests.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using NSubstitute.Analyzers.Tests.Shared; namespace NSubstitute.Analyzers.Tests.CSharp.CodeFixProviderTests.SyncOverAsyncThrowsCodeFixProviderTests; @@ -52,7 +53,7 @@ public void Test() }} }}"; - await VerifyFix(source, newSource); + await VerifyFix(source, newSource, NSubstituteVersion.NSubstitute4_2_2); } public override async Task ReplacesThrowsWithReturns_WhenUsedInProperty(string method, string updatedMethod) @@ -103,7 +104,7 @@ public void Test() }} }}"; - await VerifyFix(source, newSource); + await VerifyFix(source, newSource, NSubstituteVersion.NSubstitute4_2_2); } public override async Task ReplacesThrowsWithReturns_WhenUsedInIndexer(string method, string updatedMethod) @@ -154,6 +155,159 @@ public void Test() }} }}"; + await VerifyFix(source, newSource, NSubstituteVersion.NSubstitute4_2_2); + } + + public override async Task ReplacesThrowsWithThrowsAsync_WhenUsedInMethod(string method, string updatedMethod) + { + var source = $@"using System; +using System.Threading.Tasks; +using NSubstitute; +using NSubstitute.ExceptionExtensions; + +namespace MyNamespace +{{ + public interface IFoo + {{ + Task Bar(); + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = NSubstitute.Substitute.For(); + ExceptionExtensions.{method}(substitute.Bar()); + ExceptionExtensions.{method}(value: substitute.Bar()); + }} + }} +}}"; + + var newSource = $@"using System; +using System.Threading.Tasks; +using NSubstitute; +using NSubstitute.ExceptionExtensions; + +namespace MyNamespace +{{ + public interface IFoo + {{ + Task Bar(); + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = NSubstitute.Substitute.For(); + ExceptionExtensions.{updatedMethod}(substitute.Bar()); + ExceptionExtensions.{updatedMethod}(value: substitute.Bar()); + }} + }} +}}"; + + await VerifyFix(source, newSource); + } + + public override async Task ReplacesThrowsWithThrowsAsync_WhenUsedInProperty(string method, string updatedMethod) + { + var source = $@"using System; +using System.Threading.Tasks; +using NSubstitute; +using NSubstitute.ExceptionExtensions; + +namespace MyNamespace +{{ + public interface IFoo + {{ + Task Bar {{ get; set; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = NSubstitute.Substitute.For(); + ExceptionExtensions.{method}(substitute.Bar); + ExceptionExtensions.{method}(value: substitute.Bar); + }} + }} +}}"; + + var newSource = $@"using System; +using System.Threading.Tasks; +using NSubstitute; +using NSubstitute.ExceptionExtensions; + +namespace MyNamespace +{{ + public interface IFoo + {{ + Task Bar {{ get; set; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = NSubstitute.Substitute.For(); + ExceptionExtensions.{updatedMethod}(substitute.Bar); + ExceptionExtensions.{updatedMethod}(value: substitute.Bar); + }} + }} +}}"; + + await VerifyFix(source, newSource); + } + + public override async Task ReplacesThrowsWithThrowsAsync_WhenUsedInIndexer(string method, string updatedMethod) + { + var source = $@"using System; +using System.Threading.Tasks; +using NSubstitute; +using NSubstitute.ExceptionExtensions; + +namespace MyNamespace +{{ + public interface IFoo + {{ + Task this[int x] {{ get; set; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = NSubstitute.Substitute.For(); + ExceptionExtensions.{method}(substitute[0]); + ExceptionExtensions.{method}(value: substitute[0]); + }} + }} +}}"; + + var newSource = $@"using System; +using System.Threading.Tasks; +using NSubstitute; +using NSubstitute.ExceptionExtensions; + +namespace MyNamespace +{{ + public interface IFoo + {{ + Task this[int x] {{ get; set; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = NSubstitute.Substitute.For(); + ExceptionExtensions.{updatedMethod}(substitute[0]); + ExceptionExtensions.{updatedMethod}(value: substitute[0]); + }} + }} +}}"; + await VerifyFix(source, newSource); } } \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.Shared/CodeFixProviders/CodeFixCodeActionsVerifier.cs b/tests/NSubstitute.Analyzers.Tests.Shared/CodeFixProviders/CodeFixCodeActionsVerifier.cs index 141c2cf1..2cda1ede 100644 --- a/tests/NSubstitute.Analyzers.Tests.Shared/CodeFixProviders/CodeFixCodeActionsVerifier.cs +++ b/tests/NSubstitute.Analyzers.Tests.Shared/CodeFixProviders/CodeFixCodeActionsVerifier.cs @@ -26,42 +26,46 @@ protected CodeFixCodeActionsVerifier(WorkspaceFactory workspaceFactory) protected override string AnalyzerSettings { get; } = Json.Encode(new object()); - protected async Task VerifyCodeActions(string source, params string[] expectedCodeActionTitles) + protected async Task VerifyCodeActions(string source, NSubstituteVersion version, params string[] expectedCodeActionTitles) { - var codeActions = await RegisterCodeFixes(source); + var codeActions = await RegisterCodeFixes(source, version); codeActions.Should().NotBeNull(); codeActions.Select(action => action.Title).Should().BeEquivalentTo(expectedCodeActionTitles ?? Array.Empty()); } - private async Task> RegisterCodeFixes(string source) + protected Task VerifyCodeActions(string source, params string[] expectedCodeActionTitles) { - using (var workspace = new AdhocWorkspace()) - { - var actions = new List(); - var project = AddProject(workspace.CurrentSolution, source); + return VerifyCodeActions(source, NSubstituteVersion.Latest, expectedCodeActionTitles); + } - var document = project.Documents.Single(); + private async Task> RegisterCodeFixes(string source, NSubstituteVersion version) + { + using var workspace = new AdhocWorkspace(); + var actions = new List(); + var project = AddProject(workspace.CurrentSolution, source); + project = UpdateNSubstituteMetadataReference(project, version); - var compilation = await document.Project.GetCompilationAsync(); - var compilationDiagnostics = compilation.GetDiagnostics(); + var document = project.Documents.Single(); - VerifyNoCompilerDiagnosticErrors(compilationDiagnostics); + var compilation = await document.Project.GetCompilationAsync(); + var compilationDiagnostics = compilation.GetDiagnostics(); - var analyzerDiagnostics = await compilation.GetSortedAnalyzerDiagnostics( - DiagnosticAnalyzer, - project.AnalyzerOptions); + VerifyNoCompilerDiagnosticErrors(compilationDiagnostics); - foreach (var context in analyzerDiagnostics.Select(diagnostic => new CodeFixContext( - document, - analyzerDiagnostics[0], - (action, array) => actions.Add(action), - CancellationToken.None))) - { - await CodeFixProvider.RegisterCodeFixesAsync(context); - } + var analyzerDiagnostics = await compilation.GetSortedAnalyzerDiagnostics( + DiagnosticAnalyzer, + project.AnalyzerOptions); - return actions; + foreach (var context in analyzerDiagnostics.Select(diagnostic => new CodeFixContext( + document, + analyzerDiagnostics[0], + (action, array) => actions.Add(action), + CancellationToken.None))) + { + await CodeFixProvider.RegisterCodeFixesAsync(context); } + + return actions; } } \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.Shared/CodeFixProviders/CodeFixVerifier.cs b/tests/NSubstitute.Analyzers.Tests.Shared/CodeFixProviders/CodeFixVerifier.cs index bcc7ddbe..4f24adf0 100644 --- a/tests/NSubstitute.Analyzers.Tests.Shared/CodeFixProviders/CodeFixVerifier.cs +++ b/tests/NSubstitute.Analyzers.Tests.Shared/CodeFixProviders/CodeFixVerifier.cs @@ -24,86 +24,71 @@ protected CodeFixVerifier(WorkspaceFactory workspaceFactory) protected abstract DiagnosticAnalyzer DiagnosticAnalyzer { get; } + protected Task VerifyFix( + string oldSource, + string newSource, + NSubstituteVersion version) => VerifyFix(oldSource, newSource, null, version); + protected async Task VerifyFix( string oldSource, string newSource, int? codeFixIndex = null, NSubstituteVersion version = NSubstituteVersion.Latest) { - using (var workspace = new AdhocWorkspace()) - { - var project = AddProject(workspace.CurrentSolution, oldSource); - - project = UpdateNSubstituteMetadataReference(project, version); + using var workspace = new AdhocWorkspace(); + var project = AddProject(workspace.CurrentSolution, oldSource); - var document = project.Documents.Single(); - var compilation = await project.GetCompilationAsync(); + project = UpdateNSubstituteMetadataReference(project, version); - var compilerDiagnostics = compilation.GetDiagnostics(); + var document = project.Documents.Single(); + var compilation = await project.GetCompilationAsync(); - VerifyNoCompilerDiagnosticErrors(compilerDiagnostics); + var compilerDiagnostics = compilation.GetDiagnostics(); - var analyzerDiagnostics = await compilation.GetSortedAnalyzerDiagnostics( - DiagnosticAnalyzer, - project.AnalyzerOptions); + VerifyNoCompilerDiagnosticErrors(compilerDiagnostics); - var previousAnalyzerDiagnostics = analyzerDiagnostics; - var attempts = analyzerDiagnostics.Length; + var analyzerDiagnostics = await compilation.GetSortedAnalyzerDiagnostics( + DiagnosticAnalyzer, + project.AnalyzerOptions); - for (var i = 0; i < attempts; ++i) - { - var actions = new List(); - var context = new CodeFixContext(document, analyzerDiagnostics[0], (a, d) => actions.Add(a), CancellationToken.None); - await CodeFixProvider.RegisterCodeFixesAsync(context); + var previousAnalyzerDiagnostics = analyzerDiagnostics; + var attempts = analyzerDiagnostics.Length; - if (!actions.Any()) - { - break; - } + for (var i = 0; i < attempts; ++i) + { + var actions = new List(); + var context = new CodeFixContext(document, analyzerDiagnostics[0], (a, d) => actions.Add(a), CancellationToken.None); + await CodeFixProvider.RegisterCodeFixesAsync(context); - document = await document.ApplyCodeAction(actions[codeFixIndex ?? 0]); - compilation = await document.Project.GetCompilationAsync(); + if (!actions.Any()) + { + break; + } - compilerDiagnostics = compilation.GetDiagnostics(); + document = await document.ApplyCodeAction(actions[codeFixIndex ?? 0]); + compilation = await document.Project.GetCompilationAsync(); - VerifyNoCompilerDiagnosticErrors(compilerDiagnostics); + compilerDiagnostics = compilation.GetDiagnostics(); - analyzerDiagnostics = await compilation.GetSortedAnalyzerDiagnostics( - DiagnosticAnalyzer, - project.AnalyzerOptions); + VerifyNoCompilerDiagnosticErrors(compilerDiagnostics); - // check if there are analyzer diagnostics left after the code fix - var newAnalyzerDiagnostics = analyzerDiagnostics.Except(previousAnalyzerDiagnostics).ToList(); - if (analyzerDiagnostics.Length == previousAnalyzerDiagnostics.Length && newAnalyzerDiagnostics.Any()) - { - Execute.Assertion.Fail( - $"Fix didn't fix analyzer diagnostics: {newAnalyzerDiagnostics.ToDebugString()} New document:{Environment.NewLine}{await document.ToFullString()}"); - } + analyzerDiagnostics = await compilation.GetSortedAnalyzerDiagnostics( + DiagnosticAnalyzer, + project.AnalyzerOptions); - previousAnalyzerDiagnostics = analyzerDiagnostics; + // check if there are analyzer diagnostics left after the code fix + var newAnalyzerDiagnostics = analyzerDiagnostics.Except(previousAnalyzerDiagnostics).ToList(); + if (analyzerDiagnostics.Length == previousAnalyzerDiagnostics.Length && newAnalyzerDiagnostics.Any()) + { + Execute.Assertion.Fail( + $"Fix didn't fix analyzer diagnostics: {newAnalyzerDiagnostics.ToDebugString()} New document:{Environment.NewLine}{await document.ToFullString()}"); } - var actual = await document.ToFullString(); - - actual.Should().Be(newSource); - } - } - - private static Project UpdateNSubstituteMetadataReference(Project project, NSubstituteVersion version) - { - if (version == NSubstituteVersion.Latest) - { - return project; + previousAnalyzerDiagnostics = analyzerDiagnostics; } - project = project.RemoveMetadataReference(RuntimeMetadataReference.NSubstituteLatestReference); - - project = version switch - { - NSubstituteVersion.NSubstitute4_2_2 => project.AddMetadataReference(RuntimeMetadataReference.NSubstitute422Reference), - _ => throw new ArgumentException($"NSubstitute {version} is not supported", nameof(version)) - }; + var actual = await document.ToFullString(); - return project; + actual.Should().Be(newSource); } } \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.Shared/CodeFixProviders/ISyncOverAsyncThrowsCodeFixActionsVerifier.cs b/tests/NSubstitute.Analyzers.Tests.Shared/CodeFixProviders/ISyncOverAsyncThrowsCodeFixActionsVerifier.cs index bb00f09f..83514f4e 100644 --- a/tests/NSubstitute.Analyzers.Tests.Shared/CodeFixProviders/ISyncOverAsyncThrowsCodeFixActionsVerifier.cs +++ b/tests/NSubstitute.Analyzers.Tests.Shared/CodeFixProviders/ISyncOverAsyncThrowsCodeFixActionsVerifier.cs @@ -4,7 +4,7 @@ namespace NSubstitute.Analyzers.Tests.Shared.CodeFixProviders; public interface ISyncOverAsyncThrowsCodeFixActionsVerifier { - Task CreatesCodeAction_WhenOverloadSupported(string method, string expectedCodeActionTitle); + Task CreatesCodeAction_WhenOverloadSupported(string method, NSubstituteVersion version, string expectedCodeActionTitle); - Task DoesNotCreateCodeAction_WhenOverloadNotSupported(string method); + Task DoesNotCreateCodeAction_WhenOverloadNotSupported(string method, NSubstituteVersion version); } \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.Shared/CodeFixProviders/ISyncOverAsyncThrowsCodeFixVerifier.cs b/tests/NSubstitute.Analyzers.Tests.Shared/CodeFixProviders/ISyncOverAsyncThrowsCodeFixVerifier.cs index e0201067..9290be9e 100644 --- a/tests/NSubstitute.Analyzers.Tests.Shared/CodeFixProviders/ISyncOverAsyncThrowsCodeFixVerifier.cs +++ b/tests/NSubstitute.Analyzers.Tests.Shared/CodeFixProviders/ISyncOverAsyncThrowsCodeFixVerifier.cs @@ -9,4 +9,10 @@ public interface ISyncOverAsyncThrowsCodeFixVerifier Task ReplacesThrowsWithReturns_WhenUsedInProperty(string method, string updatedMethod); Task ReplacesThrowsWithReturns_WhenUsedInIndexer(string method, string updatedMethod); + + Task ReplacesThrowsWithThrowsAsync_WhenUsedInMethod(string method, string updatedMethod); + + Task ReplacesThrowsWithThrowsAsync_WhenUsedInProperty(string method, string updatedMethod); + + Task ReplacesThrowsWithThrowsAsync_WhenUsedInIndexer(string method, string updatedMethod); } \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.Shared/CodeVerifier.cs b/tests/NSubstitute.Analyzers.Tests.Shared/CodeVerifier.cs index 0d598be0..de485b4f 100644 --- a/tests/NSubstitute.Analyzers.Tests.Shared/CodeVerifier.cs +++ b/tests/NSubstitute.Analyzers.Tests.Shared/CodeVerifier.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Immutable; using System.Globalization; using System.Linq; @@ -45,4 +46,22 @@ protected static void VerifyNoCompilerDiagnosticErrors(ImmutableArray project.AddMetadataReference(RuntimeMetadataReference.NSubstitute422Reference), + _ => throw new ArgumentException($"NSubstitute {version} is not supported", nameof(version)) + }; + + return project; + } } \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/SyncOverAsyncThrowsCodeFixProviderTests/SyncOverAsyncThrowsCodeFixActionsTests.cs b/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/SyncOverAsyncThrowsCodeFixProviderTests/SyncOverAsyncThrowsCodeFixActionsTests.cs index 2b77e72e..3eb61687 100644 --- a/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/SyncOverAsyncThrowsCodeFixProviderTests/SyncOverAsyncThrowsCodeFixActionsTests.cs +++ b/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/SyncOverAsyncThrowsCodeFixProviderTests/SyncOverAsyncThrowsCodeFixActionsTests.cs @@ -1,6 +1,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.Diagnostics; +using NSubstitute.Analyzers.Tests.Shared; using NSubstitute.Analyzers.Tests.Shared.CodeFixProviders; using NSubstitute.Analyzers.VisualBasic.CodeFixProviders; using NSubstitute.Analyzers.VisualBasic.DiagnosticAnalyzers; @@ -15,9 +16,11 @@ public class SyncOverAsyncThrowsCodeFixActionsTests : VisualBasicCodeFixActionsV protected override DiagnosticAnalyzer DiagnosticAnalyzer { get; } = new SyncOverAsyncThrowsAnalyzer(); [Theory] - [InlineData("Throws", "Replace with Returns")] - [InlineData("ThrowsForAnyArgs", "Replace with ReturnsForAnyArgs")] - public async Task CreatesCodeAction_WhenOverloadSupported(string method, string expectedCodeActionTitle) + [InlineData("Throws", NSubstituteVersion.NSubstitute4_2_2, "Replace with Returns")] + [InlineData("ThrowsForAnyArgs", NSubstituteVersion.NSubstitute4_2_2, "Replace with ReturnsForAnyArgs")] + [InlineData("Throws", NSubstituteVersion.Latest, "Replace with ThrowsAsync")] + [InlineData("ThrowsForAnyArgs", NSubstituteVersion.Latest, "Replace with ThrowsAsyncForAnyArgs")] + public async Task CreatesCodeAction_WhenOverloadSupported(string method, NSubstituteVersion version, string expectedCodeActionTitle) { var source = $@"Imports System Imports System.Threading.Tasks @@ -37,13 +40,15 @@ End Sub End Class End Namespace"; - await VerifyCodeActions(source, expectedCodeActionTitle); + await VerifyCodeActions(source, version, expectedCodeActionTitle); } [Theory] - [InlineData("Throws")] - [InlineData("ThrowsForAnyArgs")] - public async Task DoesNotCreateCodeAction_WhenOverloadNotSupported(string method) + [InlineData("Throws", NSubstituteVersion.NSubstitute4_2_2)] + [InlineData("ThrowsForAnyArgs", NSubstituteVersion.NSubstitute4_2_2)] + [InlineData("Throws", NSubstituteVersion.Latest)] + [InlineData("ThrowsForAnyArgs", NSubstituteVersion.Latest)] + public async Task DoesNotCreateCodeAction_WhenOverloadNotSupported(string method, NSubstituteVersion version) { var source = $@"Imports System Imports System.Threading.Tasks @@ -64,6 +69,6 @@ End Sub End Class End Namespace"; - await VerifyCodeActions(source); + await VerifyCodeActions(source, version); } } \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/SyncOverAsyncThrowsCodeFixProviderTests/SyncOverAsyncThrowsCodeFixVerifier.cs b/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/SyncOverAsyncThrowsCodeFixProviderTests/SyncOverAsyncThrowsCodeFixVerifier.cs index 35a0fcec..bbc1559e 100644 --- a/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/SyncOverAsyncThrowsCodeFixProviderTests/SyncOverAsyncThrowsCodeFixVerifier.cs +++ b/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/SyncOverAsyncThrowsCodeFixProviderTests/SyncOverAsyncThrowsCodeFixVerifier.cs @@ -20,6 +20,15 @@ public static IEnumerable ThrowsTestCases } } + public static IEnumerable ThrowsAsyncTestCases + { + get + { + yield return new object[] { "Throws", "ThrowsAsync" }; + yield return new object[] { "ThrowsForAnyArgs", "ThrowsAsyncForAnyArgs" }; + } + } + protected override CodeFixProvider CodeFixProvider { get; } = new SyncOverAsyncThrowsCodeFixProvider(); protected override DiagnosticAnalyzer DiagnosticAnalyzer { get; } = new SyncOverAsyncThrowsAnalyzer(); @@ -35,4 +44,16 @@ public static IEnumerable ThrowsTestCases [Theory] [MemberData(nameof(ThrowsTestCases))] public abstract Task ReplacesThrowsWithReturns_WhenUsedInIndexer(string method, string updatedMethod); + + [Theory] + [MemberData(nameof(ThrowsAsyncTestCases))] + public abstract Task ReplacesThrowsWithThrowsAsync_WhenUsedInMethod(string method, string updatedMethod); + + [Theory] + [MemberData(nameof(ThrowsAsyncTestCases))] + public abstract Task ReplacesThrowsWithThrowsAsync_WhenUsedInProperty(string method, string updatedMethod); + + [Theory] + [MemberData(nameof(ThrowsAsyncTestCases))] + public abstract Task ReplacesThrowsWithThrowsAsync_WhenUsedInIndexer(string method, string updatedMethod); } \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/SyncOverAsyncThrowsCodeFixProviderTests/ThrowsAsExtensionMethodTests.cs b/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/SyncOverAsyncThrowsCodeFixProviderTests/ThrowsAsExtensionMethodTests.cs index 536ffa4a..ae89371d 100644 --- a/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/SyncOverAsyncThrowsCodeFixProviderTests/ThrowsAsExtensionMethodTests.cs +++ b/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/SyncOverAsyncThrowsCodeFixProviderTests/ThrowsAsExtensionMethodTests.cs @@ -132,4 +132,19 @@ End Class await VerifyFix(source, newSource); } + + public override Task ReplacesThrowsWithThrowsAsync_WhenUsedInMethod(string method, string updatedMethod) + { + throw new System.NotImplementedException(); + } + + public override Task ReplacesThrowsWithThrowsAsync_WhenUsedInProperty(string method, string updatedMethod) + { + throw new System.NotImplementedException(); + } + + public override Task ReplacesThrowsWithThrowsAsync_WhenUsedInIndexer(string method, string updatedMethod) + { + throw new System.NotImplementedException(); + } } \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/SyncOverAsyncThrowsCodeFixProviderTests/ThrowsAsExtensionsMethodWithGenericTypeSpecifiedTests.cs b/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/SyncOverAsyncThrowsCodeFixProviderTests/ThrowsAsExtensionsMethodWithGenericTypeSpecifiedTests.cs index 46a98913..36875b46 100644 --- a/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/SyncOverAsyncThrowsCodeFixProviderTests/ThrowsAsExtensionsMethodWithGenericTypeSpecifiedTests.cs +++ b/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/SyncOverAsyncThrowsCodeFixProviderTests/ThrowsAsExtensionsMethodWithGenericTypeSpecifiedTests.cs @@ -126,4 +126,19 @@ End Class await VerifyFix(source, newSource); } + + public override Task ReplacesThrowsWithThrowsAsync_WhenUsedInMethod(string method, string updatedMethod) + { + throw new System.NotImplementedException(); + } + + public override Task ReplacesThrowsWithThrowsAsync_WhenUsedInProperty(string method, string updatedMethod) + { + throw new System.NotImplementedException(); + } + + public override Task ReplacesThrowsWithThrowsAsync_WhenUsedInIndexer(string method, string updatedMethod) + { + throw new System.NotImplementedException(); + } } \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/SyncOverAsyncThrowsCodeFixProviderTests/ThrowsAsOrdinaryMethodTests.cs b/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/SyncOverAsyncThrowsCodeFixProviderTests/ThrowsAsOrdinaryMethodTests.cs index c7e262cc..a24423cf 100644 --- a/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/SyncOverAsyncThrowsCodeFixProviderTests/ThrowsAsOrdinaryMethodTests.cs +++ b/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/SyncOverAsyncThrowsCodeFixProviderTests/ThrowsAsOrdinaryMethodTests.cs @@ -138,4 +138,19 @@ End Class await VerifyFix(source, newSource); } + + public override Task ReplacesThrowsWithThrowsAsync_WhenUsedInMethod(string method, string updatedMethod) + { + throw new System.NotImplementedException(); + } + + public override Task ReplacesThrowsWithThrowsAsync_WhenUsedInProperty(string method, string updatedMethod) + { + throw new System.NotImplementedException(); + } + + public override Task ReplacesThrowsWithThrowsAsync_WhenUsedInIndexer(string method, string updatedMethod) + { + throw new System.NotImplementedException(); + } } \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/SyncOverAsyncThrowsCodeFixProviderTests/ThrowsAsOrdinaryMethodWithGenericTypeSpecifiedTests.cs b/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/SyncOverAsyncThrowsCodeFixProviderTests/ThrowsAsOrdinaryMethodWithGenericTypeSpecifiedTests.cs index ce21b2e3..d1c75fae 100644 --- a/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/SyncOverAsyncThrowsCodeFixProviderTests/ThrowsAsOrdinaryMethodWithGenericTypeSpecifiedTests.cs +++ b/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/SyncOverAsyncThrowsCodeFixProviderTests/ThrowsAsOrdinaryMethodWithGenericTypeSpecifiedTests.cs @@ -132,4 +132,19 @@ End Class await VerifyFix(source, newSource); } + + public override Task ReplacesThrowsWithThrowsAsync_WhenUsedInMethod(string method, string updatedMethod) + { + throw new System.NotImplementedException(); + } + + public override Task ReplacesThrowsWithThrowsAsync_WhenUsedInProperty(string method, string updatedMethod) + { + throw new System.NotImplementedException(); + } + + public override Task ReplacesThrowsWithThrowsAsync_WhenUsedInIndexer(string method, string updatedMethod) + { + throw new System.NotImplementedException(); + } } \ No newline at end of file