diff --git a/src/NSubstitute.Analyzers.CSharp/DiagnosticAnalyzers/CallInfoDoAnalyzer.cs b/src/NSubstitute.Analyzers.CSharp/DiagnosticAnalyzers/CallInfoDoAnalyzer.cs new file mode 100644 index 00000000..5773acb2 --- /dev/null +++ b/src/NSubstitute.Analyzers.CSharp/DiagnosticAnalyzers/CallInfoDoAnalyzer.cs @@ -0,0 +1,49 @@ +using System; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using NSubstitute.Analyzers.CSharp.Extensions; +using NSubstitute.Analyzers.Shared.Extensions; + +namespace NSubstitute.Analyzers.CSharp.DiagnosticAnalyzers +{ + internal class CallInfoDoAnalyzer : CallInfoAnalyzer + { + private readonly Lazy _whenSubstituteCallFinderProxy = new Lazy(() => new WhenSubstituteCallFinder()); + + private WhenSubstituteCallFinder WhenSubstituteCallFinder => _whenSubstituteCallFinderProxy.Value; + + protected override bool SupportsCallInfo(SyntaxNodeAnalysisContext syntaxNodeContext, InvocationExpressionSyntax syntax, IMethodSymbol methodSymbol) + { + if (methodSymbol.Name != "Do") + { + return false; + } + + var allArguments = GetArgumentExpressions(syntax); + return allArguments.Any(arg => syntaxNodeContext.SemanticModel.GetTypeInfo(arg).IsCallInfoDelegate(syntaxNodeContext.SemanticModel)); + } + + protected override SyntaxNode GetSubstituteCall(SyntaxNodeAnalysisContext syntaxNodeContext, IMethodSymbol methodSymbol, InvocationExpressionSyntax invocationExpressionSyntax) + { + var parentInvocationExpression = invocationExpressionSyntax.GetParentInvocationExpression(); + + if (parentInvocationExpression == null) + { + return null; + } + + if (syntaxNodeContext.SemanticModel.GetSymbolInfo(parentInvocationExpression).Symbol is IMethodSymbol parentInvocationSymbol) + { + var argumentExpression = parentInvocationSymbol.MethodKind == MethodKind.ReducedExtension + ? parentInvocationExpression.ArgumentList.Arguments.First().Expression + : parentInvocationExpression.ArgumentList.Arguments.Skip(1).First().Expression; + + return WhenSubstituteCallFinder.Find(syntaxNodeContext, argumentExpression).FirstOrDefault(); + } + + return null; + } + } +} \ No newline at end of file diff --git a/src/NSubstitute.Analyzers.CSharp/DiagnosticAnalyzers/NonSubstitutableMemberWhenAnalyzer.cs b/src/NSubstitute.Analyzers.CSharp/DiagnosticAnalyzers/NonSubstitutableMemberWhenAnalyzer.cs index 16aaf349..3b3823a0 100644 --- a/src/NSubstitute.Analyzers.CSharp/DiagnosticAnalyzers/NonSubstitutableMemberWhenAnalyzer.cs +++ b/src/NSubstitute.Analyzers.CSharp/DiagnosticAnalyzers/NonSubstitutableMemberWhenAnalyzer.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -18,56 +19,15 @@ public NonSubstitutableMemberWhenAnalyzer() protected override SyntaxKind InvocationExpressionKind { get; } = SyntaxKind.InvocationExpression; + private readonly Lazy _whenSubstituteCallFinderProxy = new Lazy(() => new WhenSubstituteCallFinder()); + + private WhenSubstituteCallFinder WhenSubstituteCallFinder => _whenSubstituteCallFinderProxy.Value; + protected override IEnumerable GetExpressionsForAnalysys(SyntaxNodeAnalysisContext syntaxNodeAnalysisContext, IMethodSymbol methodSymbol, InvocationExpressionSyntax invocationExpressionSyntax) { var argumentListArguments = invocationExpressionSyntax.ArgumentList.Arguments; var argumentSyntax = methodSymbol.MethodKind == MethodKind.ReducedExtension ? argumentListArguments.First() : argumentListArguments.Skip(1).First(); - return GetExpressionsForAnalysys(syntaxNodeAnalysisContext, argumentSyntax.Expression); - } - - private IEnumerable GetExpressionsForAnalysys(SyntaxNodeAnalysisContext syntaxNodeContext, SyntaxNode argumentSyntax) - { - SyntaxNode body = null; - switch (argumentSyntax) - { - case SimpleLambdaExpressionSyntax simpleLambdaExpressionSyntax: - body = simpleLambdaExpressionSyntax.Body; - break; - case AnonymousFunctionExpressionSyntax anonymousFunctionExpressionSyntax: - body = anonymousFunctionExpressionSyntax.Body; - break; - case LocalFunctionStatementSyntax localFunctionStatementSyntax: - body = (SyntaxNode)localFunctionStatementSyntax.Body ?? localFunctionStatementSyntax.ExpressionBody; - break; - case MethodDeclarationSyntax methodDeclarationSyntax: - body = (SyntaxNode)methodDeclarationSyntax.Body ?? methodDeclarationSyntax.ExpressionBody; - break; - case IdentifierNameSyntax identifierNameSyntax: - var symbol = syntaxNodeContext.SemanticModel.GetSymbolInfo(identifierNameSyntax); - if (symbol.Symbol != null && symbol.Symbol.Locations.Any()) - { - var location = symbol.Symbol.Locations.First(); - var syntaxNode = location.SourceTree.GetRoot().FindNode(location.SourceSpan); - - foreach (var expressionsForAnalysy in GetExpressionsForAnalysys(syntaxNodeContext, syntaxNode)) - { - yield return expressionsForAnalysy; - } - } - - break; - } - - if (body == null) - { - yield break; - } - - foreach (var invocationExpressionSyntax in body.DescendantNodes().Where(node => node.IsKind(SyntaxKind.SimpleMemberAccessExpression) || - node.IsKind(SyntaxKind.ElementAccessExpression))) - { - yield return invocationExpressionSyntax; - } + return WhenSubstituteCallFinder.Find(syntaxNodeAnalysisContext, argumentSyntax.Expression); } } } \ No newline at end of file diff --git a/src/NSubstitute.Analyzers.CSharp/DiagnosticAnalyzers/WhenSubstituteCallFinder.cs b/src/NSubstitute.Analyzers.CSharp/DiagnosticAnalyzers/WhenSubstituteCallFinder.cs new file mode 100644 index 00000000..7c40bf62 --- /dev/null +++ b/src/NSubstitute.Analyzers.CSharp/DiagnosticAnalyzers/WhenSubstituteCallFinder.cs @@ -0,0 +1,60 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace NSubstitute.Analyzers.CSharp.DiagnosticAnalyzers +{ + /// + /// Finds calls considered to be substitute calls in expressions + /// + internal class WhenSubstituteCallFinder + { + public IEnumerable Find(SyntaxNodeAnalysisContext syntaxNodeContext, SyntaxNode argumentSyntax) + { + SyntaxNode body = null; + switch (argumentSyntax) + { + case SimpleLambdaExpressionSyntax simpleLambdaExpressionSyntax: + body = simpleLambdaExpressionSyntax.Body; + break; + case AnonymousFunctionExpressionSyntax anonymousFunctionExpressionSyntax: + body = anonymousFunctionExpressionSyntax.Body; + break; + case LocalFunctionStatementSyntax localFunctionStatementSyntax: + body = (SyntaxNode)localFunctionStatementSyntax.Body ?? localFunctionStatementSyntax.ExpressionBody; + break; + case MethodDeclarationSyntax methodDeclarationSyntax: + body = (SyntaxNode)methodDeclarationSyntax.Body ?? methodDeclarationSyntax.ExpressionBody; + break; + case IdentifierNameSyntax identifierNameSyntax: + var symbol = syntaxNodeContext.SemanticModel.GetSymbolInfo(identifierNameSyntax); + if (symbol.Symbol != null && symbol.Symbol.Locations.Any()) + { + var location = symbol.Symbol.Locations.First(); + var syntaxNode = location.SourceTree.GetRoot().FindNode(location.SourceSpan); + + foreach (var expressionForAnalysis in Find(syntaxNodeContext, syntaxNode)) + { + yield return expressionForAnalysis; + } + } + + break; + } + + if (body == null) + { + yield break; + } + + foreach (var invocationExpressionSyntax in body.DescendantNodes().Where(node => node.IsKind(SyntaxKind.SimpleMemberAccessExpression) || + node.IsKind(SyntaxKind.ElementAccessExpression))) + { + yield return invocationExpressionSyntax; + } + } + } +} \ No newline at end of file diff --git a/src/NSubstitute.Analyzers.Shared/DiagnosticAnalyzers/AbstractCallInfoAnalyzer.cs b/src/NSubstitute.Analyzers.Shared/DiagnosticAnalyzers/AbstractCallInfoAnalyzer.cs index 2c7db134..ef0455bb 100644 --- a/src/NSubstitute.Analyzers.Shared/DiagnosticAnalyzers/AbstractCallInfoAnalyzer.cs +++ b/src/NSubstitute.Analyzers.Shared/DiagnosticAnalyzers/AbstractCallInfoAnalyzer.cs @@ -68,6 +68,31 @@ public override void Initialize(AnalysisContext context) protected abstract bool IsAssignableTo(Compilation compilation, ITypeSymbol fromSymbol, ITypeSymbol toSymbol); + protected virtual bool SupportsCallInfo(SyntaxNodeAnalysisContext syntaxNodeContext, TInvocationExpressionSyntax syntax, IMethodSymbol methodSymbol) + { + if (MethodNames.TryGetValue(methodSymbol.Name, out var typeName) == false) + { + return false; + } + + var allArguments = GetArgumentExpressions(syntax); + IEnumerable argumentsForAnalysis; + if (methodSymbol.MethodKind == MethodKind.ReducedExtension) + argumentsForAnalysis = allArguments; + else if (methodSymbol.IsExtensionMethod) + argumentsForAnalysis = allArguments.Skip(1); + else + argumentsForAnalysis = allArguments; + + var symbol = syntaxNodeContext.SemanticModel.GetSymbolInfo(syntax); + + var supportsCallInfo = + symbol.Symbol?.ContainingAssembly?.Name.Equals(MetadataNames.NSubstituteAssemblyName, StringComparison.OrdinalIgnoreCase) == true && + symbol.Symbol?.ContainingType?.ToString().Equals(typeName, StringComparison.OrdinalIgnoreCase) == true; + + return supportsCallInfo && argumentsForAnalysis.Any(arg => syntaxNodeContext.SemanticModel.GetTypeInfo(arg).IsCallInfoDelegate(syntaxNodeContext.SemanticModel)); + } + private void AnalyzeInvocation(SyntaxNodeAnalysisContext syntaxNodeContext) { var invocationExpression = (TInvocationExpressionSyntax)syntaxNodeContext.Node; @@ -264,31 +289,6 @@ private bool AnalyzeAssignment(SyntaxNodeAnalysisContext syntaxNodeContext, ILis return false; } - private bool SupportsCallInfo(SyntaxNodeAnalysisContext syntaxNodeContext, TInvocationExpressionSyntax syntax, IMethodSymbol methodSymbol) - { - if (MethodNames.TryGetValue(methodSymbol.Name, out var typeName) == false) - { - return false; - } - - var allArguments = GetArgumentExpressions(syntax); - IEnumerable argumentsForAnalysis; - if (methodSymbol.MethodKind == MethodKind.ReducedExtension) - argumentsForAnalysis = allArguments; - else if (methodSymbol.IsExtensionMethod) - argumentsForAnalysis = allArguments.Skip(1); - else - argumentsForAnalysis = allArguments; - - var symbol = syntaxNodeContext.SemanticModel.GetSymbolInfo(syntax); - - var supportsCallInfo = - symbol.Symbol?.ContainingAssembly?.Name.Equals(MetadataNames.NSubstituteAssemblyName, StringComparison.OrdinalIgnoreCase) == true && - symbol.Symbol?.ContainingType?.ToString().Equals(typeName, StringComparison.OrdinalIgnoreCase) == true; - - return supportsCallInfo && argumentsForAnalysis.Any(arg => syntaxNodeContext.SemanticModel.GetTypeInfo(arg).IsCallInfoDelegate(syntaxNodeContext.SemanticModel)); - } - private IList GetSubstituteCallParameters(SyntaxNodeAnalysisContext syntaxNodeContext, IMethodSymbol methodSymbol, TInvocationExpressionSyntax invocationExpression) { var parentMethodCallSyntax = GetSubstituteCall(syntaxNodeContext, methodSymbol, invocationExpression); diff --git a/src/NSubstitute.Analyzers.VisualBasic/DiagnosticAnalyzers/NonSubstitutableMemberWhenAnalyzer.cs b/src/NSubstitute.Analyzers.VisualBasic/DiagnosticAnalyzers/NonSubstitutableMemberWhenAnalyzer.cs index ed38b502..ed049dd8 100644 --- a/src/NSubstitute.Analyzers.VisualBasic/DiagnosticAnalyzers/NonSubstitutableMemberWhenAnalyzer.cs +++ b/src/NSubstitute.Analyzers.VisualBasic/DiagnosticAnalyzers/NonSubstitutableMemberWhenAnalyzer.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; @@ -18,97 +19,15 @@ public NonSubstitutableMemberWhenAnalyzer() protected override SyntaxKind InvocationExpressionKind { get; } = SyntaxKind.InvocationExpression; + private readonly Lazy _whenSubstituteCallFinderProxy = new Lazy(() => new WhenSubstituteCallFinder()); + + private WhenSubstituteCallFinder WhenSubstituteCallFinder => _whenSubstituteCallFinderProxy.Value; + protected override IEnumerable GetExpressionsForAnalysys(SyntaxNodeAnalysisContext syntaxNodeAnalysisContext, IMethodSymbol methodSymbol, InvocationExpressionSyntax invocationExpressionSyntax) { var argumentListArguments = invocationExpressionSyntax.ArgumentList.Arguments; var argumentSyntax = methodSymbol.MethodKind == MethodKind.ReducedExtension ? argumentListArguments.First() : argumentListArguments.Skip(1).First(); - return GetExpressionsForAnalysys(syntaxNodeAnalysisContext, argumentSyntax.GetExpression()); - } - - private IEnumerable GetExpressionsForAnalysys(SyntaxNodeAnalysisContext syntaxNodeContext, SyntaxNode syntax) - { - SyntaxNode body = null; - switch (syntax) - { - case SingleLineLambdaExpressionSyntax _: - case ExpressionStatementSyntax _: - case LocalDeclarationStatementSyntax _: - case AssignmentStatementSyntax _: - body = syntax; - break; - case MultiLineLambdaExpressionSyntax simpleLambdaExpressionSyntax: - foreach (var syntaxNode in IterateStatements(simpleLambdaExpressionSyntax.Statements)) - { - yield return syntaxNode; - } - - break; - case MethodBlockSyntax methodBlockSyntax: - foreach (var syntaxNode in IterateStatements(methodBlockSyntax.Statements)) - { - yield return syntaxNode; - } - - break; - case UnaryExpressionSyntax unaryExpressionSyntax: - foreach (var syntaxNode in GetExpressionsForAnalysys(syntaxNodeContext, unaryExpressionSyntax.Operand)) - { - yield return syntaxNode; - } - - break; - case IdentifierNameSyntax identifierNameSyntax: - var symbol = syntaxNodeContext.SemanticModel.GetSymbolInfo(identifierNameSyntax); - if (symbol.Symbol != null && symbol.Symbol.Locations.Any()) - { - var location = symbol.Symbol.Locations.First(); - var syntaxNode = location.SourceTree.GetRoot().FindNode(location.SourceSpan); - - SyntaxNode innerNode = null; - if (syntaxNode is MethodStatementSyntax methodStatementSyntax) - { - innerNode = methodStatementSyntax.Parent; - } - - innerNode = innerNode ?? syntaxNode; - foreach (var expressionsForAnalysy in GetExpressionsForAnalysys(syntaxNodeContext, innerNode)) - { - yield return expressionsForAnalysy; - } - } - - break; - } - - if (body == null) - { - yield break; - } - - var memberAccessExpressions = body.DescendantNodes().Where(node => node.IsKind(SyntaxKind.SimpleMemberAccessExpression)); - var invocationExpressions = body.DescendantNodes().Where(node => node.IsKind(SyntaxKind.InvocationExpression)); - - // rather ugly but prevents reporting two times the same thing - // as VB syntax is based on statements, you can't access body of method directly - if (invocationExpressions.Any()) - { - foreach (var invocationExpression in invocationExpressions) - { - yield return invocationExpression; - } - } - else if (memberAccessExpressions.Any()) - { - foreach (var memberAccessExpression in memberAccessExpressions) - { - yield return memberAccessExpression; - } - } - - IEnumerable IterateStatements(IEnumerable statements) - { - return statements.SelectMany(statement => GetExpressionsForAnalysys(syntaxNodeContext, statement)); - } + return WhenSubstituteCallFinder.Find(syntaxNodeAnalysisContext, argumentSyntax.GetExpression()); } } } \ No newline at end of file diff --git a/src/NSubstitute.Analyzers.VisualBasic/DiagnosticAnalyzers/WhenSubstituteCallFinder.cs b/src/NSubstitute.Analyzers.VisualBasic/DiagnosticAnalyzers/WhenSubstituteCallFinder.cs new file mode 100644 index 00000000..9894831a --- /dev/null +++ b/src/NSubstitute.Analyzers.VisualBasic/DiagnosticAnalyzers/WhenSubstituteCallFinder.cs @@ -0,0 +1,98 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.VisualBasic; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; + +namespace NSubstitute.Analyzers.VisualBasic.DiagnosticAnalyzers +{ + internal class WhenSubstituteCallFinder + { + public IEnumerable Find(SyntaxNodeAnalysisContext syntaxNodeContext, SyntaxNode argumentSyntax) + { + SyntaxNode body = null; + switch (argumentSyntax) + { + case SingleLineLambdaExpressionSyntax _: + case ExpressionStatementSyntax _: + case LocalDeclarationStatementSyntax _: + case AssignmentStatementSyntax _: + body = argumentSyntax; + break; + case MultiLineLambdaExpressionSyntax simpleLambdaExpressionSyntax: + foreach (var syntaxNode in IterateStatements(simpleLambdaExpressionSyntax.Statements)) + { + yield return syntaxNode; + } + + break; + case MethodBlockSyntax methodBlockSyntax: + foreach (var syntaxNode in IterateStatements(methodBlockSyntax.Statements)) + { + yield return syntaxNode; + } + + break; + case UnaryExpressionSyntax unaryExpressionSyntax: + foreach (var syntaxNode in Find(syntaxNodeContext, unaryExpressionSyntax.Operand)) + { + yield return syntaxNode; + } + + break; + case IdentifierNameSyntax identifierNameSyntax: + var symbol = ModelExtensions.GetSymbolInfo(syntaxNodeContext.SemanticModel, identifierNameSyntax); + if (symbol.Symbol != null && symbol.Symbol.Locations.Any()) + { + var location = symbol.Symbol.Locations.First(); + var syntaxNode = location.SourceTree.GetRoot().FindNode(location.SourceSpan); + + SyntaxNode innerNode = null; + if (syntaxNode is MethodStatementSyntax methodStatementSyntax) + { + innerNode = methodStatementSyntax.Parent; + } + + innerNode = innerNode ?? syntaxNode; + foreach (var expressionsForAnalysy in Find(syntaxNodeContext, innerNode)) + { + yield return expressionsForAnalysy; + } + } + + break; + } + + if (body == null) + { + yield break; + } + + var memberAccessExpressions = body.DescendantNodes().Where(node => node.IsKind(SyntaxKind.SimpleMemberAccessExpression)).ToList(); + var invocationExpressions = body.DescendantNodes().Where(node => node.IsKind(SyntaxKind.InvocationExpression)).ToList(); + + // rather ugly but prevents reporting two times the same thing + // as VB syntax is based on statements, you can't access body of method directly + if (invocationExpressions.Any()) + { + foreach (var invocationExpression in invocationExpressions) + { + yield return invocationExpression; + } + } + else if (memberAccessExpressions.Any()) + { + foreach (var memberAccessExpression in memberAccessExpressions) + { + yield return memberAccessExpression; + } + } + + IEnumerable IterateStatements(IEnumerable statements) + { + return statements.SelectMany(statement => Find(syntaxNodeContext, statement)); + } + } + } +} \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/CallInfoDoAnalyzerTests/CallInfoDoAnalyzerVerifier.cs b/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/CallInfoDoAnalyzerTests/CallInfoDoAnalyzerVerifier.cs new file mode 100644 index 00000000..27fc0c9d --- /dev/null +++ b/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/CallInfoDoAnalyzerTests/CallInfoDoAnalyzerVerifier.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; +using NSubstitute.Analyzers.CSharp.DiagnosticAnalyzers; +using NSubstitute.Analyzers.Tests.CSharp.DiagnosticAnalyzerTests.CallInfoAnalyzerTests; +using NSubstitute.Analyzers.Tests.Shared.Extensibility; + +namespace NSubstitute.Analyzers.Tests.CSharp.DiagnosticAnalyzerTests.CallInfoDoAnalyzerTests +{ + public abstract class CallInfoDoAnalyzerVerifier : CallInfoDiagnosticVerifier + { + protected override DiagnosticAnalyzer GetDiagnosticAnalyzer() + { + return new CallInfoDoAnalyzer(); + } + } +} \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/CallInfoDoAnalyzerTests/DoMethodPrecededByExtensionMethodTests.cs b/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/CallInfoDoAnalyzerTests/DoMethodPrecededByExtensionMethodTests.cs new file mode 100644 index 00000000..36c8784d --- /dev/null +++ b/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/CallInfoDoAnalyzerTests/DoMethodPrecededByExtensionMethodTests.cs @@ -0,0 +1,664 @@ +using System.Threading.Tasks; +using NSubstitute.Analyzers.Tests.Shared.Extensibility; + +namespace NSubstitute.Analyzers.Tests.CSharp.DiagnosticAnalyzerTests.CallInfoDoAnalyzerTests +{ + [CombinatoryData("When", "WhenForAnyArgs")] + public class DoMethodPrecededByExtensionMethodTests : CallInfoDoAnalyzerVerifier + { + public override async Task ReportsNoDiagnostics_WhenSubstituteMethodCannotBeInferred(string method, string call, string argAccess) + { + var source = $@"using System; +using NSubstitute; + +namespace MyNamespace +{{ + public interface Foo + {{ + int Bar(int x); + + int Barr {{ get; }} + + int this[int x] {{ get; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var sub = NSubstitute.Substitute.For(); + var returnValue = sub.{method}(substitute => {{var _ = {call}; }}); + returnValue.Do(callInfo => + {{ + {argAccess} + }}); + }} + }} +}}"; + + await VerifyNoDiagnostic(source); + } + + public override async Task ReportsDiagnostic_WhenAccessingArgumentOutOfBounds(string method, string call, string argAccess) + { + var source = $@"using System; +using NSubstitute; + +namespace MyNamespace +{{ + public interface Foo + {{ + int Bar(int x); + + int Barr {{ get; }} + + int this[int x] {{ get; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var sub = NSubstitute.Substitute.For(); + sub.{method}(substitute => {{var _ = {call}; }}).Do(callInfo => + {{ + {argAccess} + }}); + }} + }} +}}"; + await VerifyDiagnostic(source, CallInfoArgumentOutOfRangeDescriptor, "There is no argument at position 1"); + } + + public override async Task ReportsNoDiagnostic_WhenAccessingArgumentOutOfBound_AndPositionIsNotLiteralExpression(string method, string call, string argAccess) + { + var source = $@"using System; +using NSubstitute; + +namespace MyNamespace +{{ + public interface Foo + {{ + int Bar(int x, int y); + + int Barr {{ get; }} + + int this[int x, int y] {{ get; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var sub = NSubstitute.Substitute.For(); + sub.{method}(substitute => {{var _ = {call}; }}).Do(callInfo => + {{ + {argAccess} + }}); + }} + }} +}}"; + await VerifyNoDiagnostic(source); + } + + public override async Task ReportsNoDiagnostic_WhenAccessingArgumentWithinBounds(string method, string call, string argAccess) + { + var source = $@"using System; +using NSubstitute; + +namespace MyNamespace +{{ + public interface Foo + {{ + int Bar(int x, int y); + + int Barr {{ get; }} + + int this[int x, int y] {{ get; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var sub = NSubstitute.Substitute.For(); + sub.{method}(substitute => {{var _ = {call}; }}).Do(callInfo => + {{ + {argAccess} + }}); + }} + }} +}}"; + await VerifyNoDiagnostic(source); + } + + public override async Task ReportsNoDiagnostic_WhenManuallyCasting_ToSupportedType(string method, string call, string argAccess) + { + var source = $@"using System; +using NSubstitute; + +namespace MyNamespace +{{ + public interface Foo + {{ + int Bar(int x, Bar y); + + int this[int x, Bar y] {{ get; }} + }} + + public class BarBase + {{ + }} + + public class Bar : BarBase + {{ + }} + + public class FooTests + {{ + public void Test() + {{ + var sub = NSubstitute.Substitute.For(); + sub.{method}(substitute => {{var _ = {call}; }}).Do(callInfo => + {{ + {argAccess} + }}); + }} + }} +}}"; + + await VerifyNoDiagnostic(source); + } + + public override async Task ReportsDiagnostic_WhenManuallyCasting_ToUnsupportedType(string method, string call, string argAccess) + { + var source = $@"using System; +using NSubstitute; + +namespace MyNamespace +{{ + public interface Foo + {{ + int Bar(int x, double y); + + int Foo(int x, FooBar bar); + + int this[int x, double y] {{ get; }} + + int this[int x, FooBar bar] {{ get; }} + }} + + public class Bar + {{ + }} + + public class FooBar : Bar + {{ + }} + + public class FooTests + {{ + public void Test() + {{ + var sub = NSubstitute.Substitute.For(); + sub.{method}(substitute => {{var _ = {call}; }}).Do(callInfo => + {{ + {argAccess} + }}); + }} + }} +}}"; + + await VerifyDiagnostic(source, CallInfoCouldNotConvertParameterAtPositionDescriptor, "Couldn't convert parameter at position 1 to type MyNamespace.Bar."); + } + + public override async Task ReportsNoDiagnostic_WhenCasting_WithArgAt_ToSupportedType(string method, string call, string argAccess) + { + var source = $@"using System; +using NSubstitute; + +namespace MyNamespace +{{ + public interface Foo + {{ + int Bar(int x, Bar y); + + int this[int x, Bar y] {{ get; }} + }} + + public class BarBase + {{ + }} + + public class Bar : BarBase + {{ + }} + + public class FooTests + {{ + public void Test() + {{ + var sub = NSubstitute.Substitute.For(); + sub.{method}(substitute => {{var _ = {call}; }}).Do(callInfo => + {{ + {argAccess} + }}); + }} + }} +}}"; + + await VerifyNoDiagnostic(source); + } + + public override async Task ReportsDiagnostic_WhenCasting_WithArgAt_ToUnsupportedType(string method, string call, string argAccess, string message) + { + var source = $@"using System; +using NSubstitute; + +namespace MyNamespace +{{ + public interface Foo + {{ + int Bar(int x, double y); + + int Foo(int x, FooBar bar); + + int this[int x, double y] {{ get; }} + + int this[int x, FooBar bar] {{ get; }} + }} + + public class Bar + {{ + }} + + public class FooBar : Bar + {{ + }} + + public class FooTests + {{ + public void Test() + {{ + var sub = NSubstitute.Substitute.For(); + sub.{method}(substitute => {{var _ = {call}; }}).Do(callInfo => + {{ + {argAccess} + }}); + }} + }} +}}"; + + await VerifyDiagnostic(source, CallInfoCouldNotConvertParameterAtPositionDescriptor, message); + } + + public override async Task ReportsNoDiagnostic_WhenCastingElementsFromArgTypes(string method, string call, string argAccess) + { + var source = $@"using System; +using NSubstitute; + +namespace MyNamespace +{{ + public interface Foo + {{ + int Bar(Bar x); + + int this[Bar x] {{ get; }} + }} + + public class Bar + {{ + }} + + public class FooTests + {{ + public void Test() + {{ + var sub = NSubstitute.Substitute.For(); + sub.{method}(substitute => {{var _ = {call}; }}).Do(callInfo => + {{ + {argAccess} + }}); + }} + }} +}}"; + await VerifyNoDiagnostic(source); + } + + public override async Task ReportsNoDiagnostic_WhenAssigningValueToNotRefNorOutArgumentViaIndirectCall(string method, string call, string argAccess) + { + var source = $@"using System; +using NSubstitute; + +namespace MyNamespace +{{ + public interface Foo + {{ + int Bar(Bar x); + + int this[Bar x] {{ get; }} + }} + + public class Bar + {{ + }} + + public class FooTests + {{ + public void Test() + {{ + var sub = NSubstitute.Substitute.For(); + sub.{method}(substitute => {{var _ = {call}; }}).Do(callInfo => + {{ + {argAccess} + }}); + }} + }} +}}"; + await VerifyNoDiagnostic(source); + } + + public override async Task ReportsDiagnostic_WhenAccessingArgumentByTypeNotInInvocation(string method, string call, string argAccess, string message) + { + var source = $@"using System; +using NSubstitute; + +namespace MyNamespace +{{ + public interface Foo + {{ + int Bar(int x); + + int Barr {{ get; }} + + int this[int x] {{ get; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var sub = NSubstitute.Substitute.For(); + sub.{method}(substitute => {{var _ = {call}; }}).Do(callInfo => + {{ + {argAccess} + }}); + }} + }} +}}"; + + await VerifyDiagnostic(source, CallInfoCouldNotFindArgumentToThisCallDescriptor, message); + } + + public override async Task ReportsNoDiagnostic_WhenAccessingArgumentByTypeInInInvocation(string method, string call, string argAccess) + { + var source = $@"using System; +using NSubstitute; + +namespace MyNamespace +{{ + public interface IFoo + {{ + int Bar(int x); + + int Bar(Foo x); + + int this[int x] {{ get; }} + + int this[Foo x] {{ get; }} + }} + + public class FooBase + {{ + }} + + public class Foo : FooBase + {{ + }} + + public class FooTests + {{ + public void Test() + {{ + var sub = NSubstitute.Substitute.For(); + sub.{method}(substitute => {{var _ = {call}; }}).Do(callInfo => + {{ + {argAccess} + }}); + }} + }} +}}"; + + await VerifyNoDiagnostic(source); + } + + public override async Task ReportsDiagnostic_WhenAccessingArgumentByTypeMultipleTimesInInvocation(string method, string call, string argAccess, string message) + { + var source = $@"using NSubstitute; + +namespace MyNamespace +{{ + public interface Foo + {{ + int Bar(int x, int y); + + int this[int x, int y] {{ get; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var sub = NSubstitute.Substitute.For(); + sub.{method}(substitute => {{var _ = {call}; }}).Do(callInfo => + {{ + {argAccess} + }}); + }} + }} +}}"; + + await VerifyDiagnostic(source, CallInfoMoreThanOneArgumentOfTypeDescriptor, message); + } + + public override async Task ReportsNoDiagnostic_WhenAccessingArgumentByTypeMultipleDifferentTypesInInvocation(string method, string call) + { + var source = $@"using NSubstitute; + +namespace MyNamespace +{{ + public interface Foo + {{ + int Bar(int x, double y); + + int this[int x, double y] {{ get; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var sub = NSubstitute.Substitute.For(); + sub.{method}(substitute => {{var _ = {call}; }}).Do(callInfo => + {{ + callInfo.Arg(); + }}); + }} + }} +}}"; + + await VerifyNoDiagnostic(source); + } + + public override async Task ReportsDiagnostic_WhenAssigningValueToNotOutNorRefArgument(string method, string call) + { + var source = $@"using NSubstitute; + +namespace MyNamespace +{{ + public interface Foo + {{ + int Bar(int x, double y); + + int this[int x, double y] {{ get; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var sub = NSubstitute.Substitute.For(); + sub.{method}(substitute => {{var _ = {call}; }}).Do(callInfo => + {{ + [|callInfo[1]|] = 1; + }}); + }} + }} +}}"; + + await VerifyDiagnostic(source, CallInfoArgumentIsNotOutOrRefDescriptor, "Could not set argument 1 (double) as it is not an out or ref argument."); + } + + public override async Task ReportsNoDiagnostic_WhenAssigningValueToRefArgument(string method) + { + var source = $@"using NSubstitute; + +namespace MyNamespace +{{ + public interface Foo + {{ + int Bar(ref int x); + }} + + public class FooTests + {{ + public void Test() + {{ + int value = 0; + var sub = NSubstitute.Substitute.For(); + sub.{method}(substitute => {{var _ = substitute.Bar(ref value); }}).Do(callInfo => + {{ + callInfo[0] = 1; + }}); + }} + }} +}}"; + await VerifyNoDiagnostic(source); + } + + public override async Task ReportsNoDiagnostic_WhenAssigningValueToOutArgument(string method) + { + var source = $@"using NSubstitute; + +namespace MyNamespace +{{ + public interface Foo + {{ + int Bar(out int x); + }} + + public class FooTests + {{ + public void Test() + {{ + int value = 0; + var sub = NSubstitute.Substitute.For(); + sub.{method}(substitute => {{var _ = substitute.Bar(out value); }}).Do(callInfo => + {{ + callInfo[0] = 1; + }}); + }} + }} +}}"; + await VerifyNoDiagnostic(source); + } + + public override async Task ReportsDiagnostic_WhenAssigningValueToOutOfBoundsArgument(string method) + { + var source = $@"using NSubstitute; + +namespace MyNamespace +{{ + public interface Foo + {{ + int Bar(out int x); + }} + + public class FooTests + {{ + public void Test() + {{ + int value = 0; + var sub = NSubstitute.Substitute.For(); + sub.{method}(substitute => {{var _ = substitute.Bar(out value); }}).Do(callInfo => + {{ + [|callInfo[1]|] = 1; + }}); + }} + }} +}}"; + + await VerifyDiagnostic(source, CallInfoArgumentOutOfRangeDescriptor, "There is no argument at position 1"); + } + + public override async Task ReportsDiagnostic_WhenAssigningType_NotAssignableTo_Argument(string method, string left, string right, string expectedMessage) + { + var source = $@"using NSubstitute; +using System.Collections.Generic; + +namespace MyNamespace +{{ + public interface Foo + {{ + int Bar(out {left} x); + }} + + public class FooTests + {{ + public void Test() + {{ + {left} value = default({left}); + var sub = NSubstitute.Substitute.For(); + + sub.{method}(substitute => {{var _ = substitute.Bar(out value); }}).Do(callInfo => + {{ + [|callInfo[0]|] = {right}; + }}); + }} + }} +}}"; + + await VerifyDiagnostic(source, CallInfoArgumentSetWithIncompatibleValueDescriptor, expectedMessage); + } + + public override async Task ReportsNoDiagnostic_WhenAssigningType_AssignableTo_Argument(string method, string left, string right) + { + var source = $@"using NSubstitute; +using System.Collections.Generic; + +namespace MyNamespace +{{ + public interface Foo + {{ + int Bar(out {left} x); + }} + + public class FooTests + {{ + public void Test() + {{ + {left} value = default({left}); + var sub = NSubstitute.Substitute.For(); + + sub.{method}(substitute => {{var _ = substitute.Bar(out value); }}).Do(callInfo => + {{ + callInfo[0] = {right}; + }}); + }} + }} +}}"; + + await VerifyNoDiagnostic(source); + } + } +} \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/CallInfoDoAnalyzerTests/DoMethodPrecededByOrdinaryMethodTests.cs b/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/CallInfoDoAnalyzerTests/DoMethodPrecededByOrdinaryMethodTests.cs new file mode 100644 index 00000000..291dc802 --- /dev/null +++ b/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/CallInfoDoAnalyzerTests/DoMethodPrecededByOrdinaryMethodTests.cs @@ -0,0 +1,664 @@ +using System.Threading.Tasks; +using NSubstitute.Analyzers.Tests.Shared.Extensibility; + +namespace NSubstitute.Analyzers.Tests.CSharp.DiagnosticAnalyzerTests.CallInfoDoAnalyzerTests +{ + [CombinatoryData("SubstituteExtensions.When", "SubstituteExtensions.WhenForAnyArgs")] + public class DoMethodPrecededByOrdinaryMethodTests : CallInfoDoAnalyzerVerifier + { + public override async Task ReportsNoDiagnostics_WhenSubstituteMethodCannotBeInferred(string method, string call, string argAccess) + { + var source = $@"using System; +using NSubstitute; + +namespace MyNamespace +{{ + public interface Foo + {{ + int Bar(int x); + + int Barr {{ get; }} + + int this[int x] {{ get; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var sub = NSubstitute.Substitute.For(); + var returnValue = {method}(sub, substitute => {{var _ = {call}; }}); + returnValue.Do(callInfo => + {{ + {argAccess} + }}); + }} + }} +}}"; + + await VerifyNoDiagnostic(source); + } + + public override async Task ReportsDiagnostic_WhenAccessingArgumentOutOfBounds(string method, string call, string argAccess) + { + var source = $@"using System; +using NSubstitute; + +namespace MyNamespace +{{ + public interface Foo + {{ + int Bar(int x); + + int Barr {{ get; }} + + int this[int x] {{ get; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var sub = NSubstitute.Substitute.For(); + {method}(sub, substitute => {{var _ = {call}; }}).Do(callInfo => + {{ + {argAccess} + }}); + }} + }} +}}"; + await VerifyDiagnostic(source, CallInfoArgumentOutOfRangeDescriptor, "There is no argument at position 1"); + } + + public override async Task ReportsNoDiagnostic_WhenAccessingArgumentOutOfBound_AndPositionIsNotLiteralExpression(string method, string call, string argAccess) + { + var source = $@"using System; +using NSubstitute; + +namespace MyNamespace +{{ + public interface Foo + {{ + int Bar(int x, int y); + + int Barr {{ get; }} + + int this[int x, int y] {{ get; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var sub = NSubstitute.Substitute.For(); + {method}(sub, substitute => {{var _ = {call}; }}).Do(callInfo => + {{ + {argAccess} + }}); + }} + }} +}}"; + await VerifyNoDiagnostic(source); + } + + public override async Task ReportsNoDiagnostic_WhenAccessingArgumentWithinBounds(string method, string call, string argAccess) + { + var source = $@"using System; +using NSubstitute; + +namespace MyNamespace +{{ + public interface Foo + {{ + int Bar(int x, int y); + + int Barr {{ get; }} + + int this[int x, int y] {{ get; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var sub = NSubstitute.Substitute.For(); + {method}(sub, substitute => {{var _ = {call}; }}).Do(callInfo => + {{ + {argAccess} + }}); + }} + }} +}}"; + await VerifyNoDiagnostic(source); + } + + public override async Task ReportsNoDiagnostic_WhenManuallyCasting_ToSupportedType(string method, string call, string argAccess) + { + var source = $@"using System; +using NSubstitute; + +namespace MyNamespace +{{ + public interface Foo + {{ + int Bar(int x, Bar y); + + int this[int x, Bar y] {{ get; }} + }} + + public class BarBase + {{ + }} + + public class Bar : BarBase + {{ + }} + + public class FooTests + {{ + public void Test() + {{ + var sub = NSubstitute.Substitute.For(); + {method}(sub, substitute => {{var _ = {call}; }}).Do(callInfo => + {{ + {argAccess} + }}); + }} + }} +}}"; + + await VerifyNoDiagnostic(source); + } + + public override async Task ReportsDiagnostic_WhenManuallyCasting_ToUnsupportedType(string method, string call, string argAccess) + { + var source = $@"using System; +using NSubstitute; + +namespace MyNamespace +{{ + public interface Foo + {{ + int Bar(int x, double y); + + int Foo(int x, FooBar bar); + + int this[int x, double y] {{ get; }} + + int this[int x, FooBar bar] {{ get; }} + }} + + public class Bar + {{ + }} + + public class FooBar : Bar + {{ + }} + + public class FooTests + {{ + public void Test() + {{ + var sub = NSubstitute.Substitute.For(); + {method}(sub, substitute => {{var _ = {call}; }}).Do(callInfo => + {{ + {argAccess} + }}); + }} + }} +}}"; + + await VerifyDiagnostic(source, CallInfoCouldNotConvertParameterAtPositionDescriptor, "Couldn't convert parameter at position 1 to type MyNamespace.Bar."); + } + + public override async Task ReportsNoDiagnostic_WhenCasting_WithArgAt_ToSupportedType(string method, string call, string argAccess) + { + var source = $@"using System; +using NSubstitute; + +namespace MyNamespace +{{ + public interface Foo + {{ + int Bar(int x, Bar y); + + int this[int x, Bar y] {{ get; }} + }} + + public class BarBase + {{ + }} + + public class Bar : BarBase + {{ + }} + + public class FooTests + {{ + public void Test() + {{ + var sub = NSubstitute.Substitute.For(); + {method}(sub, substitute => {{var _ = {call}; }}).Do(callInfo => + {{ + {argAccess} + }}); + }} + }} +}}"; + + await VerifyNoDiagnostic(source); + } + + public override async Task ReportsDiagnostic_WhenCasting_WithArgAt_ToUnsupportedType(string method, string call, string argAccess, string message) + { + var source = $@"using System; +using NSubstitute; + +namespace MyNamespace +{{ + public interface Foo + {{ + int Bar(int x, double y); + + int Foo(int x, FooBar bar); + + int this[int x, double y] {{ get; }} + + int this[int x, FooBar bar] {{ get; }} + }} + + public class Bar + {{ + }} + + public class FooBar : Bar + {{ + }} + + public class FooTests + {{ + public void Test() + {{ + var sub = NSubstitute.Substitute.For(); + {method}(sub, substitute => {{var _ = {call}; }}).Do(callInfo => + {{ + {argAccess} + }}); + }} + }} +}}"; + + await VerifyDiagnostic(source, CallInfoCouldNotConvertParameterAtPositionDescriptor, message); + } + + public override async Task ReportsNoDiagnostic_WhenCastingElementsFromArgTypes(string method, string call, string argAccess) + { + var source = $@"using System; +using NSubstitute; + +namespace MyNamespace +{{ + public interface Foo + {{ + int Bar(Bar x); + + int this[Bar x] {{ get; }} + }} + + public class Bar + {{ + }} + + public class FooTests + {{ + public void Test() + {{ + var sub = NSubstitute.Substitute.For(); + {method}(sub, substitute => {{var _ = {call}; }}).Do(callInfo => + {{ + {argAccess} + }}); + }} + }} +}}"; + await VerifyNoDiagnostic(source); + } + + public override async Task ReportsNoDiagnostic_WhenAssigningValueToNotRefNorOutArgumentViaIndirectCall(string method, string call, string argAccess) + { + var source = $@"using System; +using NSubstitute; + +namespace MyNamespace +{{ + public interface Foo + {{ + int Bar(Bar x); + + int this[Bar x] {{ get; }} + }} + + public class Bar + {{ + }} + + public class FooTests + {{ + public void Test() + {{ + var sub = NSubstitute.Substitute.For(); + {method}(sub, substitute => {{var _ = {call}; }}).Do(callInfo => + {{ + {argAccess} + }}); + }} + }} +}}"; + await VerifyNoDiagnostic(source); + } + + public override async Task ReportsDiagnostic_WhenAccessingArgumentByTypeNotInInvocation(string method, string call, string argAccess, string message) + { + var source = $@"using System; +using NSubstitute; + +namespace MyNamespace +{{ + public interface Foo + {{ + int Bar(int x); + + int Barr {{ get; }} + + int this[int x] {{ get; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var sub = NSubstitute.Substitute.For(); + {method}(sub, substitute => {{var _ = {call}; }}).Do(callInfo => + {{ + {argAccess} + }}); + }} + }} +}}"; + + await VerifyDiagnostic(source, CallInfoCouldNotFindArgumentToThisCallDescriptor, message); + } + + public override async Task ReportsNoDiagnostic_WhenAccessingArgumentByTypeInInInvocation(string method, string call, string argAccess) + { + var source = $@"using System; +using NSubstitute; + +namespace MyNamespace +{{ + public interface IFoo + {{ + int Bar(int x); + + int Bar(Foo x); + + int this[int x] {{ get; }} + + int this[Foo x] {{ get; }} + }} + + public class FooBase + {{ + }} + + public class Foo : FooBase + {{ + }} + + public class FooTests + {{ + public void Test() + {{ + var sub = NSubstitute.Substitute.For(); + {method}(sub, substitute => {{var _ = {call}; }}).Do(callInfo => + {{ + {argAccess} + }}); + }} + }} +}}"; + + await VerifyNoDiagnostic(source); + } + + public override async Task ReportsDiagnostic_WhenAccessingArgumentByTypeMultipleTimesInInvocation(string method, string call, string argAccess, string message) + { + var source = $@"using NSubstitute; + +namespace MyNamespace +{{ + public interface Foo + {{ + int Bar(int x, int y); + + int this[int x, int y] {{ get; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var sub = NSubstitute.Substitute.For(); + {method}(sub, substitute => {{var _ = {call}; }}).Do(callInfo => + {{ + {argAccess} + }}); + }} + }} +}}"; + + await VerifyDiagnostic(source, CallInfoMoreThanOneArgumentOfTypeDescriptor, message); + } + + public override async Task ReportsNoDiagnostic_WhenAccessingArgumentByTypeMultipleDifferentTypesInInvocation(string method, string call) + { + var source = $@"using NSubstitute; + +namespace MyNamespace +{{ + public interface Foo + {{ + int Bar(int x, double y); + + int this[int x, double y] {{ get; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var sub = NSubstitute.Substitute.For(); + {method}(sub, substitute => {{var _ = {call}; }}).Do(callInfo => + {{ + callInfo.Arg(); + }}); + }} + }} +}}"; + + await VerifyNoDiagnostic(source); + } + + public override async Task ReportsDiagnostic_WhenAssigningValueToNotOutNorRefArgument(string method, string call) + { + var source = $@"using NSubstitute; + +namespace MyNamespace +{{ + public interface Foo + {{ + int Bar(int x, double y); + + int this[int x, double y] {{ get; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var sub = NSubstitute.Substitute.For(); + {method}(sub, substitute => {{var _ = {call}; }}).Do(callInfo => + {{ + [|callInfo[1]|] = 1; + }}); + }} + }} +}}"; + + await VerifyDiagnostic(source, CallInfoArgumentIsNotOutOrRefDescriptor, "Could not set argument 1 (double) as it is not an out or ref argument."); + } + + public override async Task ReportsNoDiagnostic_WhenAssigningValueToRefArgument(string method) + { + var source = $@"using NSubstitute; + +namespace MyNamespace +{{ + public interface Foo + {{ + int Bar(ref int x); + }} + + public class FooTests + {{ + public void Test() + {{ + int value = 0; + var sub = NSubstitute.Substitute.For(); + {method}(sub, substitute => {{var _ = substitute.Bar(ref value); }}).Do(callInfo => + {{ + callInfo[0] = 1; + }}); + }} + }} +}}"; + await VerifyNoDiagnostic(source); + } + + public override async Task ReportsNoDiagnostic_WhenAssigningValueToOutArgument(string method) + { + var source = $@"using NSubstitute; + +namespace MyNamespace +{{ + public interface Foo + {{ + int Bar(out int x); + }} + + public class FooTests + {{ + public void Test() + {{ + int value = 0; + var sub = NSubstitute.Substitute.For(); + {method}(sub, substitute => {{var _ = substitute.Bar(out value); }}).Do(callInfo => + {{ + callInfo[0] = 1; + }}); + }} + }} +}}"; + await VerifyNoDiagnostic(source); + } + + public override async Task ReportsDiagnostic_WhenAssigningValueToOutOfBoundsArgument(string method) + { + var source = $@"using NSubstitute; + +namespace MyNamespace +{{ + public interface Foo + {{ + int Bar(out int x); + }} + + public class FooTests + {{ + public void Test() + {{ + int value = 0; + var sub = NSubstitute.Substitute.For(); + {method}(sub, substitute => {{var _ = substitute.Bar(out value); }}).Do(callInfo => + {{ + [|callInfo[1]|] = 1; + }}); + }} + }} +}}"; + + await VerifyDiagnostic(source, CallInfoArgumentOutOfRangeDescriptor, "There is no argument at position 1"); + } + + public override async Task ReportsDiagnostic_WhenAssigningType_NotAssignableTo_Argument(string method, string left, string right, string expectedMessage) + { + var source = $@"using NSubstitute; +using System.Collections.Generic; + +namespace MyNamespace +{{ + public interface Foo + {{ + int Bar(out {left} x); + }} + + public class FooTests + {{ + public void Test() + {{ + {left} value = default({left}); + var sub = NSubstitute.Substitute.For(); + + {method}(sub, substitute => {{var _ = substitute.Bar(out value); }}).Do(callInfo => + {{ + [|callInfo[0]|] = {right}; + }}); + }} + }} +}}"; + + await VerifyDiagnostic(source, CallInfoArgumentSetWithIncompatibleValueDescriptor, expectedMessage); + } + + public override async Task ReportsNoDiagnostic_WhenAssigningType_AssignableTo_Argument(string method, string left, string right) + { + var source = $@"using NSubstitute; +using System.Collections.Generic; + +namespace MyNamespace +{{ + public interface Foo + {{ + int Bar(out {left} x); + }} + + public class FooTests + {{ + public void Test() + {{ + {left} value = default({left}); + var sub = NSubstitute.Substitute.For(); + + {method}(sub, substitute => {{var _ = substitute.Bar(out value); }}).Do(callInfo => + {{ + callInfo[0] = {right}; + }}); + }} + }} +}}"; + + await VerifyNoDiagnostic(source); + } + } +} \ No newline at end of file