Skip to content

Commit

Permalink
[GH-159|GH-160] - argument matcher analyzers improvements - handling
Browse files Browse the repository at this point in the history
Arg.Do and When on property level
  • Loading branch information
tpodolak committed Sep 5, 2021
1 parent 8329d06 commit 16f5540
Show file tree
Hide file tree
Showing 14 changed files with 702 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using NSubstitute.Analyzers.CSharp.Extensions;
using NSubstitute.Analyzers.Shared.DiagnosticAnalyzers;
using NSubstitute.Analyzers.Shared.Extensions;

namespace NSubstitute.Analyzers.CSharp.DiagnosticAnalyzers
{
Expand All @@ -14,7 +16,8 @@ internal sealed class NonSubstitutableMemberArgumentMatcherAnalyzer : AbstractNo
(int)SyntaxKind.InvocationExpression,
(int)SyntaxKind.ElementAccessExpression,
(int)SyntaxKind.AddAssignmentExpression,
(int)SyntaxKind.ObjectCreationExpression);
(int)SyntaxKind.ObjectCreationExpression,
(int)SyntaxKind.SimpleAssignmentExpression);

private static ImmutableHashSet<int> IgnoredAncestors { get; } =
ImmutableHashSet.Create(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,23 @@
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
using NSubstitute.Analyzers.Shared.Extensions;

namespace NSubstitute.Analyzers.Shared.DiagnosticAnalyzers
{
internal abstract class AbstractNonSubstitutableMemberArgumentMatcherAnalyzer<TSyntaxKind, TInvocationExpressionSyntax> : AbstractDiagnosticAnalyzer
where TInvocationExpressionSyntax : SyntaxNode
where TSyntaxKind : struct
where TSyntaxKind : struct, Enum
{
private readonly Action<SyntaxNodeAnalysisContext> _analyzeInvocationAction;

private readonly INonSubstitutableMemberAnalysis _nonSubstitutableMemberAnalysis;

private readonly int _invocationExpressionRawKind;

private readonly int[] _parentInvocationSyntaxNodeHierarchy;

protected abstract ImmutableHashSet<int> MaybeAllowedArgMatcherAncestors { get; }

protected abstract ImmutableHashSet<int> IgnoredArgMatcherAncestors { get; }
Expand All @@ -29,6 +34,8 @@ protected AbstractNonSubstitutableMemberArgumentMatcherAnalyzer(
_nonSubstitutableMemberAnalysis = nonSubstitutableMemberAnalysis;
_analyzeInvocationAction = AnalyzeInvocation;
SupportedDiagnostics = ImmutableArray.Create(DiagnosticDescriptorsProvider.NonSubstitutableMemberArgumentMatcherUsage);
_invocationExpressionRawKind = (int)Convert.ChangeType(InvocationExpressionKind, typeof(int));
_parentInvocationSyntaxNodeHierarchy = new[] { _invocationExpressionRawKind };
}

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; }
Expand All @@ -43,22 +50,23 @@ private void AnalyzeInvocation(SyntaxNodeAnalysisContext syntaxNodeContext)
var invocationExpression = (TInvocationExpressionSyntax)syntaxNodeContext.Node;
var methodSymbolInfo = syntaxNodeContext.SemanticModel.GetSymbolInfo(invocationExpression);

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

var symbol = methodSymbolInfo.Symbol;

if (symbol.IsArgMatcherLikeMethod() == false)
if (methodSymbol.IsArgMatcherLikeMethod() == false)
{
return;
}

AnalyzeArgLikeMethod(syntaxNodeContext, invocationExpression);
AnalyzeArgLikeMethod(syntaxNodeContext, invocationExpression, methodSymbol);
}

private void AnalyzeArgLikeMethod(SyntaxNodeAnalysisContext syntaxNodeContext, TInvocationExpressionSyntax argInvocationExpression)
private void AnalyzeArgLikeMethod(
SyntaxNodeAnalysisContext syntaxNodeContext,
TInvocationExpressionSyntax argInvocationExpression,
IMethodSymbol invocationExpressionSymbol)
{
var enclosingExpression = FindMaybeAllowedEnclosingExpression(argInvocationExpression);

Expand All @@ -84,27 +92,172 @@ private void AnalyzeArgLikeMethod(SyntaxNodeAnalysisContext syntaxNodeContext, T
return;
}

if (syntaxNodeContext.SemanticModel.GetOperation(enclosingExpression).IsEventAssignmentOperation())
var operation = syntaxNodeContext.SemanticModel.GetOperation(enclosingExpression);

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

var enclosingExpressionSymbol = syntaxNodeContext.SemanticModel.GetSymbolInfo(enclosingExpression).Symbol;
var memberReferenceOperation = GetMemberReferenceOperation(operation);

if (enclosingExpressionSymbol == null)
if (AnalyzeEnclosingExpression(
syntaxNodeContext,
argInvocationExpression,
enclosingExpression,
memberReferenceOperation))
{
return;
}

var analysisResult = _nonSubstitutableMemberAnalysis.Analyze(syntaxNodeContext, enclosingExpression);
AnalyzeAssignment(
syntaxNodeContext,
argInvocationExpression,
invocationExpressionSymbol,
memberReferenceOperation);
}

private bool AnalyzeEnclosingExpression(
SyntaxNodeAnalysisContext syntaxNodeContext,
TInvocationExpressionSyntax argInvocationExpression,
SyntaxNode enclosingExpression,
IMemberReferenceOperation memberReferenceOperation)
{
var enclosingExpressionSymbolInfo = syntaxNodeContext.SemanticModel.GetSymbolInfo(enclosingExpression);
var enclosingExpressionSymbol = memberReferenceOperation?.Member ??
enclosingExpressionSymbolInfo.Symbol;

if (enclosingExpressionSymbol == null)
{
return AnalyzeEnclosingExpressionCandidateSymbols(
syntaxNodeContext,
argInvocationExpression,
enclosingExpression,
enclosingExpressionSymbolInfo);
}

if (analysisResult.CanBeSubstituted == false)
if (_nonSubstitutableMemberAnalysis.Analyze(
syntaxNodeContext,
enclosingExpression,
enclosingExpressionSymbol).CanBeSubstituted != false)
{
return false;
}

syntaxNodeContext.TryReportDiagnostic(
Diagnostic.Create(
DiagnosticDescriptorsProvider.NonSubstitutableMemberArgumentMatcherUsage,
argInvocationExpression.GetLocation()),
enclosingExpressionSymbol);

return true;
}

private bool AnalyzeEnclosingExpressionCandidateSymbols(
SyntaxNodeAnalysisContext syntaxNodeContext,
TInvocationExpressionSyntax argInvocationExpression,
SyntaxNode enclosingExpression,
SymbolInfo enclosingExpressionSymbolInfo)
{
if (enclosingExpressionSymbolInfo.CandidateSymbols.Length == 0)
{
var diagnostic = Diagnostic.Create(
DiagnosticDescriptorsProvider.NonSubstitutableMemberArgumentMatcherUsage,
argInvocationExpression.GetLocation());

syntaxNodeContext.TryReportDiagnostic(diagnostic, enclosingExpressionSymbol);
syntaxNodeContext.TryReportDiagnostic(diagnostic, null);
return true;
}

foreach (var candidateSymbol in enclosingExpressionSymbolInfo.CandidateSymbols)
{
if (_nonSubstitutableMemberAnalysis.Analyze(
syntaxNodeContext,
enclosingExpression,
candidateSymbol).CanBeSubstituted == false)
{
var diagnostic = Diagnostic.Create(
DiagnosticDescriptorsProvider.NonSubstitutableMemberArgumentMatcherUsage,
argInvocationExpression.GetLocation());

syntaxNodeContext.TryReportDiagnostic(diagnostic, candidateSymbol);
return true;
}
}

return false;
}

private void AnalyzeAssignment(
SyntaxNodeAnalysisContext syntaxNodeContext,
TInvocationExpressionSyntax argInvocationExpression,
IMethodSymbol argInvocationExpressionSymbol,
IMemberReferenceOperation memberReferenceOperation)
{
if (memberReferenceOperation == null)
{
return;
}

var syntaxNode = memberReferenceOperation.Syntax;

if (IsWithinWhenLikeMethod(syntaxNodeContext, syntaxNode))
{
return;
}

if (argInvocationExpressionSymbol.IsArgDoLikeMethod())
{
return;
}

if (IsPrecededByReceivedLikeMethod(syntaxNodeContext, syntaxNode))
{
return;
}

var diagnostic = Diagnostic.Create(
DiagnosticDescriptorsProvider.NonSubstitutableMemberArgumentMatcherUsage,
argInvocationExpression.GetLocation());

syntaxNodeContext.TryReportDiagnostic(diagnostic, memberReferenceOperation.Member);
}

private bool IsPrecededByReceivedLikeMethod(SyntaxNodeAnalysisContext syntaxNodeContext, SyntaxNode syntaxNode)
{
var parentInvocationSyntaxNode = syntaxNode.GetParentNode(_parentInvocationSyntaxNodeHierarchy);
return parentInvocationSyntaxNode != null &&
syntaxNodeContext.SemanticModel.GetSymbolInfo(parentInvocationSyntaxNode).Symbol.IsReceivedLikeMethod();
}

private bool IsWithinWhenLikeMethod(SyntaxNodeAnalysisContext syntaxNodeContext, SyntaxNode syntaxNode)
{
var invocation = syntaxNode.Ancestors().FirstOrDefault(ancestor => ancestor.RawKind == _invocationExpressionRawKind);

return invocation != null && syntaxNodeContext.SemanticModel.GetSymbolInfo(invocation).Symbol.IsWhenLikeMethod();
}

private static IMemberReferenceOperation GetMemberReferenceOperation(IOperation operation)
{
switch (operation)
{
case IAssignmentOperation assignmentOperation
when assignmentOperation.Target is IMemberReferenceOperation memberReferenceOperation:
return memberReferenceOperation;
case IBinaryOperation binaryOperation
when binaryOperation.LeftOperand is IMemberReferenceOperation binaryMemberReferenceOperation:
return binaryMemberReferenceOperation;
case IBinaryOperation binaryOperation
when binaryOperation.LeftOperand is IConversionOperation conversionOperation &&
conversionOperation.Operand is IMemberReferenceOperation conversionMemberReference:
return conversionMemberReference;
case IExpressionStatementOperation expressionStatementOperation
when
expressionStatementOperation.Operation is ISimpleAssignmentOperation simpleAssignmentOperation &&
simpleAssignmentOperation.Target is IMemberReferenceOperation memberReferenceOperation:
return memberReferenceOperation;
default:
return null;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ public static bool IsEventAssignmentOperation(this IOperation operation)
return assignmentOperation.Kind == OperationKind.EventAssignment;
case IEventAssignmentOperation _:
return true;
case IExpressionStatementOperation expressionStatementOperation:
return IsEventAssignmentOperation(expressionStatementOperation.Operation);
default:
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,19 +63,20 @@ public static bool IsArgMatcherLikeMethod(this ISymbol symbol)
return IsMember(symbol, MetadataNames.ArgMatchersMethodNames) || IsMember(symbol, MetadataNames.ArgMatchersCompatMethodNames);
}

public static bool IsArgDoLikeMethod(this ISymbol symbol)
{
return IsMember(symbol, MetadataNames.ArgDoMethodName, MetadataNames.NSubstituteArgFullTypeName) ||
IsMember(symbol, MetadataNames.ArgDoMethodName, MetadataNames.NSubstituteArgCompatFullTypeName);
}

public static bool IsSubstituteCreateLikeMethod(this ISymbol symbol)
{
return IsMember(symbol, MetadataNames.CreateSubstituteMethodNames);
}

private static bool IsMember(this ISymbol symbol, IReadOnlyDictionary<string, string> memberTypeMap)
{
if (symbol == null)
{
return false;
}

return memberTypeMap.TryGetValue(symbol.Name, out var containingType) && IsMember(symbol, containingType);
return symbol != null && memberTypeMap.TryGetValue(symbol.Name, out var containingType) && IsMember(symbol, containingType);
}

private static bool IsMember(ISymbol symbol, string memberName, string containingType)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;

namespace NSubstitute.Analyzers.Shared.Extensions
Expand All @@ -12,26 +11,6 @@ public static SyntaxNode GetParentNode(this SyntaxNode syntaxNode, IEnumerable<i
return GetNodeInHierarchy(syntaxNode.DescendantNodes(), parentNodeHierarchyKinds);
}

public static SyntaxNode GetAncestorNode(this SyntaxNode syntaxNode, IEnumerable<int> ancestorNodeHierarchyKinds)
{
return GetNodeInHierarchy(syntaxNode.Ancestors(), ancestorNodeHierarchyKinds);
}

public static SyntaxNode GetAncestorNode(this SyntaxNode syntaxNode, ImmutableArray<ImmutableArray<int>> ancestorHierarchies)
{
foreach (var possibleAncestorPath in ancestorHierarchies)
{
var node = syntaxNode.GetAncestorNode(possibleAncestorPath);

if (node != null)
{
return node;
}
}

return null;
}

public static Location GetSubstitutionNodeActualLocation<TMemberAccessExpression>(this SyntaxNode node, ISymbol symbol)
where TMemberAccessExpression : SyntaxNode
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
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 Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ internal sealed class NonSubstitutableMemberArgumentMatcherAnalyzer : AbstractNo
internal static ImmutableHashSet<int> MaybeAllowedAncestors { get; } = ImmutableHashSet.Create(
(int)SyntaxKind.InvocationExpression,
(int)SyntaxKind.AddHandlerStatement,
(int)SyntaxKind.ObjectCreationExpression);
(int)SyntaxKind.ObjectCreationExpression,
(int)SyntaxKind.EqualsExpression,
(int)SyntaxKind.SimpleAssignmentStatement);

private static ImmutableHashSet<int> IgnoredAncestors { get; } = ImmutableHashSet.Create(
(int)SyntaxKind.VariableDeclarator);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using Microsoft.CodeAnalysis.VisualBasic;
using Microsoft.CodeAnalysis.VisualBasic.Syntax;
using NSubstitute.Analyzers.Shared.DiagnosticAnalyzers;
using NSubstitute.Analyzers.Shared.Extensions;
using NSubstitute.Analyzers.VisualBasic.Extensions;

namespace NSubstitute.Analyzers.VisualBasic.DiagnosticAnalyzers
Expand Down
Loading

0 comments on commit 16f5540

Please sign in to comment.