Skip to content

Commit

Permalink
[GH-76] - callInfo analysis for When like method - c#
Browse files Browse the repository at this point in the history
  • Loading branch information
tpodolak committed Apr 3, 2019
1 parent 2b6326f commit 2cf8c88
Show file tree
Hide file tree
Showing 9 changed files with 1,590 additions and 160 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using System;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using NSubstitute.Analyzers.CSharp.Extensions;
using NSubstitute.Analyzers.Shared.Extensions;

namespace NSubstitute.Analyzers.CSharp.DiagnosticAnalyzers
{
internal class CallInfoDoAnalyzer : CallInfoAnalyzer
{
private readonly Lazy<WhenSubstituteCallFinder> _whenSubstituteCallFinderProxy = new Lazy<WhenSubstituteCallFinder>(() => new WhenSubstituteCallFinder());

private WhenSubstituteCallFinder WhenSubstituteCallFinder => _whenSubstituteCallFinderProxy.Value;

protected override bool SupportsCallInfo(SyntaxNodeAnalysisContext syntaxNodeContext, InvocationExpressionSyntax syntax, IMethodSymbol methodSymbol)
{
if (methodSymbol.Name != "Do")
{
return false;
}

var allArguments = GetArgumentExpressions(syntax);
return allArguments.Any(arg => syntaxNodeContext.SemanticModel.GetTypeInfo(arg).IsCallInfoDelegate(syntaxNodeContext.SemanticModel));
}

protected override SyntaxNode GetSubstituteCall(SyntaxNodeAnalysisContext syntaxNodeContext, IMethodSymbol methodSymbol, InvocationExpressionSyntax invocationExpressionSyntax)
{
var parentInvocationExpression = invocationExpressionSyntax.GetParentInvocationExpression();

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

if (syntaxNodeContext.SemanticModel.GetSymbolInfo(parentInvocationExpression).Symbol is IMethodSymbol parentInvocationSymbol)
{
var argumentExpression = parentInvocationSymbol.MethodKind == MethodKind.ReducedExtension
? parentInvocationExpression.ArgumentList.Arguments.First().Expression
: parentInvocationExpression.ArgumentList.Arguments.Skip(1).First().Expression;

return WhenSubstituteCallFinder.Find(syntaxNodeContext, argumentExpression).FirstOrDefault();
}

return null;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
Expand All @@ -18,56 +19,15 @@ public NonSubstitutableMemberWhenAnalyzer()

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

private readonly Lazy<WhenSubstituteCallFinder> _whenSubstituteCallFinderProxy = new Lazy<WhenSubstituteCallFinder>(() => new WhenSubstituteCallFinder());

private WhenSubstituteCallFinder WhenSubstituteCallFinder => _whenSubstituteCallFinderProxy.Value;

protected override IEnumerable<SyntaxNode> GetExpressionsForAnalysys(SyntaxNodeAnalysisContext syntaxNodeAnalysisContext, IMethodSymbol methodSymbol, InvocationExpressionSyntax invocationExpressionSyntax)
{
var argumentListArguments = invocationExpressionSyntax.ArgumentList.Arguments;
var argumentSyntax = methodSymbol.MethodKind == MethodKind.ReducedExtension ? argumentListArguments.First() : argumentListArguments.Skip(1).First();
return GetExpressionsForAnalysys(syntaxNodeAnalysisContext, argumentSyntax.Expression);
}

private IEnumerable<SyntaxNode> GetExpressionsForAnalysys(SyntaxNodeAnalysisContext syntaxNodeContext, SyntaxNode argumentSyntax)
{
SyntaxNode body = null;
switch (argumentSyntax)
{
case SimpleLambdaExpressionSyntax simpleLambdaExpressionSyntax:
body = simpleLambdaExpressionSyntax.Body;
break;
case AnonymousFunctionExpressionSyntax anonymousFunctionExpressionSyntax:
body = anonymousFunctionExpressionSyntax.Body;
break;
case LocalFunctionStatementSyntax localFunctionStatementSyntax:
body = (SyntaxNode)localFunctionStatementSyntax.Body ?? localFunctionStatementSyntax.ExpressionBody;
break;
case MethodDeclarationSyntax methodDeclarationSyntax:
body = (SyntaxNode)methodDeclarationSyntax.Body ?? methodDeclarationSyntax.ExpressionBody;
break;
case IdentifierNameSyntax identifierNameSyntax:
var symbol = syntaxNodeContext.SemanticModel.GetSymbolInfo(identifierNameSyntax);
if (symbol.Symbol != null && symbol.Symbol.Locations.Any())
{
var location = symbol.Symbol.Locations.First();
var syntaxNode = location.SourceTree.GetRoot().FindNode(location.SourceSpan);

foreach (var expressionsForAnalysy in GetExpressionsForAnalysys(syntaxNodeContext, syntaxNode))
{
yield return expressionsForAnalysy;
}
}

break;
}

if (body == null)
{
yield break;
}

foreach (var invocationExpressionSyntax in body.DescendantNodes().Where(node => node.IsKind(SyntaxKind.SimpleMemberAccessExpression) ||
node.IsKind(SyntaxKind.ElementAccessExpression)))
{
yield return invocationExpressionSyntax;
}
return WhenSubstituteCallFinder.Find(syntaxNodeAnalysisContext, argumentSyntax.Expression);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;

namespace NSubstitute.Analyzers.CSharp.DiagnosticAnalyzers
{
/// <summary>
/// Finds calls considered to be substitute calls in expressions
/// </summary>
internal class WhenSubstituteCallFinder
{
public IEnumerable<SyntaxNode> Find(SyntaxNodeAnalysisContext syntaxNodeContext, SyntaxNode argumentSyntax)
{
SyntaxNode body = null;
switch (argumentSyntax)
{
case SimpleLambdaExpressionSyntax simpleLambdaExpressionSyntax:
body = simpleLambdaExpressionSyntax.Body;
break;
case AnonymousFunctionExpressionSyntax anonymousFunctionExpressionSyntax:
body = anonymousFunctionExpressionSyntax.Body;
break;
case LocalFunctionStatementSyntax localFunctionStatementSyntax:
body = (SyntaxNode)localFunctionStatementSyntax.Body ?? localFunctionStatementSyntax.ExpressionBody;
break;
case MethodDeclarationSyntax methodDeclarationSyntax:
body = (SyntaxNode)methodDeclarationSyntax.Body ?? methodDeclarationSyntax.ExpressionBody;
break;
case IdentifierNameSyntax identifierNameSyntax:
var symbol = syntaxNodeContext.SemanticModel.GetSymbolInfo(identifierNameSyntax);
if (symbol.Symbol != null && symbol.Symbol.Locations.Any())
{
var location = symbol.Symbol.Locations.First();
var syntaxNode = location.SourceTree.GetRoot().FindNode(location.SourceSpan);

foreach (var expressionForAnalysis in Find(syntaxNodeContext, syntaxNode))
{
yield return expressionForAnalysis;
}
}

break;
}

if (body == null)
{
yield break;
}

foreach (var invocationExpressionSyntax in body.DescendantNodes().Where(node => node.IsKind(SyntaxKind.SimpleMemberAccessExpression) ||
node.IsKind(SyntaxKind.ElementAccessExpression)))
{
yield return invocationExpressionSyntax;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,31 @@ public override void Initialize(AnalysisContext context)

protected abstract bool IsAssignableTo(Compilation compilation, ITypeSymbol fromSymbol, ITypeSymbol toSymbol);

protected virtual bool SupportsCallInfo(SyntaxNodeAnalysisContext syntaxNodeContext, TInvocationExpressionSyntax syntax, IMethodSymbol methodSymbol)
{
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 =
symbol.Symbol?.ContainingAssembly?.Name.Equals(MetadataNames.NSubstituteAssemblyName, StringComparison.OrdinalIgnoreCase) == true &&
symbol.Symbol?.ContainingType?.ToString().Equals(typeName, StringComparison.OrdinalIgnoreCase) == true;

return supportsCallInfo && argumentsForAnalysis.Any(arg => syntaxNodeContext.SemanticModel.GetTypeInfo(arg).IsCallInfoDelegate(syntaxNodeContext.SemanticModel));
}

private void AnalyzeInvocation(SyntaxNodeAnalysisContext syntaxNodeContext)
{
var invocationExpression = (TInvocationExpressionSyntax)syntaxNodeContext.Node;
Expand Down Expand Up @@ -264,31 +289,6 @@ private bool AnalyzeAssignment(SyntaxNodeAnalysisContext syntaxNodeContext, ILis
return false;
}

private bool SupportsCallInfo(SyntaxNodeAnalysisContext syntaxNodeContext, TInvocationExpressionSyntax syntax, IMethodSymbol methodSymbol)
{
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 =
symbol.Symbol?.ContainingAssembly?.Name.Equals(MetadataNames.NSubstituteAssemblyName, StringComparison.OrdinalIgnoreCase) == true &&
symbol.Symbol?.ContainingType?.ToString().Equals(typeName, StringComparison.OrdinalIgnoreCase) == true;

return supportsCallInfo && argumentsForAnalysis.Any(arg => syntaxNodeContext.SemanticModel.GetTypeInfo(arg).IsCallInfoDelegate(syntaxNodeContext.SemanticModel));
}

private IList<IParameterSymbol> GetSubstituteCallParameters(SyntaxNodeAnalysisContext syntaxNodeContext, IMethodSymbol methodSymbol, TInvocationExpressionSyntax invocationExpression)
{
var parentMethodCallSyntax = GetSubstituteCall(syntaxNodeContext, methodSymbol, invocationExpression);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
Expand All @@ -18,97 +19,15 @@ public NonSubstitutableMemberWhenAnalyzer()

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

private readonly Lazy<WhenSubstituteCallFinder> _whenSubstituteCallFinderProxy = new Lazy<WhenSubstituteCallFinder>(() => new WhenSubstituteCallFinder());

private WhenSubstituteCallFinder WhenSubstituteCallFinder => _whenSubstituteCallFinderProxy.Value;

protected override IEnumerable<SyntaxNode> GetExpressionsForAnalysys(SyntaxNodeAnalysisContext syntaxNodeAnalysisContext, IMethodSymbol methodSymbol, InvocationExpressionSyntax invocationExpressionSyntax)
{
var argumentListArguments = invocationExpressionSyntax.ArgumentList.Arguments;
var argumentSyntax = methodSymbol.MethodKind == MethodKind.ReducedExtension ? argumentListArguments.First() : argumentListArguments.Skip(1).First();
return GetExpressionsForAnalysys(syntaxNodeAnalysisContext, argumentSyntax.GetExpression());
}

private IEnumerable<SyntaxNode> GetExpressionsForAnalysys(SyntaxNodeAnalysisContext syntaxNodeContext, SyntaxNode syntax)
{
SyntaxNode body = null;
switch (syntax)
{
case SingleLineLambdaExpressionSyntax _:
case ExpressionStatementSyntax _:
case LocalDeclarationStatementSyntax _:
case AssignmentStatementSyntax _:
body = syntax;
break;
case MultiLineLambdaExpressionSyntax simpleLambdaExpressionSyntax:
foreach (var syntaxNode in IterateStatements(simpleLambdaExpressionSyntax.Statements))
{
yield return syntaxNode;
}

break;
case MethodBlockSyntax methodBlockSyntax:
foreach (var syntaxNode in IterateStatements(methodBlockSyntax.Statements))
{
yield return syntaxNode;
}

break;
case UnaryExpressionSyntax unaryExpressionSyntax:
foreach (var syntaxNode in GetExpressionsForAnalysys(syntaxNodeContext, unaryExpressionSyntax.Operand))
{
yield return syntaxNode;
}

break;
case IdentifierNameSyntax identifierNameSyntax:
var symbol = syntaxNodeContext.SemanticModel.GetSymbolInfo(identifierNameSyntax);
if (symbol.Symbol != null && symbol.Symbol.Locations.Any())
{
var location = symbol.Symbol.Locations.First();
var syntaxNode = location.SourceTree.GetRoot().FindNode(location.SourceSpan);

SyntaxNode innerNode = null;
if (syntaxNode is MethodStatementSyntax methodStatementSyntax)
{
innerNode = methodStatementSyntax.Parent;
}

innerNode = innerNode ?? syntaxNode;
foreach (var expressionsForAnalysy in GetExpressionsForAnalysys(syntaxNodeContext, innerNode))
{
yield return expressionsForAnalysy;
}
}

break;
}

if (body == null)
{
yield break;
}

var memberAccessExpressions = body.DescendantNodes().Where(node => node.IsKind(SyntaxKind.SimpleMemberAccessExpression));
var invocationExpressions = body.DescendantNodes().Where(node => node.IsKind(SyntaxKind.InvocationExpression));

// rather ugly but prevents reporting two times the same thing
// as VB syntax is based on statements, you can't access body of method directly
if (invocationExpressions.Any())
{
foreach (var invocationExpression in invocationExpressions)
{
yield return invocationExpression;
}
}
else if (memberAccessExpressions.Any())
{
foreach (var memberAccessExpression in memberAccessExpressions)
{
yield return memberAccessExpression;
}
}

IEnumerable<SyntaxNode> IterateStatements(IEnumerable<StatementSyntax> statements)
{
return statements.SelectMany(statement => GetExpressionsForAnalysys(syntaxNodeContext, statement));
}
return WhenSubstituteCallFinder.Find(syntaxNodeAnalysisContext, argumentSyntax.GetExpression());
}
}
}
Loading

0 comments on commit 2cf8c88

Please sign in to comment.