From 1a55c7e22f8c4a6319ece1582248e5886c21327d Mon Sep 17 00:00:00 2001 From: tomasz-podolak Date: Sat, 11 Jun 2022 20:25:44 +0200 Subject: [PATCH] GH-174 - analyzing usages of WithAnyArgs like methods --- Directory.Build.props | 2 +- NSubstitute.Analyzers.sln | 1 + .../WithAnyArgsDiagnosticsSource.cs | 40 +++ .../Models/Foo.cs | 2 +- .../Models/IFoo.cs | 2 +- .../WithAnyArgsDiagnosticsSource.vb | 54 ++++ .../Models/Foo.vb | 2 +- .../Models/IFoo.vb | 2 +- .../CSharpDiagnosticAnalyzersBenchmarks.cs | 5 +- .../AbstractDiagnosticAnalyzersBenchmarks.cs | 2 + ...isualBasicDiagnosticAnalyzersBenchmarks.cs | 3 + documentation/rules/NS5004.md | 48 +++ documentation/rules/README.md | 3 +- .../WithAnyArgsArgumentMatcherAnalyzer.cs | 26 ++ .../AbstractDiagnosticDescriptorsProvider.cs | 2 + ...tractWithAnyArgsArgumentMatcherAnalyzer.cs | 224 ++++++++++++++ ...ticCategories.cs => DiagnosticCategory.cs} | 0 .../DiagnosticDescriptors.cs | 8 + .../DiagnosticIdentifiers.cs | 1 + .../Extensions/SubstituteSymbolExtensions.cs | 25 ++ .../IDiagnosticDescriptorsProvider.cs | 2 + .../MetadataNames.cs | 41 +++ .../Resources.Designer.cs | 18 ++ .../Resources.resx | 15 +- .../WithAnyArgsArgumentMatcherAnalyzer.cs | 27 ++ .../BenchmarksConventionTests.cs | 53 ++-- ...bstitute.Analyzers.Tests.Benchmarks.csproj | 3 +- ...MemberArgumentMatcherDiagnosticVerifier.cs | 2 +- ...SubstitutableMemberArgumentMatcherTests.cs | 2 +- ...nyArgsArgumentMatcherDiagnosticVerifier.cs | 104 +++++++ .../ReceivedAsExtensionMethodTests.cs | 249 +++++++++++++++ .../ReceivedAsOrdinaryMethodTests.cs | 283 ++++++++++++++++++ .../ReturnsAsExtensionMethodTests.cs | 166 ++++++++++ .../ReturnsAsOrdinaryMethodTests.cs | 166 ++++++++++ .../ThrowsAsExtensionMethodTests.cs | 163 ++++++++++ .../ThrowsAsOrdinaryMethodTests.cs | 163 ++++++++++ .../WhenAsExtensionMethodTests.cs | 164 ++++++++++ ...nyArgsArgumentMatcherDiagnosticVerifier.cs | 118 ++++++++ ...nyArgsArgumentMatcherDiagnosticVerifier.cs | 18 ++ ...MemberArgumentMatcherDiagnosticVerifier.cs | 2 +- ...nyArgsArgumentMatcherDiagnosticVerifier.cs | 20 ++ ...MemberArgumentMatcherDiagnosticVerifier.cs | 2 +- ...SubstitutableMemberArgumentMatcherTests.cs | 2 +- ...bstitutableMemberWhenDiagnosticVerifier.cs | 12 +- .../ReceivedAsExtensionMethodTests.cs | 225 ++++++++++++++ .../ReceivedAsOrdinaryMethodTests.cs | 229 ++++++++++++++ ...nyArgsArgumentMatcherDiagnosticVerifier.cs | 145 +++++++++ 47 files changed, 2805 insertions(+), 41 deletions(-) create mode 100644 benchmarks/NSubstitute.Analyzers.Benchmarks.Source.CSharp/DiagnosticsSources/WithAnyArgsDiagnosticsSource.cs create mode 100644 benchmarks/NSubstitute.Analyzers.Benchmarks.Source.VisualBasic/DiagnosticsSources/WithAnyArgsDiagnosticsSource.vb create mode 100644 documentation/rules/NS5004.md create mode 100644 src/NSubstitute.Analyzers.CSharp/DiagnosticAnalyzers/WithAnyArgsArgumentMatcherAnalyzer.cs create mode 100644 src/NSubstitute.Analyzers.Shared/DiagnosticAnalyzers/AbstractWithAnyArgsArgumentMatcherAnalyzer.cs rename src/NSubstitute.Analyzers.Shared/{DiagnosticCategories.cs => DiagnosticCategory.cs} (100%) create mode 100644 src/NSubstitute.Analyzers.VisualBasic/DiagnosticAnalyzers/WithAnyArgsArgumentMatcherAnalyzer.cs create mode 100644 tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/WithAnyArgsArgumentMatcherAnalyzerTests/ForAnyArgsArgumentMatcherDiagnosticVerifier.cs create mode 100644 tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/WithAnyArgsArgumentMatcherAnalyzerTests/ReceivedAsExtensionMethodTests.cs create mode 100644 tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/WithAnyArgsArgumentMatcherAnalyzerTests/ReceivedAsOrdinaryMethodTests.cs create mode 100644 tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/WithAnyArgsArgumentMatcherAnalyzerTests/ReturnsAsExtensionMethodTests.cs create mode 100644 tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/WithAnyArgsArgumentMatcherAnalyzerTests/ReturnsAsOrdinaryMethodTests.cs create mode 100644 tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/WithAnyArgsArgumentMatcherAnalyzerTests/ThrowsAsExtensionMethodTests.cs create mode 100644 tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/WithAnyArgsArgumentMatcherAnalyzerTests/ThrowsAsOrdinaryMethodTests.cs create mode 100644 tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/WithAnyArgsArgumentMatcherAnalyzerTests/WhenAsExtensionMethodTests.cs create mode 100644 tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/WithAnyArgsArgumentMatcherAnalyzerTests/WithAnyArgsArgumentMatcherDiagnosticVerifier.cs create mode 100644 tests/NSubstitute.Analyzers.Tests.Shared/DiagnosticAnalyzers/IForAnyArgsArgumentMatcherDiagnosticVerifier.cs create mode 100644 tests/NSubstitute.Analyzers.Tests.Shared/DiagnosticAnalyzers/IWithAnyArgsArgumentMatcherDiagnosticVerifier.cs create mode 100644 tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/WithAnyArgsArgumentMatcherAnalyzerTests/ReceivedAsExtensionMethodTests.cs create mode 100644 tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/WithAnyArgsArgumentMatcherAnalyzerTests/ReceivedAsOrdinaryMethodTests.cs create mode 100644 tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/WithAnyArgsArgumentMatcherAnalyzerTests/WithAnyArgsArgumentMatcherDiagnosticVerifier.cs diff --git a/Directory.Build.props b/Directory.Build.props index 8162e6e9..827d02ea 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -8,7 +8,7 @@ - + true diff --git a/NSubstitute.Analyzers.sln b/NSubstitute.Analyzers.sln index 376b393c..e127a92e 100644 --- a/NSubstitute.Analyzers.sln +++ b/NSubstitute.Analyzers.sln @@ -47,6 +47,7 @@ ProjectSection(SolutionItems) = preProject documentation\rules\NS5001.md = documentation\rules\NS5001.md documentation\rules\NS5001.md = documentation\rules\NS5002.md documentation\rules\NS5001.md = documentation\rules\NS5003.md + documentation\rules\NS5001.md = documentation\rules\NS5004.md EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{1629DF5F-9BC0-49C0-975E-E45C3E58EB3A}" diff --git a/benchmarks/NSubstitute.Analyzers.Benchmarks.Source.CSharp/DiagnosticsSources/WithAnyArgsDiagnosticsSource.cs b/benchmarks/NSubstitute.Analyzers.Benchmarks.Source.CSharp/DiagnosticsSources/WithAnyArgsDiagnosticsSource.cs new file mode 100644 index 00000000..428344ef --- /dev/null +++ b/benchmarks/NSubstitute.Analyzers.Benchmarks.Source.CSharp/DiagnosticsSources/WithAnyArgsDiagnosticsSource.cs @@ -0,0 +1,40 @@ +using NSubstitute.Analyzers.Benchmarks.Source.CSharp.Models; + +namespace NSubstitute.Analyzers.Benchmarks.Source.CSharp.DiagnosticsSources +{ + public class WithAnyArgsDiagnosticsSource + { + public void NS5004_InvalidArgumentMatcherUsedWithAnyArgs() + { + var substitute = Substitute.For(); + + _ = substitute.DidNotReceiveWithAnyArgs()[Arg.Is(1)]; + _ = substitute.DidNotReceiveWithAnyArgs()[Arg.Do(_ => { })]; + substitute.DidNotReceiveWithAnyArgs().IntReturningProperty = Arg.Is(1); + substitute.DidNotReceiveWithAnyArgs().IntReturningProperty = Arg.Do(_ => { }); + substitute.DidNotReceiveWithAnyArgs() + .ObjectReturningMethodWithArguments(Arg.Is(1), Arg.Is(1), Arg.Do(_ => { })); + + _ = substitute.DidNotReceiveWithAnyArgs()[Arg.Is(1)]; + _ = substitute.DidNotReceiveWithAnyArgs()[Arg.Do(_ => { })]; + substitute.DidNotReceiveWithAnyArgs().IntReturningProperty = Arg.Is(1); + substitute.DidNotReceiveWithAnyArgs().IntReturningProperty = Arg.Do(_ => { }); + substitute.DidNotReceiveWithAnyArgs() + .ObjectReturningMethodWithArguments(Arg.Is(1), Arg.Is(1), Arg.Do(_ => { })); + + _ = SubstituteExtensions.DidNotReceiveWithAnyArgs(substitute)[Arg.Is(1)]; + _ = SubstituteExtensions.DidNotReceiveWithAnyArgs(substitute)[Arg.Do(_ => { })]; + SubstituteExtensions.DidNotReceiveWithAnyArgs(substitute).IntReturningProperty = Arg.Is(1); + SubstituteExtensions.DidNotReceiveWithAnyArgs(substitute).IntReturningProperty = Arg.Do(_ => { }); + SubstituteExtensions.DidNotReceiveWithAnyArgs(substitute) + .ObjectReturningMethodWithArguments(Arg.Is(1), Arg.Is(1), Arg.Do(_ => { })); + + _ = SubstituteExtensions.DidNotReceiveWithAnyArgs(substitute)[Arg.Is(1)]; + _ = SubstituteExtensions.DidNotReceiveWithAnyArgs(substitute)[Arg.Do(_ => { })]; + SubstituteExtensions.DidNotReceiveWithAnyArgs(substitute).IntReturningProperty = Arg.Is(1); + SubstituteExtensions.DidNotReceiveWithAnyArgs(substitute).IntReturningProperty = Arg.Do(_ => { }); + SubstituteExtensions.DidNotReceiveWithAnyArgs(substitute) + .ObjectReturningMethodWithArguments(Arg.Is(1), Arg.Is(1), Arg.Do(_ => { })); + } + } +} diff --git a/benchmarks/NSubstitute.Analyzers.Benchmarks.Source.CSharp/Models/Foo.cs b/benchmarks/NSubstitute.Analyzers.Benchmarks.Source.CSharp/Models/Foo.cs index 06c97121..4c08092d 100644 --- a/benchmarks/NSubstitute.Analyzers.Benchmarks.Source.CSharp/Models/Foo.cs +++ b/benchmarks/NSubstitute.Analyzers.Benchmarks.Source.CSharp/Models/Foo.cs @@ -42,7 +42,7 @@ public Task GenericTaskReturningAsyncMethod() public ConfiguredCall ConfiguredCallReturningProperty { get; } - public int IntReturningProperty { get; } + public int IntReturningProperty { get; set; } public IFoo ObjectReturningProperty { get; } diff --git a/benchmarks/NSubstitute.Analyzers.Benchmarks.Source.CSharp/Models/IFoo.cs b/benchmarks/NSubstitute.Analyzers.Benchmarks.Source.CSharp/Models/IFoo.cs index 436fb090..3dc8aff5 100644 --- a/benchmarks/NSubstitute.Analyzers.Benchmarks.Source.CSharp/Models/IFoo.cs +++ b/benchmarks/NSubstitute.Analyzers.Benchmarks.Source.CSharp/Models/IFoo.cs @@ -18,7 +18,7 @@ public interface IFoo ConfiguredCall ConfiguredCallReturningProperty { get; } - int IntReturningProperty { get; } + int IntReturningProperty { get; set; } IFoo ObjectReturningProperty { get; } diff --git a/benchmarks/NSubstitute.Analyzers.Benchmarks.Source.VisualBasic/DiagnosticsSources/WithAnyArgsDiagnosticsSource.vb b/benchmarks/NSubstitute.Analyzers.Benchmarks.Source.VisualBasic/DiagnosticsSources/WithAnyArgsDiagnosticsSource.vb new file mode 100644 index 00000000..263a5383 --- /dev/null +++ b/benchmarks/NSubstitute.Analyzers.Benchmarks.Source.VisualBasic/DiagnosticsSources/WithAnyArgsDiagnosticsSource.vb @@ -0,0 +1,54 @@ +Imports System +Imports NSubstitute.Analyzers.Benchmarks.Source.VisualBasic.Models + +Namespace DiagnosticsSources + Public Class WithAnyArgsDiagnosticsSource + Public Sub NS5004_InvalidArgumentMatcherUsedWithAnyArgs() + Dim substitute = NSubstitute.Substitute.[For](Of IFoo)() + Dim fist = substitute.DidNotReceiveWithAnyArgs()(Arg.[Is](1)) + Dim second = substitute.DidNotReceiveWithAnyArgs()(Arg.[Do](Of Integer)(Function(x) + Throw New Exception + End Function)) + substitute.DidNotReceiveWithAnyArgs().IntReturningProperty = Arg.[Is](1) + substitute.DidNotReceiveWithAnyArgs().IntReturningProperty = Arg.[Do](Of Integer)(Function(x) + Throw New Exception + End Function) + substitute.DidNotReceiveWithAnyArgs().ObjectReturningMethodWithArguments(Arg.[Is](1), Arg.[Is](1), Arg.[Do](Of Integer)(Function(x) + Throw New Exception + End Function)) + Dim third = substitute.DidNotReceiveWithAnyArgs()(Arg.[Is](1)) + Dim fourth = substitute.DidNotReceiveWithAnyArgs()(Arg.[Do](Of Integer)(Function(x) + Throw New Exception() + End Function)) + substitute.DidNotReceiveWithAnyArgs().IntReturningProperty = Arg.[Is](1) + substitute.DidNotReceiveWithAnyArgs().IntReturningProperty = Arg.[Do](Of Integer)(Function(x) + Throw New Exception() + End Function) + substitute.DidNotReceiveWithAnyArgs().ObjectReturningMethodWithArguments(Arg.[Is](1), Arg.[Is](1), Arg.[Do](Of Integer)(Function(x) + Throw New Exception() + End Function)) + Dim fifth = SubstituteExtensions.DidNotReceiveWithAnyArgs(substitute)(Arg.[Is](1)) + Dim sixth = SubstituteExtensions.DidNotReceiveWithAnyArgs(substitute)(Arg.[Do](Of Integer)(Function(x) + Throw New Exception() + End Function)) + SubstituteExtensions.DidNotReceiveWithAnyArgs(substitute).IntReturningProperty = Arg.[Is](1) + SubstituteExtensions.DidNotReceiveWithAnyArgs(substitute).IntReturningProperty = Arg.[Do](Of Integer)(Function(x) + Throw New Exception() + End Function) + SubstituteExtensions.DidNotReceiveWithAnyArgs(substitute).ObjectReturningMethodWithArguments(Arg.[Is](1), Arg.[Is](1), Arg.[Do](Of Integer)(Function(x) + Throw New Exception() + End Function)) + Dim seventh = SubstituteExtensions.DidNotReceiveWithAnyArgs(substitute)(Arg.[Is](1)) + Dim eigth = SubstituteExtensions.DidNotReceiveWithAnyArgs(substitute)(Arg.[Do](Of Integer)(Function(x) + Throw New Exception() + End Function)) + SubstituteExtensions.DidNotReceiveWithAnyArgs(substitute).IntReturningProperty = Arg.[Is](1) + SubstituteExtensions.DidNotReceiveWithAnyArgs(substitute).IntReturningProperty = Arg.[Do](Of Integer)(Function(x) + Throw New Exception() + End Function) + SubstituteExtensions.DidNotReceiveWithAnyArgs(substitute).ObjectReturningMethodWithArguments(Arg.[Is](1), Arg.[Is](1), Arg.[Do](Of Integer)(Function(x) + Throw New Exception() + End Function)) + End Sub + End Class +End Namespace diff --git a/benchmarks/NSubstitute.Analyzers.Benchmarks.Source.VisualBasic/Models/Foo.vb b/benchmarks/NSubstitute.Analyzers.Benchmarks.Source.VisualBasic/Models/Foo.vb index 36624676..519b30f4 100644 --- a/benchmarks/NSubstitute.Analyzers.Benchmarks.Source.VisualBasic/Models/Foo.vb +++ b/benchmarks/NSubstitute.Analyzers.Benchmarks.Source.VisualBasic/Models/Foo.vb @@ -62,7 +62,7 @@ Namespace Models End Get End Property - Public ReadOnly Property IntReturningProperty As Integer Implements IFoo.IntReturningProperty + Public Property IntReturningProperty As Integer Implements IFoo.IntReturningProperty Public ReadOnly Property ObjectReturningProperty As IFoo Implements IFoo.ObjectReturningProperty Default Public ReadOnly Property Item(a As Integer) As Integer Implements IFoo.Item diff --git a/benchmarks/NSubstitute.Analyzers.Benchmarks.Source.VisualBasic/Models/IFoo.vb b/benchmarks/NSubstitute.Analyzers.Benchmarks.Source.VisualBasic/Models/IFoo.vb index d24aba2b..45857605 100644 --- a/benchmarks/NSubstitute.Analyzers.Benchmarks.Source.VisualBasic/Models/IFoo.vb +++ b/benchmarks/NSubstitute.Analyzers.Benchmarks.Source.VisualBasic/Models/IFoo.vb @@ -16,7 +16,7 @@ Namespace Models ReadOnly Property ConfiguredCallReturningProperty As ConfiguredCall - ReadOnly Property IntReturningProperty As Integer + Property IntReturningProperty As Integer ReadOnly Property ObjectReturningProperty As IFoo diff --git a/benchmarks/NSubstitute.Analyzers.Benchmarks/CSharp/CSharpDiagnosticAnalyzersBenchmarks.cs b/benchmarks/NSubstitute.Analyzers.Benchmarks/CSharp/CSharpDiagnosticAnalyzersBenchmarks.cs index a5988118..252fe780 100644 --- a/benchmarks/NSubstitute.Analyzers.Benchmarks/CSharp/CSharpDiagnosticAnalyzersBenchmarks.cs +++ b/benchmarks/NSubstitute.Analyzers.Benchmarks/CSharp/CSharpDiagnosticAnalyzersBenchmarks.cs @@ -33,6 +33,8 @@ public class CSharpDiagnosticAnalyzersBenchmarks : AbstractDiagnosticAnalyzersBe protected override AnalyzerBenchmark SyncOverAsyncThrowsAnalyzerBenchmark { get; } + protected override AnalyzerBenchmark WithAnyArgsAnalyzerBenchmark { get; } + protected override AbstractSolutionLoader SolutionLoader { get; } protected override string SourceProjectFolderName { get; } @@ -58,5 +60,6 @@ public CSharpDiagnosticAnalyzersBenchmarks() ReceivedInReceivedInOrderAnalyzerBenchmark = AnalyzerBenchmark.CreateBenchmark(Solution, new ReceivedInReceivedInOrderAnalyzer()); AsyncReceivedInOrderCallbackAnalyzerBenchmark = AnalyzerBenchmark.CreateBenchmark(Solution, new AsyncReceivedInOrderCallbackAnalyzer()); SyncOverAsyncThrowsAnalyzerBenchmark = AnalyzerBenchmark.CreateBenchmark(Solution, new SyncOverAsyncThrowsAnalyzer()); - } + WithAnyArgsAnalyzerBenchmark = AnalyzerBenchmark.CreateBenchmark(Solution, new WithAnyArgsArgumentMatcherAnalyzer()); + } } \ No newline at end of file diff --git a/benchmarks/NSubstitute.Analyzers.Benchmarks/Shared/AbstractDiagnosticAnalyzersBenchmarks.cs b/benchmarks/NSubstitute.Analyzers.Benchmarks/Shared/AbstractDiagnosticAnalyzersBenchmarks.cs index 2f463fdd..ce156a07 100644 --- a/benchmarks/NSubstitute.Analyzers.Benchmarks/Shared/AbstractDiagnosticAnalyzersBenchmarks.cs +++ b/benchmarks/NSubstitute.Analyzers.Benchmarks/Shared/AbstractDiagnosticAnalyzersBenchmarks.cs @@ -40,6 +40,8 @@ public abstract class AbstractDiagnosticAnalyzersBenchmarks protected abstract AnalyzerBenchmark SyncOverAsyncThrowsAnalyzerBenchmark { get; } + protected abstract AnalyzerBenchmark WithAnyArgsAnalyzerBenchmark { get; } + protected abstract AbstractSolutionLoader SolutionLoader { get; } protected abstract string SourceProjectFolderName { get; } diff --git a/benchmarks/NSubstitute.Analyzers.Benchmarks/VisualBasic/VisualBasicDiagnosticAnalyzersBenchmarks.cs b/benchmarks/NSubstitute.Analyzers.Benchmarks/VisualBasic/VisualBasicDiagnosticAnalyzersBenchmarks.cs index 1e9055b7..0d1a495d 100644 --- a/benchmarks/NSubstitute.Analyzers.Benchmarks/VisualBasic/VisualBasicDiagnosticAnalyzersBenchmarks.cs +++ b/benchmarks/NSubstitute.Analyzers.Benchmarks/VisualBasic/VisualBasicDiagnosticAnalyzersBenchmarks.cs @@ -33,6 +33,8 @@ public class VisualBasicDiagnosticAnalyzersBenchmarks : AbstractDiagnosticAnalyz protected override AnalyzerBenchmark SyncOverAsyncThrowsAnalyzerBenchmark { get; } + protected override AnalyzerBenchmark WithAnyArgsAnalyzerBenchmark { get; } + protected override AbstractSolutionLoader SolutionLoader { get; } protected override string SourceProjectFolderName { get; } @@ -58,5 +60,6 @@ public VisualBasicDiagnosticAnalyzersBenchmarks() ReceivedInReceivedInOrderAnalyzerBenchmark = AnalyzerBenchmark.CreateBenchmark(Solution, new ReceivedInReceivedInOrderAnalyzer()); AsyncReceivedInOrderCallbackAnalyzerBenchmark = AnalyzerBenchmark.CreateBenchmark(Solution, new AsyncReceivedInOrderCallbackAnalyzer()); SyncOverAsyncThrowsAnalyzerBenchmark = AnalyzerBenchmark.CreateBenchmark(Solution, new SyncOverAsyncThrowsAnalyzer()); + WithAnyArgsAnalyzerBenchmark = AnalyzerBenchmark.CreateBenchmark(Solution, new WithAnyArgsArgumentMatcherAnalyzer()); } } \ No newline at end of file diff --git a/documentation/rules/NS5004.md b/documentation/rules/NS5004.md new file mode 100644 index 00000000..e0206469 --- /dev/null +++ b/documentation/rules/NS5004.md @@ -0,0 +1,48 @@ +# NS5004 + + + + + + + + + + +
CheckIdNS5004
CategoryUsage
+ +## Cause + +Argument matcher used with WithAnyArgs method. + +## Rule description + +A violation of this rule occurs when `DidNotReceiveWithAnyArgs` or `ReceivedWithAnyArgs` is used in combination with arg matchers other than `Arg.Any`. + +## How to fix violations + +To fix a violation of this rule, replace arg matchers used in combination with `DidNotReceiveWithAnyArgs` or `ReceivedWithAnyArgs` with `Arg.Any` + +For example: + +````c# +// Incorrect: +sub.DidNotReceiveWithAnyArgs().Bar(Arg.Is(1)); + +// Correct: +sub.DidNotReceiveWithAnyArgs().Bar(Arg.Any()); +```` + +## How to suppress violations + +This warning can be suppressed by disabling the warning in the **ruleset** file for the project. +The warning can also be suppressed programmatically for an assembly: +````c# +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "NS5004:Argument matcher used with WithAnyArgs method.", Justification = "Reviewed")] +```` + +Or for a specific code block: +````c# +#pragma warning disable NS5004 // Argument matcher used with WithAnyArgs method. +// the code which produces warning +#pragma warning restore NS5004 // Argument matcher used with WithAnyArgs method. diff --git a/documentation/rules/README.md b/documentation/rules/README.md index ad3710bd..15ae2a1f 100644 --- a/documentation/rules/README.md +++ b/documentation/rules/README.md @@ -2,7 +2,7 @@ ## Rules | ID | Category | Cause | -|---|---|---| +|---|---|--| | [NS1000](NS1000.md) | Non-substitutable member | Substituting for non-virtual member of a class. | | [NS1001](NS1001.md) | Non-substitutable member | Checking received calls for non-virtual member of a class. | | [NS1002](NS1002.md) | Non-substitutable member | Substituting for non-virtual member of a class. | @@ -29,3 +29,4 @@ | [NS5001](NS5001.md) | Usage | Usage of received-like method in `Received.InOrder` callback. | | [NS5002](NS5002.md) | Usage | Usage of async callback in `Received.InOrder` method. | | [NS5003](NS5003.md) | Usage | Sync `Throws` used in async method. | +| [NS5004](NS5004.md) | Usage | Argument matcher used with WithAnyArgs method. | diff --git a/src/NSubstitute.Analyzers.CSharp/DiagnosticAnalyzers/WithAnyArgsArgumentMatcherAnalyzer.cs b/src/NSubstitute.Analyzers.CSharp/DiagnosticAnalyzers/WithAnyArgsArgumentMatcherAnalyzer.cs new file mode 100644 index 00000000..fb347b87 --- /dev/null +++ b/src/NSubstitute.Analyzers.CSharp/DiagnosticAnalyzers/WithAnyArgsArgumentMatcherAnalyzer.cs @@ -0,0 +1,26 @@ +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using NSubstitute.Analyzers.Shared.DiagnosticAnalyzers; + +namespace NSubstitute.Analyzers.CSharp.DiagnosticAnalyzers; + +[DiagnosticAnalyzer(LanguageNames.CSharp)] +internal sealed class WithAnyArgsArgumentMatcherAnalyzer : AbstractWithAnyArgsArgumentMatcherAnalyzer +{ + internal static ImmutableHashSet MaybeAllowedAncestors { get; } = ImmutableHashSet.Create( + (int)SyntaxKind.InvocationExpression, + (int)SyntaxKind.ElementAccessExpression, + (int)SyntaxKind.SimpleAssignmentExpression); + + public WithAnyArgsArgumentMatcherAnalyzer() + : base(CSharp.DiagnosticDescriptorsProvider.Instance, SubstitutionNodeFinder.Instance) + { + } + + protected override ImmutableHashSet MaybeAllowedArgMatcherAncestors { get; } = MaybeAllowedAncestors; + + protected override SyntaxKind InvocationExpressionKind { get; } = SyntaxKind.InvocationExpression; +} \ No newline at end of file diff --git a/src/NSubstitute.Analyzers.Shared/AbstractDiagnosticDescriptorsProvider.cs b/src/NSubstitute.Analyzers.Shared/AbstractDiagnosticDescriptorsProvider.cs index ce607118..f63dbb34 100644 --- a/src/NSubstitute.Analyzers.Shared/AbstractDiagnosticDescriptorsProvider.cs +++ b/src/NSubstitute.Analyzers.Shared/AbstractDiagnosticDescriptorsProvider.cs @@ -52,6 +52,8 @@ internal class AbstractDiagnosticDescriptorsProvider : IDiagnosticDescriptors public DiagnosticDescriptor NonSubstitutableMemberArgumentMatcherUsage { get; } = DiagnosticDescriptors.NonSubstitutableMemberArgumentMatcherUsage; + public DiagnosticDescriptor WithAnyArgsArgumentMatcherUsage { get; } = DiagnosticDescriptors.WithAnyArgsArgumentMatcherUsage; + public DiagnosticDescriptor ReceivedUsedInReceivedInOrder { get; } = DiagnosticDescriptors.ReceivedUsedInReceivedInOrder; public DiagnosticDescriptor AsyncCallbackUsedInReceivedInOrder { get; } = DiagnosticDescriptors.AsyncCallbackUsedInReceivedInOrder; diff --git a/src/NSubstitute.Analyzers.Shared/DiagnosticAnalyzers/AbstractWithAnyArgsArgumentMatcherAnalyzer.cs b/src/NSubstitute.Analyzers.Shared/DiagnosticAnalyzers/AbstractWithAnyArgsArgumentMatcherAnalyzer.cs new file mode 100644 index 00000000..44a94e1b --- /dev/null +++ b/src/NSubstitute.Analyzers.Shared/DiagnosticAnalyzers/AbstractWithAnyArgsArgumentMatcherAnalyzer.cs @@ -0,0 +1,224 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; +using NSubstitute.Analyzers.Shared.Extensions; + +namespace NSubstitute.Analyzers.Shared.DiagnosticAnalyzers; + +internal abstract class AbstractWithAnyArgsArgumentMatcherAnalyzer : AbstractDiagnosticAnalyzer + where TInvocationExpressionSyntax : SyntaxNode + where TSyntaxKind : struct, Enum +{ + private readonly ISubstitutionNodeFinder _substitutionNodeFinder; + private readonly Action _analyzeInvocationAction; + + protected abstract ImmutableHashSet MaybeAllowedArgMatcherAncestors { get; } + + protected abstract TSyntaxKind InvocationExpressionKind { get; } + + protected AbstractWithAnyArgsArgumentMatcherAnalyzer( + IDiagnosticDescriptorsProvider diagnosticDescriptorsProvider, + ISubstitutionNodeFinder substitutionNodeFinder) + : base(diagnosticDescriptorsProvider) + { + _substitutionNodeFinder = substitutionNodeFinder; + _analyzeInvocationAction = AnalyzeInvocation; + SupportedDiagnostics = ImmutableArray.Create(DiagnosticDescriptorsProvider.WithAnyArgsArgumentMatcherUsage); + } + + public override ImmutableArray SupportedDiagnostics { get; } + + protected override void InitializeAnalyzer(AnalysisContext context) + { + context.RegisterSyntaxNodeAction(_analyzeInvocationAction, InvocationExpressionKind); + } + + private void AnalyzeInvocation(SyntaxNodeAnalysisContext syntaxNodeContext) + { + var invocationExpression = (TInvocationExpressionSyntax)syntaxNodeContext.Node; + var methodSymbolInfo = syntaxNodeContext.SemanticModel.GetSymbolInfo(invocationExpression); + + if (methodSymbolInfo.Symbol is not IMethodSymbol methodSymbol) + { + return; + } + + if (methodSymbol.IsWithAnyArgsIncompatibleArgMatcherLikeMethod()) + { + AnalyzeArgLikeMethodForReceivedWithAnyArgs(syntaxNodeContext, invocationExpression, methodSymbol); + return; + } + + if (methodSymbol.IsReturnForAnyArgsLikeMethod() || methodSymbol.IsThrowsForAnyArgsMethod()) + { + AnalyzeReturnsLikeMethod(syntaxNodeContext, invocationExpression); + return; + } + + if (methodSymbol.IsWhenForAnyArgsLikeMethod()) + { + AnalyzeWhenLikeMethod(syntaxNodeContext, invocationExpression); + } + } + + private void AnalyzeWhenLikeMethod( + SyntaxNodeAnalysisContext syntaxNodeContext, + TInvocationExpressionSyntax whenMethod) + { + ImmutableArray Arguments(IPropertyReferenceOperation propertyReferenceOperation) + { + var builder = ImmutableArray.CreateBuilder(propertyReferenceOperation.Arguments.Length); + builder.AddRange(propertyReferenceOperation.Arguments); + + if (propertyReferenceOperation.Parent is IAssignmentOperation assignmentOperation) + { + builder.Add(assignmentOperation.Value); + } + + return builder.ToImmutable(); + } + + if (syntaxNodeContext.SemanticModel.GetOperation(whenMethod) is not IInvocationOperation invocationOperation) + { + return; + } + + foreach (var syntaxNode in _substitutionNodeFinder.FindForWhenExpression(syntaxNodeContext, invocationOperation)) + { + var substitutedOperation = syntaxNodeContext.SemanticModel.GetOperation(syntaxNode); + + IReadOnlyList arguments = substitutedOperation switch + { + IInvocationOperation substituteInvocationOperation => substituteInvocationOperation.Arguments, + IPropertyReferenceOperation propertyReferenceOperation => Arguments(propertyReferenceOperation), + _ => ImmutableArray.Empty + }; + + foreach (var operation in arguments) + { + AnalyzeArgument(syntaxNodeContext, operation); + } + } + } + + private void AnalyzeReturnsLikeMethod( + SyntaxNodeAnalysisContext syntaxNodeContext, + TInvocationExpressionSyntax returnsInvocationExpression) + { + if (syntaxNodeContext.SemanticModel.GetOperation(returnsInvocationExpression) is not IInvocationOperation + invocationOperation) + { + return; + } + + var substitutedOperation = + _substitutionNodeFinder.FindOperationForStandardExpression(invocationOperation); + + IReadOnlyList arguments = substitutedOperation switch + { + IInvocationOperation substituteInvocationOperation => substituteInvocationOperation.Arguments, + IPropertyReferenceOperation propertyReferenceOperation => propertyReferenceOperation.Arguments, + _ => ImmutableArray.Empty + }; + + foreach (var argumentOperation in arguments) + { + AnalyzeArgument(syntaxNodeContext, argumentOperation); + } + } + + private void AnalyzeArgument(SyntaxNodeAnalysisContext syntaxNodeContext, IOperation operation) + { + if (operation is IConversionOperation conversionOperation) + { + AnalyzeArgument(syntaxNodeContext, conversionOperation.Operand); + return; + } + + if (operation is IArgumentOperation argumentOperation) + { + AnalyzeArgument(syntaxNodeContext, argumentOperation.Value); + return; + } + + if (operation is IInvocationOperation argInvocationOperation && + argInvocationOperation.TargetMethod.IsWithAnyArgsIncompatibleArgMatcherLikeMethod()) + { + syntaxNodeContext.TryReportDiagnostic( + Diagnostic.Create( + DiagnosticDescriptorsProvider.WithAnyArgsArgumentMatcherUsage, + argInvocationOperation.Syntax.GetLocation()), + argInvocationOperation.TargetMethod); + } + } + + private void AnalyzeArgLikeMethodForReceivedWithAnyArgs( + SyntaxNodeAnalysisContext syntaxNodeContext, + TInvocationExpressionSyntax argInvocationExpression, + IMethodSymbol invocationExpressionSymbol) + { + var enclosingExpression = FindMaybeAllowedEnclosingExpression(argInvocationExpression); + + if (enclosingExpression == null) + { + return; + } + + var operation = syntaxNodeContext.SemanticModel.GetOperation(enclosingExpression); + + if (operation is IInvocationOperation invOperation && + invOperation.Instance is IInvocationOperation inv2 && + inv2.TargetMethod.IsReceivedWithAnyArgsLikeMethod()) + { + syntaxNodeContext.TryReportDiagnostic( + Diagnostic.Create( + DiagnosticDescriptorsProvider.WithAnyArgsArgumentMatcherUsage, + argInvocationExpression.GetLocation()), + invocationExpressionSymbol); + return; + } + + var memberReferenceOperation = GetMemberReferenceOperation(operation); + + if (memberReferenceOperation is not { Instance: IInvocationOperation invocationOperation } || + !invocationOperation.TargetMethod.IsReceivedWithAnyArgsLikeMethod()) + { + return; + } + + syntaxNodeContext.TryReportDiagnostic( + Diagnostic.Create( + DiagnosticDescriptorsProvider.WithAnyArgsArgumentMatcherUsage, + argInvocationExpression.GetLocation()), + invocationExpressionSymbol); + } + + private static IMemberReferenceOperation GetMemberReferenceOperation(IOperation operation) + { + return operation switch + { + IAssignmentOperation { Target: IMemberReferenceOperation memberReferenceOperation } => + memberReferenceOperation, + IBinaryOperation { LeftOperand: IMemberReferenceOperation binaryMemberReferenceOperation } => + binaryMemberReferenceOperation, + IBinaryOperation { LeftOperand: IConversionOperation { Operand: IMemberReferenceOperation conversionMemberReference } } => + conversionMemberReference, + IExpressionStatementOperation { Operation: ISimpleAssignmentOperation { Target: IMemberReferenceOperation memberReferenceOperation } } => + memberReferenceOperation, + _ => null + }; + } + + private SyntaxNode FindMaybeAllowedEnclosingExpression(TInvocationExpressionSyntax invocationExpression) => + FindEnclosingExpression(invocationExpression, MaybeAllowedArgMatcherAncestors); + + private static SyntaxNode FindEnclosingExpression(TInvocationExpressionSyntax invocationExpression, ImmutableHashSet ancestors) + { + return invocationExpression.Ancestors() + .FirstOrDefault(ancestor => ancestors.Contains(ancestor.RawKind)); + } +} \ No newline at end of file diff --git a/src/NSubstitute.Analyzers.Shared/DiagnosticCategories.cs b/src/NSubstitute.Analyzers.Shared/DiagnosticCategory.cs similarity index 100% rename from src/NSubstitute.Analyzers.Shared/DiagnosticCategories.cs rename to src/NSubstitute.Analyzers.Shared/DiagnosticCategory.cs diff --git a/src/NSubstitute.Analyzers.Shared/DiagnosticDescriptors.cs b/src/NSubstitute.Analyzers.Shared/DiagnosticDescriptors.cs index 573cb8aa..b05ae0bb 100644 --- a/src/NSubstitute.Analyzers.Shared/DiagnosticDescriptors.cs +++ b/src/NSubstitute.Analyzers.Shared/DiagnosticDescriptors.cs @@ -205,6 +205,14 @@ internal class DiagnosticDescriptors defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); + public static DiagnosticDescriptor WithAnyArgsArgumentMatcherUsage { get; } = + CreateDiagnosticDescriptor( + name: nameof(WithAnyArgsArgumentMatcherUsage), + id: DiagnosticIdentifiers.WithAnyArgsArgumentMatcherUsage, + category: DiagnosticCategory.Usage.GetDisplayName(), + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true); + public static DiagnosticDescriptor ReceivedUsedInReceivedInOrder { get; } = CreateDiagnosticDescriptor( name: nameof(ReceivedUsedInReceivedInOrder), id: DiagnosticIdentifiers.ReceivedUsedInReceivedInOrder, diff --git a/src/NSubstitute.Analyzers.Shared/DiagnosticIdentifiers.cs b/src/NSubstitute.Analyzers.Shared/DiagnosticIdentifiers.cs index c704dce0..28d5a613 100644 --- a/src/NSubstitute.Analyzers.Shared/DiagnosticIdentifiers.cs +++ b/src/NSubstitute.Analyzers.Shared/DiagnosticIdentifiers.cs @@ -32,4 +32,5 @@ internal class DiagnosticIdentifiers public const string ReceivedUsedInReceivedInOrder = "NS5001"; public const string AsyncCallbackUsedInReceivedInOrder = "NS5002"; public const string SyncOverAsyncThrows = "NS5003"; + public const string WithAnyArgsArgumentMatcherUsage = "NS5004"; } \ No newline at end of file diff --git a/src/NSubstitute.Analyzers.Shared/Extensions/SubstituteSymbolExtensions.cs b/src/NSubstitute.Analyzers.Shared/Extensions/SubstituteSymbolExtensions.cs index 86d8ba04..a628ba77 100644 --- a/src/NSubstitute.Analyzers.Shared/Extensions/SubstituteSymbolExtensions.cs +++ b/src/NSubstitute.Analyzers.Shared/Extensions/SubstituteSymbolExtensions.cs @@ -37,6 +37,11 @@ public static bool IsReturnLikeMethod(this ISymbol symbol) return IsMember(symbol, MetadataNames.ReturnsMethodNames); } + public static bool IsReturnForAnyArgsLikeMethod(this ISymbol symbol) + { + return IsMember(symbol, MetadataNames.ReturnsForAnyArgsMethodNames); + } + public static bool IsThrowLikeMethod(this ISymbol symbol) { return IsMember(symbol, MetadataNames.ThrowsMethodNames); @@ -56,6 +61,11 @@ public static bool IsReceivedLikeMethod(this ISymbol symbol) IsMember(symbol, MetadataNames.ReceivedWithQuantityMethodNames); } + public static bool IsReceivedWithAnyArgsLikeMethod(this ISymbol symbol) + { + return IsMember(symbol, MetadataNames.ReceivedWithAnyArgsMethodNames) || IsMember(symbol, MetadataNames.ReceivedWithAnyArgsQuantityMethodNames); + } + public static bool IsReceivedInOrderMethod(this ISymbol symbol) { return IsMember(symbol, MetadataNames.NSubstituteInOrderMethod, MetadataNames.NSubstituteReceivedFullTypeName); @@ -66,11 +76,26 @@ public static bool IsWhenLikeMethod(this ISymbol symbol) return IsMember(symbol, MetadataNames.WhenMethodNames); } + public static bool IsWhenForAnyArgsLikeMethod(this ISymbol symbol) + { + return IsMember(symbol, MetadataNames.WhenForAnyArgsMethodNames); + } + public static bool IsArgMatcherLikeMethod(this ISymbol symbol) { return IsMember(symbol, MetadataNames.ArgMatchersMethodNames) || IsMember(symbol, MetadataNames.ArgMatchersCompatMethodNames); } + public static bool IsWithAnyArgsIncompatibleArgMatcherLikeMethod(this ISymbol symbol) + { + return IsMember(symbol, MetadataNames.ArgMatchersMethodNames2) || IsMember(symbol, MetadataNames.ArgMatchersCompatMethodNames2); + } + + public static bool IsWithAnyArgsIncompatibleArgMatcherLikeMethodReturns(this ISymbol symbol) + { + return IsMember(symbol, MetadataNames.ArgMatchersMethodNames2) || IsMember(symbol, MetadataNames.ArgMatchersCompatMethodNames2); + } + public static bool IsArgDoLikeMethod(this ISymbol symbol) { return IsMember(symbol, MetadataNames.ArgDoMethodName, MetadataNames.NSubstituteArgFullTypeName) || diff --git a/src/NSubstitute.Analyzers.Shared/IDiagnosticDescriptorsProvider.cs b/src/NSubstitute.Analyzers.Shared/IDiagnosticDescriptorsProvider.cs index 8521ab63..2f326e70 100644 --- a/src/NSubstitute.Analyzers.Shared/IDiagnosticDescriptorsProvider.cs +++ b/src/NSubstitute.Analyzers.Shared/IDiagnosticDescriptorsProvider.cs @@ -52,6 +52,8 @@ internal interface IDiagnosticDescriptorsProvider DiagnosticDescriptor NonSubstitutableMemberArgumentMatcherUsage { get; } + DiagnosticDescriptor WithAnyArgsArgumentMatcherUsage { get; } + DiagnosticDescriptor ReceivedUsedInReceivedInOrder { get; } DiagnosticDescriptor AsyncCallbackUsedInReceivedInOrder { get; } diff --git a/src/NSubstitute.Analyzers.Shared/MetadataNames.cs b/src/NSubstitute.Analyzers.Shared/MetadataNames.cs index 4d36d511..89df42d3 100644 --- a/src/NSubstitute.Analyzers.Shared/MetadataNames.cs +++ b/src/NSubstitute.Analyzers.Shared/MetadataNames.cs @@ -55,6 +55,12 @@ internal class MetadataNames [NSubstituteReturnsNullForAnyArgsMethod] = NSubstituteReturnsExtensionsFullTypeName }; + public static readonly IReadOnlyDictionary ReturnsForAnyArgsMethodNames = new Dictionary + { + [NSubstituteReturnsForAnyArgsMethod] = NSubstituteSubstituteExtensionsFullTypeName, + [NSubstituteReturnsNullForAnyArgsMethod] = NSubstituteReturnsExtensionsFullTypeName + }; + public static readonly IReadOnlyDictionary ThrowsMethodNames = new Dictionary { [NSubstituteThrowsMethod] = NSubstituteExceptionExtensionsFullTypeName, @@ -75,12 +81,29 @@ internal class MetadataNames [NSubstituteReceivedWithAnyArgsMethod] = NSubstituteReceivedExtensionsFullTypeName, }; + public static readonly IReadOnlyDictionary ReceivedWithAnyArgsMethodNames = new Dictionary + { + [NSubstituteReceivedWithAnyArgsMethod] = NSubstituteSubstituteExtensionsFullTypeName, + [NSubstituteDidNotReceiveWithAnyArgsMethod] = NSubstituteSubstituteExtensionsFullTypeName + }; + + public static readonly IReadOnlyDictionary ReceivedWithAnyArgsQuantityMethodNames = new Dictionary + { + [NSubstituteReceivedWithAnyArgsMethod] = NSubstituteReceivedExtensionsFullTypeName, + [NSubstituteDidNotReceiveWithAnyArgsMethod] = NSubstituteReceivedExtensionsFullTypeName + }; + public static readonly IReadOnlyDictionary WhenMethodNames = new Dictionary { [NSubstituteWhenMethod] = NSubstituteSubstituteExtensionsFullTypeName, [NSubstituteWhenForAnyArgsMethod] = NSubstituteSubstituteExtensionsFullTypeName }; + public static readonly IReadOnlyDictionary WhenForAnyArgsMethodNames = new Dictionary + { + [NSubstituteWhenForAnyArgsMethod] = NSubstituteSubstituteExtensionsFullTypeName + }; + public static readonly IReadOnlyDictionary ArgMatchersMethodNames = new Dictionary { [ArgIsMethodName] = NSubstituteArgFullTypeName, @@ -99,6 +122,24 @@ internal class MetadataNames [ArgInvokeDelegateMethodName] = NSubstituteArgCompatFullTypeName }; + // TODO + public static readonly IReadOnlyDictionary ArgMatchersMethodNames2 = new Dictionary + { + [ArgIsMethodName] = NSubstituteArgFullTypeName, + [ArgDoMethodName] = NSubstituteArgFullTypeName, + [ArgInvokeMethodName] = NSubstituteArgFullTypeName, + [ArgInvokeDelegateMethodName] = NSubstituteArgFullTypeName + }; + + // TODO + public static readonly IReadOnlyDictionary ArgMatchersCompatMethodNames2 = new Dictionary + { + [ArgIsMethodName] = NSubstituteArgCompatFullTypeName, + [ArgDoMethodName] = NSubstituteArgCompatFullTypeName, + [ArgInvokeMethodName] = NSubstituteArgCompatFullTypeName, + [ArgInvokeDelegateMethodName] = NSubstituteArgCompatFullTypeName + }; + public static readonly IReadOnlyDictionary InitialReEntryMethodNames = new Dictionary { [NSubstituteReturnsMethod] = NSubstituteSubstituteExtensionsFullTypeName, diff --git a/src/NSubstitute.Analyzers.Shared/Resources.Designer.cs b/src/NSubstitute.Analyzers.Shared/Resources.Designer.cs index ed819ac7..d35312c9 100644 --- a/src/NSubstitute.Analyzers.Shared/Resources.Designer.cs +++ b/src/NSubstitute.Analyzers.Shared/Resources.Designer.cs @@ -424,6 +424,24 @@ internal static string NonSubstitutableMemberArgumentMatcherUsageTitle { } } + internal static string WithAnyArgsArgumentMatcherUsageDescription { + get { + return ResourceManager.GetString("WithAnyArgsArgumentMatcherUsageDescription", resourceCulture); + } + } + + internal static string WithAnyArgsArgumentMatcherUsageMessageFormat { + get { + return ResourceManager.GetString("WithAnyArgsArgumentMatcherUsageMessageFormat", resourceCulture); + } + } + + internal static string WithAnyArgsArgumentMatcherUsageTitle { + get { + return ResourceManager.GetString("WithAnyArgsArgumentMatcherUsageTitle", resourceCulture); + } + } + internal static string ReceivedUsedInReceivedInOrderDescription { get { return ResourceManager.GetString("ReceivedUsedInReceivedInOrderDescription", resourceCulture); diff --git a/src/NSubstitute.Analyzers.Shared/Resources.resx b/src/NSubstitute.Analyzers.Shared/Resources.resx index e35f5169..453b3664 100644 --- a/src/NSubstitute.Analyzers.Shared/Resources.resx +++ b/src/NSubstitute.Analyzers.Shared/Resources.resx @@ -375,6 +375,19 @@ Argument matcher used with a non-virtual member of a class. The title of the diagnostic. + + + Argument matcher used with WithAnyArgs method. + An optional longer localizable description of the diagnostic. + + + Argument matcher used with WithAnyArgs method. + The format-able message the diagnostic displays. + + + Argument matcher used with WithAnyArgs method. + The title of the diagnostic. + Received-like method used in Received.InOrder block. @@ -418,4 +431,4 @@ Synchronous exception thrown from async method. - \ No newline at end of file + diff --git a/src/NSubstitute.Analyzers.VisualBasic/DiagnosticAnalyzers/WithAnyArgsArgumentMatcherAnalyzer.cs b/src/NSubstitute.Analyzers.VisualBasic/DiagnosticAnalyzers/WithAnyArgsArgumentMatcherAnalyzer.cs new file mode 100644 index 00000000..3b8faccb --- /dev/null +++ b/src/NSubstitute.Analyzers.VisualBasic/DiagnosticAnalyzers/WithAnyArgsArgumentMatcherAnalyzer.cs @@ -0,0 +1,27 @@ +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.VisualBasic; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; +using NSubstitute.Analyzers.Shared.DiagnosticAnalyzers; + +namespace NSubstitute.Analyzers.VisualBasic.DiagnosticAnalyzers; + +[DiagnosticAnalyzer(LanguageNames.VisualBasic)] +internal sealed class WithAnyArgsArgumentMatcherAnalyzer : AbstractWithAnyArgsArgumentMatcherAnalyzer +{ + internal static ImmutableHashSet MaybeAllowedAncestors { get; } = ImmutableHashSet.Create( + (int)SyntaxKind.InvocationExpression, + (int)SyntaxKind.ObjectCreationExpression, + (int)SyntaxKind.EqualsExpression, + (int)SyntaxKind.SimpleAssignmentStatement); + + public WithAnyArgsArgumentMatcherAnalyzer() + : base(VisualBasic.DiagnosticDescriptorsProvider.Instance, SubstitutionNodeFinder.Instance) + { + } + + protected override ImmutableHashSet MaybeAllowedArgMatcherAncestors { get; } = MaybeAllowedAncestors; + + protected override SyntaxKind InvocationExpressionKind { get; } = SyntaxKind.InvocationExpression; +} \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.Benchmarks/ConventionTests/BenchmarksConventionTests.cs b/tests/NSubstitute.Analyzers.Tests.Benchmarks/ConventionTests/BenchmarksConventionTests.cs index f03b1a71..3d6a3d4c 100644 --- a/tests/NSubstitute.Analyzers.Tests.Benchmarks/ConventionTests/BenchmarksConventionTests.cs +++ b/tests/NSubstitute.Analyzers.Tests.Benchmarks/ConventionTests/BenchmarksConventionTests.cs @@ -20,6 +20,9 @@ public class BenchmarksConventionTests private static readonly Assembly[] AnalyzersAssemblies; private static readonly BenchmarkDescriptor[] BenchmarkDescriptors; + private static readonly IReadOnlyList SupportedLanguages = + new[] { LanguageNames.CSharp, LanguageNames.VisualBasic }; + static BenchmarksConventionTests() { var benchmarksAssembly = typeof(Program).Assembly; @@ -37,15 +40,24 @@ public void BenchmarksShouldBeDefinedForAllAnalyzers() } [Fact] - public void BenchmarksShouldProduceAllDiagnostics() + public async Task BenchmarksShouldProduceAllDiagnostics() { var allDiagnosticIds = BenchmarkDescriptors .SelectMany(benchmark => benchmark.Benchmark.Analyzer.SupportedDiagnostics.Select(diag => diag.Id)) .Distinct() .OrderBy(diag => diag) .ToList(); + var expectedDiagnosticIds = SupportedLanguages + .Select(language => new DiagnosticIdsWithLanguage(language, allDiagnosticIds)).ToList(); + + var producedDiagnosticIds = await GetProducedDiagnosticIds(); + + producedDiagnosticIds.Should().BeEquivalentTo(expectedDiagnosticIds, opts => opts.WithStrictOrdering()); + } - var producedDiagnostics = BenchmarkDescriptors.Select(async benchmark => + private async Task> GetProducedDiagnosticIds() + { + var producedDiagnostics = await BenchmarkDescriptors.ToAsyncEnumerable().SelectAwait(async benchmark => { var propertyInfo = benchmark.Property.DeclaringType.GetProperty( nameof(AbstractDiagnosticAnalyzersBenchmarks.Solution), @@ -53,18 +65,17 @@ public void BenchmarksShouldProduceAllDiagnostics() var solution = propertyInfo.GetValue(benchmark.DeclaringTypeInstance) as Solution; return await GetDiagnostics(solution, benchmark.Benchmark.Analyzer); - }).SelectMany(task => task.Result).ToList(); + }).ToListAsync(); - var producedDiagnosticIds = producedDiagnostics - .SelectMany(diag => diag.Select(x => x.Id)) - .Distinct() - .OrderBy(diag => diag) + return producedDiagnostics.GroupBy(diagnostic => diagnostic.Language).Select(grouping => + new DiagnosticIdsWithLanguage( + grouping.Key, + grouping.SelectMany(diagWithAnalyzer => diagWithAnalyzer.DiagnosticIds).Distinct().OrderBy(diagId => diagId).ToList())) + .OrderBy(diag => diag.Language) .ToList(); - - producedDiagnosticIds.Should().BeEquivalentTo(allDiagnosticIds); } - private async Task>> GetDiagnostics(Solution solution, DiagnosticAnalyzer analyzer) + private async Task GetDiagnostics(Solution solution, DiagnosticAnalyzer analyzer) { var diagnostics = new List>(); foreach (var project in solution.Projects) @@ -74,7 +85,7 @@ private async Task>> GetDiagnostics(Sol diagnostics.Add(await compilationWithAnalyzers.GetAnalyzerDiagnosticsAsync()); } - return diagnostics; + return new DiagnosticsWithAnalyzer(analyzer, diagnostics.SelectMany(diag => diag).ToList()); } private static BenchmarkDescriptor[] GetAnalyzerBenchmarks(Assembly benchmarksAssembly) @@ -96,19 +107,15 @@ private static BenchmarkDescriptor[] GetAnalyzerBenchmarks(Assembly benchmarksAs return benchmarkAnalyzers; } - private class BenchmarkDescriptor - { - public FieldInfo Property { get; } - - public AnalyzerBenchmark Benchmark { get; } + private record BenchmarkDescriptor(FieldInfo Property, AnalyzerBenchmark Benchmark, AbstractDiagnosticAnalyzersBenchmarks DeclaringTypeInstance); - public AbstractDiagnosticAnalyzersBenchmarks DeclaringTypeInstance { get; } + private record DiagnosticsWithAnalyzer(DiagnosticAnalyzer Analyzer, IReadOnlyList Diagnostics) + { + public string Language { get; } = + Analyzer.GetType().GetCustomAttribute().Languages.Single(); - public BenchmarkDescriptor(FieldInfo property, AnalyzerBenchmark benchmark, AbstractDiagnosticAnalyzersBenchmarks declaringTypeInstance) - { - Property = property; - Benchmark = benchmark; - DeclaringTypeInstance = declaringTypeInstance; - } + public IReadOnlyList DiagnosticIds => Diagnostics.Select(diag => diag.Id).ToList(); } + + private record DiagnosticIdsWithLanguage(string Language, IReadOnlyList DiagnosticIds); } \ No newline at end of file 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 87b33dfd..796fc888 100644 --- a/tests/NSubstitute.Analyzers.Tests.Benchmarks/NSubstitute.Analyzers.Tests.Benchmarks.csproj +++ b/tests/NSubstitute.Analyzers.Tests.Benchmarks/NSubstitute.Analyzers.Tests.Benchmarks.csproj @@ -7,8 +7,9 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + + diff --git a/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/NonSubstitutableMemberArgumentMatcherAnalyzerTests/NonSubstitutableMemberArgumentMatcherDiagnosticVerifier.cs b/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/NonSubstitutableMemberArgumentMatcherAnalyzerTests/NonSubstitutableMemberArgumentMatcherDiagnosticVerifier.cs index 576d852b..9704dc2e 100644 --- a/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/NonSubstitutableMemberArgumentMatcherAnalyzerTests/NonSubstitutableMemberArgumentMatcherDiagnosticVerifier.cs +++ b/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/NonSubstitutableMemberArgumentMatcherAnalyzerTests/NonSubstitutableMemberArgumentMatcherDiagnosticVerifier.cs @@ -133,7 +133,7 @@ public abstract class NonSubstitutableMemberArgumentMatcherDiagnosticVerifier : [CombinatoryTheory] [MemberData(nameof(CorrectlyUsedArgTestCasesWithoutDelegates))] - public abstract Task ReportsNoDiagnostics_WhenAssigningArgMatchersToSubstitutableMemberPrecededByReceivedLikeMethod(string receivedMethod, string arg); + public abstract Task ReportsDiagnostics_WhenAssigningInvalidArgMatchersToMemberPrecededByWithAnyArgsLikeMethod(string receivedMethod, string arg); public static IEnumerable MisusedArgTestCases { diff --git a/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/NonSubstitutableMemberArgumentMatcherAnalyzerTests/NonSubstitutableMemberArgumentMatcherTests.cs b/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/NonSubstitutableMemberArgumentMatcherAnalyzerTests/NonSubstitutableMemberArgumentMatcherTests.cs index 7892d5fc..fd05da0b 100644 --- a/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/NonSubstitutableMemberArgumentMatcherAnalyzerTests/NonSubstitutableMemberArgumentMatcherTests.cs +++ b/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/NonSubstitutableMemberArgumentMatcherAnalyzerTests/NonSubstitutableMemberArgumentMatcherTests.cs @@ -899,7 +899,7 @@ public void Test() "ReceivedWithAnyArgs()", "DidNotReceive()", "DidNotReceiveWithAnyArgs()")] - public override async Task ReportsNoDiagnostics_WhenAssigningArgMatchersToSubstitutableMemberPrecededByReceivedLikeMethod(string receivedMethod, string arg) + public override async Task ReportsDiagnostics_WhenAssigningInvalidArgMatchersToMemberPrecededByWithAnyArgsLikeMethod(string receivedMethod, string arg) { var source = $@"using System; using NSubstitute; diff --git a/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/WithAnyArgsArgumentMatcherAnalyzerTests/ForAnyArgsArgumentMatcherDiagnosticVerifier.cs b/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/WithAnyArgsArgumentMatcherAnalyzerTests/ForAnyArgsArgumentMatcherDiagnosticVerifier.cs new file mode 100644 index 00000000..ce86f8d8 --- /dev/null +++ b/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/WithAnyArgsArgumentMatcherAnalyzerTests/ForAnyArgsArgumentMatcherDiagnosticVerifier.cs @@ -0,0 +1,104 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using NSubstitute.Analyzers.CSharp; +using NSubstitute.Analyzers.CSharp.DiagnosticAnalyzers; +using NSubstitute.Analyzers.Shared; +using NSubstitute.Analyzers.Shared.Settings; +using NSubstitute.Analyzers.Tests.Shared.DiagnosticAnalyzers; +using NSubstitute.Analyzers.Tests.Shared.Extensibility; +using Xunit; + +namespace NSubstitute.Analyzers.Tests.CSharp.DiagnosticAnalyzerTests.WithAnyArgsArgumentMatcherAnalyzerTests; + +public abstract class ForAnyArgsArgumentMatcherDiagnosticVerifier : CSharpDiagnosticVerifier, IForAnyArgsArgumentMatcherDiagnosticVerifier +{ + internal AnalyzersSettings Settings { get; set; } + + protected DiagnosticDescriptor WithAnyArgsArgumentMatcherUsage { get; } = DiagnosticDescriptors.WithAnyArgsArgumentMatcherUsage; + + protected override DiagnosticAnalyzer DiagnosticAnalyzer { get; } = new WithAnyArgsArgumentMatcherAnalyzer(); + + [CombinatoryTheory] + [MemberData(nameof(MisusedArgTestCasesWithoutDelegates))] + public abstract Task ReportsDiagnostics_WhenUsingInvalidArgMatchersWithInvocationFollowedByForAnyArgsLikeMethod(string returnsMethod, string arg); + + [CombinatoryTheory] + [MemberData(nameof(CorrectlyUsedArgTestCasesWithoutDelegates))] + public abstract Task ReportsNoDiagnostics_WhenUsingArgMatchersWithInvocationNotFollowedByForAnyArgsLikeMethod(string returnsMethod, string arg); + + [CombinatoryTheory] + [MemberData(nameof(ArgAnyTestCases))] + public abstract Task ReportsNoDiagnostics_WhenUsingArgAnyMatcherWithInvocationFollowedByForAnyArgsLikeMethod(string returnsMethod, string arg); + + [CombinatoryTheory] + [MemberData(nameof(MisusedArgTestCasesWithoutDelegates))] + public abstract Task ReportsDiagnostics_WhenUsingInvalidArgMatchersWithIndexerFollowedByForAnyArgsLikeMethod(string returnsMethod, string arg); + + [CombinatoryTheory] + [MemberData(nameof(CorrectlyUsedArgTestCasesWithoutDelegates))] + public abstract Task ReportsNoDiagnostics_WhenUsingArgMatchersWithIndexerNotFollowedByForAnyArgsLikeMethod(string returnsMethod, string arg); + + [CombinatoryTheory] + [MemberData(nameof(ArgAnyTestCases))] + public abstract Task ReportsNoDiagnostics_WhenUsingArgAnyMatcherWithIndexerFollowedByForAnyArgsLikeMethod(string returnsMethod, string arg); + + public static IEnumerable MisusedArgTestCases + { + get + { + 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(1)|]" }; + yield return new object[] { "[|Arg.Compat.Invoke(1)|]" }; + yield return new object[] { "[|Arg.InvokeDelegate>()|]" }; + yield return new object[] { "[|Arg.Compat.InvokeDelegate>()|]" }; + } + } + + public static IEnumerable MisusedArgTestCasesWithoutDelegates => + MisusedArgTestCases.Where(arguments => arguments.All(argument => !argument.ToString().Contains("Invoke"))); + + public static IEnumerable ArgAnyTestCases => + CorrectlyUsedArgTestCases.Where(arguments => arguments.All(argument => argument.ToString().Contains("Any"))); + + public static IEnumerable CorrectlyUsedArgTestCases + { + 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.Is((int __) => __ > 0)" }; + yield return new object[] { "true ? Arg.Is((int __) => __ > 0) : 0" }; + 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.Compat.Is((int __) => __ > 0) " }; + yield return new object[] { "true ? Arg.Compat.Is((int __) => __ > 0) : 0" }; + yield return new object[] { "Arg.Do(__ => {})" }; + yield return new object[] { "Arg.Compat.Do(__ => {})" }; + yield return new object[] { "Arg.Invoke(1)" }; + yield return new object[] { "Arg.Compat.Invoke(1)" }; + yield return new object[] { "Arg.InvokeDelegate>()" }; + yield return new object[] { "Arg.Compat.InvokeDelegate>()" }; + } + } + + public static IEnumerable CorrectlyUsedArgTestCasesWithoutDelegates => + CorrectlyUsedArgTestCases.Where(arguments => arguments.All(argument => !argument.ToString().Contains("Invoke"))); +} \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/WithAnyArgsArgumentMatcherAnalyzerTests/ReceivedAsExtensionMethodTests.cs b/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/WithAnyArgsArgumentMatcherAnalyzerTests/ReceivedAsExtensionMethodTests.cs new file mode 100644 index 00000000..f53f2d9f --- /dev/null +++ b/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/WithAnyArgsArgumentMatcherAnalyzerTests/ReceivedAsExtensionMethodTests.cs @@ -0,0 +1,249 @@ +using System.Threading.Tasks; +using NSubstitute.Analyzers.Tests.Shared.Extensibility; + +namespace NSubstitute.Analyzers.Tests.CSharp.DiagnosticAnalyzerTests.WithAnyArgsArgumentMatcherAnalyzerTests; + +public class ReceivedAsExtensionMethodTests : WithAnyArgsArgumentMatcherDiagnosticVerifier +{ + [CombinatoryData( + "DidNotReceive()", + "Received(Quantity.None())", + "Received()")] + public override async Task ReportsNoDiagnostics_WhenAssigningArgMatchersToMemberNotPrecededByWithAnyArgsLikeMethodForDelegate(string receivedMethod, string arg) + { + var source = $@"using System; +using NSubstitute; +using NSubstitute.ReceivedExtensions; + +namespace MyNamespace +{{ + public interface IFoo + {{ + Action Foo {{ get; set; }} + Action this[int? x] {{ get; set; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = Substitute.For(); + substitute.{receivedMethod}.Foo = {arg}; + substitute.{receivedMethod}[1] = {arg}; + }} + }} +}}"; + await VerifyNoDiagnostic(source); + } + + [CombinatoryData( + "ReceivedWithAnyArgs(Quantity.None())", + "ReceivedWithAnyArgs()", + "DidNotReceiveWithAnyArgs()")] + public override async Task ReportsNoDiagnostics_WhenAssigningArgAnyMatcherToMemberPrecededByWithAnyArgsLikeMethod(string receivedMethod, string arg) + { + var source = $@"using System; +using NSubstitute; +using NSubstitute.ReceivedExtensions; + +namespace MyNamespace +{{ + public interface IFoo + {{ + int? Foo {{ get; set; }} + int? this[int? x] {{ get; set; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = Substitute.For(); + substitute.{receivedMethod}.Foo = {arg}; + substitute.{receivedMethod}[1] = {arg}; + }} + }} +}}"; + await VerifyNoDiagnostic(source); + } + + [CombinatoryData( + "DidNotReceive()", + "Received(Quantity.None())", + "Received()")] + public override async Task ReportsNoDiagnostics_WhenAssigningArgMatchersToMemberNotPrecededByWithAnyArgsLikeMethod(string receivedMethod, string arg) + { + var source = $@"using System; +using NSubstitute; +using NSubstitute.ReceivedExtensions; + +namespace MyNamespace +{{ + public interface IFoo + {{ + int? Foo {{ get; set; }} + int? this[int? x] {{ get; set; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = Substitute.For(); + substitute.{receivedMethod}.Foo = {arg}; + substitute.{receivedMethod}[1] = {arg}; + }} + }} +}}"; + await VerifyNoDiagnostic(source); + } + + [CombinatoryData( + "ReceivedWithAnyArgs(Quantity.None())", + "ReceivedWithAnyArgs()", + "DidNotReceiveWithAnyArgs()")] + public override async Task ReportsDiagnostics_WhenAssigningInvalidArgMatchersToMemberPrecededByWithAnyArgsLikeMethod(string receivedMethod, string arg) + { + var source = $@"using System; +using NSubstitute; +using NSubstitute.ReceivedExtensions; + +namespace MyNamespace +{{ + public interface IFoo + {{ + int? Foo {{ get; set; }} + int? this[int? x] {{ get; set; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = Substitute.For(); + substitute.{receivedMethod}.Foo = {arg}; + substitute.{receivedMethod}[1] = {arg}; + }} + }} +}}"; + await VerifyDiagnostic(source, WithAnyArgsArgumentMatcherUsage); + } + + [CombinatoryData( + "ReceivedWithAnyArgs(Quantity.None())", + "ReceivedWithAnyArgs()", + "DidNotReceiveWithAnyArgs()")] + public override async Task ReportsDiagnostics_WhenAssigningInvalidArgMatchersToMemberPrecededByWithAnyArgsLikeMethodForDelegate(string receivedMethod, string arg) + { + var source = $@"using System; +using NSubstitute; +using NSubstitute.ReceivedExtensions; + +namespace MyNamespace +{{ + public interface IFoo + {{ + Action Foo {{ get; set; }} + Action this[int? x] {{ get; set; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = Substitute.For(); + substitute.{receivedMethod}.Foo = {arg}; + substitute.{receivedMethod}[1] = {arg}; + }} + }} +}}"; + await VerifyDiagnostic(source, WithAnyArgsArgumentMatcherUsage); + } + + [CombinatoryData( + "ReceivedWithAnyArgs(Quantity.None())", + "ReceivedWithAnyArgs()", + "DidNotReceiveWithAnyArgs()")] + public override async Task ReportsNoDiagnostics_WhenUsingArgAnyMatcherWithInvocationPrecededByWithAnyArgsLikeMethod(string receivedMethod, string arg) + { + var source = $@"using System; + using NSubstitute; + using NSubstitute.ReceivedExtensions; + + namespace MyNamespace + {{ + public interface Foo + {{ + void Bar(int? x); + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = Substitute.For(); + substitute.{receivedMethod}.Bar({arg}); + }} + }} + }}"; + await VerifyNoDiagnostic(source); + } + + [CombinatoryData( + "DidNotReceive()", + "Received(Quantity.None())", + "Received()")] + public override async Task ReportsNoDiagnostics_WhenUsingArgMatchersWithInvocationNotPrecededByWithAnyArgsLikeMethod(string receivedMethod, string arg) + { + var source = $@"using System; + using NSubstitute; + using NSubstitute.ReceivedExtensions; + + namespace MyNamespace + {{ + public interface Foo + {{ + void Bar(int? x); + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = Substitute.For(); + substitute.{receivedMethod}.Bar({arg}); + }} + }} + }}"; + await VerifyNoDiagnostic(source); + } + + [CombinatoryData( + "ReceivedWithAnyArgs(Quantity.None())", + "ReceivedWithAnyArgs()", + "DidNotReceiveWithAnyArgs()")] + public override async Task ReportsDiagnostics_WhenUsingInvalidArgMatchersWithInvocationPrecededByWithAnyArgsLikeMethod(string receivedMethod, string arg) + { + var source = $@"using System; + using NSubstitute; + using NSubstitute.ReceivedExtensions; + + namespace MyNamespace + {{ + public interface Foo + {{ + void Bar(int? x); + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = Substitute.For(); + substitute.{receivedMethod}.Bar({arg}); + }} + }} + }}"; + await VerifyDiagnostic(source, WithAnyArgsArgumentMatcherUsage); + } +} \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/WithAnyArgsArgumentMatcherAnalyzerTests/ReceivedAsOrdinaryMethodTests.cs b/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/WithAnyArgsArgumentMatcherAnalyzerTests/ReceivedAsOrdinaryMethodTests.cs new file mode 100644 index 00000000..75c8914d --- /dev/null +++ b/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/WithAnyArgsArgumentMatcherAnalyzerTests/ReceivedAsOrdinaryMethodTests.cs @@ -0,0 +1,283 @@ +using System.Threading.Tasks; +using NSubstitute.Analyzers.Tests.Shared.Extensibility; + +namespace NSubstitute.Analyzers.Tests.CSharp.DiagnosticAnalyzerTests.WithAnyArgsArgumentMatcherAnalyzerTests; + +public class ReceivedAsOrdinaryMethodTests : WithAnyArgsArgumentMatcherDiagnosticVerifier +{ + [CombinatoryData( + "SubstituteExtensions.DidNotReceive(substitute)", + "SubstituteExtensions.DidNotReceive(substitute: substitute)", + "ReceivedExtensions.Received(substitute, Quantity.None())", + "ReceivedExtensions.Received(substitute: substitute, requiredQuantity: Quantity.None())", + "ReceivedExtensions.Received(requiredQuantity: Quantity.None(), substitute: substitute)", + "SubstituteExtensions.Received(substitute)", + "SubstituteExtensions.Received(substitute: substitute)")] + public override async Task ReportsNoDiagnostics_WhenAssigningArgMatchersToMemberNotPrecededByWithAnyArgsLikeMethodForDelegate(string receivedMethod, string arg) + { + var source = $@"using System; +using NSubstitute; +using NSubstitute.ReceivedExtensions; + +namespace MyNamespace +{{ + public interface IFoo + {{ + Action Foo {{ get; set; }} + Action this[int? x] {{ get; set; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = Substitute.For(); + {receivedMethod}.Foo = {arg}; + {receivedMethod}[1] = {arg}; + }} + }} +}}"; + await VerifyNoDiagnostic(source); + } + + [CombinatoryData( + "ReceivedExtensions.ReceivedWithAnyArgs(substitute, Quantity.None())", + "ReceivedExtensions.ReceivedWithAnyArgs(substitute: substitute, requiredQuantity: Quantity.None())", + "ReceivedExtensions.ReceivedWithAnyArgs(requiredQuantity: Quantity.None(), substitute: substitute)", + "SubstituteExtensions.ReceivedWithAnyArgs(substitute)", + "SubstituteExtensions.ReceivedWithAnyArgs(substitute: substitute)", + "SubstituteExtensions.DidNotReceiveWithAnyArgs(substitute)", + "SubstituteExtensions.DidNotReceiveWithAnyArgs(substitute: substitute)")] + public override async Task ReportsNoDiagnostics_WhenAssigningArgAnyMatcherToMemberPrecededByWithAnyArgsLikeMethod(string receivedMethod, string arg) + { + var source = $@"using System; +using NSubstitute; +using NSubstitute.ReceivedExtensions; + +namespace MyNamespace +{{ + public interface IFoo + {{ + int? Foo {{ get; set; }} + int? this[int? x] {{ get; set; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = Substitute.For(); + {receivedMethod}.Foo = {arg}; + {receivedMethod}[1] = {arg}; + }} + }} +}}"; + await VerifyNoDiagnostic(source); + } + + [CombinatoryData( + "SubstituteExtensions.DidNotReceive(substitute)", + "SubstituteExtensions.DidNotReceive(substitute: substitute)", + "ReceivedExtensions.Received(substitute, Quantity.None())", + "ReceivedExtensions.Received(substitute: substitute, requiredQuantity: Quantity.None())", + "ReceivedExtensions.Received(requiredQuantity: Quantity.None(), substitute: substitute)", + "SubstituteExtensions.Received(substitute)", + "SubstituteExtensions.Received(substitute: substitute)")] + public override async Task ReportsNoDiagnostics_WhenAssigningArgMatchersToMemberNotPrecededByWithAnyArgsLikeMethod(string receivedMethod, string arg) + { + var source = $@"using System; +using NSubstitute; +using NSubstitute.ReceivedExtensions; + +namespace MyNamespace +{{ + public interface IFoo + {{ + int? Foo {{ get; set; }} + int? this[int? x] {{ get; set; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = Substitute.For(); + {receivedMethod}.Foo = {arg}; + {receivedMethod}[1] = {arg}; + }} + }} +}}"; + await VerifyNoDiagnostic(source); + } + + [CombinatoryData( + "ReceivedExtensions.ReceivedWithAnyArgs(substitute, Quantity.None())", + "ReceivedExtensions.ReceivedWithAnyArgs(substitute: substitute, requiredQuantity: Quantity.None())", + "ReceivedExtensions.ReceivedWithAnyArgs(requiredQuantity: Quantity.None(), substitute: substitute)", + "SubstituteExtensions.ReceivedWithAnyArgs(substitute)", + "SubstituteExtensions.ReceivedWithAnyArgs(substitute: substitute)", + "SubstituteExtensions.DidNotReceiveWithAnyArgs(substitute)", + "SubstituteExtensions.DidNotReceiveWithAnyArgs(substitute: substitute)")] + public override async Task ReportsDiagnostics_WhenAssigningInvalidArgMatchersToMemberPrecededByWithAnyArgsLikeMethod(string receivedMethod, string arg) + { + var source = $@"using System; +using NSubstitute; +using NSubstitute.ReceivedExtensions; + +namespace MyNamespace +{{ + public interface IFoo + {{ + int? Foo {{ get; set; }} + int? this[int? x] {{ get; set; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = Substitute.For(); + {receivedMethod}.Foo = {arg}; + {receivedMethod}[1] = {arg}; + }} + }} +}}"; + await VerifyDiagnostic(source, WithAnyArgsArgumentMatcherUsage); + } + + [CombinatoryData( + "ReceivedExtensions.ReceivedWithAnyArgs(substitute, Quantity.None())", + "ReceivedExtensions.ReceivedWithAnyArgs(substitute: substitute, requiredQuantity: Quantity.None())", + "ReceivedExtensions.ReceivedWithAnyArgs(requiredQuantity: Quantity.None(), substitute: substitute)", + "SubstituteExtensions.ReceivedWithAnyArgs(substitute)", + "SubstituteExtensions.ReceivedWithAnyArgs(substitute: substitute)", + "SubstituteExtensions.DidNotReceiveWithAnyArgs(substitute)", + "SubstituteExtensions.DidNotReceiveWithAnyArgs(substitute: substitute)")] + public override async Task ReportsDiagnostics_WhenAssigningInvalidArgMatchersToMemberPrecededByWithAnyArgsLikeMethodForDelegate(string receivedMethod, string arg) + { + var source = $@"using System; +using NSubstitute; +using NSubstitute.ReceivedExtensions; + +namespace MyNamespace +{{ + public interface IFoo + {{ + Action Foo {{ get; set; }} + Action this[int? x] {{ get; set; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = Substitute.For(); + {receivedMethod}.Foo = {arg}; + {receivedMethod}[1] = {arg}; + }} + }} +}}"; + await VerifyDiagnostic(source, WithAnyArgsArgumentMatcherUsage); + } + + [CombinatoryData( + "ReceivedExtensions.ReceivedWithAnyArgs(substitute, Quantity.None())", + "ReceivedExtensions.ReceivedWithAnyArgs(substitute: substitute, requiredQuantity: Quantity.None())", + "ReceivedExtensions.ReceivedWithAnyArgs(requiredQuantity: Quantity.None(), substitute: substitute)", + "SubstituteExtensions.ReceivedWithAnyArgs(substitute)", + "SubstituteExtensions.ReceivedWithAnyArgs(substitute: substitute)", + "SubstituteExtensions.DidNotReceiveWithAnyArgs(substitute)", + "SubstituteExtensions.DidNotReceiveWithAnyArgs(substitute: substitute)")] + public override async Task ReportsNoDiagnostics_WhenUsingArgAnyMatcherWithInvocationPrecededByWithAnyArgsLikeMethod(string receivedMethod, string arg) + { + var source = $@"using System; +using NSubstitute; +using NSubstitute.ReceivedExtensions; + +namespace MyNamespace +{{ + public interface Foo + {{ + void Bar(int? x); + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = Substitute.For(); + {receivedMethod}.Bar({arg}); + }} + }} +}}"; + + await VerifyNoDiagnostic(source); + } + + [CombinatoryData( + "SubstituteExtensions.DidNotReceive(substitute)", + "SubstituteExtensions.DidNotReceive(substitute: substitute)", + "ReceivedExtensions.Received(substitute, Quantity.None())", + "ReceivedExtensions.Received(substitute: substitute, requiredQuantity: Quantity.None())", + "ReceivedExtensions.Received(requiredQuantity: Quantity.None(), substitute: substitute)", + "SubstituteExtensions.Received(substitute)", + "SubstituteExtensions.Received(substitute: substitute)")] + public override async Task ReportsNoDiagnostics_WhenUsingArgMatchersWithInvocationNotPrecededByWithAnyArgsLikeMethod(string receivedMethod, string arg) + { + var source = $@"using System; +using NSubstitute; +using NSubstitute.ReceivedExtensions; + +namespace MyNamespace +{{ + public interface Foo + {{ + void Bar(int? x); + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = Substitute.For(); + {receivedMethod}.Bar({arg}); + }} + }} +}}"; + + await VerifyNoDiagnostic(source); + } + + [CombinatoryData( + "ReceivedExtensions.ReceivedWithAnyArgs(substitute, Quantity.None())", + "ReceivedExtensions.ReceivedWithAnyArgs(substitute: substitute, requiredQuantity: Quantity.None())", + "ReceivedExtensions.ReceivedWithAnyArgs(requiredQuantity: Quantity.None(), substitute: substitute)", + "SubstituteExtensions.ReceivedWithAnyArgs(substitute)", + "SubstituteExtensions.ReceivedWithAnyArgs(substitute: substitute)", + "SubstituteExtensions.DidNotReceiveWithAnyArgs(substitute)", + "SubstituteExtensions.DidNotReceiveWithAnyArgs(substitute: substitute)")] + public override async Task ReportsDiagnostics_WhenUsingInvalidArgMatchersWithInvocationPrecededByWithAnyArgsLikeMethod(string receivedMethod, string arg) + { + var source = $@"using System; +using NSubstitute; +using NSubstitute.ReceivedExtensions; + +namespace MyNamespace +{{ + public interface Foo + {{ + void Bar(int? x); + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = Substitute.For(); + {receivedMethod}.Bar({arg}); + }} + }} +}}"; + await VerifyDiagnostic(source, WithAnyArgsArgumentMatcherUsage); + } +} diff --git a/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/WithAnyArgsArgumentMatcherAnalyzerTests/ReturnsAsExtensionMethodTests.cs b/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/WithAnyArgsArgumentMatcherAnalyzerTests/ReturnsAsExtensionMethodTests.cs new file mode 100644 index 00000000..df338b18 --- /dev/null +++ b/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/WithAnyArgsArgumentMatcherAnalyzerTests/ReturnsAsExtensionMethodTests.cs @@ -0,0 +1,166 @@ +using System.Threading.Tasks; +using NSubstitute.Analyzers.Tests.Shared.Extensibility; + +namespace NSubstitute.Analyzers.Tests.CSharp.DiagnosticAnalyzerTests.WithAnyArgsArgumentMatcherAnalyzerTests; + +public class ReturnsAsExtensionMethodTests : ForAnyArgsArgumentMatcherDiagnosticVerifier +{ + [CombinatoryData("ReturnsForAnyArgs(null)", "ReturnsNullForAnyArgs()")] + public override async Task ReportsDiagnostics_WhenUsingInvalidArgMatchersWithInvocationFollowedByForAnyArgsLikeMethod(string returnsMethod, string arg) + { + var source = $@"using System; +using NSubstitute; +using NSubstitute.ReturnsExtensions; + +namespace MyNamespace +{{ + public interface Foo + {{ + object Bar(int? x); + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = Substitute.For(); + substitute.Bar({arg}).{returnsMethod}; + }} + }} +}}"; + await VerifyDiagnostic(source, WithAnyArgsArgumentMatcherUsage); + } + + [CombinatoryData("Returns(null)", "ReturnsNull()", "ReturnsForAll((object)null)")] + public override async Task ReportsNoDiagnostics_WhenUsingArgMatchersWithInvocationNotFollowedByForAnyArgsLikeMethod(string returnsMethod, string arg) + { + var source = $@"using System; +using NSubstitute; +using NSubstitute.ReturnsExtensions; +using NSubstitute.Extensions; + +namespace MyNamespace +{{ + public interface Foo + {{ + object Bar(int? x); + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = Substitute.For(); + substitute.Bar({arg}).{returnsMethod}; + }} + }} +}}"; + await VerifyNoDiagnostic(source); + } + + [CombinatoryData("ReturnsForAnyArgs(null)", "ReturnsNullForAnyArgs()")] + public override async Task ReportsNoDiagnostics_WhenUsingArgAnyMatcherWithInvocationFollowedByForAnyArgsLikeMethod(string returnsMethod, string arg) + { + var source = $@"using System; +using NSubstitute; +using NSubstitute.ReturnsExtensions; + +namespace MyNamespace +{{ + public interface Foo + {{ + object Bar(int? x); + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = Substitute.For(); + substitute.Bar({arg}).{returnsMethod}; + }} + }} +}}"; + await VerifyNoDiagnostic(source); + } + + [CombinatoryData("ReturnsForAnyArgs(null)", "ReturnsNullForAnyArgs()")] + public override async Task ReportsDiagnostics_WhenUsingInvalidArgMatchersWithIndexerFollowedByForAnyArgsLikeMethod(string returnsMethod, string arg) + { + var source = $@"using System; +using NSubstitute; +using NSubstitute.ReturnsExtensions; + +namespace MyNamespace +{{ + public interface Foo + {{ + object this[int? x] {{ get; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = Substitute.For(); + substitute[{arg}].{returnsMethod}; + }} + }} +}}"; + await VerifyDiagnostic(source, WithAnyArgsArgumentMatcherUsage); + } + + [CombinatoryData("Returns(null)", "ReturnsNull()", "ReturnsForAll((object)null)")] + public override async Task ReportsNoDiagnostics_WhenUsingArgMatchersWithIndexerNotFollowedByForAnyArgsLikeMethod(string returnsMethod, string arg) + { + var source = $@"using System; +using NSubstitute; +using NSubstitute.ReturnsExtensions; +using NSubstitute.Extensions; + +namespace MyNamespace +{{ + public interface Foo + {{ + object this[int? x] {{ get; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = Substitute.For(); + substitute[{arg}].{returnsMethod}; + }} + }} +}}"; + await VerifyNoDiagnostic(source); + } + + [CombinatoryData("ReturnsForAnyArgs(null)", "ReturnsNullForAnyArgs()")] + public override async Task ReportsNoDiagnostics_WhenUsingArgAnyMatcherWithIndexerFollowedByForAnyArgsLikeMethod(string returnsMethod, string arg) + { + var source = $@"using System; +using NSubstitute; +using NSubstitute.ReturnsExtensions; +using NSubstitute.Extensions; + +namespace MyNamespace +{{ + public interface Foo + {{ + object this[int? x] {{ get; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = Substitute.For(); + substitute[{arg}].{returnsMethod}; + }} + }} +}}"; + await VerifyNoDiagnostic(source); + } +} \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/WithAnyArgsArgumentMatcherAnalyzerTests/ReturnsAsOrdinaryMethodTests.cs b/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/WithAnyArgsArgumentMatcherAnalyzerTests/ReturnsAsOrdinaryMethodTests.cs new file mode 100644 index 00000000..616fc6fd --- /dev/null +++ b/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/WithAnyArgsArgumentMatcherAnalyzerTests/ReturnsAsOrdinaryMethodTests.cs @@ -0,0 +1,166 @@ +using System.Threading.Tasks; +using NSubstitute.Analyzers.Tests.Shared.Extensibility; + +namespace NSubstitute.Analyzers.Tests.CSharp.DiagnosticAnalyzerTests.WithAnyArgsArgumentMatcherAnalyzerTests; + +public class ReturnsAsOrdinaryMethodTests : ForAnyArgsArgumentMatcherDiagnosticVerifier +{ + [CombinatoryData("SubstituteExtensions.ReturnsForAnyArgs({0}, null)", "ReturnsExtensions.ReturnsNullForAnyArgs({0})")] + public override async Task ReportsDiagnostics_WhenUsingInvalidArgMatchersWithInvocationFollowedByForAnyArgsLikeMethod(string returnsMethod, string arg) + { + var source = $@"using System; +using NSubstitute; +using NSubstitute.ReturnsExtensions; + +namespace MyNamespace +{{ + public interface Foo + {{ + object Bar(int? x); + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = Substitute.For(); + {string.Format(returnsMethod, $"substitute.Bar({arg})")}; + }} + }} +}}"; + await VerifyDiagnostic(source, WithAnyArgsArgumentMatcherUsage); + } + + [CombinatoryData("SubstituteExtensions.Returns({0}, null)", "ReturnsExtensions.ReturnsNull({0})", "ReturnsForAllExtensions.ReturnsForAll({0}, (object)null)")] + public override async Task ReportsNoDiagnostics_WhenUsingArgMatchersWithInvocationNotFollowedByForAnyArgsLikeMethod(string returnsMethod, string arg) + { + var source = $@"using System; +using NSubstitute; +using NSubstitute.ReturnsExtensions; +using NSubstitute.Extensions; + +namespace MyNamespace +{{ + public interface Foo + {{ + object Bar(int? x); + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = Substitute.For(); + {string.Format(returnsMethod, $"substitute.Bar({arg})")}; + }} + }} +}}"; + await VerifyNoDiagnostic(source); + } + + [CombinatoryData("SubstituteExtensions.ReturnsForAnyArgs({0}, null)", "ReturnsExtensions.ReturnsNullForAnyArgs({0})")] + public override async Task ReportsNoDiagnostics_WhenUsingArgAnyMatcherWithInvocationFollowedByForAnyArgsLikeMethod(string returnsMethod, string arg) + { + var source = $@"using System; +using NSubstitute; +using NSubstitute.ReturnsExtensions; + +namespace MyNamespace +{{ + public interface Foo + {{ + object Bar(int? x); + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = Substitute.For(); + {string.Format(returnsMethod, $"substitute.Bar({arg})")}; + }} + }} +}}"; + await VerifyNoDiagnostic(source); + } + + [CombinatoryData("SubstituteExtensions.ReturnsForAnyArgs({0}, null)", "ReturnsExtensions.ReturnsNullForAnyArgs({0})")] + public override async Task ReportsDiagnostics_WhenUsingInvalidArgMatchersWithIndexerFollowedByForAnyArgsLikeMethod(string returnsMethod, string arg) + { + var source = $@"using System; +using NSubstitute; +using NSubstitute.ReturnsExtensions; + +namespace MyNamespace +{{ + public interface Foo + {{ + object this[int? x] {{ get; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = Substitute.For(); + {string.Format(returnsMethod, $"substitute[{arg}]")}; + }} + }} +}}"; + await VerifyDiagnostic(source, WithAnyArgsArgumentMatcherUsage); + } + + [CombinatoryData("SubstituteExtensions.Returns({0}, null)", "ReturnsExtensions.ReturnsNull({0})", "ReturnsForAllExtensions.ReturnsForAll({0}, (object)null)")] + public override async Task ReportsNoDiagnostics_WhenUsingArgMatchersWithIndexerNotFollowedByForAnyArgsLikeMethod(string returnsMethod, string arg) + { + var source = $@"using System; +using NSubstitute; +using NSubstitute.ReturnsExtensions; +using NSubstitute.Extensions; + +namespace MyNamespace +{{ + public interface Foo + {{ + object this[int? x] {{ get; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = Substitute.For(); + {string.Format(returnsMethod, $"substitute[{arg}]")}; + }} + }} +}}"; + await VerifyNoDiagnostic(source); + } + + [CombinatoryData("SubstituteExtensions.ReturnsForAnyArgs({0}, null)", "ReturnsExtensions.ReturnsNullForAnyArgs({0})")] + public override async Task ReportsNoDiagnostics_WhenUsingArgAnyMatcherWithIndexerFollowedByForAnyArgsLikeMethod(string returnsMethod, string arg) + { + var source = $@"using System; +using NSubstitute; +using NSubstitute.ReturnsExtensions; +using NSubstitute.Extensions; + +namespace MyNamespace +{{ + public interface Foo + {{ + object this[int? x] {{ get; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = Substitute.For(); + {string.Format(returnsMethod, $"substitute[{arg}]")}; + }} + }} +}}"; + await VerifyNoDiagnostic(source); + } +} \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/WithAnyArgsArgumentMatcherAnalyzerTests/ThrowsAsExtensionMethodTests.cs b/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/WithAnyArgsArgumentMatcherAnalyzerTests/ThrowsAsExtensionMethodTests.cs new file mode 100644 index 00000000..b9650401 --- /dev/null +++ b/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/WithAnyArgsArgumentMatcherAnalyzerTests/ThrowsAsExtensionMethodTests.cs @@ -0,0 +1,163 @@ +using System.Threading.Tasks; +using NSubstitute.Analyzers.Tests.Shared.Extensibility; + +namespace NSubstitute.Analyzers.Tests.CSharp.DiagnosticAnalyzerTests.WithAnyArgsArgumentMatcherAnalyzerTests; + +public class ThrowsAsExtensionMethodTests : ForAnyArgsArgumentMatcherDiagnosticVerifier +{ + [CombinatoryData("ThrowsForAnyArgs()", "ThrowsForAnyArgs(new Exception())")] + public override async Task ReportsDiagnostics_WhenUsingInvalidArgMatchersWithInvocationFollowedByForAnyArgsLikeMethod(string returnsMethod, string arg) + { + var source = $@"using System; +using NSubstitute; +using NSubstitute.ExceptionExtensions; + +namespace MyNamespace +{{ + public interface Foo + {{ + object Bar(int? x); + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = Substitute.For(); + substitute.Bar({arg}).{returnsMethod}; + }} + }} +}}"; + await VerifyDiagnostic(source, WithAnyArgsArgumentMatcherUsage); + } + + [CombinatoryData("Throws()", "Throws(new Exception())")] + public override async Task ReportsNoDiagnostics_WhenUsingArgMatchersWithInvocationNotFollowedByForAnyArgsLikeMethod(string returnsMethod, string arg) + { + var source = $@"using System; +using NSubstitute; +using NSubstitute.ExceptionExtensions; + +namespace MyNamespace +{{ + public interface Foo + {{ + object Bar(int? x); + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = Substitute.For(); + substitute.Bar({arg}).{returnsMethod}; + }} + }} +}}"; + await VerifyNoDiagnostic(source); + } + + [CombinatoryData("ThrowsForAnyArgs()", "ThrowsForAnyArgs(new Exception())")] + public override async Task ReportsNoDiagnostics_WhenUsingArgAnyMatcherWithInvocationFollowedByForAnyArgsLikeMethod(string returnsMethod, string arg) + { + var source = $@"using System; +using NSubstitute; +using NSubstitute.ExceptionExtensions; + +namespace MyNamespace +{{ + public interface Foo + {{ + object Bar(int? x); + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = Substitute.For(); + substitute.Bar({arg}).{returnsMethod}; + }} + }} +}}"; + await VerifyNoDiagnostic(source); + } + + [CombinatoryData("ThrowsForAnyArgs()", "ThrowsForAnyArgs(new Exception())")] + public override async Task ReportsDiagnostics_WhenUsingInvalidArgMatchersWithIndexerFollowedByForAnyArgsLikeMethod(string returnsMethod, string arg) + { + var source = $@"using System; +using NSubstitute; +using NSubstitute.ExceptionExtensions; + +namespace MyNamespace +{{ + public interface Foo + {{ + object this[int? x] {{ get; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = Substitute.For(); + substitute[{arg}].{returnsMethod}; + }} + }} +}}"; + await VerifyDiagnostic(source, WithAnyArgsArgumentMatcherUsage); + } + + [CombinatoryData("Throws()", "Throws(new Exception())")] + public override async Task ReportsNoDiagnostics_WhenUsingArgMatchersWithIndexerNotFollowedByForAnyArgsLikeMethod(string returnsMethod, string arg) + { + var source = $@"using System; +using NSubstitute; +using NSubstitute.ExceptionExtensions; + +namespace MyNamespace +{{ + public interface Foo + {{ + object this[int? x] {{ get; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = Substitute.For(); + substitute[{arg}].{returnsMethod}; + }} + }} +}}"; + await VerifyNoDiagnostic(source); + } + + [CombinatoryData("ThrowsForAnyArgs()", "ThrowsForAnyArgs(new Exception())")] + public override async Task ReportsNoDiagnostics_WhenUsingArgAnyMatcherWithIndexerFollowedByForAnyArgsLikeMethod(string returnsMethod, string arg) + { + var source = $@"using System; +using NSubstitute; +using NSubstitute.ExceptionExtensions; + +namespace MyNamespace +{{ + public interface Foo + {{ + object this[int? x] {{ get; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = Substitute.For(); + substitute[{arg}].{returnsMethod}; + }} + }} +}}"; + await VerifyNoDiagnostic(source); + } +} \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/WithAnyArgsArgumentMatcherAnalyzerTests/ThrowsAsOrdinaryMethodTests.cs b/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/WithAnyArgsArgumentMatcherAnalyzerTests/ThrowsAsOrdinaryMethodTests.cs new file mode 100644 index 00000000..f8eb4373 --- /dev/null +++ b/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/WithAnyArgsArgumentMatcherAnalyzerTests/ThrowsAsOrdinaryMethodTests.cs @@ -0,0 +1,163 @@ +using System.Threading.Tasks; +using NSubstitute.Analyzers.Tests.Shared.Extensibility; + +namespace NSubstitute.Analyzers.Tests.CSharp.DiagnosticAnalyzerTests.WithAnyArgsArgumentMatcherAnalyzerTests; + +public class ThrowsAsOrdinaryMethodTests : ForAnyArgsArgumentMatcherDiagnosticVerifier +{ + [CombinatoryData("ExceptionExtensions.ThrowsForAnyArgs({0})", "ExceptionExtensions.ThrowsForAnyArgs({0}, new Exception())")] + public override async Task ReportsDiagnostics_WhenUsingInvalidArgMatchersWithInvocationFollowedByForAnyArgsLikeMethod(string returnsMethod, string arg) + { + var source = $@"using System; +using NSubstitute; +using NSubstitute.ExceptionExtensions; + +namespace MyNamespace +{{ + public interface Foo + {{ + object Bar(int? x); + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = Substitute.For(); + {string.Format(returnsMethod, $"substitute.Bar({arg})")}; + }} + }} +}}"; + await VerifyDiagnostic(source, WithAnyArgsArgumentMatcherUsage); + } + + [CombinatoryData("ExceptionExtensions.Throws({0})", "ExceptionExtensions.Throws({0}, new Exception())")] + public override async Task ReportsNoDiagnostics_WhenUsingArgMatchersWithInvocationNotFollowedByForAnyArgsLikeMethod(string returnsMethod, string arg) + { + var source = $@"using System; +using NSubstitute; +using NSubstitute.ExceptionExtensions; + +namespace MyNamespace +{{ + public interface Foo + {{ + object Bar(int? x); + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = Substitute.For(); + {string.Format(returnsMethod, $"substitute.Bar({arg})")}; + }} + }} +}}"; + await VerifyNoDiagnostic(source); + } + + [CombinatoryData("ExceptionExtensions.ThrowsForAnyArgs({0})", "ExceptionExtensions.ThrowsForAnyArgs({0}, new Exception())")] + public override async Task ReportsNoDiagnostics_WhenUsingArgAnyMatcherWithInvocationFollowedByForAnyArgsLikeMethod(string returnsMethod, string arg) + { + var source = $@"using System; +using NSubstitute; +using NSubstitute.ExceptionExtensions; + +namespace MyNamespace +{{ + public interface Foo + {{ + object Bar(int? x); + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = Substitute.For(); + {string.Format(returnsMethod, $"substitute.Bar({arg})")}; + }} + }} +}}"; + await VerifyNoDiagnostic(source); + } + + [CombinatoryData("ExceptionExtensions.ThrowsForAnyArgs({0})", "ExceptionExtensions.ThrowsForAnyArgs({0}, new Exception())")] + public override async Task ReportsDiagnostics_WhenUsingInvalidArgMatchersWithIndexerFollowedByForAnyArgsLikeMethod(string returnsMethod, string arg) + { + var source = $@"using System; +using NSubstitute; +using NSubstitute.ExceptionExtensions; + +namespace MyNamespace +{{ + public interface Foo + {{ + object this[int? x] {{ get; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = Substitute.For(); + {string.Format(returnsMethod, $"substitute[{arg}]")}; + }} + }} +}}"; + await VerifyDiagnostic(source, WithAnyArgsArgumentMatcherUsage); + } + + [CombinatoryData("ExceptionExtensions.Throws({0})", "ExceptionExtensions.Throws({0}, new Exception())")] + public override async Task ReportsNoDiagnostics_WhenUsingArgMatchersWithIndexerNotFollowedByForAnyArgsLikeMethod(string returnsMethod, string arg) + { + var source = $@"using System; +using NSubstitute; +using NSubstitute.ExceptionExtensions; + +namespace MyNamespace +{{ + public interface Foo + {{ + object this[int? x] {{ get; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = Substitute.For(); + {string.Format(returnsMethod, $"substitute[{arg}]")}; + }} + }} +}}"; + await VerifyNoDiagnostic(source); + } + + [CombinatoryData("ExceptionExtensions.ThrowsForAnyArgs({0})", "ExceptionExtensions.ThrowsForAnyArgs({0}, new Exception())")] + public override async Task ReportsNoDiagnostics_WhenUsingArgAnyMatcherWithIndexerFollowedByForAnyArgsLikeMethod(string returnsMethod, string arg) + { + var source = $@"using System; +using NSubstitute; +using NSubstitute.ExceptionExtensions; + +namespace MyNamespace +{{ + public interface Foo + {{ + object this[int? x] {{ get; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = Substitute.For(); + {string.Format(returnsMethod, $"substitute[{arg}]")}; + }} + }} +}}"; + await VerifyNoDiagnostic(source); + } +} \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/WithAnyArgsArgumentMatcherAnalyzerTests/WhenAsExtensionMethodTests.cs b/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/WithAnyArgsArgumentMatcherAnalyzerTests/WhenAsExtensionMethodTests.cs new file mode 100644 index 00000000..a35350d3 --- /dev/null +++ b/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/WithAnyArgsArgumentMatcherAnalyzerTests/WhenAsExtensionMethodTests.cs @@ -0,0 +1,164 @@ +using System.Threading.Tasks; +using NSubstitute.Analyzers.Tests.Shared.Extensibility; + +namespace NSubstitute.Analyzers.Tests.CSharp.DiagnosticAnalyzerTests.WithAnyArgsArgumentMatcherAnalyzerTests; + +public class WhenAsExtensionMethodTests : ForAnyArgsArgumentMatcherDiagnosticVerifier +{ + [CombinatoryData("WhenForAnyArgs")] + public override async Task ReportsDiagnostics_WhenUsingInvalidArgMatchersWithInvocationFollowedByForAnyArgsLikeMethod(string returnsMethod, string arg) + { + var source = $@"using System; +using NSubstitute; +using NSubstitute.ExceptionExtensions; + +namespace MyNamespace +{{ + public interface Foo + {{ + object Bar(int? x); + object FooBar {{ get; set; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = Substitute.For(); + substitute.{returnsMethod}(x => x.FooBar = {arg}).Do(_ => {{}}); + }} + }} +}}"; + await VerifyDiagnostic(source, WithAnyArgsArgumentMatcherUsage); + } + + [CombinatoryData("Throws()", "Throws(new Exception())")] + public override async Task ReportsNoDiagnostics_WhenUsingArgMatchersWithInvocationNotFollowedByForAnyArgsLikeMethod(string returnsMethod, string arg) + { + var source = $@"using System; +using NSubstitute; +using NSubstitute.ExceptionExtensions; + +namespace MyNamespace +{{ + public interface Foo + {{ + object Bar(int? x); + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = Substitute.For(); + substitute.Bar({arg}).{returnsMethod}; + }} + }} +}}"; + await VerifyNoDiagnostic(source); + } + + [CombinatoryData("ThrowsForAnyArgs()", "ThrowsForAnyArgs(new Exception())")] + public override async Task ReportsNoDiagnostics_WhenUsingArgAnyMatcherWithInvocationFollowedByForAnyArgsLikeMethod(string returnsMethod, string arg) + { + var source = $@"using System; +using NSubstitute; +using NSubstitute.ExceptionExtensions; + +namespace MyNamespace +{{ + public interface Foo + {{ + object Bar(int? x); + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = Substitute.For(); + substitute.Bar({arg}).{returnsMethod}; + }} + }} +}}"; + await VerifyNoDiagnostic(source); + } + + [CombinatoryData("ThrowsForAnyArgs()", "ThrowsForAnyArgs(new Exception())")] + public override async Task ReportsDiagnostics_WhenUsingInvalidArgMatchersWithIndexerFollowedByForAnyArgsLikeMethod(string returnsMethod, string arg) + { + var source = $@"using System; +using NSubstitute; +using NSubstitute.ExceptionExtensions; + +namespace MyNamespace +{{ + public interface Foo + {{ + object this[int? x] {{ get; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = Substitute.For(); + substitute[{arg}].{returnsMethod}; + }} + }} +}}"; + await VerifyDiagnostic(source, WithAnyArgsArgumentMatcherUsage); + } + + [CombinatoryData("Throws()", "Throws(new Exception())")] + public override async Task ReportsNoDiagnostics_WhenUsingArgMatchersWithIndexerNotFollowedByForAnyArgsLikeMethod(string returnsMethod, string arg) + { + var source = $@"using System; +using NSubstitute; +using NSubstitute.ExceptionExtensions; + +namespace MyNamespace +{{ + public interface Foo + {{ + object this[int? x] {{ get; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = Substitute.For(); + substitute[{arg}].{returnsMethod}; + }} + }} +}}"; + await VerifyNoDiagnostic(source); + } + + [CombinatoryData("ThrowsForAnyArgs()", "ThrowsForAnyArgs(new Exception())")] + public override async Task ReportsNoDiagnostics_WhenUsingArgAnyMatcherWithIndexerFollowedByForAnyArgsLikeMethod(string returnsMethod, string arg) + { + var source = $@"using System; +using NSubstitute; +using NSubstitute.ExceptionExtensions; + +namespace MyNamespace +{{ + public interface Foo + {{ + object this[int? x] {{ get; }} + }} + + public class FooTests + {{ + public void Test() + {{ + var substitute = Substitute.For(); + substitute[{arg}].{returnsMethod}; + }} + }} +}}"; + await VerifyNoDiagnostic(source); + } +} \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/WithAnyArgsArgumentMatcherAnalyzerTests/WithAnyArgsArgumentMatcherDiagnosticVerifier.cs b/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/WithAnyArgsArgumentMatcherAnalyzerTests/WithAnyArgsArgumentMatcherDiagnosticVerifier.cs new file mode 100644 index 00000000..fe374725 --- /dev/null +++ b/tests/NSubstitute.Analyzers.Tests.CSharp/DiagnosticAnalyzerTests/WithAnyArgsArgumentMatcherAnalyzerTests/WithAnyArgsArgumentMatcherDiagnosticVerifier.cs @@ -0,0 +1,118 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using NSubstitute.Analyzers.CSharp; +using NSubstitute.Analyzers.CSharp.DiagnosticAnalyzers; +using NSubstitute.Analyzers.Shared; +using NSubstitute.Analyzers.Shared.Settings; +using NSubstitute.Analyzers.Tests.Shared.DiagnosticAnalyzers; +using NSubstitute.Analyzers.Tests.Shared.Extensibility; +using Xunit; + +namespace NSubstitute.Analyzers.Tests.CSharp.DiagnosticAnalyzerTests.WithAnyArgsArgumentMatcherAnalyzerTests; + +public abstract class WithAnyArgsArgumentMatcherDiagnosticVerifier : CSharpDiagnosticVerifier, IWithAnyArgsArgumentMatcherDiagnosticVerifier +{ + internal AnalyzersSettings Settings { get; set; } + + protected DiagnosticDescriptor WithAnyArgsArgumentMatcherUsage { get; } = DiagnosticDescriptors.WithAnyArgsArgumentMatcherUsage; + + protected override DiagnosticAnalyzer DiagnosticAnalyzer { get; } = new WithAnyArgsArgumentMatcherAnalyzer(); + + [CombinatoryTheory] + [MemberData(nameof(CorrectlyUsedArgTestCasesWithoutDelegates))] + public abstract Task ReportsNoDiagnostics_WhenAssigningArgMatchersToMemberNotPrecededByWithAnyArgsLikeMethod(string receivedMethod, string arg); + + [CombinatoryTheory] + [MemberData(nameof(ArgAnyTestCases))] + public abstract Task ReportsNoDiagnostics_WhenAssigningArgAnyMatcherToMemberPrecededByWithAnyArgsLikeMethod(string receivedMethod, string arg); + + [CombinatoryTheory] + [MemberData(nameof(CorrectlyUsedArgTestCasesDelegates))] + public abstract Task ReportsNoDiagnostics_WhenAssigningArgMatchersToMemberNotPrecededByWithAnyArgsLikeMethodForDelegate(string receivedMethod, string arg); + + [CombinatoryTheory] + [MemberData(nameof(MisusedArgTestCasesWithoutDelegates))] + public abstract Task ReportsDiagnostics_WhenAssigningInvalidArgMatchersToMemberPrecededByWithAnyArgsLikeMethod(string receivedMethod, string arg); + + [CombinatoryTheory] + [MemberData(nameof(MisusedArgTestCasesDelegates))] + public abstract Task ReportsDiagnostics_WhenAssigningInvalidArgMatchersToMemberPrecededByWithAnyArgsLikeMethodForDelegate(string receivedMethod, string arg); + + [CombinatoryTheory] + [MemberData(nameof(CorrectlyUsedArgTestCasesWithoutDelegates))] + public abstract Task ReportsNoDiagnostics_WhenUsingArgMatchersWithInvocationNotPrecededByWithAnyArgsLikeMethod(string receivedMethod, string arg); + + [CombinatoryTheory] + [MemberData(nameof(ArgAnyTestCases))] + public abstract Task ReportsNoDiagnostics_WhenUsingArgAnyMatcherWithInvocationPrecededByWithAnyArgsLikeMethod(string receivedMethod, string arg); + + [CombinatoryTheory] + [MemberData(nameof(MisusedArgTestCasesWithoutDelegates))] + public abstract Task ReportsDiagnostics_WhenUsingInvalidArgMatchersWithInvocationPrecededByWithAnyArgsLikeMethod(string receivedMethod, string arg); + + public static IEnumerable MisusedArgTestCases + { + get + { + 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(1)|]" }; + yield return new object[] { "[|Arg.Compat.Invoke(1)|]" }; + yield return new object[] { "[|Arg.InvokeDelegate>()|]" }; + yield return new object[] { "[|Arg.Compat.InvokeDelegate>()|]" }; + } + } + + public static IEnumerable MisusedArgTestCasesWithoutDelegates => + MisusedArgTestCases.Where(arguments => arguments.All(argument => !argument.ToString().Contains("Invoke"))); + + public static IEnumerable MisusedArgTestCasesDelegates => + MisusedArgTestCases.Where(arguments => arguments.All(argument => argument.ToString().Contains("Invoke"))); + + public static IEnumerable ArgAnyTestCases => + CorrectlyUsedArgTestCases.Where(arguments => arguments.All(argument => argument.ToString().Contains("Any"))); + + public static IEnumerable CorrectlyUsedArgTestCases + { + 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.Is((int __) => __ > 0)" }; + yield return new object[] { "true ? Arg.Is((int __) => __ > 0) : 0" }; + 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.Compat.Is((int __) => __ > 0) " }; + yield return new object[] { "true ? Arg.Compat.Is((int __) => __ > 0) : 0" }; + yield return new object[] { "Arg.Do(__ => {})" }; + yield return new object[] { "Arg.Compat.Do(__ => {})" }; + yield return new object[] { "Arg.Invoke(1)" }; + yield return new object[] { "Arg.Compat.Invoke(1)" }; + yield return new object[] { "Arg.InvokeDelegate>()" }; + yield return new object[] { "Arg.Compat.InvokeDelegate>()" }; + } + } + + public static IEnumerable CorrectlyUsedArgTestCasesWithoutDelegates => + CorrectlyUsedArgTestCases.Where(arguments => arguments.All(argument => !argument.ToString().Contains("Invoke"))); + + public static IEnumerable CorrectlyUsedArgTestCasesDelegates => + CorrectlyUsedArgTestCases.Where(arguments => arguments.All(argument => argument.ToString().Contains("Invoke"))); +} \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.Shared/DiagnosticAnalyzers/IForAnyArgsArgumentMatcherDiagnosticVerifier.cs b/tests/NSubstitute.Analyzers.Tests.Shared/DiagnosticAnalyzers/IForAnyArgsArgumentMatcherDiagnosticVerifier.cs new file mode 100644 index 00000000..715c667c --- /dev/null +++ b/tests/NSubstitute.Analyzers.Tests.Shared/DiagnosticAnalyzers/IForAnyArgsArgumentMatcherDiagnosticVerifier.cs @@ -0,0 +1,18 @@ +using System.Threading.Tasks; + +namespace NSubstitute.Analyzers.Tests.Shared.DiagnosticAnalyzers; + +public interface IForAnyArgsArgumentMatcherDiagnosticVerifier +{ + Task ReportsDiagnostics_WhenUsingInvalidArgMatchersWithInvocationFollowedByForAnyArgsLikeMethod(string returnsMethod, string arg); + + Task ReportsNoDiagnostics_WhenUsingArgMatchersWithInvocationNotFollowedByForAnyArgsLikeMethod(string returnsMethod, string arg); + + Task ReportsNoDiagnostics_WhenUsingArgAnyMatcherWithInvocationFollowedByForAnyArgsLikeMethod(string returnsMethod, string arg); + + Task ReportsDiagnostics_WhenUsingInvalidArgMatchersWithIndexerFollowedByForAnyArgsLikeMethod(string returnsMethod, string arg); + + Task ReportsNoDiagnostics_WhenUsingArgMatchersWithIndexerNotFollowedByForAnyArgsLikeMethod(string returnsMethod, string arg); + + Task ReportsNoDiagnostics_WhenUsingArgAnyMatcherWithIndexerFollowedByForAnyArgsLikeMethod(string returnsMethod, string arg); +} \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.Shared/DiagnosticAnalyzers/INonSubstitutableMemberArgumentMatcherDiagnosticVerifier.cs b/tests/NSubstitute.Analyzers.Tests.Shared/DiagnosticAnalyzers/INonSubstitutableMemberArgumentMatcherDiagnosticVerifier.cs index 305eb863..6cd49e65 100644 --- a/tests/NSubstitute.Analyzers.Tests.Shared/DiagnosticAnalyzers/INonSubstitutableMemberArgumentMatcherDiagnosticVerifier.cs +++ b/tests/NSubstitute.Analyzers.Tests.Shared/DiagnosticAnalyzers/INonSubstitutableMemberArgumentMatcherDiagnosticVerifier.cs @@ -54,7 +54,7 @@ public interface INonSubstitutableMemberArgumentMatcherDiagnosticVerifier Task ReportsDiagnostics_WhenDirectlyAssigningNotAllowedArgMatchersToMember(string arg); - Task ReportsNoDiagnostics_WhenAssigningArgMatchersToSubstitutableMemberPrecededByReceivedLikeMethod(string receivedMethod, string arg); + Task ReportsDiagnostics_WhenAssigningInvalidArgMatchersToMemberPrecededByWithAnyArgsLikeMethod(string receivedMethod, string arg); Task ReportsDiagnostics_WhenAssigningArgMatchersToNonSubstitutableMember_InWhenLikeMethod(string whenMethod, string arg); diff --git a/tests/NSubstitute.Analyzers.Tests.Shared/DiagnosticAnalyzers/IWithAnyArgsArgumentMatcherDiagnosticVerifier.cs b/tests/NSubstitute.Analyzers.Tests.Shared/DiagnosticAnalyzers/IWithAnyArgsArgumentMatcherDiagnosticVerifier.cs new file mode 100644 index 00000000..ebb466ec --- /dev/null +++ b/tests/NSubstitute.Analyzers.Tests.Shared/DiagnosticAnalyzers/IWithAnyArgsArgumentMatcherDiagnosticVerifier.cs @@ -0,0 +1,20 @@ +using System.Threading.Tasks; + +namespace NSubstitute.Analyzers.Tests.Shared.DiagnosticAnalyzers; + +public interface IWithAnyArgsArgumentMatcherDiagnosticVerifier +{ + Task ReportsNoDiagnostics_WhenAssigningArgMatchersToMemberNotPrecededByWithAnyArgsLikeMethod(string receivedMethod, string arg); + + Task ReportsNoDiagnostics_WhenAssigningArgAnyMatcherToMemberPrecededByWithAnyArgsLikeMethod(string receivedMethod, string arg); + + Task ReportsNoDiagnostics_WhenAssigningArgMatchersToMemberNotPrecededByWithAnyArgsLikeMethodForDelegate(string receivedMethod, string arg); + + Task ReportsDiagnostics_WhenUsingInvalidArgMatchersWithInvocationPrecededByWithAnyArgsLikeMethod(string receivedMethod, string arg); + + Task ReportsDiagnostics_WhenAssigningInvalidArgMatchersToMemberPrecededByWithAnyArgsLikeMethodForDelegate(string receivedMethod, string arg); + + Task ReportsNoDiagnostics_WhenUsingArgMatchersWithInvocationNotPrecededByWithAnyArgsLikeMethod(string receivedMethod, string arg); + + Task ReportsNoDiagnostics_WhenUsingArgAnyMatcherWithInvocationPrecededByWithAnyArgsLikeMethod(string receivedMethod, string arg); +} \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/NonSubstitutableMemberArgumentMatcherAnalyzerTests/NonSubstitutableMemberArgumentMatcherDiagnosticVerifier.cs b/tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/NonSubstitutableMemberArgumentMatcherAnalyzerTests/NonSubstitutableMemberArgumentMatcherDiagnosticVerifier.cs index 986edfff..baaf7e79 100644 --- a/tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/NonSubstitutableMemberArgumentMatcherAnalyzerTests/NonSubstitutableMemberArgumentMatcherDiagnosticVerifier.cs +++ b/tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/NonSubstitutableMemberArgumentMatcherAnalyzerTests/NonSubstitutableMemberArgumentMatcherDiagnosticVerifier.cs @@ -125,7 +125,7 @@ public abstract class NonSubstitutableMemberArgumentMatcherDiagnosticVerifier : [CombinatoryTheory] [MemberData(nameof(CorrectlyUsedArgTestCasesWithoutDelegates))] - public abstract Task ReportsNoDiagnostics_WhenAssigningArgMatchersToSubstitutableMemberPrecededByReceivedLikeMethod(string receivedMethod, string arg); + public abstract Task ReportsDiagnostics_WhenAssigningInvalidArgMatchersToMemberPrecededByWithAnyArgsLikeMethod(string receivedMethod, string arg); [CombinatoryTheory] [MemberData(nameof(MisusedArgTestCasesWithoutDelegates))] diff --git a/tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/NonSubstitutableMemberArgumentMatcherAnalyzerTests/NonSubstitutableMemberArgumentMatcherTests.cs b/tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/NonSubstitutableMemberArgumentMatcherAnalyzerTests/NonSubstitutableMemberArgumentMatcherTests.cs index 34e4f0cb..13c2a394 100644 --- a/tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/NonSubstitutableMemberArgumentMatcherAnalyzerTests/NonSubstitutableMemberArgumentMatcherTests.cs +++ b/tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/NonSubstitutableMemberArgumentMatcherAnalyzerTests/NonSubstitutableMemberArgumentMatcherTests.cs @@ -825,7 +825,7 @@ End Namespace "ReceivedWithAnyArgs()", "DidNotReceive()", "DidNotReceiveWithAnyArgs()")] - public override async Task ReportsNoDiagnostics_WhenAssigningArgMatchersToSubstitutableMemberPrecededByReceivedLikeMethod(string receivedMethod, string arg) + public override async Task ReportsDiagnostics_WhenAssigningInvalidArgMatchersToMemberPrecededByWithAnyArgsLikeMethod(string receivedMethod, string arg) { var source = $@"Imports System Imports NSubstitute diff --git a/tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/NonSubstitutableMemberWhenAnalyzerTests/NonSubstitutableMemberWhenDiagnosticVerifier.cs b/tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/NonSubstitutableMemberWhenAnalyzerTests/NonSubstitutableMemberWhenDiagnosticVerifier.cs index 6318b2b6..d216f7b9 100644 --- a/tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/NonSubstitutableMemberWhenAnalyzerTests/NonSubstitutableMemberWhenDiagnosticVerifier.cs +++ b/tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/NonSubstitutableMemberWhenAnalyzerTests/NonSubstitutableMemberWhenDiagnosticVerifier.cs @@ -187,12 +187,14 @@ Dim x as Integer [InlineData( @"Sub(sb As Foo) Dim x = [|sb.Bar|] - End Sub", "Friend member Bar can not be intercepted without InternalsVisibleToAttribute.")] + End Sub", + "Friend member Bar can not be intercepted without InternalsVisibleToAttribute.")] [InlineData(@"Sub(sb As Foo) [|sb.FooBar(Arg.Any(Of Integer)())|]", "Friend member FooBar can not be intercepted without InternalsVisibleToAttribute.")] [InlineData( @"Sub(sb As Foo) Dim x = [|sb(Arg.Any(Of Integer)())|] - End Sub", "Friend member Item can not be intercepted without InternalsVisibleToAttribute.")] + End Sub", + "Friend member Item can not be intercepted without InternalsVisibleToAttribute.")] public abstract Task ReportsDiagnostics_WhenSettingValueForInternalVirtualMember_AndInternalsVisibleToNotApplied(string method, string call, string message); [CombinatoryTheory] @@ -211,12 +213,14 @@ Dim x as Integer [InlineData( @"Sub(sb As Foo) Dim x = [|sb.Bar|] - End Sub", "Friend member Bar can not be intercepted without InternalsVisibleToAttribute.")] + End Sub", + "Friend member Bar can not be intercepted without InternalsVisibleToAttribute.")] [InlineData(@"Sub(sb As Foo) [|sb.FooBar(Arg.Any(Of Integer)())|]", "Friend member FooBar can not be intercepted without InternalsVisibleToAttribute.")] [InlineData( @"Sub(sb As Foo) Dim x = [|sb(Arg.Any(Of Integer)())|] - End Sub", "Friend member Item can not be intercepted without InternalsVisibleToAttribute.")] + End Sub", + "Friend member Item can not be intercepted without InternalsVisibleToAttribute.")] public abstract Task ReportsDiagnostics_WhenSettingValueForInternalVirtualMember_AndInternalsVisibleToAppliedToWrongAssembly(string method, string call, string message); [CombinatoryTheory] diff --git a/tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/WithAnyArgsArgumentMatcherAnalyzerTests/ReceivedAsExtensionMethodTests.cs b/tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/WithAnyArgsArgumentMatcherAnalyzerTests/ReceivedAsExtensionMethodTests.cs new file mode 100644 index 00000000..06ee2501 --- /dev/null +++ b/tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/WithAnyArgsArgumentMatcherAnalyzerTests/ReceivedAsExtensionMethodTests.cs @@ -0,0 +1,225 @@ +using System.Threading.Tasks; +using NSubstitute.Analyzers.Tests.Shared.Extensibility; + +namespace NSubstitute.Analyzers.Tests.VisualBasic.DiagnosticAnalyzersTests.WithAnyArgsArgumentMatcherAnalyzerTests; + +public class ReceivedAsExtensionMethodTests : WithAnyArgsArgumentMatcherDiagnosticVerifier +{ + [CombinatoryData( + "DidNotReceive()", + "Received(Quantity.None())", + "Received()")] + public override async Task ReportsNoDiagnostics_WhenAssigningArgMatchersToMemberNotPrecededByWithAnyArgsLikeMethodForDelegate(string receivedMethod, string arg) + { + var source = $@"Imports System +Imports NSubstitute +Imports NSubstitute.ReceivedExtensions + +Namespace MyNamespace + Interface IFoo + Property Foo As Action(Of Integer) + Default Property Item(ByVal x As Integer?) As Action(Of Integer) + End Interface + + Public Class FooTests + Public Sub Test() + Dim substitute = NSubstitute.Substitute.[For](Of IFoo)() + substitute.{receivedMethod}.Foo = {arg} + substitute.{receivedMethod}(1) = {arg} + End Sub + End Class +End Namespace +"; + await VerifyNoDiagnostic(source); + } + + [CombinatoryData( + "ReceivedWithAnyArgs(Quantity.None())", + "ReceivedWithAnyArgs()", + "DidNotReceiveWithAnyArgs()")] + public override async Task ReportsNoDiagnostics_WhenAssigningArgAnyMatcherToMemberPrecededByWithAnyArgsLikeMethod(string receivedMethod, string arg) + { + var source = $@"Imports System +Imports NSubstitute +Imports NSubstitute.ReceivedExtensions + +Namespace MyNamespace + Interface IFoo + Property Foo As Integer? + Default Property Item(ByVal x As Integer?) As Integer? + End Interface + + Public Class FooTests + Public Sub Test() + Dim substitute = NSubstitute.Substitute.[For](Of IFoo)() + substitute.{receivedMethod}.Foo = {arg} + substitute.{receivedMethod}(1) = {arg} + End Sub + End Class +End Namespace +"; + await VerifyNoDiagnostic(source); + } + + [CombinatoryData( + "DidNotReceive()", + "Received(Quantity.None())", + "Received()")] + public override async Task ReportsNoDiagnostics_WhenAssigningArgMatchersToMemberNotPrecededByWithAnyArgsLikeMethod(string receivedMethod, string arg) + { + var source = $@"Imports System +Imports NSubstitute +Imports NSubstitute.ReceivedExtensions + +Namespace MyNamespace + Interface IFoo + Property Foo As Integer? + Default Property Item(ByVal x As Integer?) As Integer? + End Interface + + Public Class FooTests + Public Sub Test() + Dim substitute = NSubstitute.Substitute.[For](Of IFoo)() + substitute.{receivedMethod}.Foo = {arg} + substitute.{receivedMethod}(1) = {arg} + End Sub + End Class +End Namespace +"; + await VerifyNoDiagnostic(source); + } + + [CombinatoryData( + "ReceivedWithAnyArgs(Quantity.None())", + "ReceivedWithAnyArgs()", + "DidNotReceiveWithAnyArgs()")] + public override async Task ReportsDiagnostics_WhenAssigningInvalidArgMatchersToMemberPrecededByWithAnyArgsLikeMethod(string receivedMethod, string arg) + { + var source = $@"Imports System +Imports NSubstitute +Imports NSubstitute.ReceivedExtensions + +Namespace MyNamespace + Interface IFoo + Property Foo As Integer? + Default Property Item(ByVal x As Integer?) As Integer? + End Interface + + Public Class FooTests + Public Sub Test() + Dim substitute = NSubstitute.Substitute.[For](Of IFoo)() + substitute.{receivedMethod}.Foo = {arg} + substitute.{receivedMethod}(1) = {arg} + End Sub + End Class +End Namespace +"; + await VerifyDiagnostic(source, WithAnyArgsArgumentMatcherUsage); + } + + [CombinatoryData( + "ReceivedWithAnyArgs(Quantity.None())", + "ReceivedWithAnyArgs()", + "DidNotReceiveWithAnyArgs()")] + public override async Task ReportsDiagnostics_WhenAssigningInvalidArgMatchersToMemberPrecededByWithAnyArgsLikeMethodForDelegate(string receivedMethod, string arg) + { + var source = $@"Imports System +Imports NSubstitute +Imports NSubstitute.ReceivedExtensions + +Namespace MyNamespace + Interface IFoo + Property Foo As Action(Of Integer) + Default Property Item(ByVal x As Integer?) As Action(Of Integer) + End Interface + + Public Class FooTests + Public Sub Test() + Dim substitute = NSubstitute.Substitute.[For](Of IFoo)() + substitute.{receivedMethod}.Foo = {arg} + substitute.{receivedMethod}(1) = {arg} + End Sub + End Class +End Namespace +"; + await VerifyDiagnostic(source, WithAnyArgsArgumentMatcherUsage); + } + + [CombinatoryData( + "ReceivedWithAnyArgs(Quantity.None())", + "ReceivedWithAnyArgs()", + "DidNotReceiveWithAnyArgs()")] + public override async Task ReportsNoDiagnostics_WhenUsingArgAnyMatcherWithInvocationPrecededByWithAnyArgsLikeMethod(string receivedMethod, string arg) + { + var source = $@"Imports System +Imports NSubstitute +Imports NSubstitute.ReceivedExtensions + +Namespace MyNamespace +Interface Foo + Sub Bar(ByVal x As Integer?) +End Interface + +Public Class FooTests + Public Sub Test() + Dim substitute = NSubstitute.Substitute.[For](Of Foo)() + substitute.{receivedMethod}.Bar({arg}) + End Sub +End Class +End Namespace +"; + await VerifyNoDiagnostic(source); + } + + [CombinatoryData( + "DidNotReceive()", + "Received(Quantity.None())", + "Received()")] + public override async Task ReportsNoDiagnostics_WhenUsingArgMatchersWithInvocationNotPrecededByWithAnyArgsLikeMethod(string receivedMethod, string arg) + { + var source = $@"Imports System +Imports NSubstitute +Imports NSubstitute.ReceivedExtensions + +Namespace MyNamespace + Interface Foo + Sub Bar(ByVal x As Integer?) + End Interface + + Public Class FooTests + Public Sub Test() + Dim substitute = NSubstitute.Substitute.[For](Of Foo)() + substitute.{receivedMethod}.Bar({arg}) + End Sub + End Class +End Namespace"; + + await VerifyNoDiagnostic(source); + } + + [CombinatoryData( + "ReceivedWithAnyArgs(Quantity.None())", + "ReceivedWithAnyArgs()", + "DidNotReceiveWithAnyArgs()")] + public override async Task ReportsDiagnostics_WhenUsingInvalidArgMatchersWithInvocationPrecededByWithAnyArgsLikeMethod(string receivedMethod, string arg) + { + var source = $@"Imports System +Imports NSubstitute +Imports NSubstitute.ReceivedExtensions + +Namespace MyNamespace + Interface Foo + Sub Bar(ByVal x As Integer?) + End Interface + + Public Class FooTests + Public Sub Test() + Dim substitute = NSubstitute.Substitute.[For](Of Foo)() + substitute.{receivedMethod}.Bar({arg}) + End Sub + End Class +End Namespace +"; + await VerifyDiagnostic(source, WithAnyArgsArgumentMatcherUsage); + } +} \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/WithAnyArgsArgumentMatcherAnalyzerTests/ReceivedAsOrdinaryMethodTests.cs b/tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/WithAnyArgsArgumentMatcherAnalyzerTests/ReceivedAsOrdinaryMethodTests.cs new file mode 100644 index 00000000..dbabcd6c --- /dev/null +++ b/tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/WithAnyArgsArgumentMatcherAnalyzerTests/ReceivedAsOrdinaryMethodTests.cs @@ -0,0 +1,229 @@ +using System.Threading.Tasks; +using NSubstitute.Analyzers.Tests.Shared.Extensibility; + +namespace NSubstitute.Analyzers.Tests.VisualBasic.DiagnosticAnalyzersTests.WithAnyArgsArgumentMatcherAnalyzerTests; + +public class ReceivedAsOrdinaryMethodTests : WithAnyArgsArgumentMatcherDiagnosticVerifier +{ + [CombinatoryData( + "SubstituteExtensions.DidNotReceive(substitute)", + "SubstituteExtensions.DidNotReceive(substitute:= substitute)", + "ReceivedExtensions.Received(substitute, Quantity.None())", + "ReceivedExtensions.Received(substitute:= substitute, requiredQuantity:= Quantity.None())", + "ReceivedExtensions.Received(requiredQuantity:= Quantity.None(), substitute:= substitute)", + "SubstituteExtensions.Received(substitute)", + "SubstituteExtensions.Received(substitute:= substitute)")] + public override async Task ReportsNoDiagnostics_WhenAssigningArgMatchersToMemberNotPrecededByWithAnyArgsLikeMethodForDelegate(string receivedMethod, string arg) + { + var source = $@"Imports System +Imports NSubstitute +Imports NSubstitute.ReceivedExtensions + +Namespace MyNamespace + Interface IFoo + Property Foo As Action(Of Integer) + Default Property Item(ByVal x As Integer?) As Action(Of Integer) + End Interface + + Public Class FooTests + Public Sub Test() + Dim substitute = NSubstitute.Substitute.[For](Of IFoo)() + {receivedMethod}.Foo = {arg} + {receivedMethod}(1) = {arg} + End Sub + End Class +End Namespace +"; + await VerifyNoDiagnostic(source); + } + + [CombinatoryData( + "ReceivedWithAnyArgs(Quantity.None())", + "ReceivedWithAnyArgs()", + "DidNotReceiveWithAnyArgs()")] + public override async Task ReportsNoDiagnostics_WhenAssigningArgAnyMatcherToMemberPrecededByWithAnyArgsLikeMethod(string receivedMethod, string arg) + { + var source = $@"Imports System +Imports NSubstitute +Imports NSubstitute.ReceivedExtensions + +Namespace MyNamespace + Interface IFoo + Property Foo As Integer? + Default Property Item(ByVal x As Integer?) As Integer? + End Interface + + Public Class FooTests + Public Sub Test() + Dim substitute = NSubstitute.Substitute.[For](Of IFoo)() + substitute.{receivedMethod}.Foo = {arg} + substitute.{receivedMethod}(1) = {arg} + End Sub + End Class +End Namespace +"; + await VerifyNoDiagnostic(source); + } + + [CombinatoryData( + "DidNotReceive()", + "Received(Quantity.None())", + "Received()")] + public override async Task ReportsNoDiagnostics_WhenAssigningArgMatchersToMemberNotPrecededByWithAnyArgsLikeMethod(string receivedMethod, string arg) + { + var source = $@"Imports System +Imports NSubstitute +Imports NSubstitute.ReceivedExtensions + +Namespace MyNamespace + Interface IFoo + Property Foo As Integer? + Default Property Item(ByVal x As Integer?) As Integer? + End Interface + + Public Class FooTests + Public Sub Test() + Dim substitute = NSubstitute.Substitute.[For](Of IFoo)() + substitute.{receivedMethod}.Foo = {arg} + substitute.{receivedMethod}(1) = {arg} + End Sub + End Class +End Namespace +"; + await VerifyNoDiagnostic(source); + } + + [CombinatoryData( + "ReceivedWithAnyArgs(Quantity.None())", + "ReceivedWithAnyArgs()", + "DidNotReceiveWithAnyArgs()")] + public override async Task ReportsDiagnostics_WhenAssigningInvalidArgMatchersToMemberPrecededByWithAnyArgsLikeMethod(string receivedMethod, string arg) + { + var source = $@"Imports System +Imports NSubstitute +Imports NSubstitute.ReceivedExtensions + +Namespace MyNamespace + Interface IFoo + Property Foo As Integer? + Default Property Item(ByVal x As Integer?) As Integer? + End Interface + + Public Class FooTests + Public Sub Test() + Dim substitute = NSubstitute.Substitute.[For](Of IFoo)() + substitute.{receivedMethod}.Foo = {arg} + substitute.{receivedMethod}(1) = {arg} + End Sub + End Class +End Namespace +"; + await VerifyDiagnostic(source, WithAnyArgsArgumentMatcherUsage); + } + + [CombinatoryData( + "ReceivedWithAnyArgs(Quantity.None())", + "ReceivedWithAnyArgs()", + "DidNotReceiveWithAnyArgs()")] + public override async Task ReportsDiagnostics_WhenAssigningInvalidArgMatchersToMemberPrecededByWithAnyArgsLikeMethodForDelegate(string receivedMethod, string arg) + { + var source = $@"Imports System +Imports NSubstitute +Imports NSubstitute.ReceivedExtensions + +Namespace MyNamespace + Interface IFoo + Property Foo As Action(Of Integer) + Default Property Item(ByVal x As Integer?) As Action(Of Integer) + End Interface + + Public Class FooTests + Public Sub Test() + Dim substitute = NSubstitute.Substitute.[For](Of IFoo)() + substitute.{receivedMethod}.Foo = {arg} + substitute.{receivedMethod}(1) = {arg} + End Sub + End Class +End Namespace +"; + await VerifyDiagnostic(source, WithAnyArgsArgumentMatcherUsage); + } + + [CombinatoryData( + "ReceivedWithAnyArgs(Quantity.None())", + "ReceivedWithAnyArgs()", + "DidNotReceiveWithAnyArgs()")] + public override async Task ReportsNoDiagnostics_WhenUsingArgAnyMatcherWithInvocationPrecededByWithAnyArgsLikeMethod(string receivedMethod, string arg) + { + var source = $@"Imports System +Imports NSubstitute +Imports NSubstitute.ReceivedExtensions + +Namespace MyNamespace +Interface Foo + Sub Bar(ByVal x As Integer?) +End Interface + +Public Class FooTests + Public Sub Test() + Dim substitute = NSubstitute.Substitute.[For](Of Foo)() + substitute.{receivedMethod}.Bar({arg}) + End Sub +End Class +End Namespace +"; + await VerifyNoDiagnostic(source); + } + + [CombinatoryData( + "DidNotReceive()", + "Received(Quantity.None())", + "Received()")] + public override async Task ReportsNoDiagnostics_WhenUsingArgMatchersWithInvocationNotPrecededByWithAnyArgsLikeMethod(string receivedMethod, string arg) + { + var source = $@"Imports System +Imports NSubstitute +Imports NSubstitute.ReceivedExtensions + +Namespace MyNamespace + Interface Foo + Sub Bar(ByVal x As Integer?) + End Interface + + Public Class FooTests + Public Sub Test() + Dim substitute = NSubstitute.Substitute.[For](Of Foo)() + substitute.{receivedMethod}.Bar({arg}) + End Sub + End Class +End Namespace"; + + await VerifyNoDiagnostic(source); + } + + [CombinatoryData( + "ReceivedWithAnyArgs(Quantity.None())", + "ReceivedWithAnyArgs()", + "DidNotReceiveWithAnyArgs()")] + public override async Task ReportsDiagnostics_WhenUsingInvalidArgMatchersWithInvocationPrecededByWithAnyArgsLikeMethod(string receivedMethod, string arg) + { + var source = $@"Imports System +Imports NSubstitute +Imports NSubstitute.ReceivedExtensions + +Namespace MyNamespace + Interface Foo + Sub Bar(ByVal x As Integer?) + End Interface + + Public Class FooTests + Public Sub Test() + Dim substitute = NSubstitute.Substitute.[For](Of Foo)() + substitute.{receivedMethod}.Bar({arg}) + End Sub + End Class +End Namespace +"; + await VerifyDiagnostic(source, WithAnyArgsArgumentMatcherUsage); + } +} \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/WithAnyArgsArgumentMatcherAnalyzerTests/WithAnyArgsArgumentMatcherDiagnosticVerifier.cs b/tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/WithAnyArgsArgumentMatcherAnalyzerTests/WithAnyArgsArgumentMatcherDiagnosticVerifier.cs new file mode 100644 index 00000000..10120d60 --- /dev/null +++ b/tests/NSubstitute.Analyzers.Tests.VisualBasic/DiagnosticAnalyzersTests/WithAnyArgsArgumentMatcherAnalyzerTests/WithAnyArgsArgumentMatcherDiagnosticVerifier.cs @@ -0,0 +1,145 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using NSubstitute.Analyzers.Shared; +using NSubstitute.Analyzers.Shared.Settings; +using NSubstitute.Analyzers.Tests.Shared.DiagnosticAnalyzers; +using NSubstitute.Analyzers.Tests.Shared.Extensibility; +using NSubstitute.Analyzers.VisualBasic; +using NSubstitute.Analyzers.VisualBasic.DiagnosticAnalyzers; +using Xunit; + +namespace NSubstitute.Analyzers.Tests.VisualBasic.DiagnosticAnalyzersTests.WithAnyArgsArgumentMatcherAnalyzerTests; + +public abstract class WithAnyArgsArgumentMatcherDiagnosticVerifier : VisualBasicDiagnosticVerifier, IWithAnyArgsArgumentMatcherDiagnosticVerifier +{ + internal AnalyzersSettings Settings { get; set; } + + protected DiagnosticDescriptor WithAnyArgsArgumentMatcherUsage { get; } = DiagnosticDescriptors.WithAnyArgsArgumentMatcherUsage; + + protected override DiagnosticAnalyzer DiagnosticAnalyzer { get; } = new WithAnyArgsArgumentMatcherAnalyzer(); + + [CombinatoryTheory] + [MemberData(nameof(CorrectlyUsedArgTestCasesWithoutDelegates))] + public abstract Task ReportsNoDiagnostics_WhenAssigningArgMatchersToMemberNotPrecededByWithAnyArgsLikeMethod(string receivedMethod, string arg); + + [CombinatoryTheory] + [MemberData(nameof(ArgAnyTestCases))] + public abstract Task ReportsNoDiagnostics_WhenAssigningArgAnyMatcherToMemberPrecededByWithAnyArgsLikeMethod(string receivedMethod, string arg); + + [CombinatoryTheory] + [MemberData(nameof(CorrectlyUsedArgTestCasesDelegates))] + public abstract Task ReportsNoDiagnostics_WhenAssigningArgMatchersToMemberNotPrecededByWithAnyArgsLikeMethodForDelegate(string receivedMethod, string arg); + + [CombinatoryTheory] + [MemberData(nameof(MisusedArgTestCasesWithoutDelegates))] + public abstract Task ReportsDiagnostics_WhenAssigningInvalidArgMatchersToMemberPrecededByWithAnyArgsLikeMethod(string receivedMethod, string arg); + + [CombinatoryTheory] + [MemberData(nameof(MisusedArgTestCasesDelegates))] + public abstract Task ReportsDiagnostics_WhenAssigningInvalidArgMatchersToMemberPrecededByWithAnyArgsLikeMethodForDelegate(string receivedMethod, string arg); + + [CombinatoryTheory] + [MemberData(nameof(CorrectlyUsedArgTestCasesWithoutDelegates))] + public abstract Task ReportsNoDiagnostics_WhenUsingArgMatchersWithInvocationNotPrecededByWithAnyArgsLikeMethod(string receivedMethod, string arg); + + [CombinatoryTheory] + [MemberData(nameof(ArgAnyTestCases))] + public abstract Task ReportsNoDiagnostics_WhenUsingArgAnyMatcherWithInvocationPrecededByWithAnyArgsLikeMethod(string receivedMethod, string arg); + + [CombinatoryTheory] + [MemberData(nameof(MisusedArgTestCasesWithoutDelegates))] + public abstract Task ReportsDiagnostics_WhenUsingInvalidArgMatchersWithInvocationPrecededByWithAnyArgsLikeMethod(string receivedMethod, string arg); + + public static IEnumerable MisusedArgTestCases + { + get { return MisusedArgs.Select(argArray => argArray.Select(arg => arg).ToArray()); } + } + + public static IEnumerable MisusedArgTestCasesWithoutDelegates => + MisusedArgTestCases.Where(arguments => arguments.All(argument => !argument.ToString().Contains("Invoke"))); + + public static IEnumerable ArgAnyTestCases => + CorrectlyUsedArgTestCases.Where(arguments => arguments.All(argument => argument.ToString().Contains("Any"))); + + public static IEnumerable MisusedArgTestCasesDelegates => + MisusedArgTestCases.Where(arguments => arguments.All(argument => argument.ToString().Contains("Invoke"))); + + public static IEnumerable CorrectlyUsedArgTestCases + { + get + { + yield return new object[] { "Arg.Any(Of Integer)()" }; + 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.Is(Function(ByVal __ As Integer) __ > 0)" }; + yield return new object[] { "If(True, Arg.Is(Function(ByVal __ As Integer) __ > 0), 0)" }; + 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.Compat.Is(Function(ByVal __ As Integer) __ > 0)" }; + yield return new object[] { "If(True, Arg.Compat.Is(Function(ByVal __ As Integer) __ > 0), 0)" }; + yield return new object[] { "Arg.Invoke(1)" }; + yield return new object[] { "Arg.Compat.Invoke(1)" }; + yield return new object[] { "Arg.InvokeDelegate(Of Action(Of Integer))()" }; + yield return new object[] { "Arg.Compat.InvokeDelegate(Of Action(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 CorrectlyUsedArgTestCasesWithoutDelegates => CorrectlyUsedArgTestCases.Where(arguments => + arguments.All(argument => !argument.ToString().Contains("Invoke"))); + + public static IEnumerable CorrectlyUsedArgTestCasesDelegates => + CorrectlyUsedArgTestCases.Where(arguments => arguments.All(argument => argument.ToString().Contains("Invoke"))); + + public static IEnumerable MisusedArgs + { + get + { + 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(1)|]" }; + yield return new[] { "[|Arg.Compat.Invoke(1)|]" }; + yield return new[] { "[|Arg.InvokeDelegate(Of Action(Of Integer))()|]" }; + yield return new[] { "[|Arg.Compat.InvokeDelegate(Of Action(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)|]" + }; + } + } +} \ No newline at end of file