Skip to content

Commit

Permalink
GH-108 - detecting non-virtual calls in Received.InOrder (#146)
Browse files Browse the repository at this point in the history
* [GH-108] - detecting non-virtual calls in Received.InOrder blocks
  • Loading branch information
tpodolak authored Oct 20, 2020
1 parent 8d9dc15 commit 5eb085d
Show file tree
Hide file tree
Showing 48 changed files with 2,924 additions and 312 deletions.
3 changes: 2 additions & 1 deletion NSubstitute.Analyzers.sln
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ ProjectSection(SolutionItems) = preProject
documentation\rules\NS1001.md = documentation\rules\NS1001.md
documentation\rules\NS1002.md = documentation\rules\NS1002.md
documentation\rules\NS1003.md = documentation\rules\NS1003.md
documentation\rules\NS1003.md = documentation\rules\NS1004.md
documentation\rules\NS1004.md = documentation\rules\NS1004.md
documentation\rules\NS1005.md = documentation\rules\NS1005.md
documentation\rules\NS2000.md = documentation\rules\NS2000.md
documentation\rules\NS2001.md = documentation\rules\NS2001.md
documentation\rules\NS2002.md = documentation\rules\NS2002.md
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using NSubstitute.Analyzers.Benchmarks.Source.CSharp.Models;

namespace NSubstitute.Analyzers.Benchmarks.Source.CSharp.DiagnosticsSources
{
public class NonSubstitutableMemberReceivedInOrderDiagnosticSource
{
public void NS1005_NonVirtualSetupSpecification()
{
var substitute = Substitute.For<Foo>();

Received.InOrder(() =>
{
substitute.ObjectReturningMethod();
var methodItem = substitute.ObjectReturningMethod();
substitute.ObjectReturningMethodWithArguments(substitute.IntReturningProperty, 1, 1m);
_ = substitute.IntReturningProperty;
var propertyItem = substitute.IntReturningProperty;
_ = substitute[0];
var indexerItem = substitute[0];
var otherIndexerItem = substitute[0];
var usedIndexerItem = otherIndexerItem;
});
}

public void NS1003_InternalVirtualSetupSpecification()
{
var substitute = Substitute.For<Foo>();

substitute.InternalObjectReturningMethod();
var methodItem = substitute.InternalObjectReturningMethod();
substitute.InternalObjectReturningMethodWithArguments(substitute.IntReturningProperty);
_ = substitute.InternalObjectReturningProperty;
var propertyItem = substitute.InternalObjectReturningProperty;
_ = substitute[0];
var indexerItem = substitute[0, 0, 0];
var otherIndexerItem = substitute[0, 0, 0];
var usedIndexerItem = otherIndexerItem;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ public class CSharpDiagnosticAnalyzersBenchmarks : AbstractDiagnosticAnalyzersBe

protected override AnalyzerBenchmark NonSubstitutableMemberWhenAnalyzerBenchmark { get; }

protected override AnalyzerBenchmark NonSubstitutableMemberReceivedInOrderAnalyzerBenchmark { get; }

protected override AnalyzerBenchmark ReEntrantSetupAnalyzerBenchmark { get; }

protected override AnalyzerBenchmark SubstituteAnalyzerBenchmark { get; }
Expand Down Expand Up @@ -46,6 +48,7 @@ public CSharpDiagnosticAnalyzersBenchmarks()
NonSubstitutableMemberAnalyzerBenchmark = AnalyzerBenchmark.CreateBenchmark(Solution, new NonSubstitutableMemberAnalyzer());
NonSubstitutableMemberReceivedAnalyzerBenchmark = AnalyzerBenchmark.CreateBenchmark(Solution, new NonSubstitutableMemberReceivedAnalyzer());
NonSubstitutableMemberWhenAnalyzerBenchmark = AnalyzerBenchmark.CreateBenchmark(Solution, new NonSubstitutableMemberWhenAnalyzer());
NonSubstitutableMemberReceivedInOrderAnalyzerBenchmark = AnalyzerBenchmark.CreateBenchmark(Solution, new NonSubstitutableMemberReceivedInOrderAnalyzer());
ReEntrantSetupAnalyzerBenchmark = AnalyzerBenchmark.CreateBenchmark(Solution, new ReEntrantSetupAnalyzer());
SubstituteAnalyzerBenchmark = AnalyzerBenchmark.CreateBenchmark(Solution, new SubstituteAnalyzer());
UnusedReceivedAnalyzerBenchmark = AnalyzerBenchmark.CreateBenchmark(Solution, new UnusedReceivedAnalyzer());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ public abstract class AbstractDiagnosticAnalyzersBenchmarks

protected abstract AnalyzerBenchmark NonSubstitutableMemberWhenAnalyzerBenchmark { get; }

protected abstract AnalyzerBenchmark NonSubstitutableMemberReceivedInOrderAnalyzerBenchmark { get; }

protected abstract AnalyzerBenchmark ReEntrantSetupAnalyzerBenchmark { get; }

protected abstract AnalyzerBenchmark SubstituteAnalyzerBenchmark { get; }
Expand Down Expand Up @@ -79,6 +81,12 @@ public void NonSubstitutableMemberWhenAnalyzer()
NonSubstitutableMemberWhenAnalyzerBenchmark.Run();
}

[Benchmark]
public void NonSubstitutableMemberReceivedInOrderAnalyzer()
{
NonSubstitutableMemberReceivedInOrderAnalyzerBenchmark.Run();
}

[Benchmark]
public void ReEntrantSetupAnalyzer()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ public class VisualBasicDiagnosticAnalyzersBenchmarks : AbstractDiagnosticAnalyz

protected override AnalyzerBenchmark NonSubstitutableMemberWhenAnalyzerBenchmark { get; }

protected override AnalyzerBenchmark NonSubstitutableMemberReceivedInOrderAnalyzerBenchmark { get; }

protected override AnalyzerBenchmark ReEntrantSetupAnalyzerBenchmark { get; }

protected override AnalyzerBenchmark SubstituteAnalyzerBenchmark { get; }
Expand Down Expand Up @@ -46,6 +48,7 @@ public VisualBasicDiagnosticAnalyzersBenchmarks()
NonSubstitutableMemberAnalyzerBenchmark = AnalyzerBenchmark.CreateBenchmark(Solution, new NonSubstitutableMemberAnalyzer());
NonSubstitutableMemberReceivedAnalyzerBenchmark = AnalyzerBenchmark.CreateBenchmark(Solution, new NonSubstitutableMemberReceivedAnalyzer());
NonSubstitutableMemberWhenAnalyzerBenchmark = AnalyzerBenchmark.CreateBenchmark(Solution, new NonSubstitutableMemberWhenAnalyzer());
NonSubstitutableMemberReceivedInOrderAnalyzerBenchmark = AnalyzerBenchmark.CreateBenchmark(Solution, new NonSubstitutableMemberReceivedInOrderAnalyzer());
ReEntrantSetupAnalyzerBenchmark = AnalyzerBenchmark.CreateBenchmark(Solution, new ReEntrantSetupAnalyzer());
SubstituteAnalyzerBenchmark = AnalyzerBenchmark.CreateBenchmark(Solution, new SubstituteAnalyzer());
UnusedReceivedAnalyzerBenchmark = AnalyzerBenchmark.CreateBenchmark(Solution, new UnusedReceivedAnalyzer());
Expand Down
39 changes: 39 additions & 0 deletions documentation/rules/NS1005.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# NS1005

<table>
<tr>
<td>CheckId</td>
<td>NS1005</td>
</tr>
<tr>
<td>Category</td>
<td>Non-substitutable member</td>
</tr>
</table>

## Cause

Checking call order for non-virtual member of a class.

## Rule description

A violation of this rule occurs when `Received.InOrder` checks call order for non-virtual member of a class.

## How to fix violations

To fix a violation of this rule, make the member of your class virtual or substitute for interface.

## How to suppress violations

This warning can be suppressed by disabling the warning in the **ruleset** file for the project or by suppressing it (for selected members) in `nsubstitute.json` file. See the [configuration](../Configuration.md) section for details on how to set this up.
The warning can also be suppressed programmatically for an assembly:
````c#
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Non-substitutable member", "NS1005:Non-virtual setup specification.", Justification = "Reviewed")]
````

Or for a specific code block:
````c#
#pragma warning disable NS1005 // Non-virtual setup specification.
// the code which produces warning
#pragma warning restore NS1005 // Non-virtual setup specification.
````
3 changes: 2 additions & 1 deletion documentation/rules/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
| [NS1002](NS1002.md) | Non-substitutable member | Substituting for non-virtual member of a class. |
| [NS1003](NS1003.md) | Non-substitutable member | Substituting for an internal member of a class without proxies having visibility into internal members. |
| [NS1004](NS1004.md) | Non-substitutable member | Argument matcher used with a non-virtual member of a class. |
| [NS1005](NS1005.md) | Non-substitutable member | Checking call order for non-virtual member of a class. |
| [NS2000](NS2000.md) | Substitute creation | Substitute.ForPartsOf used with interface or delegate. |
| [NS2001](NS2001.md) | Substitute creation | NSubstitute used with class which does not expose public or protected constructor. |
| [NS2002](NS2002.md) | Substitute creation | NSubstitute used with class which does not expose parameterless constructor. |
Expand All @@ -26,4 +27,4 @@
| [NS4000](NS4000.md) | Call configuration | Calling substitute from within `Returns` block. |
| [NS5000](NS5000.md) | Usage | Checking received calls without specifying member. |
| [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. |
| [NS5002](NS5002.md) | Usage | Usage of async callback in `Received.InOrder` method. |
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using NSubstitute.Analyzers.Shared.DiagnosticAnalyzers;

namespace NSubstitute.Analyzers.CSharp.DiagnosticAnalyzers
{
internal class NonSubstitutableMemberAnalysis : AbstractNonSubstitutableMemberAnalysis
{
public static NonSubstitutableMemberAnalysis Instance { get; } = new NonSubstitutableMemberAnalysis();

private NonSubstitutableMemberAnalysis()
{
}

protected override ImmutableHashSet<Type> KnownNonVirtualSyntaxKinds { get; } = ImmutableHashSet.Create(
typeof(LiteralExpressionSyntax));
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using NSubstitute.Analyzers.CSharp.Extensions;
using NSubstitute.Analyzers.Shared.DiagnosticAnalyzers;

namespace NSubstitute.Analyzers.CSharp.DiagnosticAnalyzers
Expand All @@ -16,9 +12,6 @@ internal sealed class NonSubstitutableMemberAnalyzer : AbstractNonSubstitutableM
{
protected override SyntaxKind InvocationExpressionKind { get; } = SyntaxKind.InvocationExpression;

protected override ImmutableHashSet<Type> KnownNonVirtualSyntaxKinds { get; } = ImmutableHashSet.Create(
typeof(LiteralExpressionSyntax));

protected override ImmutableHashSet<int> SupportedMemberAccesses { get; } = ImmutableHashSet.Create(
(int)SyntaxKind.InvocationExpression,
(int)SyntaxKind.SimpleMemberAccessExpression,
Expand All @@ -30,7 +23,7 @@ internal sealed class NonSubstitutableMemberAnalyzer : AbstractNonSubstitutableM
(int)SyntaxKind.StringLiteralExpression);

public NonSubstitutableMemberAnalyzer()
: base(NSubstitute.Analyzers.CSharp.DiagnosticDescriptorsProvider.Instance, SubstitutionNodeFinder.Instance)
: base(NSubstitute.Analyzers.CSharp.DiagnosticDescriptorsProvider.Instance, SubstitutionNodeFinder.Instance, NonSubstitutableMemberAnalysis.Instance)
{
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ internal sealed class NonSubstitutableMemberArgumentMatcherAnalyzer : AbstractNo
(int)SyntaxKind.VariableDeclarator));

public NonSubstitutableMemberArgumentMatcherAnalyzer()
: base(NSubstitute.Analyzers.CSharp.DiagnosticDescriptorsProvider.Instance)
: base(NonSubstitutableMemberAnalysis.Instance, NSubstitute.Analyzers.CSharp.DiagnosticDescriptorsProvider.Instance)
{
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ internal sealed class NonSubstitutableMemberReceivedAnalyzer : AbstractNonSubsti
protected override SyntaxKind InvocationExpressionKind { get; } = SyntaxKind.InvocationExpression;

public NonSubstitutableMemberReceivedAnalyzer()
: base(NSubstitute.Analyzers.CSharp.DiagnosticDescriptorsProvider.Instance)
: base(NSubstitute.Analyzers.CSharp.DiagnosticDescriptorsProvider.Instance, NonSubstitutableMemberAnalysis.Instance)
{
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
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 NonSubstitutableMemberReceivedInOrderAnalyzer : AbstractNonSubstitutableMemberReceivedInOrderAnalyzer<SyntaxKind, InvocationExpressionSyntax, MemberAccessExpressionSyntax, BlockSyntax>
{
private static ImmutableArray<int> IgnoredPaths { get; } =
ImmutableArray.Create((int)SyntaxKind.Argument, (int)SyntaxKind.VariableDeclarator, (int)SyntaxKind.AddAssignmentExpression);

public NonSubstitutableMemberReceivedInOrderAnalyzer()
: base(SubstitutionNodeFinder.Instance, NonSubstitutableMemberAnalysis.Instance, NSubstitute.Analyzers.CSharp.DiagnosticDescriptorsProvider.Instance)
{
}

protected override SyntaxKind InvocationExpressionKind { get; } = SyntaxKind.InvocationExpression;

protected override ImmutableArray<int> IgnoredAncestorPaths { get; } = IgnoredPaths;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
Expand All @@ -13,7 +10,7 @@ namespace NSubstitute.Analyzers.CSharp.DiagnosticAnalyzers
internal sealed class NonSubstitutableMemberWhenAnalyzer : AbstractNonSubstitutableMemberWhenAnalyzer<SyntaxKind, InvocationExpressionSyntax>
{
public NonSubstitutableMemberWhenAnalyzer()
: base(NSubstitute.Analyzers.CSharp.DiagnosticDescriptorsProvider.Instance, SubstitutionNodeFinder.Instance)
: base(NSubstitute.Analyzers.CSharp.DiagnosticDescriptorsProvider.Instance, SubstitutionNodeFinder.Instance, NonSubstitutableMemberAnalysis.Instance)
{
}

Expand Down
Loading

0 comments on commit 5eb085d

Please # to comment.