Skip to content

Commit

Permalink
[GH-153] - using operation API for ConflictingArgumentAssignment anal…
Browse files Browse the repository at this point in the history
…yzer
  • Loading branch information
tpodolak committed Dec 20, 2020
1 parent 198221c commit e3f98bd
Show file tree
Hide file tree
Showing 8 changed files with 70 additions and 164 deletions.
Original file line number Diff line number Diff line change
@@ -1,55 +1,19 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using NSubstitute.Analyzers.CSharp.Extensions;
using NSubstitute.Analyzers.Shared;
using NSubstitute.Analyzers.Shared.DiagnosticAnalyzers;

namespace NSubstitute.Analyzers.CSharp.DiagnosticAnalyzers
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal sealed class ConflictingArgumentAssignmentsAnalyzer : AbstractConflictingArgumentAssignmentsAnalyzer<SyntaxKind, InvocationExpressionSyntax, ExpressionSyntax, ElementAccessExpressionSyntax>
internal sealed class ConflictingArgumentAssignmentsAnalyzer : AbstractConflictingArgumentAssignmentsAnalyzer<SyntaxKind, InvocationExpressionSyntax, ElementAccessExpressionSyntax>
{
public ConflictingArgumentAssignmentsAnalyzer()
: base(NSubstitute.Analyzers.CSharp.DiagnosticDescriptorsProvider.Instance, CallInfoCallFinder.Instance)
{
}

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

protected override IEnumerable<ExpressionSyntax> GetArgumentExpressions(InvocationExpressionSyntax invocationExpressionSyntax)
{
return invocationExpressionSyntax.ArgumentList.Arguments.Select(arg => arg.Expression);
}

protected override SyntaxNode GetSubstituteCall(SyntaxNodeAnalysisContext syntaxNodeContext, IMethodSymbol methodSymbol, InvocationExpressionSyntax invocationExpressionSyntax)
{
return invocationExpressionSyntax.GetParentInvocationExpression();
}

protected override int? GetIndexerPosition(SyntaxNodeAnalysisContext syntaxNodeAnalysisContext, ElementAccessExpressionSyntax indexerExpressionSyntax)
{
var position = syntaxNodeAnalysisContext.SemanticModel.GetConstantValue(indexerExpressionSyntax.ArgumentList.Arguments.First().Expression);
return (int?)(position.HasValue ? position.Value : null);
}

protected override ISymbol GetIndexerSymbol(SyntaxNodeAnalysisContext syntaxNodeAnalysisContext, ElementAccessExpressionSyntax indexerExpressionSyntax)
{
return syntaxNodeAnalysisContext.SemanticModel.GetSymbolInfo(indexerExpressionSyntax).Symbol ??
syntaxNodeAnalysisContext.SemanticModel.GetSymbolInfo(indexerExpressionSyntax.Expression).Symbol;
}

protected override SyntaxNode GetAssignmentExpression(ElementAccessExpressionSyntax indexerExpressionSyntax)
{
if (indexerExpressionSyntax.Parent is AssignmentExpressionSyntax assignmentExpressionSyntax)
{
return assignmentExpressionSyntax.Right;
}

return null;
}
}
}
12 changes: 0 additions & 12 deletions src/NSubstitute.Analyzers.CSharp/Extensions/SyntaxExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,24 +1,12 @@
using System;
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;
}

public static SyntaxNode GetSubstitutionActualNode(this SyntaxNode node, Func<SyntaxNode, ISymbol> symbolProvider)
{
return node.GetSubstitutionActualNode<MemberAccessExpressionSyntax>(symbolProvider);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,20 +75,7 @@ protected override void InitializeAnalyzer(AnalysisContext context)
protected int? GetIndexerPosition(SyntaxNodeAnalysisContext syntaxNodeAnalysisContext, TIndexerExpressionSyntax indexerExpressionSyntax)
{
var operation = syntaxNodeAnalysisContext.SemanticModel.GetOperation(indexerExpressionSyntax);

var literal = operation switch
{
IArrayElementReferenceOperation arrayElementReferenceOperation => arrayElementReferenceOperation.Indices.First() as ILiteralOperation,
IPropertyReferenceOperation propertyReferenceOperation => propertyReferenceOperation.Arguments.First().Value as ILiteralOperation,
_ => null
};

if (literal == null || literal.ConstantValue.HasValue == false)
{
return null;
}

return (int)literal.ConstantValue.Value;
return operation.GetIndexerPosition();
}

private bool SupportsCallInfo(SyntaxNodeAnalysisContext syntaxNodeContext, IInvocationOperation invocationOperation)
Expand Down Expand Up @@ -287,8 +274,7 @@ private bool AnalyzeAssignment(
return false;
}

if (syntaxNodeContext.SemanticModel.GetOperation(indexer) is IPropertyReferenceOperation
referenceOperation &&
if (syntaxNodeContext.SemanticModel.GetOperation(indexer) is IPropertyReferenceOperation referenceOperation &&
referenceOperation.Parent is ISimpleAssignmentOperation simpleAssignmentOperation)
{
var parameterSymbol = substituteCallParameters[position.Value];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
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 AbstractConflictingArgumentAssignmentsAnalyzer<TSyntaxKind, TInvocationExpressionSyntax, TExpressionSyntax, TIndexerExpressionSyntax> : AbstractDiagnosticAnalyzer
internal abstract class AbstractConflictingArgumentAssignmentsAnalyzer<TSyntaxKind, TInvocationExpressionSyntax, TIndexerExpressionSyntax> : AbstractDiagnosticAnalyzer
where TInvocationExpressionSyntax : SyntaxNode
where TExpressionSyntax : SyntaxNode
where TIndexerExpressionSyntax : SyntaxNode
where TSyntaxKind : struct
{
Expand All @@ -36,51 +36,33 @@ protected override void InitializeAnalyzer(AnalysisContext context)
context.RegisterSyntaxNodeAction(_analyzeInvocationAction, InvocationExpressionKind);
}

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

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

protected abstract int? GetIndexerPosition(SyntaxNodeAnalysisContext syntaxNodeAnalysisContext, TIndexerExpressionSyntax indexerExpressionSyntax);

protected abstract ISymbol GetIndexerSymbol(SyntaxNodeAnalysisContext syntaxNodeAnalysisContext, TIndexerExpressionSyntax indexerExpressionSyntax);

protected abstract SyntaxNode GetAssignmentExpression(TIndexerExpressionSyntax indexerExpressionSyntax);

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

if (methodSymbolInfo.Symbol?.Kind != SymbolKind.Method)
if (!(syntaxNodeContext.SemanticModel.GetOperation(syntaxNodeContext.Node) is IInvocationOperation invocationOperation))
{
return;
}

var methodSymbol = (IMethodSymbol)methodSymbolInfo.Symbol;

if (methodSymbol.IsAndDoesLikeMethod() == false)
if (invocationOperation.TargetMethod.IsAndDoesLikeMethod() == false)
{
return;
}

var previousCall = GetSubstituteCall(syntaxNodeContext, methodSymbol, invocationExpression) as TInvocationExpressionSyntax;

if (previousCall == null)
if (!(invocationOperation.GetSubstituteOperation() is IInvocationOperation substituteOperation))
{
return;
}

var andDoesIndexers = FindCallInfoIndexers(syntaxNodeContext, invocationExpression).ToList();
var andDoesIndexers = FindCallInfoIndexers(syntaxNodeContext, invocationOperation).ToList();

if (andDoesIndexers.Count == 0)
{
return;
}

var previousCallIndexers = FindCallInfoIndexers(syntaxNodeContext, previousCall);
var previousCallIndexers = FindCallInfoIndexers(syntaxNodeContext, substituteOperation);

var immutableHashSet = previousCallIndexers.Select(indexerExpression => GetIndexerPosition(syntaxNodeContext, indexerExpression))
.ToImmutableHashSet();
var immutableHashSet = previousCallIndexers.Select(indexerExpression => GetIndexerPosition(syntaxNodeContext, indexerExpression)).ToImmutableHashSet();

foreach (var indexerExpressionSyntax in andDoesIndexers)
{
Expand All @@ -94,19 +76,32 @@ private void AnalyzeInvocation(SyntaxNodeAnalysisContext syntaxNodeContext)
}
}

private int? GetIndexerPosition(SyntaxNodeAnalysisContext syntaxNodeContext, TIndexerExpressionSyntax indexerExpression)
{
return syntaxNodeContext.SemanticModel.GetOperation(indexerExpression).GetIndexerPosition();
}

private bool IsAssigned(SyntaxNodeAnalysisContext syntaxNodeAnalysisContext, TIndexerExpressionSyntax indexerExpressionSyntax)
{
return GetAssignmentExpression(indexerExpressionSyntax) != null &&
GetIndexerSymbol(syntaxNodeAnalysisContext, indexerExpressionSyntax) is IPropertySymbol propertySymbol &&
propertySymbol.ContainingType.IsCallInfoSymbol();
if (!(syntaxNodeAnalysisContext.SemanticModel.GetOperation(indexerExpressionSyntax) is IPropertyReferenceOperation propertyReferenceOperation))
{
return false;
}

if (propertyReferenceOperation.Property.ContainingType.IsCallInfoSymbol() == false)
{
return false;
}

return propertyReferenceOperation.Parent is ISimpleAssignmentOperation;
}

private IEnumerable<TIndexerExpressionSyntax> FindCallInfoIndexers(SyntaxNodeAnalysisContext syntaxNodeContext, TInvocationExpressionSyntax invocationExpressionSyntax)
private IEnumerable<TIndexerExpressionSyntax> FindCallInfoIndexers(SyntaxNodeAnalysisContext syntaxNodeContext, IInvocationOperation invocationOperation)
{
// perf - dont use linq in hotpaths
foreach (var argumentExpression in GetArgumentExpressions(invocationExpressionSyntax))
foreach (var argumentOperation in invocationOperation.GetOrderedArgumentOperationsWithoutInstanceArgument())
{
foreach (var indexerExpressionSyntax in _callInfoFinder.GetCallInfoContext(syntaxNodeContext.SemanticModel, argumentExpression).IndexerAccesses)
foreach (var indexerExpressionSyntax in _callInfoFinder.GetCallInfoContext(syntaxNodeContext.SemanticModel, argumentOperation.Syntax).IndexerAccesses)
{
if (IsAssigned(syntaxNodeContext, indexerExpressionSyntax))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public IEnumerable<SyntaxNode> Find(SyntaxNodeAnalysisContext syntaxNodeContext,

if (invocationExpressionSymbol.Name.Equals(MetadataNames.NSubstituteDoMethod, StringComparison.Ordinal))
{
var operation = this.GetSubstituteOperation(invocationOperation);
var operation = invocationOperation.GetSubstituteOperation();
return FindForWhenExpression(syntaxNodeContext, operation as IInvocationOperation);
}

Expand Down Expand Up @@ -77,15 +77,18 @@ public IEnumerable<SyntaxNode> FindForWhenExpression(SyntaxNodeAnalysisContext s

public SyntaxNode FindForAndDoesExpression(SyntaxNodeAnalysisContext syntaxNodeContext, IInvocationOperation invocationOperation, IMethodSymbol invocationExpressionSymbol)
{
if (!(GetSubstituteOperation(invocationOperation) is IInvocationOperation parentInvocationExpression))
if (!(invocationOperation.GetSubstituteOperation() is IInvocationOperation parentInvocationExpression))
{
return null;
}

return FindForStandardExpression(parentInvocationExpression);
}

public SyntaxNode FindForStandardExpression(IInvocationOperation invocationOperation) => GetSubstituteOperation(invocationOperation).Syntax;
public SyntaxNode FindForStandardExpression(IInvocationOperation invocationOperation)
{
return invocationOperation.GetSubstituteOperation().Syntax;
}

public abstract IEnumerable<SyntaxNode> FindForReceivedInOrderExpression(SyntaxNodeAnalysisContext syntaxNodeContext, TInvocationExpressionSyntax receivedInOrderExpression, IMethodSymbol receivedInOrderInvocationSymbol = null);

Expand All @@ -96,22 +99,6 @@ private bool ContainsSymbol(ITypeSymbol containerSymbol, ISymbol symbol)
return GetBaseTypesAndThis(containerSymbol).Any(typeSymbol => typeSymbol == symbol.ContainingType);
}

private IOperation GetSubstituteOperation(IInvocationOperation invocationOperation)
{
if (!invocationOperation.TargetMethod.IsExtensionMethod)
{
return invocationOperation.Children.First();
}

// unlike CSharp implementation, VisualBasic doesnt include "instance" argument for reduced extensions
if (invocationOperation.TargetMethod.MethodKind == MethodKind.ReducedExtension && invocationOperation.Language == LanguageNames.VisualBasic)
{
return invocationOperation.Children.First();
}

return invocationOperation.Arguments.First().Value;
}

private static IEnumerable<ITypeSymbol> GetBaseTypesAndThis(ITypeSymbol type)
{
var current = type;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,39 @@ public static ITypeSymbol GetArgumentOperationDeclaredTypeSymbol(this IArgumentO
return argumentOperation.Parameter.Type;
}

public static IOperation GetSubstituteOperation(this IInvocationOperation invocationOperation)
{
if (!invocationOperation.TargetMethod.IsExtensionMethod)
{
return invocationOperation.Children.First();
}

// unlike CSharp implementation, VisualBasic doesnt include "instance" argument for reduced extensions
if (invocationOperation.TargetMethod.MethodKind == MethodKind.ReducedExtension && invocationOperation.Language == LanguageNames.VisualBasic)
{
return invocationOperation.Children.First();
}

return invocationOperation.Arguments.First().Value;
}

public static int? GetIndexerPosition(this IOperation operation)
{
var literal = operation switch
{
IArrayElementReferenceOperation arrayElementReferenceOperation => arrayElementReferenceOperation.Indices.First() as ILiteralOperation,
IPropertyReferenceOperation propertyReferenceOperation => propertyReferenceOperation.Arguments.First().Value as ILiteralOperation,
_ => null
};

if (literal == null || literal.ConstantValue.HasValue == false)
{
return null;
}

return (int)literal.ConstantValue.Value;
}

private static bool IsImplicitlyProvidedArrayWithValues(IArgumentOperation arg)
{
return arg.IsImplicit &&
Expand Down
Original file line number Diff line number Diff line change
@@ -1,54 +1,19 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.VisualBasic;
using Microsoft.CodeAnalysis.VisualBasic.Syntax;
using NSubstitute.Analyzers.Shared.DiagnosticAnalyzers;
using NSubstitute.Analyzers.VisualBasic.Extensions;

namespace NSubstitute.Analyzers.VisualBasic.DiagnosticAnalyzers
{
[DiagnosticAnalyzer(LanguageNames.VisualBasic)]
internal sealed class ConflictingArgumentAssignmentsAnalyzer : AbstractConflictingArgumentAssignmentsAnalyzer<SyntaxKind, InvocationExpressionSyntax, ExpressionSyntax, InvocationExpressionSyntax>
internal sealed class ConflictingArgumentAssignmentsAnalyzer : AbstractConflictingArgumentAssignmentsAnalyzer<SyntaxKind, InvocationExpressionSyntax, InvocationExpressionSyntax>
{
public ConflictingArgumentAssignmentsAnalyzer()
: base(NSubstitute.Analyzers.VisualBasic.DiagnosticDescriptorsProvider.Instance, CallInfoCallFinder.Instance)
{
}

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

protected override IEnumerable<ExpressionSyntax> GetArgumentExpressions(InvocationExpressionSyntax invocationExpressionSyntax)
{
return invocationExpressionSyntax.ArgumentList.Arguments.Select(arg => arg.GetExpression());
}

protected override SyntaxNode GetSubstituteCall(SyntaxNodeAnalysisContext syntaxNodeContext, IMethodSymbol methodSymbol, InvocationExpressionSyntax invocationExpressionSyntax)
{
return invocationExpressionSyntax.GetParentInvocationExpression();
}

protected override int? GetIndexerPosition(SyntaxNodeAnalysisContext syntaxNodeAnalysisContext, InvocationExpressionSyntax indexerExpressionSyntax)
{
var position = syntaxNodeAnalysisContext.SemanticModel.GetConstantValue(indexerExpressionSyntax.ArgumentList.Arguments.First().GetExpression());
return (int?)(position.HasValue ? position.Value : null);
}

protected override ISymbol GetIndexerSymbol(SyntaxNodeAnalysisContext syntaxNodeAnalysisContext, InvocationExpressionSyntax indexerExpressionSyntax)
{
return syntaxNodeAnalysisContext.SemanticModel.GetSymbolInfo(indexerExpressionSyntax).Symbol ??
syntaxNodeAnalysisContext.SemanticModel.GetSymbolInfo(indexerExpressionSyntax.Expression).Symbol;
}

protected override SyntaxNode GetAssignmentExpression(InvocationExpressionSyntax indexerExpressionSyntax)
{
if (indexerExpressionSyntax.Parent is AssignmentStatementSyntax assignmentExpressionSyntax)
{
return assignmentExpressionSyntax.Right;
}

return null;
}
}
}
Loading

0 comments on commit e3f98bd

Please sign in to comment.