diff --git a/src/NSubstitute.Analyzers.CSharp/DiagnosticAnalyzers/CallInfoAnalyzer.cs b/src/NSubstitute.Analyzers.CSharp/DiagnosticAnalyzers/CallInfoAnalyzer.cs index e72b88a1..e18c3f06 100644 --- a/src/NSubstitute.Analyzers.CSharp/DiagnosticAnalyzers/CallInfoAnalyzer.cs +++ b/src/NSubstitute.Analyzers.CSharp/DiagnosticAnalyzers/CallInfoAnalyzer.cs @@ -4,6 +4,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; +using NSubstitute.Analyzers.CSharp.Extensions; using NSubstitute.Analyzers.Shared.DiagnosticAnalyzers; namespace NSubstitute.Analyzers.CSharp.DiagnosticAnalyzers @@ -18,17 +19,36 @@ 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) { - switch (methodSymbol.MethodKind) + if (methodSymbol.IsExtensionMethod) { - case MethodKind.ReducedExtension: - return invocationExpressionSyntax.Expression.DescendantNodes().First(); - case MethodKind.Ordinary: - return invocationExpressionSyntax.ArgumentList.Arguments.First().Expression; - default: - return null; + switch (methodSymbol.MethodKind) + { + case MethodKind.ReducedExtension: + return invocationExpressionSyntax.Expression.DescendantNodes().First(); + case MethodKind.Ordinary: + return invocationExpressionSyntax.ArgumentList.Arguments.First().Expression; + default: + return null; + } + } + + var parentInvocation = invocationExpressionSyntax.GetParentInvocationExpression(); + + if (parentInvocation == null) + { + return null; } + + var symbol = syntaxNodeContext.SemanticModel.GetSymbolInfo(parentInvocation); + + if (symbol.Symbol is IMethodSymbol mSymbol && mSymbol.ReducedFrom == null) + { + return parentInvocation.ArgumentList.Arguments.First().Expression; + } + + return parentInvocation.Expression.DescendantNodes().First(); } protected override IEnumerable GetArgumentExpressions(InvocationExpressionSyntax invocationExpressionSyntax) diff --git a/src/NSubstitute.Analyzers.CSharp/DiagnosticAnalyzers/CallInfoCallFinder.cs b/src/NSubstitute.Analyzers.CSharp/DiagnosticAnalyzers/CallInfoCallFinder.cs index 4cda5fbd..a27436c1 100644 --- a/src/NSubstitute.Analyzers.CSharp/DiagnosticAnalyzers/CallInfoCallFinder.cs +++ b/src/NSubstitute.Analyzers.CSharp/DiagnosticAnalyzers/CallInfoCallFinder.cs @@ -39,7 +39,7 @@ public override void VisitInvocationExpression(InvocationExpressionSyntax node) { var symbolInfo = _semanticModel.GetSymbolInfo(node); - if (symbolInfo.Symbol != null && symbolInfo.Symbol.ContainingType.ToString().Equals(MetadataNames.NSubstituteCoreFullTypeName)) + if (symbolInfo.Symbol != null && symbolInfo.Symbol.ContainingType.ToString().Equals(MetadataNames.NSubstituteCallInfoFullTypeName)) { switch (symbolInfo.Symbol.Name) { @@ -58,7 +58,7 @@ public override void VisitInvocationExpression(InvocationExpressionSyntax node) public override void VisitElementAccessExpression(ElementAccessExpressionSyntax node) { var symbolInfo = ModelExtensions.GetSymbolInfo(_semanticModel, node).Symbol ?? ModelExtensions.GetSymbolInfo(_semanticModel, node.Expression).Symbol; - if (symbolInfo != null && symbolInfo.ContainingType.ToString().Equals(MetadataNames.NSubstituteCoreFullTypeName)) + if (symbolInfo != null && symbolInfo.ContainingType.ToString().Equals(MetadataNames.NSubstituteCallInfoFullTypeName)) { DirectIndexerAccesses.Add(node); } diff --git a/src/NSubstitute.Analyzers.CSharp/Extensions/SyntaxExtensions.cs b/src/NSubstitute.Analyzers.CSharp/Extensions/SyntaxExtensions.cs new file mode 100644 index 00000000..5a9f392f --- /dev/null +++ b/src/NSubstitute.Analyzers.CSharp/Extensions/SyntaxExtensions.cs @@ -0,0 +1,21 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using NSubstitute.Analyzers.Shared.Extensions; + +namespace NSubstitute.Analyzers.CSharp.Extensions +{ + internal static class SyntaxExtensions + { + private static readonly int[] ParentInvocationKindHierarchy = + { + (int)SyntaxKind.SimpleMemberAccessExpression, + (int)SyntaxKind.InvocationExpression + }; + + public static InvocationExpressionSyntax GetParentInvocationExpression(this SyntaxNode node) + { + return node.GetParentNode(ParentInvocationKindHierarchy) as 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 a640608f..2c7db134 100644 --- a/src/NSubstitute.Analyzers.Shared/DiagnosticAnalyzers/AbstractCallInfoAnalyzer.cs +++ b/src/NSubstitute.Analyzers.Shared/DiagnosticAnalyzers/AbstractCallInfoAnalyzer.cs @@ -29,7 +29,8 @@ protected AbstractCallInfoAnalyzer(IDiagnosticDescriptorsProvider diagnosticDesc [MetadataNames.NSubstituteReturnsMethod] = MetadataNames.NSubstituteSubstituteExtensionsFullTypeName, [MetadataNames.NSubstituteReturnsForAnyArgsMethod] = MetadataNames.NSubstituteSubstituteExtensionsFullTypeName, [MetadataNames.NSubstituteThrowsMethod] = MetadataNames.NSubstituteExceptionExtensionsFullTypeName, - [MetadataNames.NSubstituteThrowsForAnyArgsMethod] = MetadataNames.NSubstituteExceptionExtensionsFullTypeName + [MetadataNames.NSubstituteThrowsForAnyArgsMethod] = MetadataNames.NSubstituteExceptionExtensionsFullTypeName, + [MetadataNames.NSubstituteAndDoesMethod] = MetadataNames.NSubstituteConfiguredCallFullTypeName }.ToImmutableDictionary(); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create( @@ -47,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); @@ -265,16 +266,20 @@ private bool AnalyzeAssignment(SyntaxNodeAnalysisContext syntaxNodeContext, ILis private bool SupportsCallInfo(SyntaxNodeAnalysisContext syntaxNodeContext, TInvocationExpressionSyntax syntax, IMethodSymbol methodSymbol) { - var allArguments = GetArgumentExpressions(syntax); - var argumentsForAnalysis = methodSymbol.MethodKind == MethodKind.ReducedExtension - ? allArguments - : allArguments.Skip(1); - 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 = @@ -286,7 +291,13 @@ 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); + + if (parentMethodCallSyntax == null) + { + return null; + } + var symbol = syntaxNodeContext.SemanticModel.GetSymbolInfo(parentMethodCallSyntax).Symbol; switch (symbol) diff --git a/src/NSubstitute.Analyzers.Shared/Extensions/SyntaxNodeExtensions.cs b/src/NSubstitute.Analyzers.Shared/Extensions/SyntaxNodeExtensions.cs new file mode 100644 index 00000000..2571b312 --- /dev/null +++ b/src/NSubstitute.Analyzers.Shared/Extensions/SyntaxNodeExtensions.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using Microsoft.CodeAnalysis; + +namespace NSubstitute.Analyzers.Shared.Extensions +{ + internal static class SyntaxNodeExtensions + { + public static SyntaxNode GetParentNode(this SyntaxNode syntaxNode, IEnumerable parentNodeHierarchyKinds) + { + using (var descendantNodesEnumerator = syntaxNode.DescendantNodes().GetEnumerator()) + { + using (var hierarchyKindEnumerator = parentNodeHierarchyKinds.GetEnumerator()) + { + while (hierarchyKindEnumerator.MoveNext() && descendantNodesEnumerator.MoveNext()) + { + if (descendantNodesEnumerator.Current.RawKind != hierarchyKindEnumerator.Current) + { + return null; + } + } + + if (hierarchyKindEnumerator.MoveNext() == false) + { + return descendantNodesEnumerator.Current; + } + } + } + + return null; + } + } +} \ No newline at end of file diff --git a/src/NSubstitute.Analyzers.Shared/Extensions/TypeInfoExtensions.cs b/src/NSubstitute.Analyzers.Shared/Extensions/TypeInfoExtensions.cs index d0bd943c..f7b90b1c 100644 --- a/src/NSubstitute.Analyzers.Shared/Extensions/TypeInfoExtensions.cs +++ b/src/NSubstitute.Analyzers.Shared/Extensions/TypeInfoExtensions.cs @@ -12,7 +12,8 @@ public static bool IsCallInfoDelegate(this TypeInfo typeInfo, SemanticModel sema var isCalledViaDelegate = typeSymbol != null && typeSymbol.TypeKind == TypeKind.Delegate && typeSymbol is INamedTypeSymbol namedTypeSymbol && - namedTypeSymbol.ConstructedFrom.Equals(semanticModel.Compilation.GetTypeByMetadataName("System.Func`2")) && + (namedTypeSymbol.ConstructedFrom.Equals(semanticModel.Compilation.GetTypeByMetadataName("System.Func`2")) || + namedTypeSymbol.ConstructedFrom.Equals(semanticModel.Compilation.GetTypeByMetadataName("System.Action`1"))) && IsCallInfoParameter(namedTypeSymbol.TypeArguments.First()); return isCalledViaDelegate; @@ -21,7 +22,7 @@ typeSymbol is INamedTypeSymbol namedTypeSymbol && private static bool IsCallInfoParameter(ITypeSymbol symbol) { return symbol.ContainingAssembly?.Name.Equals(MetadataNames.NSubstituteAssemblyName, StringComparison.OrdinalIgnoreCase) == true && - symbol.ToString().Equals(MetadataNames.NSubstituteCoreFullTypeName, StringComparison.OrdinalIgnoreCase) == true; + symbol.ToString().Equals(MetadataNames.NSubstituteCallInfoFullTypeName, StringComparison.OrdinalIgnoreCase) == true; } } } \ No newline at end of file diff --git a/src/NSubstitute.Analyzers.Shared/MetadataNames.cs b/src/NSubstitute.Analyzers.Shared/MetadataNames.cs index 6b0949b9..064cf126 100644 --- a/src/NSubstitute.Analyzers.Shared/MetadataNames.cs +++ b/src/NSubstitute.Analyzers.Shared/MetadataNames.cs @@ -6,13 +6,15 @@ internal class MetadataNames public const string NSubstituteSubstituteExtensionsFullTypeName = "NSubstitute.SubstituteExtensions"; public const string NSubstituteReturnsExtensionsFullTypeName = "NSubstitute.ReturnsExtensions.ReturnsExtensions"; public const string NSubstituteExceptionExtensionsFullTypeName = "NSubstitute.ExceptionExtensions.ExceptionExtensions"; - public const string NSubstituteCoreFullTypeName = "NSubstitute.Core.CallInfo"; + public const string NSubstituteCallInfoFullTypeName = "NSubstitute.Core.CallInfo"; + public const string NSubstituteConfiguredCallFullTypeName = "NSubstitute.Core.ConfiguredCall"; public const string NSubstituteSubstituteFullTypeName = "NSubstitute.Substitute"; public const string NSubstituteFactoryFullTypeName = "NSubstitute.Core.ISubstituteFactory"; public const string NSubstituteReturnsMethod = "Returns"; public const string NSubstituteReturnsForAnyArgsMethod = "ReturnsForAnyArgs"; public const string NSubstituteThrowsMethod = "Throws"; public const string NSubstituteThrowsForAnyArgsMethod = "ThrowsForAnyArgs"; + public const string NSubstituteAndDoesMethod = "AndDoes"; public const string NSubstituteReturnsNullMethod = "ReturnsNull"; public const string NSubstituteReturnsNullForAnyArgsMethod = "ReturnsNullForAnyArgs"; public const string NSubstituteDoMethod = "Do"; diff --git a/src/NSubstitute.Analyzers.VisualBasic/DiagnosticAnalyzers/CallInfoAnalyzer.cs b/src/NSubstitute.Analyzers.VisualBasic/DiagnosticAnalyzers/CallInfoAnalyzer.cs index e9a9c135..6046999a 100644 --- a/src/NSubstitute.Analyzers.VisualBasic/DiagnosticAnalyzers/CallInfoAnalyzer.cs +++ b/src/NSubstitute.Analyzers.VisualBasic/DiagnosticAnalyzers/CallInfoAnalyzer.cs @@ -5,6 +5,7 @@ using Microsoft.CodeAnalysis.VisualBasic; using Microsoft.CodeAnalysis.VisualBasic.Syntax; using NSubstitute.Analyzers.Shared.DiagnosticAnalyzers; +using NSubstitute.Analyzers.VisualBasic.Extensions; namespace NSubstitute.Analyzers.VisualBasic.DiagnosticAnalyzers { @@ -18,17 +19,36 @@ 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) { - switch (methodSymbol.MethodKind) + if (methodSymbol.IsExtensionMethod) { - case MethodKind.ReducedExtension: - return invocationExpressionSyntax.Expression.DescendantNodes().First(); - case MethodKind.Ordinary: - return invocationExpressionSyntax.ArgumentList.Arguments.First().GetExpression(); - default: - return null; + switch (methodSymbol.MethodKind) + { + case MethodKind.ReducedExtension: + return invocationExpressionSyntax.Expression.DescendantNodes().First(); + case MethodKind.Ordinary: + return invocationExpressionSyntax.ArgumentList.Arguments.First().GetExpression(); + default: + return null; + } } + + var parentInvocation = invocationExpressionSyntax.GetParentInvocationExpression(); + + if (parentInvocation == null) + { + return null; + } + + var symbol = syntaxNodeContext.SemanticModel.GetSymbolInfo(parentInvocation); + + if (symbol.Symbol is IMethodSymbol mSymbol && mSymbol.ReducedFrom == null) + { + return parentInvocation.ArgumentList.Arguments.First().GetExpression(); + } + + return parentInvocation.Expression.DescendantNodes().First(); } protected override IEnumerable GetArgumentExpressions(InvocationExpressionSyntax invocationExpressionSyntax) diff --git a/src/NSubstitute.Analyzers.VisualBasic/DiagnosticAnalyzers/CallInfoCallFinder.cs b/src/NSubstitute.Analyzers.VisualBasic/DiagnosticAnalyzers/CallInfoCallFinder.cs index 0ebda4d8..a9f6841f 100644 --- a/src/NSubstitute.Analyzers.VisualBasic/DiagnosticAnalyzers/CallInfoCallFinder.cs +++ b/src/NSubstitute.Analyzers.VisualBasic/DiagnosticAnalyzers/CallInfoCallFinder.cs @@ -39,7 +39,7 @@ public override void VisitInvocationExpression(InvocationExpressionSyntax node) { var symbol = _semanticModel.GetSymbolInfo(node).Symbol; - if (symbol != null && symbol.ContainingType.ToString().Equals(MetadataNames.NSubstituteCoreFullTypeName)) + if (symbol != null && symbol.ContainingType.ToString().Equals(MetadataNames.NSubstituteCallInfoFullTypeName)) { switch (symbol.Name) { @@ -59,7 +59,7 @@ public override void VisitInvocationExpression(InvocationExpressionSyntax node) { var expressionSymbol = _semanticModel.GetSymbolInfo(node.Expression).Symbol; - if (expressionSymbol != null && expressionSymbol.ContainingType.ToString().Equals(MetadataNames.NSubstituteCoreFullTypeName)) + if (expressionSymbol != null && expressionSymbol.ContainingType.ToString().Equals(MetadataNames.NSubstituteCallInfoFullTypeName)) { DirectIndexerAccesses.Add(node); } diff --git a/src/NSubstitute.Analyzers.VisualBasic/Extensions/SyntaxExtensions.cs b/src/NSubstitute.Analyzers.VisualBasic/Extensions/SyntaxExtensions.cs new file mode 100644 index 00000000..99f5cd72 --- /dev/null +++ b/src/NSubstitute.Analyzers.VisualBasic/Extensions/SyntaxExtensions.cs @@ -0,0 +1,21 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.VisualBasic; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; +using NSubstitute.Analyzers.Shared.Extensions; + +namespace NSubstitute.Analyzers.VisualBasic.Extensions +{ + internal static class SyntaxExtensions + { + private static readonly int[] ParentInvocationKindHierarchy = + { + (int)SyntaxKind.SimpleMemberAccessExpression, + (int)SyntaxKind.InvocationExpression + }; + + public static InvocationExpressionSyntax GetParentInvocationExpression(this SyntaxNode node) + { + return node.GetParentNode(ParentInvocationKindHierarchy) as InvocationExpressionSyntax; + } + } +} \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/CallInfoAnalyzerTests/AndDoesMethodPrecededByExtensionMethodTests.cs b/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/CallInfoAnalyzerTests/AndDoesMethodPrecededByExtensionMethodTests.cs new file mode 100644 index 00000000..6c7e173f --- /dev/null +++ b/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/CallInfoAnalyzerTests/AndDoesMethodPrecededByExtensionMethodTests.cs @@ -0,0 +1,663 @@ +using System.Threading.Tasks; +using NSubstitute.Analyzers.Tests.Shared.Extensibility; + +namespace NSubstitute.Analyzers.Tests.CSharp.DiagnosticAnalyzerTests.CallInfoAnalyzerTests +{ + [CombinatoryData("AndDoes")] + public class AndDoesMethodPrecededByExtensionMethodTests : 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}.Returns(1); + returnedValue.{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(); + {call}.Returns(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(); + {call}.Returns(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(); + {call}.Returns(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(); + {call}.Returns(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(); + {call}.Returns(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(); + {call}.Returns(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(); + {call}.Returns(1).{method}(callInfo => + {{ + {argAccess} + }}); + }} + }} +}}"; + + await VerifyDiagnostic(source, CallInfoCouldNotConvertParameterAtPositionDescriptor, message); + } + + public override async Task ReportsNoDiagnostic_WhenCastingElementsFromArgTypes(string method, string callInfo, 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(); + {callInfo}.Returns(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(); + {call}.Returns(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(); + {call}.Returns(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(); + {call}.Returns(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(); + {call}.Returns(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(); + {call}.Returns(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(); + {call}.Returns(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(); + substitute.Bar(ref value).Returns(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(); + substitute.Bar(out value).Returns(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(); + substitute.Bar(out value).Returns(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 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 substitute = NSubstitute.Substitute.For(); + substitute.Bar(out value).Returns(1).{method}(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 substitute = NSubstitute.Substitute.For(); + substitute.Bar(out value).Returns(1).{method}(callInfo => + {{ + callInfo[0] = {right}; + }}); + }} + }} +}}"; + + await VerifyNoDiagnostic(source); + } + } +} \ No newline at end of file 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..8ff900d6 --- /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 = SubstituteExtensions.Returns({call}, 1); + returnedValue.{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.Shared/DiagnosticAnalyzers/DiagnosticVerifier.cs b/tests/NSubstitute.Analyzers.Tests.Shared/DiagnosticAnalyzers/DiagnosticVerifier.cs index 15553c46..16fd56e3 100644 --- a/tests/NSubstitute.Analyzers.Tests.Shared/DiagnosticAnalyzers/DiagnosticVerifier.cs +++ b/tests/NSubstitute.Analyzers.Tests.Shared/DiagnosticAnalyzers/DiagnosticVerifier.cs @@ -211,7 +211,7 @@ protected Diagnostic CreateDiagnostic(DiagnosticDescriptor descriptor, TextSpan private static void VerifyDiagnosticResults(Diagnostic[] actualResults, DiagnosticAnalyzer analyzer, params Diagnostic[] expectedResults) { - actualResults.Should().HaveSameCount(expectedResults, "because diagnostic count should match. Diagnostics:", FormatDiagnostics(actualResults)); + actualResults.Should().HaveSameCount(expectedResults, "because diagnostic count should match. {0}", FormatDiagnostics(actualResults)); for (var i = 0; i < expectedResults.Length; i++) { var actual = actualResults.ElementAt(i); diff --git a/tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/CallInfoAnalyzerTests/AndDoesMethodPrecededByExtensionMethodTests.cs b/tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/CallInfoAnalyzerTests/AndDoesMethodPrecededByExtensionMethodTests.cs new file mode 100644 index 00000000..deef41e1 --- /dev/null +++ b/tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/CallInfoAnalyzerTests/AndDoesMethodPrecededByExtensionMethodTests.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 AndDoesMethodPrecededByExtensionMethodTests : 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}.Returns(1) + returnedValue.{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)() + {call}.Returns(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)() + {call}.Returns(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)() + {call}.Returns(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)() + {call}.Returns(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)() + {call}.Returns(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)() + {call}.Returns(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)() + {call}.Returns(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)() + {call}.Returns(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)() + {call}.Returns(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)() + {call}.Returns(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)() + {call}.Returns(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)() + {call}.Returns(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)() + {call}.Returns(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)() + {call}.Returns(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)() + substitute.Bar(value).Returns(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)() + substitute.Bar(value).Returns(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)() + substitute.Bar(value).Returns(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)() + substitute.Bar(value).Returns(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)() + substitute.Bar(value).Returns(1).{method}(Function(callInfo) + callInfo(0) = {right} + End Function) + End Sub + End Class +End Namespace +"; + + await VerifyNoDiagnostic(source); + } + } +} \ No newline at end of file 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..88e44f7a --- /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 = SubstituteExtensions.Returns({call}, 1) + returnedValue.{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 diff --git a/tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/VisualBasicDiagnosticVerifier.cs b/tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/VisualBasicDiagnosticVerifier.cs index d3ee6cd0..63ac5b7b 100644 --- a/tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/VisualBasicDiagnosticVerifier.cs +++ b/tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/VisualBasicDiagnosticVerifier.cs @@ -20,7 +20,7 @@ public abstract class VisualBasicDiagnosticVerifier : DiagnosticVerifier protected override CompilationOptions GetCompilationOptions() { - return new VisualBasicCompilationOptions(OutputKind.DynamicallyLinkedLibrary, optionStrict: OptionStrict.On); + return new VisualBasicCompilationOptions(OutputKind.DynamicallyLinkedLibrary, optionStrict: OptionStrict.Off); } protected override IEnumerable GetAdditionalMetadataReferences()