diff --git a/global.json b/global.json index ee30eea7..89b3b0f4 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "2.2.104" + "version": "2.2.402" } } diff --git a/src/NSubstitute.Analyzers.CSharp/CodeFixProviders/ReEntrantSetupCodeFixProvider.cs b/src/NSubstitute.Analyzers.CSharp/CodeFixProviders/ReEntrantSetupCodeFixProvider.cs new file mode 100644 index 00000000..3ae0a0a8 --- /dev/null +++ b/src/NSubstitute.Analyzers.CSharp/CodeFixProviders/ReEntrantSetupCodeFixProvider.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Simplification; +using NSubstitute.Analyzers.CSharp.Extensions; +using NSubstitute.Analyzers.Shared.CodeFixProviders; +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; + +namespace NSubstitute.Analyzers.CSharp.CodeFixProviders +{ + [ExportCodeFixProvider(LanguageNames.CSharp)] + internal sealed class ReEntrantSetupCodeFixProvider : AbstractReEntrantSetupCodeFixProvider + { + protected override int AwaitExpressionRawKind { get; } = (int)SyntaxKind.AwaitExpression; + + protected override ArgumentSyntax CreateUpdatedArgumentSyntaxNode(ArgumentSyntax argumentSyntaxNode) + { + return argumentSyntaxNode.WithExpression(CreateSimpleLambdaExpressionNode(argumentSyntaxNode.Expression)); + } + + protected override IEnumerable GetParameterExpressionsFromArrayArgument(ArgumentSyntax argumentSyntaxNode) + { + return argumentSyntaxNode.Expression.GetParameterExpressionsFromArrayArgument()?.Select(syntax => syntax); + } + + protected override ArgumentSyntax CreateUpdatedParamsArgumentSyntaxNode( + SyntaxGenerator syntaxGenerator, + ITypeSymbol typeSymbol, + ArgumentSyntax argumentSyntaxNode) + { + var expression = argumentSyntaxNode.Expression; + ArrayCreationExpressionSyntax resultArrayCreationExpressionSyntax; + + switch (expression) + { + case ArrayCreationExpressionSyntax arrayCreationExpressionSyntax: + resultArrayCreationExpressionSyntax = CreateArrayCreationExpression( + syntaxGenerator, + typeSymbol, + arrayCreationExpressionSyntax.Initializer); + break; + case ImplicitArrayCreationExpressionSyntax implicitArrayCreationExpressionSyntax: + resultArrayCreationExpressionSyntax = CreateArrayCreationExpression( + syntaxGenerator, + typeSymbol, + implicitArrayCreationExpressionSyntax.Initializer); + break; + default: + throw new ArgumentException($"{argumentSyntaxNode.Kind()} is not recognized as array initialization", nameof(argumentSyntaxNode)); + } + + return argumentSyntaxNode.WithExpression(resultArrayCreationExpressionSyntax); + } + + protected override IEnumerable GetArguments(ArgumentListSyntax argumentSyntax) + { + return argumentSyntax.Arguments; + } + + protected override SyntaxNode GetArgumentExpressionSyntax(ArgumentSyntax argumentSyntax) + { + return argumentSyntax.Expression; + } + + private static SimpleLambdaExpressionSyntax CreateSimpleLambdaExpressionNode(SyntaxNode content) + { + return SimpleLambdaExpression( + Parameter(Identifier("_").WithTrailingTrivia(Space)), + (CSharpSyntaxNode)content.WithLeadingTrivia(Space)); + } + + private static ArrayCreationExpressionSyntax CreateArrayCreationExpression( + SyntaxGenerator syntaxGenerator, + ITypeSymbol typeSymbol, + InitializerExpressionSyntax initializerExpressionSyntax) + { + var arrayType = CreateArrayTypeNode(syntaxGenerator, typeSymbol); + var syntaxes = CreateSimpleLambdaExpressions(initializerExpressionSyntax); + + var initializer = InitializerExpression(SyntaxKind.ArrayInitializerExpression, syntaxes); + + return ArrayCreationExpression(arrayType, initializer); + } + + private static SeparatedSyntaxList CreateSimpleLambdaExpressions(InitializerExpressionSyntax initializerExpressionSyntax) + { + var expressions = initializerExpressionSyntax.Expressions.Select(CreateSimpleLambdaExpressionNode); + return SeparatedList(expressions); + } + + private static ArrayTypeSyntax CreateArrayTypeNode(SyntaxGenerator syntaxGenerator, ITypeSymbol type) + { + var typeSyntax = (TypeSyntax)syntaxGenerator.TypeExpression(type); + var typeArgumentListSyntax = TypeArgumentList( + SeparatedList( + new SyntaxNodeOrToken[] + { + QualifiedName( + QualifiedName( + IdentifierName("NSubstitute"), + IdentifierName("Core")), + IdentifierName("CallInfo")), + Token(SyntaxKind.CommaToken), + typeSyntax + })); + + var qualifiedNameSyntax = QualifiedName(IdentifierName("System"), GenericName(Identifier("Func"), typeArgumentListSyntax)); + + var arrayRankSpecifierSyntaxes = SingletonList(ArrayRankSpecifier(SingletonSeparatedList(OmittedArraySizeExpression()))); + + return ArrayType(qualifiedNameSyntax, arrayRankSpecifierSyntaxes).WithAdditionalAnnotations(Simplifier.Annotation); + } + } +} \ No newline at end of file diff --git a/src/NSubstitute.Analyzers.Shared/CodeFixProviders/AbstractPartialSubstituteUsedForUnsupportedTypeCodeFixProvider.cs b/src/NSubstitute.Analyzers.Shared/CodeFixProviders/AbstractPartialSubstituteUsedForUnsupportedTypeCodeFixProvider.cs index a81388da..d1b268b3 100644 --- a/src/NSubstitute.Analyzers.Shared/CodeFixProviders/AbstractPartialSubstituteUsedForUnsupportedTypeCodeFixProvider.cs +++ b/src/NSubstitute.Analyzers.Shared/CodeFixProviders/AbstractPartialSubstituteUsedForUnsupportedTypeCodeFixProvider.cs @@ -5,6 +5,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Editing; namespace NSubstitute.Analyzers.Shared.CodeFixProviders { diff --git a/src/NSubstitute.Analyzers.Shared/CodeFixProviders/AbstractReEntrantSetupCodeFixProvider.cs b/src/NSubstitute.Analyzers.Shared/CodeFixProviders/AbstractReEntrantSetupCodeFixProvider.cs new file mode 100644 index 00000000..d3772c89 --- /dev/null +++ b/src/NSubstitute.Analyzers.Shared/CodeFixProviders/AbstractReEntrantSetupCodeFixProvider.cs @@ -0,0 +1,127 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Operations; +using Microsoft.CodeAnalysis.Simplification; + +namespace NSubstitute.Analyzers.Shared.CodeFixProviders +{ + internal abstract class AbstractReEntrantSetupCodeFixProvider : CodeFixProvider + where TArgumentListSyntax : SyntaxNode + where TArgumentSyntax : SyntaxNode + { + public sealed override ImmutableArray FixableDiagnosticIds { get; } = ImmutableArray.Create(DiagnosticIdentifiers.ReEntrantSubstituteCall); + + protected abstract TArgumentSyntax CreateUpdatedArgumentSyntaxNode(TArgumentSyntax argumentSyntaxNode); + + protected abstract TArgumentSyntax CreateUpdatedParamsArgumentSyntaxNode(SyntaxGenerator syntaxGenerator, ITypeSymbol returnedTypeSymbol, TArgumentSyntax argumentSyntaxNode); + + protected abstract SyntaxNode GetArgumentExpressionSyntax(TArgumentSyntax argumentSyntax); + + protected abstract IEnumerable GetParameterExpressionsFromArrayArgument(TArgumentSyntax argumentSyntaxNode); + + protected abstract int AwaitExpressionRawKind { get; } + + public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + var diagnostic = context.Diagnostics.FirstOrDefault(diag => + diag.Descriptor.Id == DiagnosticIdentifiers.ReEntrantSubstituteCall); + + if (diagnostic == null) + { + return; + } + + var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken); + var node = root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true); + var semanticModel = await context.Document.GetSemanticModelAsync(); + + var argumentList = GetArgumentListSyntax(node); + var allArguments = GetArguments(argumentList).ToList(); + + if (IsFixSupported(semanticModel, allArguments) == false) + { + return; + } + + var codeAction = CodeAction.Create( + "Replace with lambda", + ct => CreateChangedDocument(context, argumentList, allArguments, ct), + nameof(AbstractReEntrantSetupCodeFixProvider)); + + context.RegisterCodeFix(codeAction, diagnostic); + } + + protected abstract IEnumerable GetArguments(TArgumentListSyntax argumentSyntax); + + private async Task CreateChangedDocument( + CodeFixContext context, + TArgumentListSyntax argumentListSyntax, + IReadOnlyList argumentSyntaxes, + CancellationToken ct) + { + var documentEditor = await DocumentEditor.CreateAsync(context.Document, ct); + var semanticModel = await context.Document.GetSemanticModelAsync(ct); + var invocationSyntaxNode = argumentListSyntax.Parent; + if (!(semanticModel.GetSymbolInfo(invocationSyntaxNode).Symbol is IMethodSymbol methodSymbol)) + { + return context.Document; + } + + var skip = methodSymbol.MethodKind == MethodKind.Ordinary + ? 1 + : 0; + + foreach (var argumentSyntax in argumentSyntaxes.Skip(skip)) + { + if (IsArrayParamsArgument(semanticModel, argumentSyntax)) + { + var updatedParamsArgumentSyntaxNode = CreateUpdatedParamsArgumentSyntaxNode( + SyntaxGenerator.GetGenerator(context.Document), + methodSymbol.TypeArguments.FirstOrDefault() ?? methodSymbol.ReceiverType, + argumentSyntax); + + documentEditor.ReplaceNode(argumentSyntax, updatedParamsArgumentSyntaxNode); + } + else + { + var updatedArgumentSyntax = CreateUpdatedArgumentSyntaxNode(argumentSyntax); + + documentEditor.ReplaceNode(argumentSyntax, updatedArgumentSyntax); + } + } + + return await Simplifier.ReduceAsync(documentEditor.GetChangedDocument(), cancellationToken: ct); + } + + private bool IsFixSupported(SemanticModel semanticModel, IEnumerable arguments) + { + return arguments.All(arg => + { + var expressionSyntax = GetArgumentExpressionSyntax(arg); + var arrayExpressions = GetParameterExpressionsFromArrayArgument(arg); + + return expressionSyntax.RawKind != AwaitExpressionRawKind && + (IsArrayParamsArgument(semanticModel, arg) == false || (arrayExpressions != null && arrayExpressions.All(exp => exp.RawKind != AwaitExpressionRawKind))); + }); + } + + private bool IsArrayParamsArgument(SemanticModel semanticModel, TArgumentSyntax argumentSyntax) + { + var operation = semanticModel.GetOperation(argumentSyntax); + return operation is IArgumentOperation argumentOperation && argumentOperation.Parameter.IsParams; + } + + private static TArgumentListSyntax GetArgumentListSyntax(SyntaxNode diagnosticNode) + { + var argumentListSyntax = diagnosticNode.Ancestors().OfType().FirstOrDefault(); + return argumentListSyntax; + } + } +} \ No newline at end of file diff --git a/src/NSubstitute.Analyzers.VisualBasic/CodeFixProviders/ReEntrantSetupCodeFixProvider.cs b/src/NSubstitute.Analyzers.VisualBasic/CodeFixProviders/ReEntrantSetupCodeFixProvider.cs new file mode 100644 index 00000000..cfa81a46 --- /dev/null +++ b/src/NSubstitute.Analyzers.VisualBasic/CodeFixProviders/ReEntrantSetupCodeFixProvider.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Simplification; +using Microsoft.CodeAnalysis.VisualBasic; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; +using NSubstitute.Analyzers.Shared.CodeFixProviders; +using NSubstitute.Analyzers.VisualBasic.Extensions; +using static Microsoft.CodeAnalysis.VisualBasic.SyntaxFactory; + +namespace NSubstitute.Analyzers.VisualBasic.CodeFixProviders +{ + [ExportCodeFixProvider(LanguageNames.VisualBasic)] + internal sealed class ReEntrantSetupCodeFixProvider : AbstractReEntrantSetupCodeFixProvider + { + protected override ArgumentSyntax CreateUpdatedArgumentSyntaxNode(ArgumentSyntax argumentSyntaxNode) + { + var expressionSyntax = argumentSyntaxNode.GetExpression(); + + var lambdaExpression = CreateSingleLineLambdaExpression(expressionSyntax); + + switch (argumentSyntaxNode) + { + case SimpleArgumentSyntax simpleArgumentSyntax: + return simpleArgumentSyntax.WithExpression(lambdaExpression); + } + + return argumentSyntaxNode; + } + + protected override ArgumentSyntax CreateUpdatedParamsArgumentSyntaxNode(SyntaxGenerator syntaxGenerator, ITypeSymbol typeSymbol, ArgumentSyntax argumentSyntaxNode) + { + if (!(argumentSyntaxNode is SimpleArgumentSyntax simpleArgumentSyntax)) + { + return argumentSyntaxNode; + } + + var expression = argumentSyntaxNode.GetExpression(); + ArrayCreationExpressionSyntax resultArrayCreationExpressionSyntax; + + switch (expression) + { + case ArrayCreationExpressionSyntax arrayCreationExpressionSyntax: + resultArrayCreationExpressionSyntax = CreateArrayCreationExpression( + syntaxGenerator, + typeSymbol, + arrayCreationExpressionSyntax.Initializer.Initializers); + break; + case CollectionInitializerSyntax implicitArrayCreationExpressionSyntax: + resultArrayCreationExpressionSyntax = CreateArrayCreationExpression( + syntaxGenerator, + typeSymbol, + implicitArrayCreationExpressionSyntax.Initializers); + break; + default: + throw new ArgumentException($"{argumentSyntaxNode.Kind()} is not recognized as array initialization", nameof(argumentSyntaxNode)); + } + + return simpleArgumentSyntax.WithExpression(resultArrayCreationExpressionSyntax); + } + + protected override SyntaxNode GetArgumentExpressionSyntax(ArgumentSyntax argumentSyntax) + { + return argumentSyntax.GetExpression(); + } + + protected override IEnumerable GetParameterExpressionsFromArrayArgument(ArgumentSyntax argumentSyntaxNode) + { + return argumentSyntaxNode.GetExpression().GetParameterExpressionsFromArrayArgument()?.Select(syntax => syntax); + } + + protected override int AwaitExpressionRawKind { get; } = (int)SyntaxKind.AwaitExpression; + + protected override IEnumerable GetArguments(ArgumentListSyntax argumentSyntax) + { + return argumentSyntax.Arguments; + } + + private static ArrayCreationExpressionSyntax CreateArrayCreationExpression( + SyntaxGenerator syntaxGenerator, + ITypeSymbol typeSymbol, + SeparatedSyntaxList initializers) + { + var typeNode = CreateTypeNode(syntaxGenerator, typeSymbol); + var syntaxes = CreateSingleLineLambdaExpressions(initializers); + + var initializer = CollectionInitializer(syntaxes); + var arrayRankSpecifierSyntaxes = SingletonList(ArrayRankSpecifier()); + + return ArrayCreationExpression(Token(SyntaxKind.NewKeyword), new SyntaxList(), typeNode, null, arrayRankSpecifierSyntaxes, initializer); + } + + private static SeparatedSyntaxList CreateSingleLineLambdaExpressions(SeparatedSyntaxList expressions) + { + var singleLineLambdaExpressionSyntaxes = expressions.Select(CreateSingleLineLambdaExpression); + + return SeparatedList(singleLineLambdaExpressionSyntaxes); + } + + private static SingleLineLambdaExpressionSyntax CreateSingleLineLambdaExpression(ExpressionSyntax expressionSyntax) + { + var separatedSyntaxList = SeparatedList(SingletonList(Parameter(ModifiedIdentifier(Identifier("x"))))); + var functionLambdaHeader = FunctionLambdaHeader( + new SyntaxList(), + TokenList(), + ParameterList(separatedSyntaxList), + null); + + var lambdaExpression = SingleLineLambdaExpression( + SyntaxKind.SingleLineFunctionLambdaExpression, + functionLambdaHeader, + expressionSyntax.WithLeadingTrivia()); + return lambdaExpression; + } + + private static QualifiedNameSyntax CreateTypeNode(SyntaxGenerator syntaxGenerator, ITypeSymbol type) + { + var typeSyntax = (TypeSyntax)syntaxGenerator.TypeExpression(type); + var typeArgumentListSyntax = TypeArgumentList( + SeparatedList( + new SyntaxNodeOrToken[] + { + QualifiedName( + QualifiedName( + IdentifierName("NSubstitute"), + IdentifierName("Core")), + IdentifierName("CallInfo")), + Token(SyntaxKind.CommaToken), + typeSyntax + })); + + var qualifiedNameSyntax = QualifiedName(IdentifierName("System"), GenericName(Identifier("Func"), typeArgumentListSyntax)); + + return qualifiedNameSyntax.WithAdditionalAnnotations(Simplifier.Annotation); + } + } +} \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/ReEntrantSetupCodeFixProviderTests/ReEntrantSetupCodeFixActionsTests.cs b/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/ReEntrantSetupCodeFixProviderTests/ReEntrantSetupCodeFixActionsTests.cs new file mode 100644 index 00000000..ab0d5773 --- /dev/null +++ b/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/ReEntrantSetupCodeFixProviderTests/ReEntrantSetupCodeFixActionsTests.cs @@ -0,0 +1,106 @@ +using System; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; +using NSubstitute.Analyzers.CSharp.CodeFixProviders; +using NSubstitute.Analyzers.CSharp.DiagnosticAnalyzers; +using NSubstitute.Analyzers.Tests.Shared.CodeFixProviders; +using Xunit; + +namespace NSubstitute.Analyzers.Tests.CSharp.CodeFixProviderTests.ReEntrantSetupCodeFixProviderTests +{ + public class ReEntrantSetupCodeFixActionsTests : CSharpCodeFixActionsVerifier, IReEntrantSetupCodeFixActionsVerifier + { + [Theory] + [InlineData("await CreateReEntrantSubstituteAsync(), await CreateDefaultValue()")] + [InlineData("CreateReEntrantSubstitute(), await CreateDefaultValue()")] + [InlineData("CreateReEntrantSubstitute(), new int[] { 1, await CreateDefaultValue() }")] + public async Task DoesNotCreateCodeFixAction_WhenAnyArgumentSyntaxIsAsync(string arguments) + { + var source = $@"using System.Threading.Tasks; +using NSubstitute; + +namespace MyNamespace +{{ + public class FooTests + {{ + public interface IFoo + {{ + int Id {{ get; }} + }} + + public async Task Test() + {{ + var substitute = Substitute.For(); + substitute.Id.Returns({arguments}); + }} + + private async Task CreateReEntrantSubstituteAsync() + {{ + var substitute = Substitute.For(); + substitute.Id.Returns(1); + return await Task.FromResult(1); + }} + + private int CreateReEntrantSubstitute() + {{ + var substitute = Substitute.For(); + substitute.Id.Returns(1); + return 1; + }} + + private async Task CreateDefaultValue() + {{{{ + return await Task.FromResult(1); + }}}} + }} +}}"; + await VerifyCodeActions(source, Array.Empty()); + } + + [Theory] + [InlineData("someArray")] + [InlineData("Array.Empty()")] + public async Task DoesNotCreateCodeFixAction_WhenArrayParamsArgumentExpressionsCantBeRewritten(string paramsArgument) + { + var source = $@"using System; +using NSubstitute; + +namespace MyNamespace +{{ + public class FooTests + {{ + public interface IFoo + {{ + int Id {{ get; }} + }} + + public void Test() + {{ + var substitute = Substitute.For(); + var someArray = new int[] {{ 1, 2, 3 }}; + substitute.Id.Returns(CreateReEntrantSubstitute(), {paramsArgument}); + }} + + private int CreateReEntrantSubstitute() + {{ + var substitute = Substitute.For(); + substitute.Id.Returns(1); + return 1; + }} + }} +}}"; + await VerifyCodeActions(source, Array.Empty()); + } + + protected override DiagnosticAnalyzer GetDiagnosticAnalyzer() + { + return new ReEntrantSetupAnalyzer(); + } + + protected override CodeFixProvider GetCodeFixProvider() + { + return new ReEntrantSetupCodeFixProvider(); + } + } +} \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/ReEntrantSetupCodeFixProviderTests/ReEntrantSetupCodeFixVerifier.cs b/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/ReEntrantSetupCodeFixProviderTests/ReEntrantSetupCodeFixVerifier.cs new file mode 100644 index 00000000..adbb55d4 --- /dev/null +++ b/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/ReEntrantSetupCodeFixProviderTests/ReEntrantSetupCodeFixVerifier.cs @@ -0,0 +1,35 @@ +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; +using NSubstitute.Analyzers.CSharp.CodeFixProviders; +using NSubstitute.Analyzers.CSharp.DiagnosticAnalyzers; +using NSubstitute.Analyzers.Tests.Shared.CodeFixProviders; +using Xunit; + +namespace NSubstitute.Analyzers.Tests.CSharp.CodeFixProviderTests.ReEntrantSetupCodeFixProviderTests +{ + public abstract class ReEntrantSetupCodeFixVerifier : CSharpCodeFixVerifier, IReEntrantSetupCodeFixProviderVerifier + { + [Theory] + [InlineData("CreateReEntrantSubstitute(), CreateDefaultValue(), 1", "_ => CreateReEntrantSubstitute(), _ => CreateDefaultValue(), _ => 1")] + [InlineData("CreateReEntrantSubstitute(), new [] { CreateDefaultValue(), 1 }", "_ => CreateReEntrantSubstitute(), new System.Func[] { _ => CreateDefaultValue(), _ => 1 }")] + [InlineData("CreateReEntrantSubstitute(), new int[] { CreateDefaultValue(), 1 }", "_ => CreateReEntrantSubstitute(), new System.Func[] { _ => CreateDefaultValue(), _ => 1 }")] + [InlineData("returnThis: CreateReEntrantSubstitute()", "returnThis: _ => CreateReEntrantSubstitute()")] + [InlineData("returnThis: CreateReEntrantSubstitute(), returnThese: new [] { CreateDefaultValue(), 1 }", "returnThis: _ => CreateReEntrantSubstitute(), returnThese: new System.Func[] { _ => CreateDefaultValue(), _ => 1 }")] + [InlineData("returnThis: CreateReEntrantSubstitute(), returnThese: new int[] { CreateDefaultValue(), 1 }", "returnThis: _ => CreateReEntrantSubstitute(), returnThese: new System.Func[] { _ => CreateDefaultValue(), _ => 1 }")] + public abstract Task ReplacesArgumentExpression_WithLambda(string arguments, string rewrittenArguments); + + [Fact] + public abstract Task ReplacesArgumentExpression_WithLambdaWithReducedTypes_WhenGeneratingArrayParamsArgument(); + + protected override DiagnosticAnalyzer GetDiagnosticAnalyzer() + { + return new ReEntrantSetupAnalyzer(); + } + + protected override CodeFixProvider GetCodeFixProvider() + { + return new ReEntrantSetupCodeFixProvider(); + } + } +} \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/ReEntrantSetupCodeFixProviderTests/ReturnsAsExtensionMethodTests.cs b/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/ReEntrantSetupCodeFixProviderTests/ReturnsAsExtensionMethodTests.cs new file mode 100644 index 00000000..e1705622 --- /dev/null +++ b/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/ReEntrantSetupCodeFixProviderTests/ReturnsAsExtensionMethodTests.cs @@ -0,0 +1,171 @@ +using System.Threading.Tasks; + +namespace NSubstitute.Analyzers.Tests.CSharp.CodeFixProviderTests.ReEntrantSetupCodeFixProviderTests +{ + public class ReturnsAsExtensionMethodTests : ReEntrantSetupCodeFixVerifier + { + public override async Task ReplacesArgumentExpression_WithLambda(string arguments, string rewrittenArguments) + { + var oldSource = $@"using NSubstitute; + +namespace MyNamespace +{{ + public class FooTests + {{ + private IFoo firstSubstitute = Substitute.For(); + + public FooTests() + {{ + firstSubstitute.Id.Returns(45); + }} + + public interface IFoo + {{ + int Id {{ get; }} + }} + + public void Test() + {{ + var secondSubstitute = Substitute.For(); + secondSubstitute.Id.Returns({arguments}); + }} + + private int CreateReEntrantSubstitute() + {{ + var substitute = Substitute.For(); + substitute.Id.Returns(1); + return 1; + }} + + private int CreateDefaultValue() + {{ + return 1; + }} + }} +}}"; + + var newSource = $@"using NSubstitute; + +namespace MyNamespace +{{ + public class FooTests + {{ + private IFoo firstSubstitute = Substitute.For(); + + public FooTests() + {{ + firstSubstitute.Id.Returns(45); + }} + + public interface IFoo + {{ + int Id {{ get; }} + }} + + public void Test() + {{ + var secondSubstitute = Substitute.For(); + secondSubstitute.Id.Returns({rewrittenArguments}); + }} + + private int CreateReEntrantSubstitute() + {{ + var substitute = Substitute.For(); + substitute.Id.Returns(1); + return 1; + }} + + private int CreateDefaultValue() + {{ + return 1; + }} + }} +}}"; + await VerifyFix(oldSource, newSource); + } + + public override async Task ReplacesArgumentExpression_WithLambdaWithReducedTypes_WhenGeneratingArrayParamsArgument() + { + var oldSource = @"using NSubstitute; +using NSubstitute.Core; +using System; + +namespace MyNamespace +{ + public class FooTests + { + private IFoo firstSubstitute = Substitute.For(); + + public FooTests() + { + firstSubstitute.Id.Returns(45); + } + + public interface IFoo + { + int Id { get; } + } + + public void Test() + { + var secondSubstitute = Substitute.For(); + secondSubstitute.Id.Returns(CreateReEntrantSubstitute(), new[] { MyNamespace.ValueProvider.Value }); + } + + private int CreateReEntrantSubstitute() + { + var substitute = Substitute.For(); + substitute.Id.Returns(1); + return 1; + } + } + + public static class ValueProvider + { + public static int Value { get; } = 1; + } +}"; + + var newSource = @"using NSubstitute; +using NSubstitute.Core; +using System; + +namespace MyNamespace +{ + public class FooTests + { + private IFoo firstSubstitute = Substitute.For(); + + public FooTests() + { + firstSubstitute.Id.Returns(45); + } + + public interface IFoo + { + int Id { get; } + } + + public void Test() + { + var secondSubstitute = Substitute.For(); + secondSubstitute.Id.Returns(_ => CreateReEntrantSubstitute(), new Func[] { _ => MyNamespace.ValueProvider.Value }); + } + + private int CreateReEntrantSubstitute() + { + var substitute = Substitute.For(); + substitute.Id.Returns(1); + return 1; + } + } + + public static class ValueProvider + { + public static int Value { get; } = 1; + } +}"; + await VerifyFix(oldSource, newSource); + } + } +} \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/ReEntrantSetupCodeFixProviderTests/ReturnsAsOrdinaryMethodTests.cs b/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/ReEntrantSetupCodeFixProviderTests/ReturnsAsOrdinaryMethodTests.cs new file mode 100644 index 00000000..44dd983b --- /dev/null +++ b/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/ReEntrantSetupCodeFixProviderTests/ReturnsAsOrdinaryMethodTests.cs @@ -0,0 +1,171 @@ +using System.Threading.Tasks; + +namespace NSubstitute.Analyzers.Tests.CSharp.CodeFixProviderTests.ReEntrantSetupCodeFixProviderTests +{ + public class ReturnsAsOrdinaryMethodTests : ReEntrantSetupCodeFixVerifier + { + public override async Task ReplacesArgumentExpression_WithLambda(string arguments, string rewrittenArguments) + { + var oldSource = $@"using NSubstitute; + +namespace MyNamespace +{{ + public class FooTests + {{ + private IFoo firstSubstitute = Substitute.For(); + + public FooTests() + {{ + firstSubstitute.Id.Returns(45); + }} + + public interface IFoo + {{ + int Id {{ get; }} + }} + + public void Test() + {{ + var secondSubstitute = Substitute.For(); + SubstituteExtensions.Returns(secondSubstitute.Id, {arguments}); + }} + + private int CreateReEntrantSubstitute() + {{ + var substitute = Substitute.For(); + substitute.Id.Returns(1); + return 1; + }} + + private int CreateDefaultValue() + {{ + return 1; + }} + }} +}}"; + + var newSource = $@"using NSubstitute; + +namespace MyNamespace +{{ + public class FooTests + {{ + private IFoo firstSubstitute = Substitute.For(); + + public FooTests() + {{ + firstSubstitute.Id.Returns(45); + }} + + public interface IFoo + {{ + int Id {{ get; }} + }} + + public void Test() + {{ + var secondSubstitute = Substitute.For(); + SubstituteExtensions.Returns(secondSubstitute.Id, {rewrittenArguments}); + }} + + private int CreateReEntrantSubstitute() + {{ + var substitute = Substitute.For(); + substitute.Id.Returns(1); + return 1; + }} + + private int CreateDefaultValue() + {{ + return 1; + }} + }} +}}"; + await VerifyFix(oldSource, newSource); + } + + public override async Task ReplacesArgumentExpression_WithLambdaWithReducedTypes_WhenGeneratingArrayParamsArgument() + { + var oldSource = @"using NSubstitute; +using NSubstitute.Core; +using System; + +namespace MyNamespace +{ + public class FooTests + { + private IFoo firstSubstitute = Substitute.For(); + + public FooTests() + { + firstSubstitute.Id.Returns(45); + } + + public interface IFoo + { + int Id { get; } + } + + public void Test() + { + var secondSubstitute = Substitute.For(); + SubstituteExtensions.Returns(secondSubstitute.Id, CreateReEntrantSubstitute(), new[] { MyNamespace.ValueProvider.Value }); + } + + private int CreateReEntrantSubstitute() + { + var substitute = Substitute.For(); + substitute.Id.Returns(1); + return 1; + } + } + + public static class ValueProvider + { + public static int Value { get; } = 1; + } +}"; + + var newSource = @"using NSubstitute; +using NSubstitute.Core; +using System; + +namespace MyNamespace +{ + public class FooTests + { + private IFoo firstSubstitute = Substitute.For(); + + public FooTests() + { + firstSubstitute.Id.Returns(45); + } + + public interface IFoo + { + int Id { get; } + } + + public void Test() + { + var secondSubstitute = Substitute.For(); + SubstituteExtensions.Returns(secondSubstitute.Id, _ => CreateReEntrantSubstitute(), new Func[] { _ => MyNamespace.ValueProvider.Value }); + } + + private int CreateReEntrantSubstitute() + { + var substitute = Substitute.For(); + substitute.Id.Returns(1); + return 1; + } + } + + public static class ValueProvider + { + public static int Value { get; } = 1; + } +}"; + await VerifyFix(oldSource, newSource); + } + } +} \ 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 158453c3..e6ac37ef 100644 --- a/tests/NSubstitute.Analyzers.Tests.Shared/CodeFixProviders/CodeFixVerifier.cs +++ b/tests/NSubstitute.Analyzers.Tests.Shared/CodeFixProviders/CodeFixVerifier.cs @@ -71,7 +71,7 @@ protected static async Task ApplyFix(Document document, CodeAction cod private async Task VerifyFix(DiagnosticAnalyzer analyzer, CodeFixProvider codeFixProvider, string oldSource, string newSource, int? codeFixIndex, bool allowNewCompilerDiagnostics) { var document = CreateDocument(oldSource); - var analyzerDiagnostics = await GetSortedDiagnosticsFromDocuments(analyzer, new[] { document }, false); + var analyzerDiagnostics = await GetSortedDiagnosticsFromDocuments(analyzer, new[] { document }, allowNewCompilerDiagnostics); var compilerDiagnostics = await GetCompilerDiagnostics(document); var attempts = analyzerDiagnostics.Length; diff --git a/tests/NSubstitute.Analyzers.Tests.Shared/CodeFixProviders/IReEntrantSetupCodeFixActionsVerifier.cs b/tests/NSubstitute.Analyzers.Tests.Shared/CodeFixProviders/IReEntrantSetupCodeFixActionsVerifier.cs new file mode 100644 index 00000000..c2153f17 --- /dev/null +++ b/tests/NSubstitute.Analyzers.Tests.Shared/CodeFixProviders/IReEntrantSetupCodeFixActionsVerifier.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; + +namespace NSubstitute.Analyzers.Tests.Shared.CodeFixProviders +{ + public interface IReEntrantSetupCodeFixActionsVerifier + { + Task DoesNotCreateCodeFixAction_WhenAnyArgumentSyntaxIsAsync(string arguments); + + Task DoesNotCreateCodeFixAction_WhenArrayParamsArgumentExpressionsCantBeRewritten(string paramsArgument); + } +} \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.Shared/CodeFixProviders/IReEntrantSetupCodeFixProviderVerifier.cs b/tests/NSubstitute.Analyzers.Tests.Shared/CodeFixProviders/IReEntrantSetupCodeFixProviderVerifier.cs new file mode 100644 index 00000000..59d7a81b --- /dev/null +++ b/tests/NSubstitute.Analyzers.Tests.Shared/CodeFixProviders/IReEntrantSetupCodeFixProviderVerifier.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; + +namespace NSubstitute.Analyzers.Tests.Shared.CodeFixProviders +{ + public interface IReEntrantSetupCodeFixProviderVerifier + { + Task ReplacesArgumentExpression_WithLambda(string arguments, string rewrittenArguments); + + Task ReplacesArgumentExpression_WithLambdaWithReducedTypes_WhenGeneratingArrayParamsArgument(); + } +} \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/ReEntrantSetupCodeFixProviderTests/ReEntrantSetupCodeFixActionsTests.cs b/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/ReEntrantSetupCodeFixProviderTests/ReEntrantSetupCodeFixActionsTests.cs new file mode 100644 index 00000000..e87247ac --- /dev/null +++ b/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/ReEntrantSetupCodeFixProviderTests/ReEntrantSetupCodeFixActionsTests.cs @@ -0,0 +1,98 @@ +using System; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; +using NSubstitute.Analyzers.Tests.Shared.CodeFixProviders; +using NSubstitute.Analyzers.VisualBasic.CodeFixProviders; +using NSubstitute.Analyzers.VisualBasic.DiagnosticAnalyzers; +using Xunit; + +namespace NSubstitute.Analyzers.Tests.VisualBasic.CodeFixProvidersTests.ReEntrantSetupCodeFixProviderTests +{ + public class ReEntrantSetupCodeFixActionsTests : VisualBasicCodeFixActionsVerifier, IReEntrantSetupCodeFixActionsVerifier + { + [Theory] + [InlineData("Await CreateReEntrantSubstituteAsync(), Await CreateDefaultValue()")] + [InlineData("CreateReEntrantSubstitute(), Await CreateDefaultValue()")] + [InlineData("CreateReEntrantSubstitute(), new Integer() { 1, await CreateDefaultValue() }")] + public async Task DoesNotCreateCodeFixAction_WhenAnyArgumentSyntaxIsAsync(string arguments) + { + var source = $@"Imports System.Threading.Tasks +Imports NSubstitute + +Namespace MyNamespace + Public Class FooTests + Interface IFoo + ReadOnly Property Id As Integer + End Interface + + Public Async Function Test() As Task + Dim substitute = NSubstitute.Substitute.[For](Of IFoo)() + substitute.Id.Returns({arguments}) + End Function + + Private Async Function CreateReEntrantSubstituteAsync() As Task(Of Integer) + Dim substitute = NSubstitute.Substitute.[For](Of IFoo)() + substitute.Id.Returns(1) + Return Await Task.FromResult(1) + End Function + + Private Function CreateReEntrantSubstitute() As Integer + Dim substitute = NSubstitute.Substitute.[For](Of IFoo)() + substitute.Id.Returns(1) + Return 1 + End Function + + Private Async Function CreateDefaultValue() As Task(Of Integer) + If True Then + Return Await Task.FromResult(1) + End If + End Function + End Class +End Namespace +"; + await VerifyCodeActions(source, Array.Empty()); + } + + [Theory] + [InlineData("someArray")] + [InlineData("Array.Empty(Of Integer)()")] + public async Task DoesNotCreateCodeFixAction_WhenArrayParamsArgumentExpressionsCantBeRewritten(string paramsArgument) + { + var source = $@"Imports System +Imports NSubstitute + +Namespace MyNamespace + Public Class FooTests + Interface IFoo + ReadOnly Property Id As Integer + End Interface + + Public Sub Test() + Dim substitute = NSubstitute.Substitute.[For](Of IFoo)() + Dim someArray = New Integer() {{1, 2, 3}} + substitute.Id.Returns(CreateReEntrantSubstitute(), {paramsArgument}) + End Sub + + Private Function CreateReEntrantSubstitute() As Integer + Dim substitute = NSubstitute.Substitute.[For](Of IFoo)() + substitute.Id.Returns(1) + Return 1 + End Function + End Class +End Namespace +"; + await VerifyCodeActions(source, Array.Empty()); + } + + protected override DiagnosticAnalyzer GetDiagnosticAnalyzer() + { + return new ReEntrantSetupAnalyzer(); + } + + protected override CodeFixProvider GetCodeFixProvider() + { + return new ReEntrantSetupCodeFixProvider(); + } + } +} \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/ReEntrantSetupCodeFixProviderTests/ReEntrantSetupCodeFixVerifier.cs b/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/ReEntrantSetupCodeFixProviderTests/ReEntrantSetupCodeFixVerifier.cs new file mode 100644 index 00000000..86199d0a --- /dev/null +++ b/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/ReEntrantSetupCodeFixProviderTests/ReEntrantSetupCodeFixVerifier.cs @@ -0,0 +1,33 @@ +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; +using NSubstitute.Analyzers.Tests.Shared.CodeFixProviders; +using NSubstitute.Analyzers.VisualBasic.CodeFixProviders; +using NSubstitute.Analyzers.VisualBasic.DiagnosticAnalyzers; +using Xunit; + +namespace NSubstitute.Analyzers.Tests.VisualBasic.CodeFixProvidersTests.ReEntrantSetupCodeFixProviderTests +{ + public abstract class ReEntrantSetupCodeFixVerifier : VisualBasicCodeFixVerifier, IReEntrantSetupCodeFixProviderVerifier + { + [Theory] + [InlineData("CreateReEntrantSubstitute(), CreateDefaultValue(), 1", "Function(x) CreateReEntrantSubstitute(), Function(x) CreateDefaultValue(), Function(x) 1")] + [InlineData("CreateReEntrantSubstitute(), { CreateDefaultValue(), 1 }", "Function(x) CreateReEntrantSubstitute(), New System.Func(Of Core.CallInfo, Integer)() {Function(x) CreateDefaultValue(), Function(x) 1}")] + [InlineData("CreateReEntrantSubstitute(), New Integer() {CreateDefaultValue(), 1}", "Function(x) CreateReEntrantSubstitute(), New System.Func(Of Core.CallInfo, Integer)() {Function(x) CreateDefaultValue(), Function(x) 1}")] + [InlineData("returnThis:= CreateReEntrantSubstitute()", "returnThis:=Function(x) CreateReEntrantSubstitute()")] + public abstract Task ReplacesArgumentExpression_WithLambda(string arguments, string rewrittenArguments); + + [Fact] + public abstract Task ReplacesArgumentExpression_WithLambdaWithReducedTypes_WhenGeneratingArrayParamsArgument(); + + protected override DiagnosticAnalyzer GetDiagnosticAnalyzer() + { + return new ReEntrantSetupAnalyzer(); + } + + protected override CodeFixProvider GetCodeFixProvider() + { + return new ReEntrantSetupCodeFixProvider(); + } + } +} \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/ReEntrantSetupCodeFixProviderTests/ReturnsAsExtensionMethodTests.cs b/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/ReEntrantSetupCodeFixProviderTests/ReturnsAsExtensionMethodTests.cs new file mode 100644 index 00000000..4df67054 --- /dev/null +++ b/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/ReEntrantSetupCodeFixProviderTests/ReturnsAsExtensionMethodTests.cs @@ -0,0 +1,141 @@ +using System.Threading.Tasks; + +namespace NSubstitute.Analyzers.Tests.VisualBasic.CodeFixProvidersTests.ReEntrantSetupCodeFixProviderTests +{ + public class ReturnsAsExtensionMethodTests : ReEntrantSetupCodeFixVerifier + { + public override async Task ReplacesArgumentExpression_WithLambda(string arguments, string rewrittenArguments) + { + var oldSource = $@"Imports NSubstitute + +Namespace MyNamespace + Public Class FooTests + Private firstSubstitute As IFoo = Substitute.[For](Of IFoo)() + + Public Sub New() + firstSubstitute.Id.Returns(45) + End Sub + + Interface IFoo + ReadOnly Property Id As Integer + End Interface + + Public Sub Test() + Dim secondSubstitute = NSubstitute.Substitute.[For](Of IFoo)() + secondSubstitute.Id.Returns({arguments}) + End Sub + + Private Function CreateReEntrantSubstitute() As Integer + Dim substitute = NSubstitute.Substitute.[For](Of IFoo)() + substitute.Id.Returns(1) + Return 1 + End Function + + Private Function CreateDefaultValue() As Integer + Return 1 + End Function + End Class +End Namespace +"; + + var newSource = $@"Imports NSubstitute + +Namespace MyNamespace + Public Class FooTests + Private firstSubstitute As IFoo = Substitute.[For](Of IFoo)() + + Public Sub New() + firstSubstitute.Id.Returns(45) + End Sub + + Interface IFoo + ReadOnly Property Id As Integer + End Interface + + Public Sub Test() + Dim secondSubstitute = NSubstitute.Substitute.[For](Of IFoo)() + secondSubstitute.Id.Returns({rewrittenArguments}) + End Sub + + Private Function CreateReEntrantSubstitute() As Integer + Dim substitute = NSubstitute.Substitute.[For](Of IFoo)() + substitute.Id.Returns(1) + Return 1 + End Function + + Private Function CreateDefaultValue() As Integer + Return 1 + End Function + End Class +End Namespace +"; + await VerifyFix(oldSource, newSource); + } + + public override async Task ReplacesArgumentExpression_WithLambdaWithReducedTypes_WhenGeneratingArrayParamsArgument() + { + var oldSource = @"Imports NSubstitute +Imports NSubstitute.Core +Imports System + +Namespace MyNamespace + Public Class FooTests + Private firstSubstitute As IFoo = Substitute.[For](Of IFoo)() + Public Shared Property Value As Integer + + Public Sub New() + firstSubstitute.Id.Returns(45) + End Sub + + Interface IFoo + ReadOnly Property Id As Integer + End Interface + + Public Sub Test() + Dim secondSubstitute = NSubstitute.Substitute.[For](Of IFoo)() + secondSubstitute.Id.Returns(CreateReEntrantSubstitute(), {MyNamespace.FooTests.Value}) + End Sub + + Private Function CreateReEntrantSubstitute() As Integer + Dim substitute = NSubstitute.Substitute.[For](Of IFoo)() + substitute.Id.Returns(1) + Return 1 + End Function + End Class +End Namespace +"; + + var newSource = @"Imports NSubstitute +Imports NSubstitute.Core +Imports System + +Namespace MyNamespace + Public Class FooTests + Private firstSubstitute As IFoo = Substitute.[For](Of IFoo)() + Public Shared Property Value As Integer + + Public Sub New() + firstSubstitute.Id.Returns(45) + End Sub + + Interface IFoo + ReadOnly Property Id As Integer + End Interface + + Public Sub Test() + Dim secondSubstitute = NSubstitute.Substitute.[For](Of IFoo)() + secondSubstitute.Id.Returns(Function(x) CreateReEntrantSubstitute(), New Func(Of CallInfo, Integer)() {Function(x) MyNamespace.FooTests.Value}) + End Sub + + Private Function CreateReEntrantSubstitute() As Integer + Dim substitute = NSubstitute.Substitute.[For](Of IFoo)() + substitute.Id.Returns(1) + Return 1 + End Function + End Class +End Namespace +"; + await VerifyFix(oldSource, newSource); + } + } +} \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/ReEntrantSetupCodeFixProviderTests/ReturnsAsOrdinaryMethodTests.cs b/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/ReEntrantSetupCodeFixProviderTests/ReturnsAsOrdinaryMethodTests.cs new file mode 100644 index 00000000..38eed572 --- /dev/null +++ b/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/ReEntrantSetupCodeFixProviderTests/ReturnsAsOrdinaryMethodTests.cs @@ -0,0 +1,141 @@ +using System.Threading.Tasks; + +namespace NSubstitute.Analyzers.Tests.VisualBasic.CodeFixProvidersTests.ReEntrantSetupCodeFixProviderTests +{ + public class ReturnsAsOrdinaryMethodTests : ReEntrantSetupCodeFixVerifier + { + public override async Task ReplacesArgumentExpression_WithLambda(string arguments, string rewrittenArguments) + { + var oldSource = $@"Imports NSubstitute + +Namespace MyNamespace + Public Class FooTests + Private firstSubstitute As IFoo = Substitute.[For](Of IFoo)() + + Public Sub New() + firstSubstitute.Id.Returns(45) + End Sub + + Interface IFoo + ReadOnly Property Id As Integer + End Interface + + Public Sub Test() + Dim secondSubstitute = NSubstitute.Substitute.[For](Of IFoo)() + SubstituteExtensions.Returns(secondSubstitute.Id, {arguments}) + End Sub + + Private Function CreateReEntrantSubstitute() As Integer + Dim substitute = NSubstitute.Substitute.[For](Of IFoo)() + substitute.Id.Returns(1) + Return 1 + End Function + + Private Function CreateDefaultValue() As Integer + Return 1 + End Function + End Class +End Namespace +"; + + var newSource = $@"Imports NSubstitute + +Namespace MyNamespace + Public Class FooTests + Private firstSubstitute As IFoo = Substitute.[For](Of IFoo)() + + Public Sub New() + firstSubstitute.Id.Returns(45) + End Sub + + Interface IFoo + ReadOnly Property Id As Integer + End Interface + + Public Sub Test() + Dim secondSubstitute = NSubstitute.Substitute.[For](Of IFoo)() + SubstituteExtensions.Returns(secondSubstitute.Id, {rewrittenArguments}) + End Sub + + Private Function CreateReEntrantSubstitute() As Integer + Dim substitute = NSubstitute.Substitute.[For](Of IFoo)() + substitute.Id.Returns(1) + Return 1 + End Function + + Private Function CreateDefaultValue() As Integer + Return 1 + End Function + End Class +End Namespace +"; + await VerifyFix(oldSource, newSource); + } + + public override async Task ReplacesArgumentExpression_WithLambdaWithReducedTypes_WhenGeneratingArrayParamsArgument() + { + var oldSource = @"Imports NSubstitute +Imports NSubstitute.Core +Imports System + +Namespace MyNamespace + Public Class FooTests + Private firstSubstitute As IFoo = Substitute.[For](Of IFoo)() + Public Shared Property Value As Integer + + Public Sub New() + firstSubstitute.Id.Returns(45) + End Sub + + Interface IFoo + ReadOnly Property Id As Integer + End Interface + + Public Sub Test() + Dim secondSubstitute = NSubstitute.Substitute.[For](Of IFoo)() + SubstituteExtensions.Returns(secondSubstitute.Id, CreateReEntrantSubstitute(), {MyNamespace.FooTests.Value}) + End Sub + + Private Function CreateReEntrantSubstitute() As Integer + Dim substitute = NSubstitute.Substitute.[For](Of IFoo)() + substitute.Id.Returns(1) + Return 1 + End Function + End Class +End Namespace +"; + + var newSource = @"Imports NSubstitute +Imports NSubstitute.Core +Imports System + +Namespace MyNamespace + Public Class FooTests + Private firstSubstitute As IFoo = Substitute.[For](Of IFoo)() + Public Shared Property Value As Integer + + Public Sub New() + firstSubstitute.Id.Returns(45) + End Sub + + Interface IFoo + ReadOnly Property Id As Integer + End Interface + + Public Sub Test() + Dim secondSubstitute = NSubstitute.Substitute.[For](Of IFoo)() + SubstituteExtensions.Returns(secondSubstitute.Id, Function(x) CreateReEntrantSubstitute(), New Func(Of CallInfo, Integer)() {Function(x) MyNamespace.FooTests.Value}) + End Sub + + Private Function CreateReEntrantSubstitute() As Integer + Dim substitute = NSubstitute.Substitute.[For](Of IFoo)() + substitute.Id.Returns(1) + Return 1 + End Function + End Class +End Namespace +"; + await VerifyFix(oldSource, newSource); + } + } +} \ No newline at end of file