diff --git a/src/NSubstitute.Analyzers.CSharp/DiagnosticAnalyzers/ConflictingArgumentAssignmentsAnalyzer.cs b/src/NSubstitute.Analyzers.CSharp/DiagnosticAnalyzers/ConflictingArgumentAssignmentsAnalyzer.cs index 33e54460..fdb4982d 100644 --- a/src/NSubstitute.Analyzers.CSharp/DiagnosticAnalyzers/ConflictingArgumentAssignmentsAnalyzer.cs +++ b/src/NSubstitute.Analyzers.CSharp/DiagnosticAnalyzers/ConflictingArgumentAssignmentsAnalyzer.cs @@ -1,17 +1,13 @@ -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 + internal sealed class ConflictingArgumentAssignmentsAnalyzer : AbstractConflictingArgumentAssignmentsAnalyzer { public ConflictingArgumentAssignmentsAnalyzer() : base(NSubstitute.Analyzers.CSharp.DiagnosticDescriptorsProvider.Instance, CallInfoCallFinder.Instance) @@ -19,37 +15,5 @@ public ConflictingArgumentAssignmentsAnalyzer() } protected override SyntaxKind InvocationExpressionKind { get; } = SyntaxKind.InvocationExpression; - - protected override IEnumerable 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; - } } } \ No newline at end of file diff --git a/src/NSubstitute.Analyzers.CSharp/Extensions/SyntaxExtensions.cs b/src/NSubstitute.Analyzers.CSharp/Extensions/SyntaxExtensions.cs index 83373ccf..d1b4f1b2 100644 --- a/src/NSubstitute.Analyzers.CSharp/Extensions/SyntaxExtensions.cs +++ b/src/NSubstitute.Analyzers.CSharp/Extensions/SyntaxExtensions.cs @@ -1,6 +1,5 @@ using System; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using NSubstitute.Analyzers.Shared.Extensions; @@ -8,17 +7,6 @@ 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 symbolProvider) { return node.GetSubstitutionActualNode(symbolProvider); diff --git a/src/NSubstitute.Analyzers.Shared/DiagnosticAnalyzers/AbstractCallInfoAnalyzer.cs b/src/NSubstitute.Analyzers.Shared/DiagnosticAnalyzers/AbstractCallInfoAnalyzer.cs index ca48419a..4e5b58f2 100644 --- a/src/NSubstitute.Analyzers.Shared/DiagnosticAnalyzers/AbstractCallInfoAnalyzer.cs +++ b/src/NSubstitute.Analyzers.Shared/DiagnosticAnalyzers/AbstractCallInfoAnalyzer.cs @@ -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) @@ -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]; diff --git a/src/NSubstitute.Analyzers.Shared/DiagnosticAnalyzers/AbstractConflictingArgumentAssignmentsAnalyzer.cs b/src/NSubstitute.Analyzers.Shared/DiagnosticAnalyzers/AbstractConflictingArgumentAssignmentsAnalyzer.cs index a26b1c87..8106b46a 100644 --- a/src/NSubstitute.Analyzers.Shared/DiagnosticAnalyzers/AbstractConflictingArgumentAssignmentsAnalyzer.cs +++ b/src/NSubstitute.Analyzers.Shared/DiagnosticAnalyzers/AbstractConflictingArgumentAssignmentsAnalyzer.cs @@ -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 : AbstractDiagnosticAnalyzer + internal abstract class AbstractConflictingArgumentAssignmentsAnalyzer : AbstractDiagnosticAnalyzer where TInvocationExpressionSyntax : SyntaxNode - where TExpressionSyntax : SyntaxNode where TIndexerExpressionSyntax : SyntaxNode where TSyntaxKind : struct { @@ -36,51 +36,33 @@ protected override void InitializeAnalyzer(AnalysisContext context) context.RegisterSyntaxNodeAction(_analyzeInvocationAction, InvocationExpressionKind); } - protected abstract IEnumerable 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) { @@ -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 FindCallInfoIndexers(SyntaxNodeAnalysisContext syntaxNodeContext, TInvocationExpressionSyntax invocationExpressionSyntax) + private IEnumerable 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)) { diff --git a/src/NSubstitute.Analyzers.Shared/DiagnosticAnalyzers/AbstractSubstitutionNodeFinder.cs b/src/NSubstitute.Analyzers.Shared/DiagnosticAnalyzers/AbstractSubstitutionNodeFinder.cs index e847a533..d6dac84a 100644 --- a/src/NSubstitute.Analyzers.Shared/DiagnosticAnalyzers/AbstractSubstitutionNodeFinder.cs +++ b/src/NSubstitute.Analyzers.Shared/DiagnosticAnalyzers/AbstractSubstitutionNodeFinder.cs @@ -28,7 +28,7 @@ public IEnumerable 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); } @@ -77,7 +77,7 @@ public IEnumerable 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; } @@ -85,7 +85,10 @@ public SyntaxNode FindForAndDoesExpression(SyntaxNodeAnalysisContext syntaxNodeC return FindForStandardExpression(parentInvocationExpression); } - public SyntaxNode FindForStandardExpression(IInvocationOperation invocationOperation) => GetSubstituteOperation(invocationOperation).Syntax; + public SyntaxNode FindForStandardExpression(IInvocationOperation invocationOperation) + { + return invocationOperation.GetSubstituteOperation().Syntax; + } public abstract IEnumerable FindForReceivedInOrderExpression(SyntaxNodeAnalysisContext syntaxNodeContext, TInvocationExpressionSyntax receivedInOrderExpression, IMethodSymbol receivedInOrderInvocationSymbol = null); @@ -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 GetBaseTypesAndThis(ITypeSymbol type) { var current = type; diff --git a/src/NSubstitute.Analyzers.Shared/Extensions/IOperationExtensions.cs b/src/NSubstitute.Analyzers.Shared/Extensions/IOperationExtensions.cs index 4d0112d6..f8e5cf8e 100644 --- a/src/NSubstitute.Analyzers.Shared/Extensions/IOperationExtensions.cs +++ b/src/NSubstitute.Analyzers.Shared/Extensions/IOperationExtensions.cs @@ -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 && diff --git a/src/NSubstitute.Analyzers.VisualBasic/DiagnosticAnalyzers/ConflictingArgumentAssignmentsAnalyzer.cs b/src/NSubstitute.Analyzers.VisualBasic/DiagnosticAnalyzers/ConflictingArgumentAssignmentsAnalyzer.cs index ea6e9c14..d0a9ceac 100644 --- a/src/NSubstitute.Analyzers.VisualBasic/DiagnosticAnalyzers/ConflictingArgumentAssignmentsAnalyzer.cs +++ b/src/NSubstitute.Analyzers.VisualBasic/DiagnosticAnalyzers/ConflictingArgumentAssignmentsAnalyzer.cs @@ -1,16 +1,13 @@ -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 + internal sealed class ConflictingArgumentAssignmentsAnalyzer : AbstractConflictingArgumentAssignmentsAnalyzer { public ConflictingArgumentAssignmentsAnalyzer() : base(NSubstitute.Analyzers.VisualBasic.DiagnosticDescriptorsProvider.Instance, CallInfoCallFinder.Instance) @@ -18,37 +15,5 @@ public ConflictingArgumentAssignmentsAnalyzer() } protected override SyntaxKind InvocationExpressionKind { get; } = SyntaxKind.InvocationExpression; - - protected override IEnumerable 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; - } } } \ No newline at end of file diff --git a/src/NSubstitute.Analyzers.VisualBasic/Extensions/SyntaxExtensions.cs b/src/NSubstitute.Analyzers.VisualBasic/Extensions/SyntaxExtensions.cs index e5b070f2..667ea6c3 100644 --- a/src/NSubstitute.Analyzers.VisualBasic/Extensions/SyntaxExtensions.cs +++ b/src/NSubstitute.Analyzers.VisualBasic/Extensions/SyntaxExtensions.cs @@ -1,6 +1,5 @@ using System; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.VisualBasic; using Microsoft.CodeAnalysis.VisualBasic.Syntax; using NSubstitute.Analyzers.Shared.Extensions; @@ -8,17 +7,6 @@ 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; - } - public static SyntaxNode GetSubstitutionActualNode(this SyntaxNode node, Func symbolProvider) { return node.GetSubstitutionActualNode(symbolProvider);