Skip to content

Commit

Permalink
Merge pull request #67 from nsubstitute/GH-61-and-does
Browse files Browse the repository at this point in the history
Analyzing callInfo usages for AndDoes method
  • Loading branch information
tpodolak authored Feb 9, 2019
2 parents 66183d5 + d62e87b commit 178235b
Show file tree
Hide file tree
Showing 16 changed files with 2,572 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
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 @@ -18,17 +19,36 @@ public CallInfoAnalyzer()

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

protected override SyntaxNode GetSubstituteCall(IMethodSymbol methodSymbol, InvocationExpressionSyntax invocationExpressionSyntax)
protected override SyntaxNode GetSubstituteCall(SyntaxNodeAnalysisContext syntaxNodeContext, IMethodSymbol methodSymbol, InvocationExpressionSyntax invocationExpressionSyntax)
{
switch (methodSymbol.MethodKind)
if (methodSymbol.IsExtensionMethod)
{
case MethodKind.ReducedExtension:
return invocationExpressionSyntax.Expression.DescendantNodes().First();
case MethodKind.Ordinary:
return invocationExpressionSyntax.ArgumentList.Arguments.First().Expression;
default:
return null;
switch (methodSymbol.MethodKind)
{
case MethodKind.ReducedExtension:
return invocationExpressionSyntax.Expression.DescendantNodes().First();
case MethodKind.Ordinary:
return invocationExpressionSyntax.ArgumentList.Arguments.First().Expression;
default:
return null;
}
}

var parentInvocation = invocationExpressionSyntax.GetParentInvocationExpression();

if (parentInvocation == null)
{
return null;
}

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

if (symbol.Symbol is IMethodSymbol mSymbol && mSymbol.ReducedFrom == null)
{
return parentInvocation.ArgumentList.Arguments.First().Expression;
}

return parentInvocation.Expression.DescendantNodes().First();
}

protected override IEnumerable<ExpressionSyntax> GetArgumentExpressions(InvocationExpressionSyntax invocationExpressionSyntax)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public override void VisitInvocationExpression(InvocationExpressionSyntax node)
{
var symbolInfo = _semanticModel.GetSymbolInfo(node);

if (symbolInfo.Symbol != null && symbolInfo.Symbol.ContainingType.ToString().Equals(MetadataNames.NSubstituteCoreFullTypeName))
if (symbolInfo.Symbol != null && symbolInfo.Symbol.ContainingType.ToString().Equals(MetadataNames.NSubstituteCallInfoFullTypeName))
{
switch (symbolInfo.Symbol.Name)
{
Expand All @@ -58,7 +58,7 @@ public override void VisitInvocationExpression(InvocationExpressionSyntax node)
public override void VisitElementAccessExpression(ElementAccessExpressionSyntax node)
{
var symbolInfo = ModelExtensions.GetSymbolInfo(_semanticModel, node).Symbol ?? ModelExtensions.GetSymbolInfo(_semanticModel, node.Expression).Symbol;
if (symbolInfo != null && symbolInfo.ContainingType.ToString().Equals(MetadataNames.NSubstituteCoreFullTypeName))
if (symbolInfo != null && symbolInfo.ContainingType.ToString().Equals(MetadataNames.NSubstituteCallInfoFullTypeName))
{
DirectIndexerAccesses.Add(node);
}
Expand Down
21 changes: 21 additions & 0 deletions src/NSubstitute.Analyzers.CSharp/Extensions/SyntaxExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using NSubstitute.Analyzers.Shared.Extensions;

namespace NSubstitute.Analyzers.CSharp.Extensions
{
internal static class SyntaxExtensions
{
private static readonly int[] ParentInvocationKindHierarchy =
{
(int)SyntaxKind.SimpleMemberAccessExpression,
(int)SyntaxKind.InvocationExpression
};

public static InvocationExpressionSyntax GetParentInvocationExpression(this SyntaxNode node)
{
return node.GetParentNode(ParentInvocationKindHierarchy) as InvocationExpressionSyntax;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ protected AbstractCallInfoAnalyzer(IDiagnosticDescriptorsProvider diagnosticDesc
[MetadataNames.NSubstituteReturnsMethod] = MetadataNames.NSubstituteSubstituteExtensionsFullTypeName,
[MetadataNames.NSubstituteReturnsForAnyArgsMethod] = MetadataNames.NSubstituteSubstituteExtensionsFullTypeName,
[MetadataNames.NSubstituteThrowsMethod] = MetadataNames.NSubstituteExceptionExtensionsFullTypeName,
[MetadataNames.NSubstituteThrowsForAnyArgsMethod] = MetadataNames.NSubstituteExceptionExtensionsFullTypeName
[MetadataNames.NSubstituteThrowsForAnyArgsMethod] = MetadataNames.NSubstituteExceptionExtensionsFullTypeName,
[MetadataNames.NSubstituteAndDoesMethod] = MetadataNames.NSubstituteConfiguredCallFullTypeName
}.ToImmutableDictionary();

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(
Expand All @@ -47,7 +48,7 @@ public override void Initialize(AnalysisContext context)

protected abstract TSyntaxKind InvocationExpressionKind { get; }

protected abstract SyntaxNode GetSubstituteCall(IMethodSymbol methodSymbol, TInvocationExpressionSyntax invocationExpressionSyntax);
protected abstract SyntaxNode GetSubstituteCall(SyntaxNodeAnalysisContext syntaxNodeContext, IMethodSymbol methodSymbol, TInvocationExpressionSyntax invocationExpressionSyntax);

protected abstract IEnumerable<TExpressionSyntax> GetArgumentExpressions(TInvocationExpressionSyntax invocationExpressionSyntax);

Expand Down Expand Up @@ -265,16 +266,20 @@ private bool AnalyzeAssignment(SyntaxNodeAnalysisContext syntaxNodeContext, ILis

private bool SupportsCallInfo(SyntaxNodeAnalysisContext syntaxNodeContext, TInvocationExpressionSyntax syntax, IMethodSymbol methodSymbol)
{
var allArguments = GetArgumentExpressions(syntax);
var argumentsForAnalysis = methodSymbol.MethodKind == MethodKind.ReducedExtension
? allArguments
: allArguments.Skip(1);

if (MethodNames.TryGetValue(methodSymbol.Name, out var typeName) == false)
{
return false;
}

var allArguments = GetArgumentExpressions(syntax);
IEnumerable<TExpressionSyntax> argumentsForAnalysis;
if (methodSymbol.MethodKind == MethodKind.ReducedExtension)
argumentsForAnalysis = allArguments;
else if (methodSymbol.IsExtensionMethod)
argumentsForAnalysis = allArguments.Skip(1);
else
argumentsForAnalysis = allArguments;

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

var supportsCallInfo =
Expand All @@ -286,7 +291,13 @@ private bool SupportsCallInfo(SyntaxNodeAnalysisContext syntaxNodeContext, TInvo

private IList<IParameterSymbol> GetSubstituteCallParameters(SyntaxNodeAnalysisContext syntaxNodeContext, IMethodSymbol methodSymbol, TInvocationExpressionSyntax invocationExpression)
{
var parentMethodCallSyntax = GetSubstituteCall(methodSymbol, invocationExpression);
var parentMethodCallSyntax = GetSubstituteCall(syntaxNodeContext, methodSymbol, invocationExpression);

if (parentMethodCallSyntax == null)
{
return null;
}

var symbol = syntaxNodeContext.SemanticModel.GetSymbolInfo(parentMethodCallSyntax).Symbol;

switch (symbol)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System.Collections.Generic;
using Microsoft.CodeAnalysis;

namespace NSubstitute.Analyzers.Shared.Extensions
{
internal static class SyntaxNodeExtensions
{
public static SyntaxNode GetParentNode(this SyntaxNode syntaxNode, IEnumerable<int> parentNodeHierarchyKinds)
{
using (var descendantNodesEnumerator = syntaxNode.DescendantNodes().GetEnumerator())
{
using (var hierarchyKindEnumerator = parentNodeHierarchyKinds.GetEnumerator())
{
while (hierarchyKindEnumerator.MoveNext() && descendantNodesEnumerator.MoveNext())
{
if (descendantNodesEnumerator.Current.RawKind != hierarchyKindEnumerator.Current)
{
return null;
}
}

if (hierarchyKindEnumerator.MoveNext() == false)
{
return descendantNodesEnumerator.Current;
}
}
}

return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ public static bool IsCallInfoDelegate(this TypeInfo typeInfo, SemanticModel sema
var isCalledViaDelegate = typeSymbol != null &&
typeSymbol.TypeKind == TypeKind.Delegate &&
typeSymbol is INamedTypeSymbol namedTypeSymbol &&
namedTypeSymbol.ConstructedFrom.Equals(semanticModel.Compilation.GetTypeByMetadataName("System.Func`2")) &&
(namedTypeSymbol.ConstructedFrom.Equals(semanticModel.Compilation.GetTypeByMetadataName("System.Func`2")) ||
namedTypeSymbol.ConstructedFrom.Equals(semanticModel.Compilation.GetTypeByMetadataName("System.Action`1"))) &&
IsCallInfoParameter(namedTypeSymbol.TypeArguments.First());

return isCalledViaDelegate;
Expand All @@ -21,7 +22,7 @@ typeSymbol is INamedTypeSymbol namedTypeSymbol &&
private static bool IsCallInfoParameter(ITypeSymbol symbol)
{
return symbol.ContainingAssembly?.Name.Equals(MetadataNames.NSubstituteAssemblyName, StringComparison.OrdinalIgnoreCase) == true &&
symbol.ToString().Equals(MetadataNames.NSubstituteCoreFullTypeName, StringComparison.OrdinalIgnoreCase) == true;
symbol.ToString().Equals(MetadataNames.NSubstituteCallInfoFullTypeName, StringComparison.OrdinalIgnoreCase) == true;
}
}
}
4 changes: 3 additions & 1 deletion src/NSubstitute.Analyzers.Shared/MetadataNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ internal class MetadataNames
public const string NSubstituteSubstituteExtensionsFullTypeName = "NSubstitute.SubstituteExtensions";
public const string NSubstituteReturnsExtensionsFullTypeName = "NSubstitute.ReturnsExtensions.ReturnsExtensions";
public const string NSubstituteExceptionExtensionsFullTypeName = "NSubstitute.ExceptionExtensions.ExceptionExtensions";
public const string NSubstituteCoreFullTypeName = "NSubstitute.Core.CallInfo";
public const string NSubstituteCallInfoFullTypeName = "NSubstitute.Core.CallInfo";
public const string NSubstituteConfiguredCallFullTypeName = "NSubstitute.Core.ConfiguredCall";
public const string NSubstituteSubstituteFullTypeName = "NSubstitute.Substitute";
public const string NSubstituteFactoryFullTypeName = "NSubstitute.Core.ISubstituteFactory";
public const string NSubstituteReturnsMethod = "Returns";
public const string NSubstituteReturnsForAnyArgsMethod = "ReturnsForAnyArgs";
public const string NSubstituteThrowsMethod = "Throws";
public const string NSubstituteThrowsForAnyArgsMethod = "ThrowsForAnyArgs";
public const string NSubstituteAndDoesMethod = "AndDoes";
public const string NSubstituteReturnsNullMethod = "ReturnsNull";
public const string NSubstituteReturnsNullForAnyArgsMethod = "ReturnsNullForAnyArgs";
public const string NSubstituteDoMethod = "Do";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Microsoft.CodeAnalysis.VisualBasic;
using Microsoft.CodeAnalysis.VisualBasic.Syntax;
using NSubstitute.Analyzers.Shared.DiagnosticAnalyzers;
using NSubstitute.Analyzers.VisualBasic.Extensions;

namespace NSubstitute.Analyzers.VisualBasic.DiagnosticAnalyzers
{
Expand All @@ -18,17 +19,36 @@ public CallInfoAnalyzer()

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

protected override SyntaxNode GetSubstituteCall(IMethodSymbol methodSymbol, InvocationExpressionSyntax invocationExpressionSyntax)
protected override SyntaxNode GetSubstituteCall(SyntaxNodeAnalysisContext syntaxNodeContext, IMethodSymbol methodSymbol, InvocationExpressionSyntax invocationExpressionSyntax)
{
switch (methodSymbol.MethodKind)
if (methodSymbol.IsExtensionMethod)
{
case MethodKind.ReducedExtension:
return invocationExpressionSyntax.Expression.DescendantNodes().First();
case MethodKind.Ordinary:
return invocationExpressionSyntax.ArgumentList.Arguments.First().GetExpression();
default:
return null;
switch (methodSymbol.MethodKind)
{
case MethodKind.ReducedExtension:
return invocationExpressionSyntax.Expression.DescendantNodes().First();
case MethodKind.Ordinary:
return invocationExpressionSyntax.ArgumentList.Arguments.First().GetExpression();
default:
return null;
}
}

var parentInvocation = invocationExpressionSyntax.GetParentInvocationExpression();

if (parentInvocation == null)
{
return null;
}

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

if (symbol.Symbol is IMethodSymbol mSymbol && mSymbol.ReducedFrom == null)
{
return parentInvocation.ArgumentList.Arguments.First().GetExpression();
}

return parentInvocation.Expression.DescendantNodes().First();
}

protected override IEnumerable<ExpressionSyntax> GetArgumentExpressions(InvocationExpressionSyntax invocationExpressionSyntax)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public override void VisitInvocationExpression(InvocationExpressionSyntax node)
{
var symbol = _semanticModel.GetSymbolInfo(node).Symbol;

if (symbol != null && symbol.ContainingType.ToString().Equals(MetadataNames.NSubstituteCoreFullTypeName))
if (symbol != null && symbol.ContainingType.ToString().Equals(MetadataNames.NSubstituteCallInfoFullTypeName))
{
switch (symbol.Name)
{
Expand All @@ -59,7 +59,7 @@ public override void VisitInvocationExpression(InvocationExpressionSyntax node)
{
var expressionSymbol = _semanticModel.GetSymbolInfo(node.Expression).Symbol;

if (expressionSymbol != null && expressionSymbol.ContainingType.ToString().Equals(MetadataNames.NSubstituteCoreFullTypeName))
if (expressionSymbol != null && expressionSymbol.ContainingType.ToString().Equals(MetadataNames.NSubstituteCallInfoFullTypeName))
{
DirectIndexerAccesses.Add(node);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.VisualBasic;
using Microsoft.CodeAnalysis.VisualBasic.Syntax;
using NSubstitute.Analyzers.Shared.Extensions;

namespace NSubstitute.Analyzers.VisualBasic.Extensions
{
internal static class SyntaxExtensions
{
private static readonly int[] ParentInvocationKindHierarchy =
{
(int)SyntaxKind.SimpleMemberAccessExpression,
(int)SyntaxKind.InvocationExpression
};

public static InvocationExpressionSyntax GetParentInvocationExpression(this SyntaxNode node)
{
return node.GetParentNode(ParentInvocationKindHierarchy) as InvocationExpressionSyntax;
}
}
}
Loading

0 comments on commit 178235b

Please sign in to comment.