diff --git a/benchmarks/NSubstitute.Analyzers.Benchmarks.Source.CSharp/DiagnosticsSources/ArgumentMatcherDiagnosticsSource.cs b/benchmarks/NSubstitute.Analyzers.Benchmarks.Source.CSharp/DiagnosticsSources/ArgumentMatcherDiagnosticsSource.cs index 81521d66..322f3cb8 100644 --- a/benchmarks/NSubstitute.Analyzers.Benchmarks.Source.CSharp/DiagnosticsSources/ArgumentMatcherDiagnosticsSource.cs +++ b/benchmarks/NSubstitute.Analyzers.Benchmarks.Source.CSharp/DiagnosticsSources/ArgumentMatcherDiagnosticsSource.cs @@ -7,7 +7,6 @@ public class ArgumentMatcherDiagnosticsSource public void NS5001_ArgumentMatcherUsedWithoutSpecifyingCall() { var substitute = Substitute.For(); - substitute.ObjectReturningMethodWithArguments(Arg.Any(), Arg.Compat.Any(), Arg.Is(1m)); substitute.ObjectReturningMethodWithArguments(Arg.Any(), Arg.Compat.Any(), Arg.Is(1m)); substitute.InternalObjectReturningMethodWithArguments(Arg.Is(1)); @@ -18,4 +17,4 @@ public void NS5001_ArgumentMatcherUsedWithoutSpecifyingCall() _ = substitute[Arg.Compat.Any()]; } } -} \ No newline at end of file +} diff --git a/src/NSubstitute.Analyzers.Shared/DiagnosticAnalyzers/AbstractArgumentMatcherAnalyzer.cs b/src/NSubstitute.Analyzers.Shared/DiagnosticAnalyzers/AbstractArgumentMatcherAnalyzer.cs index 049e22d7..f9a1d758 100644 --- a/src/NSubstitute.Analyzers.Shared/DiagnosticAnalyzers/AbstractArgumentMatcherAnalyzer.cs +++ b/src/NSubstitute.Analyzers.Shared/DiagnosticAnalyzers/AbstractArgumentMatcherAnalyzer.cs @@ -74,16 +74,22 @@ private void AnalyzeArgLikeMethod(SyntaxNodeAnalysisContext syntaxNodeContext, T return; } - var symbol = syntaxNodeContext.SemanticModel.GetSymbolInfo(enclosingExpression).Symbol; - var canBeSetuped = symbol.CanBeSetuped(); + var enclosingExpressionSymbol = syntaxNodeContext.SemanticModel.GetSymbolInfo(enclosingExpression).Symbol; - if (canBeSetuped == false || symbol.MemberVisibleToProxyGenerator() == false) + if (enclosingExpressionSymbol == null) + { + return; + } + + var canBeSetuped = enclosingExpressionSymbol.CanBeSetuped(); + + if (canBeSetuped == false || enclosingExpressionSymbol.MemberVisibleToProxyGenerator() == false) { var diagnostic = Diagnostic.Create( DiagnosticDescriptorsProvider.ArgumentMatcherUsedWithoutSpecifyingCall, argInvocationExpression.GetLocation()); - TryReportDiagnostic(syntaxNodeContext, diagnostic, symbol); + TryReportDiagnostic(syntaxNodeContext, diagnostic, enclosingExpressionSymbol); } } diff --git a/src/NSubstitute.Analyzers.Shared/Extensions/SubstituteSymbolExtensions.cs b/src/NSubstitute.Analyzers.Shared/Extensions/SubstituteSymbolExtensions.cs index 1f0e91e6..7e3da104 100644 --- a/src/NSubstitute.Analyzers.Shared/Extensions/SubstituteSymbolExtensions.cs +++ b/src/NSubstitute.Analyzers.Shared/Extensions/SubstituteSymbolExtensions.cs @@ -57,16 +57,6 @@ public static bool IsArgMatcherLikeMethod(this ISymbol symbol) return IsMember(symbol, MetadataNames.ArgMatchersMethodNames) || IsMember(symbol, MetadataNames.ArgMatchersCompatMethodNames); } - public static bool IsArgInvokerLikeMethod(this ISymbol symbol) - { - return IsMember(symbol, MetadataNames.ArgInvokersMethodNames) || IsMember(symbol, MetadataNames.ArgInvokersCompatMethodNames); - } - - public static bool IsReceivedInOrderMethod(this ISymbol symbol) - { - return IsMember(symbol, MetadataNames.NSubstituteInOrderMethod, MetadataNames.NSubstituteReceivedFullTypeName); - } - public static bool IsSubstituteCreateLikeMethod(this ISymbol symbol) { return IsMember(symbol, MetadataNames.CreateSubstituteMethodNames); diff --git a/src/NSubstitute.Analyzers.Shared/MetadataNames.cs b/src/NSubstitute.Analyzers.Shared/MetadataNames.cs index 67a70279..23c62a86 100644 --- a/src/NSubstitute.Analyzers.Shared/MetadataNames.cs +++ b/src/NSubstitute.Analyzers.Shared/MetadataNames.cs @@ -8,7 +8,6 @@ internal class MetadataNames public const string NSubstituteArgFullTypeName = "NSubstitute.Arg"; public const string NSubstituteArgCompatFullTypeName = "NSubstitute.Arg.Compat"; public const string NSubstituteSubstituteExtensionsFullTypeName = "NSubstitute.SubstituteExtensions"; - public const string NSubstituteReceivedFullTypeName = "NSubstitute.Received"; public const string NSubstituteReturnsExtensionsFullTypeName = "NSubstitute.ReturnsExtensions.ReturnsExtensions"; public const string NSubstituteExceptionExtensionsFullTypeName = "NSubstitute.ExceptionExtensions.ExceptionExtensions"; public const string NSubstituteCallInfoFullTypeName = "NSubstitute.Core.CallInfo"; @@ -27,7 +26,6 @@ internal class MetadataNames public const string NSubstituteReceivedWithAnyArgsMethod = "ReceivedWithAnyArgs"; public const string NSubstituteDidNotReceiveMethod = "DidNotReceive"; public const string NSubstituteDidNotReceiveWithAnyArgsMethod = "DidNotReceiveWithAnyArgs"; - public const string NSubstituteInOrderMethod = "InOrder"; public const string NSubstituteForMethod = "For"; public const string NSubstituteForPartsOfMethod = "ForPartsOf"; public const string SubstituteFactoryCreate = "Create"; @@ -39,7 +37,6 @@ internal class MetadataNames public const string NSubstituteWhenCalledType = "WhenCalled"; public const string CallInfoArgAtMethod = "ArgAt"; public const string CallInfoArgMethod = "Arg"; - public const string CallInfoArgsMethod = "Args"; public const string CallInfoArgTypesMethod = "ArgTypes"; public const string ArgIsMethodName = "Is"; public const string ArgAnyMethodName = "Any"; @@ -78,26 +75,18 @@ internal class MetadataNames public static readonly IReadOnlyDictionary ArgMatchersMethodNames = new Dictionary { [ArgIsMethodName] = NSubstituteArgFullTypeName, - [ArgAnyMethodName] = NSubstituteArgFullTypeName - }; - - public static readonly IReadOnlyDictionary ArgMatchersCompatMethodNames = new Dictionary - { - [ArgIsMethodName] = NSubstituteArgCompatFullTypeName, - [ArgAnyMethodName] = NSubstituteArgCompatFullTypeName - }; - - public static readonly IReadOnlyDictionary ArgInvokersMethodNames = new Dictionary - { - [ArgInvokeMethodName] = NSubstituteArgFullTypeName, + [ArgAnyMethodName] = NSubstituteArgFullTypeName, [ArgDoMethodName] = NSubstituteArgFullTypeName, + [ArgInvokeMethodName] = NSubstituteArgFullTypeName, [ArgInvokeDelegateMethodName] = NSubstituteArgFullTypeName }; - public static readonly IReadOnlyDictionary ArgInvokersCompatMethodNames = new Dictionary + public static readonly IReadOnlyDictionary ArgMatchersCompatMethodNames = new Dictionary { - [ArgInvokeMethodName] = NSubstituteArgCompatFullTypeName, + [ArgIsMethodName] = NSubstituteArgCompatFullTypeName, + [ArgAnyMethodName] = NSubstituteArgCompatFullTypeName, [ArgDoMethodName] = NSubstituteArgCompatFullTypeName, + [ArgInvokeMethodName] = NSubstituteArgCompatFullTypeName, [ArgInvokeDelegateMethodName] = NSubstituteArgCompatFullTypeName }; diff --git a/tests/NSubstitute.Analyzers.Tests.Benchmarks/NSubstitute.Analyzers.Tests.Benchmarks.csproj b/tests/NSubstitute.Analyzers.Tests.Benchmarks/NSubstitute.Analyzers.Tests.Benchmarks.csproj index 34ce5fb7..e1283ef6 100644 --- a/tests/NSubstitute.Analyzers.Tests.Benchmarks/NSubstitute.Analyzers.Tests.Benchmarks.csproj +++ b/tests/NSubstitute.Analyzers.Tests.Benchmarks/NSubstitute.Analyzers.Tests.Benchmarks.csproj @@ -9,7 +9,7 @@ - + diff --git a/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/ArgumentMatcherAnalyzerTests/ArgumentMatcherDiagnosticVerifier.cs b/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/ArgumentMatcherAnalyzerTests/ArgumentMatcherDiagnosticVerifier.cs index faf6a52e..d1f467b1 100644 --- a/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/ArgumentMatcherAnalyzerTests/ArgumentMatcherDiagnosticVerifier.cs +++ b/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/ArgumentMatcherAnalyzerTests/ArgumentMatcherDiagnosticVerifier.cs @@ -5,6 +5,8 @@ using NSubstitute.Analyzers.CSharp; using NSubstitute.Analyzers.CSharp.DiagnosticAnalyzers; using NSubstitute.Analyzers.Shared; +using NSubstitute.Analyzers.Shared.Settings; +using NSubstitute.Analyzers.Shared.TinyJson; using NSubstitute.Analyzers.Tests.Shared.DiagnosticAnalyzers; using Xunit; @@ -12,6 +14,8 @@ namespace NSubstitute.Analyzers.Tests.CSharp.DiagnosticAnalyzerTests.ArgumentMat { public abstract class ArgumentMatcherDiagnosticVerifier : CSharpDiagnosticVerifier, IArgumentMatcherDiagnosticVerifier { + internal AnalyzersSettings Settings { get; set; } + protected DiagnosticDescriptor ArgumentMatcherUsedWithoutSpecifyingCall { get; } = DiagnosticDescriptors.ArgumentMatcherUsedWithoutSpecifyingCall; [Theory] @@ -94,6 +98,10 @@ public abstract class ArgumentMatcherDiagnosticVerifier : CSharpDiagnosticVerifi [MemberData(nameof(CorrectlyUsedArgTestCases))] public abstract Task ReportsNoDiagnostics_WhenUsedInProtectedInternalVirtualMember(string arg); + [Theory] + [MemberData(nameof(CorrectlyUsedArgTestCasesWithoutCasts))] + public abstract Task ReportsNoDiagnosticsForSuppressedMember_WhenSuppressingNonVirtualMethod(string arg); + protected override DiagnosticAnalyzer GetDiagnosticAnalyzer() { return new ArgumentMatcherAnalyzer(); @@ -104,9 +112,23 @@ public static IEnumerable MisusedArgTestCases get { yield return new object[] { "[|Arg.Any()|]" }; + yield return new object[] { "(int)[|Arg.Any()|]" }; + yield return new object[] { "[|Arg.Any()|] as int?" }; yield return new object[] { "[|Arg.Compat.Any()|]" }; + yield return new object[] { "(int)[|Arg.Compat.Any()|]" }; + yield return new object[] { "[|Arg.Compat.Any()|] as int?" }; yield return new object[] { "[|Arg.Is(1)|]" }; + yield return new object[] { "(int)[|Arg.Is(1)|]" }; + yield return new object[] { "[|Arg.Is(1)|] as int?" }; yield return new object[] { "[|Arg.Compat.Is(1)|]" }; + yield return new object[] { "(int)[|Arg.Compat.Is(1)|]" }; + yield return new object[] { "[|Arg.Compat.Is(1)|] as int?" }; + yield return new object[] { "[|Arg.Do(_ => {})|]" }; + yield return new object[] { "[|Arg.Compat.Do(_ => {})|]" }; + yield return new object[] { "[|Arg.Invoke()|]" }; + yield return new object[] { "[|Arg.Compat.Invoke()|]" }; + yield return new object[] { "[|Arg.InvokeDelegate()|]" }; + yield return new object[] { "[|Arg.Compat.InvokeDelegate()|]" }; } } @@ -126,7 +148,39 @@ public static IEnumerable CorrectlyUsedArgTestCases yield return new object[] { "Arg.Compat.Is(1)" }; yield return new object[] { "(int)Arg.Compat.Is(1)" }; yield return new object[] { "Arg.Compat.Is(1) as int?" }; + yield return new object[] { "Arg.Do(_ => {})" }; + yield return new object[] { "Arg.Compat.Do(_ => {})" }; + yield return new object[] { "Arg.Invoke()" }; + yield return new object[] { "Arg.Compat.Invoke()" }; + yield return new object[] { "Arg.InvokeDelegate()" }; + yield return new object[] { "(int)Arg.InvokeDelegate()" }; + yield return new object[] { "Arg.InvokeDelegate() as int?" }; + yield return new object[] { "Arg.Compat.InvokeDelegate()" }; + yield return new object[] { "(int)Arg.Compat.InvokeDelegate()" }; + yield return new object[] { "Arg.Compat.InvokeDelegate() as int?" }; + } + } + + public static IEnumerable CorrectlyUsedArgTestCasesWithoutCasts + { + get + { + yield return new object[] { "Arg.Any()" }; + yield return new object[] { "Arg.Compat.Any()" }; + yield return new object[] { "Arg.Is(1)" }; + yield return new object[] { "Arg.Compat.Is(1)" }; + yield return new object[] { "Arg.Do(_ => {})" }; + yield return new object[] { "Arg.Compat.Do(_ => {})" }; + yield return new object[] { "Arg.Invoke()" }; + yield return new object[] { "Arg.Compat.Invoke()" }; + yield return new object[] { "Arg.InvokeDelegate()" }; + yield return new object[] { "Arg.Compat.InvokeDelegate()" }; } } + + protected override string GetSettings() + { + return Settings != null ? Json.Encode(Settings) : null; + } } } \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/ArgumentMatcherAnalyzerTests/ArgumentMatcherTests.cs b/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/ArgumentMatcherAnalyzerTests/ArgumentMatcherTests.cs index cee9e9dc..42fc73e1 100644 --- a/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/ArgumentMatcherAnalyzerTests/ArgumentMatcherTests.cs +++ b/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/ArgumentMatcherAnalyzerTests/ArgumentMatcherTests.cs @@ -1,4 +1,6 @@ +using System.Collections.Generic; using System.Threading.Tasks; +using NSubstitute.Analyzers.Shared.Settings; namespace NSubstitute.Analyzers.Tests.CSharp.DiagnosticAnalyzerTests.ArgumentMatcherAnalyzerTests { @@ -6,13 +8,19 @@ public class ArgumentMatcherTests : ArgumentMatcherDiagnosticVerifier { public override async Task ReportsDiagnostics_WhenUsedInNonVirtualMethod(string arg) { - var source = $@"using NSubstitute; + var source = $@"using System; +using NSubstitute; namespace MyNamespace {{ public class Foo {{ - public int Bar(int firstArg) + public int Bar(int? firstArg) + {{ + return 2; + }} + + public int Bar(Action firstArg) {{ return 2; }} @@ -32,13 +40,19 @@ public void Test() public override async Task ReportsDiagnostics_WhenUsedInStaticMethod(string arg) { - var source = $@"using NSubstitute; + var source = $@"using System; +using NSubstitute; namespace MyNamespace {{ public class Foo {{ - public static int Bar(int firstArg) + public static int Bar(int? firstArg) + {{ + return 2; + }} + + public static int Bar(Action firstArg) {{ return 2; }} @@ -58,7 +72,8 @@ public void Test() public override async Task ReportsNoDiagnostics_WhenUsedInVirtualMethod(string arg) { - var source = $@"using NSubstitute; + var source = $@"using System; +using NSubstitute; namespace MyNamespace {{ @@ -68,6 +83,11 @@ public virtual int Bar(int? firstArg) {{ return 2; }} + + public virtual int Bar(Action firstArg) + {{ + return 2; + }} }} public class FooTests @@ -84,7 +104,8 @@ public void Test() public override async Task ReportsNoDiagnostics_WhenUsedInNonSealedOverrideMethod(string arg) { - var source = $@"using NSubstitute; + var source = $@"using System; +using NSubstitute; namespace MyNamespace {{ @@ -94,11 +115,18 @@ public virtual int Bar(int? firstArg) {{ return 2; }} + + public virtual int Bar(Action firstArg) + {{ + return 2; + }} }} public class Foo2 : Foo {{ public override int Bar(int? firstArg) => 1; + + public override int Bar(Action firstArg) => 1; }} public class FooTests @@ -115,6 +143,7 @@ public void Test() public override async Task ReportsNoDiagnostics_WhenUsedInDelegate(string arg) { + var funcArgType = arg.EndsWith("Invoke()") ? "Action" : "int?"; var source = $@"using NSubstitute; using System; @@ -124,7 +153,7 @@ public class FooTests {{ public void Test() {{ - var substitute = Substitute.For>(); + var substitute = Substitute.For>(); substitute({arg}); }} }} @@ -134,13 +163,19 @@ public void Test() public override async Task ReportsDiagnostics_WhenUsedInSealedOverrideMethod(string arg) { - var source = $@"using NSubstitute; + var source = $@"using System; +using NSubstitute; namespace MyNamespace {{ public class Foo {{ - public virtual int Bar(int firstArg) + public virtual int Bar(int? firstArg) + {{ + return 2; + }} + + public virtual int Bar(Action firstArg) {{ return 2; }} @@ -148,7 +183,9 @@ public virtual int Bar(int firstArg) public class Foo2 : Foo {{ - public sealed override int Bar(int firstArg) => 1; + public sealed override int Bar(int? firstArg) => 1; + + public sealed override int Bar(Action firstArg) => 2; }} public class FooTests @@ -166,13 +203,16 @@ public void Test() public override async Task ReportsNoDiagnostics_WhenUsedInAbstractMethod(string arg) { - var source = $@"using NSubstitute; + var source = $@"using System; +using NSubstitute; namespace MyNamespace {{ public abstract class Foo {{ public abstract int Bar(int? firstArg); + + public abstract int Bar(Action firstArg); }} public class FooTests @@ -190,13 +230,16 @@ public void Test() public override async Task ReportsNoDiagnostics_WhenUsedInInterfaceMethod(string arg) { - var source = $@"using NSubstitute; + var source = $@"using System; +using NSubstitute; namespace MyNamespace {{ public interface IFoo {{ int Bar(int? firstArg); + + int Bar(Action firstArg); }} public class FooTests @@ -213,13 +256,16 @@ public void Test() public override async Task ReportsNoDiagnostics_WhenUsedInGenericInterfaceMethod(string arg) { - var source = $@"using NSubstitute; + var source = $@"using System; +using NSubstitute; namespace MyNamespace {{ public interface IFoo {{ int Bar(int? firstArg); + + int Bar(Action firstArg); }} public class FooTests @@ -236,13 +282,16 @@ public void Test() public override async Task ReportsNoDiagnostics_WhenUsedInInterfaceIndexer(string arg) { - var source = $@"using NSubstitute; + var source = $@"using System; +using NSubstitute; namespace MyNamespace {{ public interface IFoo {{ int this[int? i] {{ get; }} + + int this[Action i] {{ get; }} }} public class FooTests @@ -259,13 +308,16 @@ public void Test() public override async Task ReportsNoDiagnostics_WhenUsedInVirtualIndexer(string arg) { - var source = $@"using NSubstitute; + var source = $@"using System; +using NSubstitute; namespace MyNamespace {{ public class Foo {{ public virtual int this[int? x] => 0; + + public virtual int this[Action x] => 0; }} public class FooTests @@ -282,13 +334,16 @@ public void Test() public override async Task ReportsDiagnostics_WhenUsedInNonVirtualIndexer(string arg) { - var source = $@"using NSubstitute; + var source = $@"using System; +using NSubstitute; namespace MyNamespace {{ public class Foo {{ - public int this[int x] => 0; + public int this[int? x] => 0; + + public int this[Action x] => 0; }} public class FooTests @@ -306,7 +361,8 @@ public void Test() public override async Task ReportsNoDiagnostics_WhenUsingUnfortunatelyNamedMethod(string arg) { - var source = $@"using NSubstitute; + var source = $@"using System; +using NSubstitute; namespace MyNamespace {{ @@ -316,6 +372,11 @@ public int Bar(int? firstArg) {{ return 1; }} + + public int Bar(Action firstArg) + {{ + return 1; + }} }} public class Arg @@ -330,16 +391,21 @@ public static T Is(T value) return default(T); }} - public static T Invoke(T value) + public static Action Invoke() {{ - return default(T); + return default(Action); }} - public static T Do(T value) + public static T Do(Action value) {{ return default(T); }} + public static T InvokeDelegate() + {{ + return default(T); + }} + public static class Compat {{ public static T Any() @@ -352,12 +418,17 @@ public static T Is(T value) return default(T); }} - public static T Invoke(T value) + public static Action Invoke() + {{ + return default(Action); + }} + + public static T Do(Action value) {{ return default(T); }} - public static T Do(T value) + public static T InvokeDelegate() {{ return default(T); }} @@ -405,7 +476,7 @@ public class FooTests {{ public void Test() {{ - {arg}; + _ = {arg}; }} }} }}"; @@ -425,6 +496,14 @@ public FooTests(int firstArg) {{ }} + public FooTests(int? firstArg) + {{ + }} + + public FooTests(Action firstArg) + {{ + }} + public void Test() {{ var x = new FooTests({arg}); @@ -436,7 +515,8 @@ public void Test() public override async Task ReportsDiagnostics_WhenUsedInInternalVirtualMember_AndInternalsVisibleToNotApplied(string arg) { - var source = $@"using NSubstitute; + var source = $@"using System; +using NSubstitute; namespace MyNamespace {{ @@ -446,6 +526,11 @@ internal virtual int FooBar(int? firstArg) {{ return 1; }} + + internal virtual int FooBar(Action firstArg) + {{ + return 1; + }} }} public class FooTests @@ -463,7 +548,8 @@ public void Test() public override async Task ReportsNoDiagnostics_WhenUsedInInternalVirtualMember_AndInternalsVisibleToApplied(string arg) { - var source = $@"using NSubstitute; + var source = $@"using System; +using NSubstitute; using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo(""OtherFirstAssembly"")] [assembly: InternalsVisibleTo(""DynamicProxyGenAssembly2"")] @@ -477,6 +563,11 @@ internal virtual int FooBar(int? firstArg) {{ return 1; }} + + internal virtual int FooBar(Action firstArg) + {{ + return 1; + }} }} public class FooTests @@ -494,7 +585,8 @@ public void Test() public override async Task ReportsDiagnostics_WhenUsedInInternalVirtualMember_AndInternalsVisibleToAppliedToWrongAssembly(string arg) { - var source = $@"using NSubstitute; + var source = $@"using System; +using NSubstitute; using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo(""OtherAssembly"")] @@ -506,6 +598,11 @@ internal virtual int FooBar(int? firstArg) {{ return 1; }} + + internal virtual int FooBar(Action firstArg) + {{ + return 1; + }} }} public class FooTests @@ -523,7 +620,8 @@ public void Test() public override async Task ReportsNoDiagnostics_WhenUsedInProtectedInternalVirtualMember(string arg) { - var source = $@"using NSubstitute; + var source = $@"using System; +using NSubstitute; namespace MyNamespace {{ @@ -533,6 +631,11 @@ protected internal virtual int FooBar(int? firstArg) {{ return 1; }} + + protected internal virtual int FooBar(Action firstArg) + {{ + return 1; + }} }} public class FooTests @@ -547,5 +650,55 @@ public void Test() await VerifyNoDiagnostic(source); } + + public override async Task ReportsNoDiagnosticsForSuppressedMember_WhenSuppressingNonVirtualMethod(string arg) + { + Settings = AnalyzersSettings.CreateWithSuppressions("M:MyNamespace.Foo.Bar(System.Int32,System.Int32)", ArgumentMatcherUsedWithoutSpecifyingCall.Id); + Settings.Suppressions.Add(new Suppression + { + Target = "M:MyNamespace.Foo.Bar(System.Action,System.Action)", + Rules = new List { ArgumentMatcherUsedWithoutSpecifyingCall.Id } + }); + var source = $@"using System; +using NSubstitute; + +namespace MyNamespace +{{ + public class Foo + {{ + public int Bar(int x) + {{ + return 1; + }} + + public int Bar(int x, int y) + {{ + return 2; + }} + + public int Bar(Action x) + {{ + return 1; + }} + + public int Bar(Action x, Action y) + {{ + return 2; + }} + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = NSubstitute.Substitute.For(); + substitute.Bar({arg}, {arg}); + substitute.Bar([|{arg}|]); + }} + }} +}}"; + + await VerifyDiagnostic(source, ArgumentMatcherUsedWithoutSpecifyingCall); + } } } \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.CSharp/NSubstitute.Analyzers.Tests.CSharp.csproj b/tests/NSubstitute.Analyzers.Tests.CSharp/NSubstitute.Analyzers.Tests.CSharp.csproj index 9d13d1eb..1e13237f 100644 --- a/tests/NSubstitute.Analyzers.Tests.CSharp/NSubstitute.Analyzers.Tests.CSharp.csproj +++ b/tests/NSubstitute.Analyzers.Tests.CSharp/NSubstitute.Analyzers.Tests.CSharp.csproj @@ -10,7 +10,7 @@ - + diff --git a/tests/NSubstitute.Analyzers.Tests.Shared/DiagnosticAnalyzers/IArgumentMatcherDiagnosticVerifier.cs b/tests/NSubstitute.Analyzers.Tests.Shared/DiagnosticAnalyzers/IArgumentMatcherDiagnosticVerifier.cs index a8c4abc0..e3e05156 100644 --- a/tests/NSubstitute.Analyzers.Tests.Shared/DiagnosticAnalyzers/IArgumentMatcherDiagnosticVerifier.cs +++ b/tests/NSubstitute.Analyzers.Tests.Shared/DiagnosticAnalyzers/IArgumentMatcherDiagnosticVerifier.cs @@ -43,5 +43,7 @@ public interface IArgumentMatcherDiagnosticVerifier Task ReportsDiagnostics_WhenUsedInInternalVirtualMember_AndInternalsVisibleToAppliedToWrongAssembly(string arg); Task ReportsNoDiagnostics_WhenUsedInProtectedInternalVirtualMember(string arg); + + Task ReportsNoDiagnosticsForSuppressedMember_WhenSuppressingNonVirtualMethod(string arg); } } \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.Shared/NSubstitute.Analyzers.Tests.Shared.csproj b/tests/NSubstitute.Analyzers.Tests.Shared/NSubstitute.Analyzers.Tests.Shared.csproj index 7fe784f8..363d7f4c 100644 --- a/tests/NSubstitute.Analyzers.Tests.Shared/NSubstitute.Analyzers.Tests.Shared.csproj +++ b/tests/NSubstitute.Analyzers.Tests.Shared/NSubstitute.Analyzers.Tests.Shared.csproj @@ -17,7 +17,7 @@ - + diff --git a/tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/ArgumentMatcherAnalyzerTests/ArgumentMatcherDiagnosticVerifier.cs b/tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/ArgumentMatcherAnalyzerTests/ArgumentMatcherDiagnosticVerifier.cs index 5de736ce..f2dc1f85 100644 --- a/tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/ArgumentMatcherAnalyzerTests/ArgumentMatcherDiagnosticVerifier.cs +++ b/tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/ArgumentMatcherAnalyzerTests/ArgumentMatcherDiagnosticVerifier.cs @@ -1,8 +1,12 @@ using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.VisualBasic; using NSubstitute.Analyzers.Shared; +using NSubstitute.Analyzers.Shared.Settings; +using NSubstitute.Analyzers.Shared.TinyJson; using NSubstitute.Analyzers.Tests.Shared.DiagnosticAnalyzers; using NSubstitute.Analyzers.VisualBasic; using NSubstitute.Analyzers.VisualBasic.DiagnosticAnalyzers; @@ -12,6 +16,8 @@ namespace NSubstitute.Analyzers.Tests.VisualBasic.DiagnosticAnalyzersTests.Argum { public abstract class ArgumentMatcherDiagnosticVerifier : VisualBasicDiagnosticVerifier, IArgumentMatcherDiagnosticVerifier { + internal AnalyzersSettings Settings { get; set; } + protected DiagnosticDescriptor ArgumentMatcherUsedWithoutSpecifyingCall { get; } = DiagnosticDescriptors.ArgumentMatcherUsedWithoutSpecifyingCall; [Theory] @@ -71,7 +77,7 @@ public abstract class ArgumentMatcherDiagnosticVerifier : VisualBasicDiagnosticV public abstract Task ReportsNoDiagnostics_WhenUsedWithPotentiallyValidAssignment(string arg); [Theory] - [MemberData(nameof(MisusedArgTestCases))] + [MemberData(nameof(MisusedArgTestCasesWithoutCast))] public abstract Task ReportsDiagnostics_WhenUsedAsStandaloneExpression(string arg); [Theory] @@ -94,19 +100,27 @@ public abstract class ArgumentMatcherDiagnosticVerifier : VisualBasicDiagnosticV [MemberData(nameof(CorrectlyUsedArgTestCases))] public abstract Task ReportsNoDiagnostics_WhenUsedInProtectedInternalVirtualMember(string arg); + [Theory] + [MemberData(nameof(CorrectlyUsedArgTestCasesWithoutCasts))] + public abstract Task ReportsNoDiagnosticsForSuppressedMember_WhenSuppressingNonVirtualMethod(string arg); + protected override DiagnosticAnalyzer GetDiagnosticAnalyzer() { return new ArgumentMatcherAnalyzer(); } public static IEnumerable MisusedArgTestCases + { + get { return MisusedArgs.Select(argArray => argArray.Select(arg => arg).ToArray()); } + } + + public static IEnumerable MisusedArgTestCasesWithoutCast { get { - yield return new object[] { "[|Arg.Any(Of Integer)()|]" }; - yield return new object[] { "[|Arg.Compat.Any(Of Integer)()|]" }; - yield return new object[] { "[|Arg.Is(1)|]" }; - yield return new object[] { "[|Arg.Compat.Is(1)|]" }; + var ignoredExpressions = new[] { "TryCast", "CType", "DirectCast" }; + + return MisusedArgs.Where(args => args.Any(arg => !ignoredExpressions.Any(arg.Contains))); } } @@ -118,22 +132,105 @@ public static IEnumerable CorrectlyUsedArgTestCases yield return new object[] { "TryCast(Arg.Any(Of Integer)(), Object)" }; yield return new object[] { "CType(Arg.Any(Of Integer)(), Integer)" }; yield return new object[] { "DirectCast(Arg.Any(Of Integer)(), Integer)" }; - yield return new object[] { "Arg.Compat.Any(Of Integer)()" }; yield return new object[] { "TryCast(Arg.Compat.Any(Of Integer)(), Object)" }; yield return new object[] { "CType(Arg.Compat.Any(Of Integer)(), Integer)" }; yield return new object[] { "DirectCast(Arg.Compat.Any(Of Integer)(), Integer)" }; - yield return new object[] { "Arg.Is(1)" }; yield return new object[] { "TryCast(Arg.Is(1), Object)" }; yield return new object[] { "CType(Arg.Is(1), Integer)" }; yield return new object[] { "DirectCast(Arg.Is(1), Integer)" }; - yield return new object[] { "Arg.Compat.Is(1)" }; yield return new object[] { "TryCast(Arg.Compat.Is(1), Object)" }; yield return new object[] { "CType(Arg.Compat.Is(1), Integer)" }; yield return new object[] { "DirectCast(Arg.Compat.Is(1), Integer)" }; + yield return new object[] { "Arg.Invoke()" }; + yield return new object[] { "Arg.Compat.Invoke()" }; + yield return new object[] { "Arg.InvokeDelegate(Of Integer)()" }; + yield return new object[] { "Arg.Compat.InvokeDelegate(Of Integer)()" }; + yield return new object[] + { + @"Arg.Do(Of Integer)(Function(doValue) +End Function)" + }; + yield return new object[] + { + @"Arg.Compat.Do(Of Integer)(Function(doValue) +End Function)" + }; } } + + public static IEnumerable CorrectlyUsedArgTestCasesWithoutCasts + { + get + { + yield return new object[] { "Arg.Any(Of Integer)()" }; + yield return new object[] { "Arg.Compat.Any(Of Integer)()" }; + yield return new object[] { "Arg.Is(1)" }; + yield return new object[] { "Arg.Compat.Is(1)" }; + yield return new object[] { "Arg.Invoke()" }; + yield return new object[] { "Arg.Compat.Invoke()" }; + yield return new object[] { "Arg.InvokeDelegate(Of Integer)()" }; + yield return new object[] { "Arg.Compat.InvokeDelegate(Of Integer)()" }; + yield return new object[] + { + @"Arg.Do(Of Integer)(Function(doValue) +End Function)" + }; + yield return new object[] + { + @"Arg.Compat.Do(Of Integer)(Function(doValue) +End Function)" + }; + } + } + + public static IEnumerable MisusedArgs + { + get + { + yield return new[] { "[|Arg.Any(Of Integer)()|]" }; + yield return new[] { "TryCast([|Arg.Any(Of Integer)()|], Object)" }; + yield return new[] { "CType([|Arg.Any(Of Integer)()|], Integer)" }; + yield return new[] { "DirectCast([|Arg.Any(Of Integer)()|], Integer)" }; + yield return new[] { "[|Arg.Compat.Any(Of Integer)()|]" }; + yield return new[] { "TryCast([|Arg.Compat.Any(Of Integer)()|], Object)" }; + yield return new[] { "CType([|Arg.Compat.Any(Of Integer)()|], Integer)" }; + yield return new[] { "DirectCast([|Arg.Compat.Any(Of Integer)()|], Integer)" }; + yield return new[] { "[|Arg.Is(1)|]" }; + yield return new[] { "TryCast([|Arg.Is(1)|], Object)" }; + yield return new[] { "CType([|Arg.Is(1)|], Integer)" }; + yield return new[] { "DirectCast([|Arg.Is(1)|], Integer)" }; + yield return new[] { "[|Arg.Compat.Is(1)|]" }; + yield return new[] { "TryCast([|Arg.Compat.Is(1)|], Object)" }; + yield return new[] { "CType([|Arg.Compat.Is(1)|], Integer)" }; + yield return new[] { "DirectCast([|Arg.Compat.Is(1)|], Integer)" }; + yield return new[] { "[|Arg.Invoke()|]" }; + yield return new[] { "[|Arg.Compat.Invoke()|]" }; + yield return new[] { "[|Arg.InvokeDelegate(Of Integer)()|]" }; + yield return new[] { "[|Arg.Compat.InvokeDelegate(Of Integer)()|]" }; + yield return new[] + { + @"[|Arg.Do(Of Integer)(Function(doValue) +End Function)|]" + }; + yield return new[] + { + @"[|Arg.Compat.Do(Of Integer)(Function(doValue) +End Function)|]" + }; + } + } + + protected override string GetSettings() + { + return Settings != null ? Json.Encode(Settings) : null; + } + + protected override CompilationOptions GetCompilationOptions() + { + return new VisualBasicCompilationOptions(OutputKind.DynamicallyLinkedLibrary, optionStrict: OptionStrict.Off); + } } } \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/ArgumentMatcherAnalyzerTests/ArgumentMatcherTests.cs b/tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/ArgumentMatcherAnalyzerTests/ArgumentMatcherTests.cs index 7cd1b3b0..73370a7d 100644 --- a/tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/ArgumentMatcherAnalyzerTests/ArgumentMatcherTests.cs +++ b/tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/ArgumentMatcherAnalyzerTests/ArgumentMatcherTests.cs @@ -1,4 +1,8 @@ +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; +using NSubstitute.Analyzers.Shared.Settings; +using Xunit; namespace NSubstitute.Analyzers.Tests.VisualBasic.DiagnosticAnalyzersTests.ArgumentMatcherAnalyzerTests { @@ -6,13 +10,24 @@ public class ArgumentMatcherTests : ArgumentMatcherDiagnosticVerifier { public override async Task ReportsDiagnostics_WhenUsedInNonVirtualMethod(string arg) { - var source = $@"Imports NSubstitute + var source = $@"Imports System +Imports NSubstitute Namespace MyNamespace Public Class Foo + + Public Function Bar(ByVal firstArg As Object) As Integer + Return 2 + End Function + Public Function Bar(ByVal firstArg As Integer) As Integer Return 2 End Function + + Public Function Bar(ByVal firstArg As Action) As Integer + Return 2 + End Function + End Class Public Class FooTests @@ -28,13 +43,22 @@ End Class public override async Task ReportsDiagnostics_WhenUsedInStaticMethod(string arg) { - var source = $@"Imports NSubstitute + var source = $@"Imports System +Imports NSubstitute Namespace MyNamespace Public Class Foo + Public Shared Function Bar(ByVal firstArg As Object) As Integer + Return 2 + End Function + Public Shared Function Bar(ByVal firstArg As Integer) As Integer Return 2 End Function + + Public Shared Function Bar(ByVal firstArg As Action) As Integer + Return 2 + End Function End Class Public Class FooTests @@ -49,13 +73,22 @@ End Class public override async Task ReportsNoDiagnostics_WhenUsedInVirtualMethod(string arg) { - var source = $@"Imports NSubstitute + var source = $@"Imports System +Imports NSubstitute Namespace MyNamespace Public Class Foo Public Overridable Function Bar(ByVal firstArg As Integer?) As Integer Return 2 End Function + + Public Overridable Function Bar(ByVal firstArg As Object) As Integer + Return 2 + End Function + + Public Overridable Function Bar(ByVal firstArg As Action) As Integer + Return 2 + End Function End Class Public Class FooTests @@ -70,13 +103,19 @@ End Class public override async Task ReportsNoDiagnostics_WhenUsedInNonSealedOverrideMethod(string arg) { - var source = $@"Imports NSubstitute + var source = $@"Imports System +Imports NSubstitute Namespace MyNamespace Public Class Foo Public Overridable Function Bar(ByVal firstArg As Integer?) As Integer Return 2 End Function + + Public Overridable Function Bar(ByVal firstArg As Action) As Integer + Return 2 + End Function + End Class Public Class Foo2 @@ -85,6 +124,10 @@ Inherits Foo Public Overrides Function Bar(ByVal firstArg As Integer?) As Integer Return 1 End Function + + Public Overrides Function Bar(ByVal firstArg As Action) As Integer + Return 1 + End Function End Class Public Class FooTests @@ -100,13 +143,15 @@ End Namespace public override async Task ReportsNoDiagnostics_WhenUsedInDelegate(string arg) { + var delegateArgType = arg.EndsWith("Invoke()") ? "Action" : "Integer?"; + var source = $@"Imports NSubstitute Imports System Namespace MyNamespace Public Class FooTests Public Sub Test() - Dim substitute = NSubstitute.Substitute.[For](Of Func(Of Integer?, Integer))() + Dim substitute = NSubstitute.Substitute.[For](Of Func(Of {delegateArgType}, Integer))() Dim x = substitute({arg}) End Sub End Class @@ -117,21 +162,39 @@ End Namespace public override async Task ReportsDiagnostics_WhenUsedInSealedOverrideMethod(string arg) { - var source = $@"Imports NSubstitute + var source = $@"Imports System +Imports NSubstitute Namespace MyNamespace Public Class Foo + + Public Overridable Function Bar(ByVal firstArg As Object) As Integer + Return 2 + End Function + Public Overridable Function Bar(ByVal firstArg As Integer) As Integer Return 2 End Function + + Public Overridable Function Bar(ByVal firstArg As Action) As Integer + Return 2 + End Function End Class Public Class Foo2 Inherits Foo + Public NotOverridable Overrides Function Bar(ByVal firstArg As Object) As Integer + Return 1 + End Function + Public NotOverridable Overrides Function Bar(ByVal firstArg As Integer) As Integer Return 1 End Function + + Public NotOverridable Overrides Function Bar(ByVal firstArg As Action) As Integer + Return 1 + End Function End Class Public Class FooTests @@ -148,11 +211,16 @@ End Namespace public override async Task ReportsNoDiagnostics_WhenUsedInAbstractMethod(string arg) { - var source = $@"Imports NSubstitute + var source = $@"Imports System +Imports NSubstitute Namespace MyNamespace Public MustInherit Class Foo + Public MustOverride Function Bar(ByVal firstArg As Object) As Integer + Public MustOverride Function Bar(ByVal firstArg As Integer?) As Integer + + Public MustOverride Function Bar(ByVal firstArg As Action) As Integer End Class Public Class FooTests @@ -168,11 +236,16 @@ End Class public override async Task ReportsNoDiagnostics_WhenUsedInInterfaceMethod(string arg) { - var source = $@"Imports NSubstitute + var source = $@"Imports System +Imports NSubstitute Namespace MyNamespace Interface IFoo Function Bar(ByVal firstArg As Integer?) As Integer + + Function Bar(ByVal firstArg As Action) As Integer + + Function Bar(ByVal firstArg As Object) As Integer End Interface Public Class FooTests @@ -188,12 +261,17 @@ End Namespace public override async Task ReportsNoDiagnostics_WhenUsedInGenericInterfaceMethod(string arg) { - var source = $@"Imports NSubstitute + var source = $@"Imports System +Imports NSubstitute Namespace MyNamespace Public Interface IFoo(Of T) + Function Bar(Of T)(ByVal firstArg as Object) As Integer + Function Bar(Of T)(ByVal firstArg as Integer?) As Integer + + Function Bar(Of T)(ByVal firstArg as Action) As Integer End Interface Public Class FooTests @@ -210,11 +288,16 @@ End Class public override async Task ReportsNoDiagnostics_WhenUsedInInterfaceIndexer(string arg) { - var source = $@"Imports NSubstitute + var source = $@"Imports System +Imports NSubstitute Namespace MyNamespace Interface IFoo Default ReadOnly Property Item(ByVal i As Integer?) As Integer + + Default ReadOnly Property Item(ByVal i As Action) As Integer + + Default ReadOnly Property Item(ByVal i As Object) As Integer End Interface Public Class FooTests @@ -230,7 +313,8 @@ End Namespace public override async Task ReportsNoDiagnostics_WhenUsedInVirtualIndexer(string arg) { - var source = $@"Imports NSubstitute + var source = $@"Imports System +Imports NSubstitute Namespace MyNamespace Public Class Foo @@ -239,6 +323,12 @@ Default Public Overridable ReadOnly Property Item(ByVal x As Integer?) As Intege Return 0 End Get End Property + + Default Public Overridable ReadOnly Property Item(ByVal x As Action) As Integer + Get + Return 0 + End Get + End Property End Class Public Class FooTests @@ -254,15 +344,29 @@ End Namespace public override async Task ReportsDiagnostics_WhenUsedInNonVirtualIndexer(string arg) { - var source = $@"Imports NSubstitute + var source = $@"Imports System +Imports NSubstitute Namespace MyNamespace Public Class Foo + + Default Public ReadOnly Property Item(ByVal x As Object) As Integer + Get + Return 0 + End Get + End Property + Default Public ReadOnly Property Item(ByVal x As Integer) As Integer Get Return 0 End Get End Property + + Default Public ReadOnly Property Item(ByVal x As Action) As Integer + Get + Return 0 + End Get + End Property End Class Public Class FooTests @@ -279,7 +383,8 @@ End Namespace public override async Task ReportsNoDiagnostics_WhenUsingUnfortunatelyNamedMethod(string arg) { - var source = $@"Imports NSubstitute + var source = $@"Imports System +Imports NSubstitute Imports System.Runtime.CompilerServices Namespace MyNamespace @@ -287,6 +392,10 @@ Public Class Foo Public Function Bar(ByVal firstArg As Integer?) As Integer Return 1 End Function + + Public Function Bar(ByVal firstArg As Action) As Integer + Return 1 + End Function End Class Public Class Arg @@ -298,11 +407,15 @@ Public Shared Function [Is](Of T)(ByVal value As T) As T Return Nothing End Function - Public Shared Function Invoke(Of T)(ByVal value As T) As T + Public Shared Function Invoke() As Action + Return Nothing + End Function + + Public Shared Function [Do](Of T)(ByVal value As Action(Of T)) As T Return Nothing End Function - Public Shared Function [Do](Of T)(ByVal value As T) As T + Public Shared Function [InvokeDelegate](Of T)() As T Return Nothing End Function @@ -315,11 +428,15 @@ Public Shared Function [Is](Of T)(ByVal value As T) As T Return Nothing End Function - Public Shared Function Invoke(Of T)(ByVal value As T) As T + Public Shared Function Invoke() As Action Return Nothing End Function - Public Shared Function [Do](Of T)(ByVal value As T) As T + Public Shared Function [Do](Of T)(ByVal value As Action(Of T)) As T + Return Nothing + End Function + + Public Shared Function [InvokeDelegate](Of T)() As T Return Nothing End Function End Class @@ -360,7 +477,7 @@ Imports NSubstitute Namespace MyNamespace Public Class FooTests Public Sub Test() - {arg} + {arg} End Sub End Class End Namespace @@ -375,9 +492,16 @@ Imports NSubstitute Namespace MyNamespace Public Class FooTests + + Public Sub New(ByVal firstArg As Object) + End Sub + Public Sub New(ByVal firstArg As Integer) End Sub + Public Sub New(ByVal firstArg As Action) + End Sub + Public Sub Test() Dim x = New FooTests({arg}) End Sub @@ -389,13 +513,23 @@ End Namespace public override async Task ReportsDiagnostics_WhenUsedInInternalVirtualMember_AndInternalsVisibleToNotApplied(string arg) { - var source = $@"Imports NSubstitute + var source = $@"Imports System +Imports NSubstitute Namespace MyNamespace Public Class Foo + + Friend Overridable Function FooBar(ByVal firstArg As Object) As Integer + Return 1 + End Function + Friend Overridable Function FooBar(ByVal firstArg As Integer?) As Integer Return 1 End Function + + Friend Overridable Function FooBar(ByVal firstArg As Action) As Integer + Return 1 + End Function End Class Public Class FooTests @@ -412,7 +546,8 @@ End Namespace public override async Task ReportsNoDiagnostics_WhenUsedInInternalVirtualMember_AndInternalsVisibleToApplied(string arg) { - var source = $@"Imports NSubstitute + var source = $@"Imports System +Imports NSubstitute Imports System.Runtime.CompilerServices @@ -423,6 +558,10 @@ Public Class Foo Friend Overridable Function FooBar(ByVal firstArg As Integer?) As Integer Return 1 End Function + + Friend Overridable Function FooBar(ByVal firstArg As Action) As Integer + Return 1 + End Function End Class Public Class FooTests @@ -439,15 +578,25 @@ End Namespace public override async Task ReportsDiagnostics_WhenUsedInInternalVirtualMember_AndInternalsVisibleToAppliedToWrongAssembly(string arg) { - var source = $@"Imports NSubstitute + var source = $@"Imports System +Imports NSubstitute Imports System.Runtime.CompilerServices Namespace MyNamespace Public Class Foo + + Friend Overridable Function FooBar(ByVal firstArg As Object) As Integer + Return 1 + End Function + Friend Overridable Function FooBar(ByVal firstArg As Integer?) As Integer Return 1 End Function + + Friend Overridable Function FooBar(ByVal firstArg As Action) As Integer + Return 1 + End Function End Class Public Class FooTests @@ -464,13 +613,18 @@ End Namespace public override async Task ReportsNoDiagnostics_WhenUsedInProtectedInternalVirtualMember(string arg) { - var source = $@"Imports NSubstitute + var source = $@"Imports System +Imports NSubstitute Namespace MyNamespace Public Class Foo Protected Friend Overridable Function FooBar(ByVal firstArg As Integer?) As Integer Return 1 End Function + + Protected Friend Overridable Function FooBar(ByVal firstArg As Action) As Integer + Return 1 + End Function End Class Public Class FooTests @@ -484,5 +638,49 @@ End Namespace await VerifyNoDiagnostic(source); } + + public override async Task ReportsNoDiagnosticsForSuppressedMember_WhenSuppressingNonVirtualMethod(string arg) + { + Settings = AnalyzersSettings.CreateWithSuppressions("M:MyNamespace.Foo.Bar(System.Int32,System.Int32)", ArgumentMatcherUsedWithoutSpecifyingCall.Id); + Settings.Suppressions.Add(new Suppression + { + Target = "M:MyNamespace.Foo.Bar(System.Action,System.Action)", + Rules = new List { ArgumentMatcherUsedWithoutSpecifyingCall.Id } + }); + + var source = $@"Imports System +Imports NSubstitute + +Namespace MyNamespace + Public Class Foo + Public Function Bar(ByVal x As Integer) As Integer + Return 1 + End Function + + Public Function Bar(ByVal x As Integer, ByVal y As Integer) As Integer + Return 2 + End Function + + Public Function Bar(ByVal x As Action) As Integer + Return 1 + End Function + + Public Function Bar(ByVal x As Action, ByVal y As Action) As Integer + Return 2 + End Function + End Class + + Public Class FooTests + Public Sub Test() + Dim substitute = NSubstitute.Substitute.[For](Of Foo)() + substitute.Bar({arg}, {arg}) + substitute.Bar([|{arg}|]) + End Sub + End Class +End Namespace +"; + + await VerifyDiagnostic(source, ArgumentMatcherUsedWithoutSpecifyingCall); + } } } \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.VisualBasic/NSubstitute.Analyzers.Tests.VisualBasic.csproj b/tests/NSubstitute.Analyzers.Tests.VisualBasic/NSubstitute.Analyzers.Tests.VisualBasic.csproj index e68d8884..351dbb45 100644 --- a/tests/NSubstitute.Analyzers.Tests.VisualBasic/NSubstitute.Analyzers.Tests.VisualBasic.csproj +++ b/tests/NSubstitute.Analyzers.Tests.VisualBasic/NSubstitute.Analyzers.Tests.VisualBasic.csproj @@ -10,7 +10,7 @@ - +