Skip to content

Commit

Permalink
Merge pull request #17 from nsubstitute/GH-12-non-virtual-received-calls
Browse files Browse the repository at this point in the history
[GH-16] - non virtual received calls
  • Loading branch information
tpodolak authored Jun 30, 2018
2 parents 0c2c5bb + de26789 commit 360486e
Show file tree
Hide file tree
Showing 47 changed files with 13,476 additions and 29 deletions.
Original file line number Diff line number Diff line change
@@ -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;
using NSubstitute.Analyzers.Shared.DiagnosticAnalyzers;

namespace NSubstitute.Analyzers.CSharp.DiagnosticAnalyzers
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal class NonVirtualSetupReceivedAnalyzer : AbstractNonVirtualSetupReceivedAnalyzer<SyntaxKind>
{
protected override ImmutableArray<Parent> PossibleParents { get; } = ImmutableArray.Create(
Parent.Create<MemberAccessExpressionSyntax>(),
Parent.Create<InvocationExpressionSyntax>(),
Parent.Create<ElementAccessExpressionSyntax>());

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

public NonVirtualSetupReceivedAnalyzer()
: base(new DiagnosticDescriptorsProvider())
{
}
}
}
33 changes: 30 additions & 3 deletions src/NSubstitute.Analyzers.CSharp/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions src/NSubstitute.Analyzers.CSharp/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -249,4 +249,16 @@
<value>Can not provide constructor arguments when substituting for a delegate.</value>
<comment>The title of the diagnostic.</comment>
</data>
<data name="NonVirtualReceivedSetupSpecificationDescription" xml:space="preserve">
<value>Non-virtual members can not be intercepted.</value>
<comment>An optional longer localizable description of the diagnostic.</comment>
</data>
<data name="NonVirtualReceivedSetupSpecificationMessageFormat" xml:space="preserve">
<value>Member {0} can not be intercepted. Only interface members and virtual, overriding, and abstract members can be intercepted.</value>
<comment>The format-able message the diagnostic displays.</comment>
</data>
<data name="NonVirtualReceivedSetupSpecificationTitle" xml:space="preserve">
<value>Non-virtual setup specification.</value>
<comment>The title of the diagnostic.</comment>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,7 @@ internal class AbstractDiagnosticDescriptorsProvider<T> : IDiagnosticDescriptors
public DiagnosticDescriptor SubstituteConstructorArgumentsForInterface { get; } = DiagnosticDescriptors<T>.SubstituteConstructorArgumentsForInterface;

public DiagnosticDescriptor SubstituteConstructorArgumentsForDelegate { get; } = DiagnosticDescriptors<T>.SubstituteConstructorArgumentsForDelegate;

public DiagnosticDescriptor NonVirtualReceivedSetupSpecification { get; } = DiagnosticDescriptors<T>.NonVirtualReceivedSetupSpecification;
}
}
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 NSubstitute.Analyzers.Shared.Extensions;

namespace NSubstitute.Analyzers.Shared.DiagnosticAnalyzers
{
Expand Down Expand Up @@ -48,12 +49,7 @@ public sealed override void Initialize(AnalysisContext context)
return false;
}

if (symbolInfo.Symbol == null)
{
return null;
}

return IsInterfaceMember(symbolInfo) || IsVirtual(symbolInfo);
return symbolInfo.Symbol?.CanBeSetuped();
}

private void AnalyzeInvocation(SyntaxNodeAnalysisContext syntaxNodeContext)
Expand Down Expand Up @@ -134,21 +130,5 @@ private bool IsValidForAnalysis(SyntaxNode accessedMember)
{
return accessedMember != null && SupportedMemberAccesses.Contains(accessedMember.RawKind);
}

private bool IsInterfaceMember(SymbolInfo symbolInfo)
{
return symbolInfo.Symbol?.ContainingType?.TypeKind == TypeKind.Interface;
}

private bool IsVirtual(SymbolInfo symbolInfo)
{
var member = symbolInfo.Symbol;

var isVirtual = member.IsVirtual
|| (member.IsOverride && !member.IsSealed)
|| member.IsAbstract;

return isVirtual;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
using System;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using NSubstitute.Analyzers.Shared.Extensions;

namespace NSubstitute.Analyzers.Shared.DiagnosticAnalyzers
{
internal abstract class AbstractNonVirtualSetupReceivedAnalyzer<TSyntaxKind> : AbstractDiagnosticAnalyzer
where TSyntaxKind : struct
{
private static readonly ImmutableHashSet<string> MethodNames = ImmutableHashSet.Create(
MetadataNames.NSubstituteReceivedMethod,
MetadataNames.NSubstituteReceivedWithAnyArgsMethod,
MetadataNames.NSubstituteDidNotReceiveMethod,
MetadataNames.NSubstituteDidNotReceiveWithAnyArgsMethod);

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(DiagnosticDescriptorsProvider.NonVirtualReceivedSetupSpecification);

protected AbstractNonVirtualSetupReceivedAnalyzer(IDiagnosticDescriptorsProvider diagnosticDescriptorsProvider)
: base(diagnosticDescriptorsProvider)
{
}

protected abstract ImmutableArray<Parent> PossibleParents { get; }

protected abstract TSyntaxKind InvocationExpressionKind { get; }

public override void Initialize(AnalysisContext context)
{
context.RegisterSyntaxNodeAction(AnalyzeInvocation, InvocationExpressionKind);
}

private void AnalyzeInvocation(SyntaxNodeAnalysisContext syntaxNodeContext)
{
var invocationExpression = syntaxNodeContext.Node;
var methodSymbolInfo = syntaxNodeContext.SemanticModel.GetSymbolInfo(invocationExpression);

if (methodSymbolInfo.Symbol?.Kind != SymbolKind.Method)
{
return;
}

var methodSymbol = (IMethodSymbol)methodSymbolInfo.Symbol;
if (methodSymbol == null)
{
return;
}

if (IsReceivedLikeMethod(syntaxNodeContext, invocationExpression, methodSymbol.Name) == false)
{
return;
}

var parentNode = GetKnownParent(invocationExpression);

if (parentNode == null)
{
return;
}

var symbolInfo = syntaxNodeContext.SemanticModel.GetSymbolInfo(parentNode);

if (symbolInfo.Symbol == null)
{
return;
}

if (symbolInfo.Symbol.CanBeSetuped())
{
return;
}

var diagnostic = Diagnostic.Create(
DiagnosticDescriptorsProvider.NonVirtualReceivedSetupSpecification,
invocationExpression.GetLocation(),
symbolInfo.Symbol.Name);

syntaxNodeContext.ReportDiagnostic(diagnostic);
}

private static bool IsReceivedLikeMethod(SyntaxNodeAnalysisContext syntaxNodeContext, SyntaxNode syntax, string memberName)
{
if (MethodNames.Contains(memberName) == false)
{
return false;
}

var symbol = syntaxNodeContext.SemanticModel.GetSymbolInfo(syntax);

return symbol.Symbol?.ContainingAssembly?.Name.Equals(MetadataNames.NSubstituteAssemblyName, StringComparison.Ordinal) == true &&
symbol.Symbol?.ContainingType?.ToString().Equals(MetadataNames.NSubstituteSubstituteExtensionsFullTypeName, StringComparison.Ordinal) == true;
}

private SyntaxNode GetKnownParent(SyntaxNode receivedSyntaxNode)
{
var typeInfo = receivedSyntaxNode.Parent.GetType().GetTypeInfo();

if (PossibleParents.Any(parent => parent.Type.GetTypeInfo().IsAssignableFrom(typeInfo)))
{
return receivedSyntaxNode.Parent;
}

return null;
}
}
}
8 changes: 8 additions & 0 deletions src/NSubstitute.Analyzers.Shared/DiagnosticDescriptors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,14 @@ internal class DiagnosticDescriptors<T>
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true);

public static DiagnosticDescriptor NonVirtualReceivedSetupSpecification { get; } =
CreateDiagnosticDescriptor(
name: nameof(NonVirtualReceivedSetupSpecification),
id: DiagnosticIdentifiers.NonVirtualReceivedSetupSpecification,
category: DiagnosticCategories.Usage,
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true);

private static DiagnosticDescriptor CreateDiagnosticDescriptor(
string name, string id, string category, DiagnosticSeverity defaultSeverity, bool isEnabledByDefault)
{
Expand Down
1 change: 1 addition & 0 deletions src/NSubstitute.Analyzers.Shared/DiagnosticIdentifiers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ internal class DiagnosticIdentifiers
public static readonly string SubstituteMultipleClasses = "NS008";
public static readonly string SubstituteConstructorArgumentsForInterface = "NS009";
public static readonly string SubstituteConstructorArgumentsForDelegate = "NS010";
public static readonly string NonVirtualReceivedSetupSpecification = "NS011";
}
}
22 changes: 21 additions & 1 deletion src/NSubstitute.Analyzers.Shared/Extensions/ISymbolExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ namespace NSubstitute.Analyzers.Shared.Extensions
{
internal static class ISymbolExtensions
{
public static bool CanBeSetuped(this ISymbol symbol)
{
return IsInterfaceMember(symbol) || IsVirtual(symbol);
}

public static bool InternalsVisibleToProxyGenerator(this ISymbol typeSymbol)
{
var internalsVisibleToAttribute = typeSymbol.ContainingAssembly.GetAttributes()
Expand All @@ -23,9 +28,24 @@ public static string ToMinimalMethodString(this ISymbol symbol, SemanticModel se
return string.Empty;
}

var minimumDisplayString = symbol.ToMinimalDisplayString(semanticModel, 0, SymbolDisplayFormat.FullyQualifiedFormat);
var minimumDisplayString =
symbol.ToMinimalDisplayString(semanticModel, 0, SymbolDisplayFormat.FullyQualifiedFormat);

return $"{symbol.ContainingType}.{minimumDisplayString}";
}

private static bool IsInterfaceMember(ISymbol symbol)
{
return symbol?.ContainingType?.TypeKind == TypeKind.Interface;
}

private static bool IsVirtual(ISymbol symbol)
{
var isVirtual = symbol.IsVirtual
|| (symbol.IsOverride && !symbol.IsSealed)
|| symbol.IsAbstract;

return isVirtual;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,7 @@ internal interface IDiagnosticDescriptorsProvider
DiagnosticDescriptor SubstituteConstructorArgumentsForInterface { get; }

DiagnosticDescriptor SubstituteConstructorArgumentsForDelegate { get; }

DiagnosticDescriptor NonVirtualReceivedSetupSpecification { get; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
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 class NonVirtualSetupReceivedAnalyzer : AbstractNonVirtualSetupReceivedAnalyzer<SyntaxKind>
{
protected override ImmutableArray<Parent> PossibleParents { get; } = ImmutableArray.Create(
Parent.Create<MemberAccessExpressionSyntax>(),
Parent.Create<InvocationExpressionSyntax>());

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

public NonVirtualSetupReceivedAnalyzer()
: base(new DiagnosticDescriptorsProvider())
{
}
}
}
Loading

0 comments on commit 360486e

Please sign in to comment.