diff --git a/src/NSubstitute.Analyzers.CSharp/DiagnosticAnalyzers/CallInfoAnalyzer.cs b/src/NSubstitute.Analyzers.CSharp/DiagnosticAnalyzers/CallInfoAnalyzer.cs index 50134bf4..c90052e5 100644 --- a/src/NSubstitute.Analyzers.CSharp/DiagnosticAnalyzers/CallInfoAnalyzer.cs +++ b/src/NSubstitute.Analyzers.CSharp/DiagnosticAnalyzers/CallInfoAnalyzer.cs @@ -16,8 +16,7 @@ internal class CallInfoAnalyzer : AbstractCallInfoAnalyzer callHierarchy = ImmutableArray.Create( typeof(MemberAccessExpressionSyntax), - typeof(InvocationExpressionSyntax), - typeof(MemberAccessExpressionSyntax)); + typeof(InvocationExpressionSyntax)); public CallInfoAnalyzer() : base(new DiagnosticDescriptorsProvider()) @@ -26,7 +25,7 @@ public CallInfoAnalyzer() protected override SyntaxKind InvocationExpressionKind { get; } = SyntaxKind.InvocationExpression; - protected override SyntaxNode GetSubstituteCall(IMethodSymbol methodSymbol, InvocationExpressionSyntax invocationExpressionSyntax) + protected override SyntaxNode GetSubstituteCall(SyntaxNodeAnalysisContext syntaxNodeContext, IMethodSymbol methodSymbol, InvocationExpressionSyntax invocationExpressionSyntax) { if (methodSymbol.IsExtensionMethod) { @@ -53,8 +52,16 @@ protected override SyntaxNode GetSubstituteCall(IMethodSymbol methodSymbol, Invo } } - if (hierarchyEnumerator.MoveNext() == false && descendantNodesEnumerator.MoveNext()) + if (hierarchyEnumerator.MoveNext() == false) { + var symbol = syntaxNodeContext.SemanticModel.GetSymbolInfo(descendantNodesEnumerator.Current); + + if (symbol.Symbol is IMethodSymbol mSymbol && mSymbol.ReducedFrom == null) + { + return ((InvocationExpressionSyntax)descendantNodesEnumerator.Current).ArgumentList.Arguments + .First().Expression; + } + return descendantNodesEnumerator.Current; } } diff --git a/src/NSubstitute.Analyzers.Shared/DiagnosticAnalyzers/AbstractCallInfoAnalyzer.cs b/src/NSubstitute.Analyzers.Shared/DiagnosticAnalyzers/AbstractCallInfoAnalyzer.cs index d34f66da..389ed4da 100644 --- a/src/NSubstitute.Analyzers.Shared/DiagnosticAnalyzers/AbstractCallInfoAnalyzer.cs +++ b/src/NSubstitute.Analyzers.Shared/DiagnosticAnalyzers/AbstractCallInfoAnalyzer.cs @@ -48,7 +48,7 @@ public override void Initialize(AnalysisContext context) protected abstract TSyntaxKind InvocationExpressionKind { get; } - protected abstract SyntaxNode GetSubstituteCall(IMethodSymbol methodSymbol, TInvocationExpressionSyntax invocationExpressionSyntax); + protected abstract SyntaxNode GetSubstituteCall(SyntaxNodeAnalysisContext syntaxNodeContext, IMethodSymbol methodSymbol, TInvocationExpressionSyntax invocationExpressionSyntax); protected abstract IEnumerable GetArgumentExpressions(TInvocationExpressionSyntax invocationExpressionSyntax); @@ -288,7 +288,7 @@ private bool SupportsCallInfo(SyntaxNodeAnalysisContext syntaxNodeContext, TInvo private IList GetSubstituteCallParameters(SyntaxNodeAnalysisContext syntaxNodeContext, IMethodSymbol methodSymbol, TInvocationExpressionSyntax invocationExpression) { - var parentMethodCallSyntax = GetSubstituteCall(methodSymbol, invocationExpression); + var parentMethodCallSyntax = GetSubstituteCall(syntaxNodeContext, methodSymbol, invocationExpression); var symbol = syntaxNodeContext.SemanticModel.GetSymbolInfo(parentMethodCallSyntax).Symbol; switch (symbol) diff --git a/src/NSubstitute.Analyzers.VisualBasic/DiagnosticAnalyzers/CallInfoAnalyzer.cs b/src/NSubstitute.Analyzers.VisualBasic/DiagnosticAnalyzers/CallInfoAnalyzer.cs index 279a2028..53baa77c 100644 --- a/src/NSubstitute.Analyzers.VisualBasic/DiagnosticAnalyzers/CallInfoAnalyzer.cs +++ b/src/NSubstitute.Analyzers.VisualBasic/DiagnosticAnalyzers/CallInfoAnalyzer.cs @@ -26,7 +26,7 @@ public CallInfoAnalyzer() protected override SyntaxKind InvocationExpressionKind { get; } = SyntaxKind.InvocationExpression; - protected override SyntaxNode GetSubstituteCall(IMethodSymbol methodSymbol, InvocationExpressionSyntax invocationExpressionSyntax) + protected override SyntaxNode GetSubstituteCall(SyntaxNodeAnalysisContext syntaxNodeContext, IMethodSymbol methodSymbol, InvocationExpressionSyntax invocationExpressionSyntax) { if (methodSymbol.IsExtensionMethod) { diff --git a/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/CallInfoAnalyzerTests/AndDoesMethodTests.cs b/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/CallInfoAnalyzerTests/AndDoesMethodPrecededByExtensionMethodTests.cs similarity index 99% rename from tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/CallInfoAnalyzerTests/AndDoesMethodTests.cs rename to tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/CallInfoAnalyzerTests/AndDoesMethodPrecededByExtensionMethodTests.cs index 9a9a6630..4b6be7f9 100644 --- a/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/CallInfoAnalyzerTests/AndDoesMethodTests.cs +++ b/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/CallInfoAnalyzerTests/AndDoesMethodPrecededByExtensionMethodTests.cs @@ -4,7 +4,7 @@ namespace NSubstitute.Analyzers.Tests.CSharp.DiagnosticAnalyzerTests.CallInfoAnalyzerTests { [CombinatoryData("AndDoes")] - public class AndDoesMethodTests : CallInfoDiagnosticVerifier + public class AndDoesMethodPrecededByExtensionMethodTests : CallInfoDiagnosticVerifier { public override async Task ReportsNoDiagnostics_WhenSubstituteMethodCannotBeInferred(string method, string call, string argAccess) { diff --git a/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/CallInfoAnalyzerTests/AndDoesMethodPrecededByOrdinaryMethodTests.cs b/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/CallInfoAnalyzerTests/AndDoesMethodPrecededByOrdinaryMethodTests.cs new file mode 100644 index 00000000..4ee6eda4 --- /dev/null +++ b/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/CallInfoAnalyzerTests/AndDoesMethodPrecededByOrdinaryMethodTests.cs @@ -0,0 +1,656 @@ +using System.Threading.Tasks; +using NSubstitute.Analyzers.Tests.Shared.Extensibility; + +namespace NSubstitute.Analyzers.Tests.CSharp.DiagnosticAnalyzerTests.CallInfoAnalyzerTests +{ + [CombinatoryData("AndDoes")] + public class AndDoesMethodPrecededByOrdinaryMethodTests : CallInfoDiagnosticVerifier + { + 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 substitute = NSubstitute.Substitute.For(); + var returnedValue = {call}; + SubstituteExtensions.Returns(returnedValue, 1).{method}(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 substitute = NSubstitute.Substitute.For(); + SubstituteExtensions.Returns({call}, 1).{method}(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 substitute = NSubstitute.Substitute.For(); + SubstituteExtensions.Returns({call}, 1).{method}(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 substitute = NSubstitute.Substitute.For(); + SubstituteExtensions.Returns({call}, 1).{method}(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 substitute = NSubstitute.Substitute.For(); + SubstituteExtensions.Returns({call}, 1).{method}(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 substitute = NSubstitute.Substitute.For(); + SubstituteExtensions.Returns({call}, 1).{method}(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 substitute = NSubstitute.Substitute.For(); + SubstituteExtensions.Returns({call}, 1).{method}(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 substitute = NSubstitute.Substitute.For(); + SubstituteExtensions.Returns({call}, 1).{method}(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 substitute = NSubstitute.Substitute.For(); + SubstituteExtensions.Returns({call}, 1).{method}(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 substitute = NSubstitute.Substitute.For(); + SubstituteExtensions.Returns({call}, 1).{method}(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 substitute = NSubstitute.Substitute.For(); + SubstituteExtensions.Returns({call}, 1).{method}(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 substitute = NSubstitute.Substitute.For(); + SubstituteExtensions.Returns({call}, 1).{method}(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 substitute = NSubstitute.Substitute.For(); + SubstituteExtensions.Returns({call}, 1).{method}(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 substitute = NSubstitute.Substitute.For(); + SubstituteExtensions.Returns({call}, 1).{method}(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 substitute = NSubstitute.Substitute.For(); + SubstituteExtensions.Returns({call}, 1).{method}(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 substitute = NSubstitute.Substitute.For(); + SubstituteExtensions.Returns(substitute.Bar(ref value), 1).{method}(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 substitute = NSubstitute.Substitute.For(); + SubstituteExtensions.Returns(substitute.Bar(out value), 1).{method}(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 substitute = NSubstitute.Substitute.For(); + SubstituteExtensions.Returns(substitute.Bar(out value), 1).{method}(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 message) + { + 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 substitute = NSubstitute.Substitute.For(); + SubstituteExtensions.Returns(substitute.Bar(out value), 1).{method}(callInfo => + {{ + [|callInfo[0]|] = {right}; + }}); + }} + }} +}}"; + + await VerifyDiagnostic(source, CallInfoArgumentSetWithIncompatibleValueDescriptor, message); + } + + 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 substitute = NSubstitute.Substitute.For(); + SubstituteExtensions.Returns(substitute.Bar(out value), 1).{method}(callInfo => + {{ + callInfo[0] = {right}; + }}); + }} + }} +}}"; + await VerifyNoDiagnostic(source); + } + } +} \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/CallInfoAnalyzerTests/AndDoesMethodTests.cs b/tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/CallInfoAnalyzerTests/AndDoesMethodPrecededByExtensionMethodTests.cs similarity index 99% rename from tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/CallInfoAnalyzerTests/AndDoesMethodTests.cs rename to tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/CallInfoAnalyzerTests/AndDoesMethodPrecededByExtensionMethodTests.cs index 0e588895..e57d52af 100644 --- a/tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/CallInfoAnalyzerTests/AndDoesMethodTests.cs +++ b/tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/CallInfoAnalyzerTests/AndDoesMethodPrecededByExtensionMethodTests.cs @@ -4,7 +4,7 @@ namespace NSubstitute.Analyzers.Tests.VisualBasic.DiagnosticAnalyzersTests.CallInfoAnalyzerTests { [CombinatoryData("AndDoes")] - public class AndDoesMethodTests : CallInfoDiagnosticVerifier + public class AndDoesMethodPrecededByExtensionMethodTests : CallInfoDiagnosticVerifier { public override async Task ReportsNoDiagnostics_WhenSubstituteMethodCannotBeInferred(string method, string call, string argAccess) { diff --git a/tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/CallInfoAnalyzerTests/AndDoesMethodPrecededByOrdinaryMethodTests.cs b/tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/CallInfoAnalyzerTests/AndDoesMethodPrecededByOrdinaryMethodTests.cs new file mode 100644 index 00000000..6f6cfe8e --- /dev/null +++ b/tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/CallInfoAnalyzerTests/AndDoesMethodPrecededByOrdinaryMethodTests.cs @@ -0,0 +1,546 @@ +using System.Threading.Tasks; +using NSubstitute.Analyzers.Tests.Shared.Extensibility; + +namespace NSubstitute.Analyzers.Tests.VisualBasic.DiagnosticAnalyzersTests.CallInfoAnalyzerTests +{ + [CombinatoryData("AndDoes")] + public class AndDoesMethodPrecededByOrdinaryMethodTests : CallInfoDiagnosticVerifier + { + public override async Task ReportsNoDiagnostics_WhenSubstituteMethodCannotBeInferred(string method, string call, string argAccess) + { + var source = $@"Imports System +Imports NSubstitute + +Namespace MyNamespace + Interface Foo + Function Bar(ByVal x As Integer) As Integer + ReadOnly Property Barr As Integer + Default ReadOnly Property Item(ByVal x As Integer) As Integer + End Interface + + Public Class FooTests + Public Sub Test() + Dim substitute = NSubstitute.Substitute.[For](Of Foo)() + Dim returnedValue = {call} + SubstituteExtensions.Returns(returnedValue, 1).{method}(Function(callInfo) + {argAccess} + End Function) + End Sub + End Class +End Namespace"; + + await VerifyNoDiagnostic(source); + } + + public override async Task ReportsDiagnostic_WhenAccessingArgumentOutOfBounds(string method, string call, string argAccess) + { + var source = $@"Imports System +Imports NSubstitute + +Namespace MyNamespace + Interface Foo + Function Bar(ByVal x As Integer) As Integer + ReadOnly Property Barr As Integer + Default ReadOnly Property Item(ByVal x As Integer) As Integer + End Interface + + Public Class FooTests + Public Sub Test() + Dim substitute = NSubstitute.Substitute.[For](Of Foo)() + SubstituteExtensions.Returns({call}, 1).{method}(Function(callInfo) + {argAccess} + End Function) + End Sub + End Class +End Namespace +"; + 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 = $@"Imports System +Imports NSubstitute + +Namespace MyNamespace + Interface Foo + Function Bar(ByVal x As Integer, ByVal y As Integer) As Integer + ReadOnly Property Barr As Integer + Default ReadOnly Property Item(ByVal x As Integer, ByVal y As Integer) As Integer + End Interface + + Public Class FooTests + Public Sub Test() + Dim substitute = NSubstitute.Substitute.[For](Of Foo)() + SubstituteExtensions.Returns({call}, 1).{method}(Function(callInfo) + {argAccess} + End Function) + End Sub + End Class +End Namespace"; + await VerifyNoDiagnostic(source); + } + + public override async Task ReportsNoDiagnostic_WhenAccessingArgumentWithinBounds(string method, string call, string argAccess) + { + var source = $@"Imports System +Imports NSubstitute + +Namespace MyNamespace + Interface Foo + Function Bar(ByVal x As Integer, ByVal y As Integer) As Integer + ReadOnly Property Barr As Integer + Default ReadOnly Property Item(ByVal x As Integer, ByVal y As Integer) As Integer + End Interface + + Public Class FooTests + Public Sub Test() + Dim substitute = NSubstitute.Substitute.[For](Of Foo)() + SubstituteExtensions.Returns({call}, 1).{method}(Function(callInfo) + {argAccess} + End Function) + End Sub + End Class +End Namespace +"; + await VerifyNoDiagnostic(source); + } + + public override async Task ReportsNoDiagnostic_WhenManuallyCasting_ToSupportedType(string method, string call, string argAccess) + { + var source = $@"Imports System +Imports NSubstitute + +Namespace MyNamespace + Interface Foo + Function Bar(ByVal x As Integer, ByVal y As Bar) As Integer + Default ReadOnly Property Item(ByVal x As Integer, ByVal y As Bar) As Integer + End Interface + + Public Class BarBase + End Class + + Public Class Bar + Inherits BarBase + End Class + + Public Class FooTests + Public Sub Test() + Dim substitute = NSubstitute.Substitute.[For](Of Foo)() + SubstituteExtensions.Returns({call}, 1).{method}(Function(callInfo) + {argAccess} + End Function) + End Sub + End Class +End Namespace"; + + await VerifyNoDiagnostic(source); + } + + public override async Task ReportsDiagnostic_WhenManuallyCasting_ToUnsupportedType(string method, string call, string argAccess) + { + var source = $@"Imports System +Imports NSubstitute + +Namespace MyNamespace + Interface Foo + Function Bar(ByVal x As Integer, ByVal y As Double) As Integer + Function Foo(ByVal x As Integer, ByVal bar As FooBar) As Integer + Default ReadOnly Property Item(ByVal x As Integer, ByVal y As Double) As Integer + Default ReadOnly Property Item(ByVal x As Integer, ByVal bar As FooBar) As Integer + End Interface + + Public Class Bar + End Class + + Public Class FooBar + Inherits Bar + End Class + + Public Class FooTests + Public Sub Test() + Dim substitute = NSubstitute.Substitute.[For](Of Foo)() + SubstituteExtensions.Returns({call}, 1).{method}(Function(callInfo) + {argAccess} + End Function) + End Sub + End Class +End Namespace +"; + + 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 = $@"Imports System +Imports NSubstitute + +Namespace MyNamespace + Interface Foo + Function Bar(ByVal x As Integer, ByVal y As Bar) As Integer + Default ReadOnly Property Item(ByVal x As Integer, ByVal y As Bar) As Integer + End Interface + + Public Class BarBase + End Class + + Public Class Bar + Inherits BarBase + End Class + + Public Class FooTests + Public Sub Test() + Dim substitute = NSubstitute.Substitute.[For](Of Foo)() + SubstituteExtensions.Returns({call}, 1).{method}(Function(callInfo) + {argAccess} + End Function) + End Sub + End Class +End Namespace"; + + await VerifyNoDiagnostic(source); + } + + public override async Task ReportsDiagnostic_WhenCasting_WithArgAt_ToUnsupportedType(string method, string call, string argAccess, string message) + { + var source = $@"Imports System +Imports NSubstitute + +Namespace MyNamespace + Interface Foo + Function Bar(ByVal x As Integer, ByVal y As Double) As Integer + Function Foo(ByVal x As Integer, ByVal bar As FooBar) As Integer + Default ReadOnly Property Item(ByVal x As Integer, ByVal y As Double) As Integer + Default ReadOnly Property Item(ByVal x As Integer, ByVal bar As FooBar) As Integer + End Interface + + Public Class Bar + End Class + + Public Class FooBar + Inherits Bar + End Class + + Public Class FooTests + Public Sub Test() + Dim substitute = NSubstitute.Substitute.[For](Of Foo)() + SubstituteExtensions.Returns({call}, 1).{method}(Function(callInfo) + {argAccess} + End Function) + End Sub + End Class +End Namespace +"; + await VerifyDiagnostic(source, CallInfoCouldNotConvertParameterAtPositionDescriptor, message); + } + + public override async Task ReportsNoDiagnostic_WhenCastingElementsFromArgTypes(string method, string call, string argAccess) + { + var source = $@"Imports System +Imports NSubstitute + +Namespace MyNamespace + Interface Foo + Function Bar(ByVal x As Bar) As Integer + Default ReadOnly Property Item(ByVal x As Bar) As Integer + End Interface + + Public Class Bar + End Class + + Public Class FooTests + Public Sub Test() + Dim substitute = NSubstitute.Substitute.[For](Of Foo)() + SubstituteExtensions.Returns({call}, 1).{method}(Function(callInfo) + {argAccess} + End Function) + End Sub + End Class +End Namespace +"; + await VerifyNoDiagnostic(source); + } + + public override async Task ReportsNoDiagnostic_WhenAssigningValueToNotRefNorOutArgumentViaIndirectCall(string method, string call, string argAccess) + { + var source = $@"Imports System +Imports NSubstitute + +Namespace MyNamespace + Interface Foo + Function Bar(ByVal x As Bar) As Integer + Default ReadOnly Property Item(ByVal x As Bar) As Integer + End Interface + + Public Class Bar + End Class + + Public Class FooTests + Public Sub Test() + Dim substitute = NSubstitute.Substitute.[For](Of Foo)() + SubstituteExtensions.Returns({call}, 1).{method}(Function(callInfo) + {argAccess} + End Function) + End Sub + End Class +End Namespace +"; + await VerifyNoDiagnostic(source); + } + + public override async Task ReportsDiagnostic_WhenAccessingArgumentByTypeNotInInvocation(string method, string call, string argAccess, string message) + { + var source = $@"Imports System +Imports NSubstitute + +Namespace MyNamespace + Interface Foo + Function Bar(ByVal x As Integer) As Integer + ReadOnly Property Barr As Integer + Default ReadOnly Property Item(ByVal x As Integer) As Integer + + End Interface + + Public Class FooTests + Public Sub Test() + Dim substitute = NSubstitute.Substitute.[For](Of Foo)() + SubstituteExtensions.Returns({call}, 1).{method}(Function(callInfo) + {argAccess} + End Function) + End Sub + End Class +End Namespace +"; + + await VerifyDiagnostic(source, CallInfoCouldNotFindArgumentToThisCallDescriptor, message); + } + + public override async Task ReportsNoDiagnostic_WhenAccessingArgumentByTypeInInInvocation(string method, string call, string argAccess) + { + var source = $@"Imports System +Imports NSubstitute + +Namespace MyNamespace + Interface IFoo + Function Bar(ByVal x As Integer) As Integer + Function Bar(ByVal x As Foo) As Integer + Default ReadOnly Property Item(ByVal x As Integer) As Integer + Default ReadOnly Property Item(ByVal x As Foo) As Integer + End Interface + + Public Class FooBase + End Class + + Public Class Foo + Inherits FooBase + End Class + + Public Class FooTests + Public Sub Test() + Dim substitute = NSubstitute.Substitute.[For](Of IFoo)() + SubstituteExtensions.Returns({call}, 1).{method}(Function(callInfo) + {argAccess} + End Function) + End Sub + End Class +End Namespace +"; + + await VerifyNoDiagnostic(source); + } + + public override async Task ReportsDiagnostic_WhenAccessingArgumentByTypeMultipleTimesInInvocation(string method, string call, string argAccess, string message) + { + var source = $@"Imports NSubstitute + +Namespace MyNamespace + Interface Foo + Function Bar(ByVal x As Integer, ByVal y As Integer) As Integer + Default ReadOnly Property Item(ByVal x As Integer, ByVal y As Integer) As Integer + End Interface + + Public Class FooTests + Public Sub Test() + Dim substitute = NSubstitute.Substitute.[For](Of Foo)() + SubstituteExtensions.Returns({call}, 1).{method}(Function(callInfo) + {argAccess} + End Function) + End Sub + End Class +End Namespace +"; + await VerifyDiagnostic(source, CallInfoMoreThanOneArgumentOfTypeDescriptor, message); + } + + public override async Task ReportsNoDiagnostic_WhenAccessingArgumentByTypeMultipleDifferentTypesInInvocation(string method, string call) + { + var source = $@"Imports NSubstitute + +Namespace MyNamespace + Interface Foo + Function Bar(ByVal x As Integer, ByVal y As Double) As Integer + Default ReadOnly Property Item(ByVal x As Integer, ByVal y As Double) As Integer + End Interface + + Public Class FooTests + Public Sub Test() + Dim substitute = NSubstitute.Substitute.[For](Of Foo)() + SubstituteExtensions.Returns({call}, 1).{method}(Function(callInfo) + callInfo.Arg(Of Integer)() + End Function) + End Sub + End Class +End Namespace +"; + + await VerifyNoDiagnostic(source); + } + + public override async Task ReportsDiagnostic_WhenAssigningValueToNotOutNorRefArgument(string method, string call) + { + var source = $@"Imports NSubstitute + +Namespace MyNamespace + Interface Foo + Function Bar(ByVal x As Integer, ByVal y As Double) As Integer + Default ReadOnly Property Item(ByVal x As Integer, ByVal y As Double) As Integer + End Interface + + Public Class FooTests + Public Sub Test() + Dim substitute = NSubstitute.Substitute.[For](Of Foo)() + SubstituteExtensions.Returns({call}, 1).{method}(Function(callInfo) + [|callInfo(1)|] = 1 + End Function) + End Sub + End Class +End Namespace +"; + 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 = $@"Imports NSubstitute + +Namespace MyNamespace + Interface Foo + Function Bar(ByRef x As Integer) As Integer + End Interface + + Public Class FooTests + Public Sub Test() + Dim value As Integer = 0 + Dim substitute = NSubstitute.Substitute.[For](Of Foo)() + SubstituteExtensions.Returns(substitute.Bar(value), 1).{method}(Function(callInfo) + callInfo(0) = 1 + End Function) + End Sub + End Class +End Namespace +"; + await VerifyNoDiagnostic(source); + } + + public override async Task ReportsNoDiagnostic_WhenAssigningValueToOutArgument(string method) + { + var source = $@"Imports NSubstitute +Imports System.Runtime.InteropServices + +Namespace MyNamespace + Interface Foo + Function Bar( ByRef x As Integer) As Integer + End Interface + + Public Class FooTests + Public Sub Test() + Dim value As Integer = 0 + Dim substitute = NSubstitute.Substitute.[For](Of Foo)() + SubstituteExtensions.Returns(substitute.Bar(value), 1).{method}(Function(callInfo) + callInfo(0) = 1 + End Function) + End Sub + End Class +End Namespace +"; + await VerifyNoDiagnostic(source); + } + + public override async Task ReportsDiagnostic_WhenAssigningValueToOutOfBoundsArgument(string method) + { + var source = $@"Imports NSubstitute +Imports System.Runtime.InteropServices + +Namespace MyNamespace + Interface Foo + Function Bar( ByRef x As Integer) As Integer + End Interface + + Public Class FooTests + Public Sub Test() + Dim value As Integer = 0 + Dim substitute = NSubstitute.Substitute.[For](Of Foo)() + SubstituteExtensions.Returns(substitute.Bar(value), 1).{method}(Function(callInfo) + [|callInfo(1)|] = 1 + End Function) + End Sub + End Class +End Namespace +"; + 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 = $@"Imports NSubstitute +Imports System.Runtime.InteropServices +Imports System.Collections.Generic + +Namespace MyNamespace + Interface Foo + Function Bar( ByRef x As {left}) As Integer + End Interface + + Public Class FooTests + Public Sub Test() + Dim value As {left} = Nothing + Dim substitute = NSubstitute.Substitute.[For](Of Foo)() + SubstituteExtensions.Returns(substitute.Bar(value), 1).{method}(Function(callInfo) + [|callInfo(0)|] = {right} + End Function) + End Sub + End Class +End Namespace +"; + + await VerifyDiagnostic(source, CallInfoArgumentSetWithIncompatibleValueDescriptor, expectedMessage); + } + + public override async Task ReportsNoDiagnostic_WhenAssigningType_AssignableTo_Argument(string method, string left, string right) + { + var source = $@"Imports NSubstitute +Imports System.Runtime.InteropServices +Imports System.Collections.Generic + +Namespace MyNamespace + Interface Foo + Function Bar( ByRef x As {left}) As Integer + End Interface + + Public Class FooTests + Public Sub Test() + Dim value As {left} = Nothing + Dim substitute = NSubstitute.Substitute.[For](Of Foo)() + SubstituteExtensions.Returns(substitute.Bar(value), 1).{method}(Function(callInfo) + callInfo(0) = {right} + End Function) + End Sub + End Class +End Namespace +"; + + await VerifyNoDiagnostic(source); + } + } +} \ No newline at end of file