Skip to content

Commit

Permalink
[GH-108] - handling event subscriptions
Browse files Browse the repository at this point in the history
  • Loading branch information
tpodolak committed Sep 11, 2020
1 parent edb6496 commit a0156dc
Show file tree
Hide file tree
Showing 7 changed files with 220 additions and 72 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,33 +10,8 @@ namespace NSubstitute.Analyzers.CSharp.DiagnosticAnalyzers
[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal sealed class NonSubstitutableMemberReceivedInOrderAnalyzer : AbstractNonSubstitutableMemberReceivedInOrderAnalyzer<SyntaxKind, InvocationExpressionSyntax, MemberAccessExpressionSyntax, BlockSyntax>
{
private static ImmutableArray<ImmutableArray<int>> IgnoredPaths { get; } = ImmutableArray.Create(
ImmutableArray.Create(
(int)SyntaxKind.Argument),
ImmutableArray.Create(
(int)SyntaxKind.AwaitExpression,
(int)SyntaxKind.Argument),
ImmutableArray.Create(
(int)SyntaxKind.CastExpression,
(int)SyntaxKind.Argument),
ImmutableArray.Create(
(int)SyntaxKind.AsExpression,
(int)SyntaxKind.Argument),
ImmutableArray.Create(
(int)SyntaxKind.EqualsValueClause,
(int)SyntaxKind.VariableDeclarator),
ImmutableArray.Create(
(int)SyntaxKind.AwaitExpression,
(int)SyntaxKind.EqualsValueClause,
(int)SyntaxKind.VariableDeclarator),
ImmutableArray.Create(
(int)SyntaxKind.AsExpression,
(int)SyntaxKind.EqualsValueClause,
(int)SyntaxKind.VariableDeclarator),
ImmutableArray.Create(
(int)SyntaxKind.CastExpression,
(int)SyntaxKind.EqualsValueClause,
(int)SyntaxKind.VariableDeclarator));
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)
Expand All @@ -45,7 +20,7 @@ public NonSubstitutableMemberReceivedInOrderAnalyzer()

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

protected override ImmutableArray<ImmutableArray<int>> IgnoredAncestorPaths { get; } = IgnoredPaths;
protected override ImmutableArray<int> IgnoredAncestorPaths { get; } = IgnoredPaths;

protected override ISymbol GetDeclarationSymbol(SemanticModel semanticModel, SyntaxNode node)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
using NSubstitute.Analyzers.Shared.Extensions;

namespace NSubstitute.Analyzers.Shared.DiagnosticAnalyzers
Expand All @@ -17,7 +18,7 @@ internal abstract class AbstractNonSubstitutableMemberReceivedInOrderAnalyzer<TS

protected abstract TSyntaxKind InvocationExpressionKind { get; }

protected abstract ImmutableArray<ImmutableArray<int>> IgnoredAncestorPaths { get; }
protected abstract ImmutableArray<int> IgnoredAncestorPaths { get; }

private readonly Action<SyntaxNodeAnalysisContext> _analyzeInvocationAction;
private readonly ISubstitutionNodeFinder<TInvocationExpressionSyntax> _substitutionNodeFinder;
Expand Down Expand Up @@ -89,6 +90,20 @@ private bool ShouldAnalyzeNode(SemanticModel semanticModel, SyntaxNode syntaxNod
return true;
}

var operation = semanticModel.GetOperation(maybeIgnoredExpression);

if (operation is IArgumentOperation &&
operation.Parent is IInvocationOperation invocationOperation &&
invocationOperation.TargetMethod.IsReceivedInOrderMethod())
{
return true;
}

if (operation.IsEventAssignmentOperation())
{
return false;
}

var symbol = GetDeclarationSymbol(semanticModel, maybeIgnoredExpression);

if (symbol == null)
Expand All @@ -110,7 +125,7 @@ private bool ShouldAnalyzeNode(SemanticModel semanticModel, SyntaxNode syntaxNod

private SyntaxNode FindIgnoredEnclosingExpression(SyntaxNode syntaxNode)
{
return syntaxNode.GetAncestorNode(IgnoredAncestorPaths);
return syntaxNode.Ancestors().FirstOrDefault(ancestor => IgnoredAncestorPaths.Contains(ancestor.RawKind));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Operations;

namespace NSubstitute.Analyzers.Shared.Extensions
{
internal static class IOperationExtensions
{
public static bool IsEventAssignmentOperation(this IOperation operation)
{
return operation is IAssignmentOperation assignmentOperation &&
assignmentOperation.Kind == OperationKind.EventAssignment;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,47 +10,10 @@ namespace NSubstitute.Analyzers.VisualBasic.DiagnosticAnalyzers
[DiagnosticAnalyzer(LanguageNames.VisualBasic)]
internal sealed class NonSubstitutableMemberReceivedInOrderAnalyzer : AbstractNonSubstitutableMemberReceivedInOrderAnalyzer<SyntaxKind, InvocationExpressionSyntax, MemberAccessExpressionSyntax, LambdaExpressionSyntax>
{
private static ImmutableArray<ImmutableArray<int>> IgnoredPaths { get; } = ImmutableArray.Create(
ImmutableArray.Create(
(int)SyntaxKind.SimpleArgument),
ImmutableArray.Create(
(int)SyntaxKind.AwaitExpression,
(int)SyntaxKind.SimpleArgument),
ImmutableArray.Create(
(int)SyntaxKind.TryCastExpression,
(int)SyntaxKind.SimpleArgument),
ImmutableArray.Create(
(int)SyntaxKind.PredefinedCastExpression,
(int)SyntaxKind.SimpleArgument),
ImmutableArray.Create(
(int)SyntaxKind.DirectCastExpression,
(int)SyntaxKind.SimpleArgument),
ImmutableArray.Create(
(int)SyntaxKind.CTypeExpression,
(int)SyntaxKind.SimpleArgument),
ImmutableArray.Create(
(int)SyntaxKind.EqualsValue,
(int)SyntaxKind.VariableDeclarator),
ImmutableArray.Create(
(int)SyntaxKind.AwaitExpression,
(int)SyntaxKind.EqualsValue,
(int)SyntaxKind.VariableDeclarator),
ImmutableArray.Create(
(int)SyntaxKind.TryCastExpression,
(int)SyntaxKind.EqualsValue,
(int)SyntaxKind.VariableDeclarator),
ImmutableArray.Create(
(int)SyntaxKind.PredefinedCastExpression,
(int)SyntaxKind.EqualsValue,
(int)SyntaxKind.VariableDeclarator),
ImmutableArray.Create(
(int)SyntaxKind.DirectCastExpression,
(int)SyntaxKind.EqualsValue,
(int)SyntaxKind.VariableDeclarator),
ImmutableArray.Create(
(int)SyntaxKind.CTypeExpression,
(int)SyntaxKind.EqualsValue,
(int)SyntaxKind.VariableDeclarator));
private static ImmutableArray<int> IgnoredPaths { get; } = ImmutableArray.Create(
(int)SyntaxKind.SimpleArgument,
(int)SyntaxKind.VariableDeclarator,
(int)SyntaxKind.AddAssignmentStatement);

public NonSubstitutableMemberReceivedInOrderAnalyzer()
: base(SubstitutionNodeFinder.Instance, NonSubstitutableMemberAnalysis.Instance, NSubstitute.Analyzers.VisualBasic.DiagnosticDescriptorsProvider.Instance)
Expand All @@ -59,7 +22,7 @@ public NonSubstitutableMemberReceivedInOrderAnalyzer()

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

protected override ImmutableArray<ImmutableArray<int>> IgnoredAncestorPaths { get; } = IgnoredPaths;
protected override ImmutableArray<int> IgnoredAncestorPaths { get; } = IgnoredPaths;

protected override ISymbol GetDeclarationSymbol(SemanticModel semanticModel, SyntaxNode node)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ namespace MyNamespace
{
public class Foo
{
public int Bar()
{
return 2;
Expand Down Expand Up @@ -222,6 +223,96 @@ public void Test()
await VerifyDiagnostic(source, _nonVirtualReceivedInOrderSetupSpecificationDescriptor, "Member this[] can not be intercepted. Only interface members and virtual, overriding, and abstract members can be intercepted.");
}

[Fact]
public async Task ReportsNoDiagnostics_WhenSubscribingToEvent()
{
var source = @"using NSubstitute;
using System;
namespace MyNamespace
{
public class Foo
{
public event Action SomeEvent;
public int Bar()
{
return 2;
}
}
public class FooTests
{
public void Test()
{
var substitute = NSubstitute.Substitute.For<Foo>();
Received.InOrder(() => substitute.SomeEvent += Arg.Any<Action>());
}
}
}";
await VerifyNoDiagnostic(source);
}

[Fact]
public async Task ReportsDiagnostics_WhenNonVirtualMethodUsedAsPartOfExpression_WithoutAssignment()
{
var source = @"using NSubstitute;
namespace MyNamespace
{
public class Foo
{
public int Bar()
{
return 2;
}
}
public class FooTests
{
public void Test()
{
var substitute = NSubstitute.Substitute.For<Foo>();
Received.InOrder(() =>
{
var a = [|substitute.Bar()|] + [|substitute.Bar()|];
var b = [|substitute.Bar()|] - [|substitute.Bar()|];
});
}
}
}";
await VerifyDiagnostic(source, _nonVirtualReceivedInOrderSetupSpecificationDescriptor, "Member Bar can not be intercepted. Only interface members and virtual, overriding, and abstract members can be intercepted.");
}

[Fact]
public async Task ReportsNoDiagnostics_WhenNonVirtualMethodUsedAsPartOfExpression_WithAssignment()
{
var source = @"using NSubstitute;
namespace MyNamespace
{
public class Foo
{
public int Bar()
{
return 2;
}
}
public class FooTests
{
public void Test()
{
var substitute = NSubstitute.Substitute.For<Foo>();
Received.InOrder(() =>
{
var a = substitute.Bar() + substitute.Bar();
var b = substitute.Bar() - substitute.Bar();
var aa = a;
var bb = b;
});
}
}
}";
await VerifyNoDiagnostic(source);
}

[Fact]
public async Task ReportsNoDiagnostics_WhenInvokingNonVirtualMethodWithUsedAssignment()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ public interface INonSubstitutableMemberReceivedInOrderDiagnosticVerifier

Task ReportsDiagnostics_WhenInvokingNonVirtualIndexerWithNonUsedAssignment();

Task ReportsNoDiagnostics_WhenSubscribingToEvent();

Task ReportsNoDiagnostics_WhenInvokingNonVirtualMethodWithUsedAssignment();

Task ReportsNoDiagnostics_WhenInvokingNonVirtualPropertyWithUsedAssignment();
Expand All @@ -37,5 +39,9 @@ public interface INonSubstitutableMemberReceivedInOrderDiagnosticVerifier
Task ReportsNoDiagnostics_WhenInvokingInternalVirtualMember_AndInternalsVisibleToApplied();

Task ReportsNoDiagnosticsForSuppressedMember_WhenSuppressingNonVirtualMethod();

Task ReportsDiagnostics_WhenNonVirtualMethodUsedAsPartOfExpression_WithoutAssignment();

Task ReportsNoDiagnostics_WhenNonVirtualMethodUsedAsPartOfExpression_WithAssignment();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,35 @@ End Namespace
await VerifyDiagnostic(source, _nonVirtualReceivedInOrderSetupSpecificationDescriptor, "Member Item can not be intercepted. Only interface members and overrideable, overriding, and must override members can be intercepted.");
}

[Fact]
public async Task ReportsNoDiagnostics_WhenSubscribingToEvent()
{
var source = @"Imports NSubstitute
Imports System
Namespace MyNamespace
Public Class Foo
Public Event SomeEvent As Action
Public Function Bar() As Integer
Return 2
End Function
End Class
Public Class FooTests
Public Sub Test()
Dim substitute = NSubstitute.Substitute.[For](Of Foo)()
NSubstitute.Received.InOrder(Function()
AddHandler substitute.SomeEvent, Arg.Any(Of Action)()
End Function)
End Sub
End Class
End Namespace
";
await VerifyNoDiagnostic(source);
}

[Fact]
public async Task ReportsNoDiagnostics_WhenInvokingNonVirtualMethodWithUsedAssignment()
{
Expand Down Expand Up @@ -603,5 +632,60 @@ End Class

await VerifyDiagnostic(source, _nonVirtualReceivedInOrderSetupSpecificationDescriptor, "Member Bar can not be intercepted. Only interface members and overrideable, overriding, and must override members can be intercepted.");
}

[Fact]
public async Task ReportsDiagnostics_WhenNonVirtualMethodUsedAsPartOfExpression_WithoutAssignment()
{
var source = @"Imports NSubstitute
Namespace MyNamespace
Public Class Foo
Public Function Bar() As Integer
Return 2
End Function
End Class
Public Class FooTests
Public Sub Test()
Dim substitute = NSubstitute.Substitute.[For](Of Foo)()
NSubstitute.Received.InOrder(Function()
Dim a = [|substitute.Bar()|] + [|substitute.Bar()|]
Dim b = [|substitute.Bar()|] - [|substitute.Bar()|]
End Function)
End Sub
End Class
End Namespace
";

await VerifyDiagnostic(source, _nonVirtualReceivedInOrderSetupSpecificationDescriptor, "Member Bar can not be intercepted. Only interface members and overrideable, overriding, and must override members can be intercepted.");
}

[Fact]
public async Task ReportsNoDiagnostics_WhenNonVirtualMethodUsedAsPartOfExpression_WithAssignment()
{
var source = @"Imports NSubstitute
Namespace MyNamespace
Public Class Foo
Public Function Bar() As Integer
Return 2
End Function
End Class
Public Class FooTests
Public Sub Test()
Dim substitute = NSubstitute.Substitute.[For](Of Foo)()
NSubstitute.Received.InOrder(Function()
Dim a = substitute.Bar() + substitute.Bar()
Dim b = substitute.Bar() - substitute.Bar()
Dim aa = a
Dim bb = b
End Function)
End Sub
End Class
End Namespace";

await VerifyNoDiagnostic(source);
}
}
}

0 comments on commit a0156dc

Please sign in to comment.