Skip to content

Commit

Permalink
Merge branch 'dev' of github.com:nsubstitute/NSubstitute.Analyzers into
Browse files Browse the repository at this point in the history
GH-153-operation-api
  • Loading branch information
tpodolak committed Dec 20, 2020
2 parents 3cabac1 + 0d364ec commit e446585
Show file tree
Hide file tree
Showing 30 changed files with 3,084 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@

namespace NSubstitute.Analyzers.CSharp.DiagnosticAnalyzers
{
internal class CallInfoCallFinder : ICallInfoFinder
internal class CallInfoCallFinder : AbstractCallInfoFinder
{
public static CallInfoCallFinder Instance { get; } = new CallInfoCallFinder();

private CallInfoCallFinder()
{
}

public CallInfoContext GetCallInfoContext(SemanticModel semanticModel, SyntaxNode syntaxNode)
protected override CallInfoContext GetCallInfoContextInternal(SemanticModel semanticModel, SyntaxNode syntaxNode)
{
var visitor = new CallInfoVisitor(semanticModel);
visitor.Visit(syntaxNode);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using NSubstitute.Analyzers.Shared.DiagnosticAnalyzers;

namespace NSubstitute.Analyzers.CSharp.DiagnosticAnalyzers
{
internal class NonSubstitutableMemberAnalysis : AbstractNonSubstitutableMemberAnalysis
{
public static NonSubstitutableMemberAnalysis Instance { get; } = new NonSubstitutableMemberAnalysis();

private NonSubstitutableMemberAnalysis()
{
}

protected override ImmutableHashSet<Type> KnownNonVirtualSyntaxKinds { get; } = ImmutableHashSet.Create(
typeof(LiteralExpressionSyntax));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Operations;

namespace NSubstitute.Analyzers.Shared.DiagnosticAnalyzers
{
internal abstract class AbstractCallInfoFinder : ICallInfoFinder
{
public CallInfoContext GetCallInfoContext(
SemanticModel semanticModel, SyntaxNode syntaxNode)
{
var callInfoParameterSymbol = GetCallInfoParameterSymbol(semanticModel, syntaxNode);
if (callInfoParameterSymbol == null)
{
return CallInfoContext.Empty;
}

var callContext = GetCallInfoContextInternal(semanticModel, syntaxNode);

return CreateFilteredCallInfoContext(semanticModel, callContext, callInfoParameterSymbol);
}

protected abstract CallInfoContext GetCallInfoContextInternal(SemanticModel semanticModel, SyntaxNode syntaxNode);

private static CallInfoContext CreateFilteredCallInfoContext(
SemanticModel semanticModel,
CallInfoContext callContext,
IParameterSymbol callInfoParameterSymbol)
{
return new CallInfoContext(
argAtInvocations: GetMatchingNodes(semanticModel, callContext.ArgAtInvocations, callInfoParameterSymbol),
argInvocations: GetMatchingNodes(semanticModel, callContext.ArgInvocations, callInfoParameterSymbol),
indexerAccesses: GetMatchingNodes(semanticModel, callContext.IndexerAccesses, callInfoParameterSymbol));
}

private static IReadOnlyList<T> GetMatchingNodes<T>(
SemanticModel semanticModel,
IReadOnlyList<T> nodes,
IParameterSymbol parameterSymbol) where T : SyntaxNode
{
return nodes.Where(node => HasMatchingParameterReference(semanticModel, node, parameterSymbol)).ToList();
}

private static bool HasMatchingParameterReference(
SemanticModel semanticModel,
SyntaxNode syntaxNode,
IParameterSymbol callInfoParameterSymbol)
{
var parameterReferenceOperation = FindMatchingParameterReference(semanticModel, syntaxNode);

return parameterReferenceOperation != null &&
parameterReferenceOperation.Parameter.Equals(callInfoParameterSymbol);
}

private static IParameterReferenceOperation FindMatchingParameterReference(SemanticModel semanticModel, SyntaxNode syntaxNode)
{
var operation = semanticModel.GetOperation(syntaxNode);
return FindMatchingParameterReference(operation);
}

private static IParameterReferenceOperation FindMatchingParameterReference(IOperation operation)
{
IParameterReferenceOperation parameterReferenceOperation = null;
switch (operation)
{
case IInvocationOperation invocationOperation:
parameterReferenceOperation = invocationOperation.Instance as IParameterReferenceOperation;
break;
case IPropertyReferenceOperation propertyReferenceOperation:
parameterReferenceOperation = propertyReferenceOperation.Instance as IParameterReferenceOperation;
break;
}

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

foreach (var innerOperation in operation?.Children ?? Enumerable.Empty<IOperation>())
{
parameterReferenceOperation = FindMatchingParameterReference(innerOperation);
if (parameterReferenceOperation != null)
{
return parameterReferenceOperation;
}
}

return null;
}

private static IParameterSymbol GetCallInfoParameterSymbol(SemanticModel semanticModel, SyntaxNode syntaxNode)
{
if (semanticModel.GetSymbolInfo(syntaxNode).Symbol is IMethodSymbol methodSymbol && methodSymbol.MethodKind != MethodKind.Constructor)
{
return methodSymbol.Parameters.FirstOrDefault();
}

return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ private IEnumerable<SyntaxNode> FindCallInfoIndexers(SyntaxNodeAnalysisContext s
// perf - dont use linq in hotpaths
foreach (var argumentOperation in invocationOperation.GetOrderedArgumentOperationsWithoutInstanceArgument())
{
foreach (var indexerExpressionSyntax in _callInfoFinder.GetCallInfoContext(syntaxNodeContext.SemanticModel, argumentOperation.Syntax).IndexerAccesses)
foreach (var indexerExpressionSyntax in _callInfoFinder.GetCallInfoContext(syntaxNodeContext.SemanticModel, argumentOperation.Value.Syntax).IndexerAccesses)
{
if (IsAssigned(syntaxNodeContext, indexerExpressionSyntax))
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using System;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using NSubstitute.Analyzers.Shared.Extensions;

namespace NSubstitute.Analyzers.Shared.DiagnosticAnalyzers
{
internal abstract class AbstractNonSubstitutableMemberAnalysis : INonSubstitutableMemberAnalysis
{
protected abstract ImmutableHashSet<Type> KnownNonVirtualSyntaxKinds { get; }

public NonSubstitutableMemberAnalysisResult Analyze(
in SyntaxNodeAnalysisContext syntaxNodeContext,
SyntaxNode accessedMember,
ISymbol symbol = null)
{
var accessedSymbol = symbol ?? syntaxNodeContext.SemanticModel.GetSymbolInfo(accessedMember).Symbol;

if (accessedSymbol == null)
{
return new NonSubstitutableMemberAnalysisResult(
nonVirtualMemberSubstitution: KnownNonVirtualSyntaxKinds.Contains(accessedMember.GetType()),
internalMemberSubstitution: false,
symbol: null,
member: accessedMember,
memberName: accessedMember.ToString());
}

var canBeSubstituted = CanBeSubstituted(syntaxNodeContext, accessedMember, accessedSymbol);

if (canBeSubstituted == false)
{
return new NonSubstitutableMemberAnalysisResult(
nonVirtualMemberSubstitution: true,
internalMemberSubstitution: false,
symbol: accessedSymbol,
member: accessedMember,
memberName: accessedSymbol.Name);
}

if (accessedSymbol.MemberVisibleToProxyGenerator() == false)
{
return new NonSubstitutableMemberAnalysisResult(
nonVirtualMemberSubstitution: false,
internalMemberSubstitution: true,
symbol: accessedSymbol,
member: accessedMember,
memberName: accessedSymbol.Name);
}

return new NonSubstitutableMemberAnalysisResult(
nonVirtualMemberSubstitution: false,
internalMemberSubstitution: false,
symbol: accessedSymbol,
member: accessedMember,
memberName: accessedSymbol.Name);
}

protected virtual bool CanBeSubstituted(
SyntaxNodeAnalysisContext syntaxNodeContext,
SyntaxNode accessedMember,
ISymbol symbol)
{
return !KnownNonVirtualSyntaxKinds.Contains(accessedMember.GetType()) &&
CanBeSubstituted(symbol);
}

private static bool CanBeSubstituted(ISymbol symbol)
{
return IsInterfaceMember(symbol) || IsVirtual(symbol);
}

private static bool IsInterfaceMember(ISymbol symbol)
{
return symbol.ContainingType?.TypeKind == TypeKind.Interface;
}

private static bool IsVirtual(ISymbol symbol)
{
var isVirtual = symbol.IsVirtual
|| (symbol.IsOverride && !symbol.IsSealed)
|| symbol.IsAbstract;

return isVirtual;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
using System;
using System.Collections.Generic;
using Microsoft.CodeAnalysis;

namespace NSubstitute.Analyzers.Shared.DiagnosticAnalyzers
{
internal class CallInfoContext
{
public static CallInfoContext Empty { get; } = new CallInfoContext(
Array.Empty<SyntaxNode>(),
Array.Empty<SyntaxNode>(),
Array.Empty<SyntaxNode>());

public IReadOnlyList<SyntaxNode> IndexerAccesses { get; }

public IReadOnlyList<SyntaxNode> ArgAtInvocations { get; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@

namespace NSubstitute.Analyzers.VisualBasic.DiagnosticAnalyzers
{
internal class CallInfoCallFinder : ICallInfoFinder
internal class CallInfoCallFinder : AbstractCallInfoFinder
{
public static CallInfoCallFinder Instance { get; } = new CallInfoCallFinder();

private CallInfoCallFinder()
{
}

public CallInfoContext GetCallInfoContext(SemanticModel semanticModel, SyntaxNode syntaxNode)
protected override CallInfoContext GetCallInfoContextInternal(SemanticModel semanticModel, SyntaxNode syntaxNode)
{
var visitor = new CallInfoVisitor(semanticModel);
visitor.Visit(syntaxNode);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.VisualBasic.Syntax;
using NSubstitute.Analyzers.Shared.DiagnosticAnalyzers;

namespace NSubstitute.Analyzers.VisualBasic.DiagnosticAnalyzers
{
internal class NonSubstitutableMemberAnalysis : AbstractNonSubstitutableMemberAnalysis
{
public static NonSubstitutableMemberAnalysis Instance { get; } = new NonSubstitutableMemberAnalysis();

private NonSubstitutableMemberAnalysis()
{
}

protected override ImmutableHashSet<Type> KnownNonVirtualSyntaxKinds { get; } = ImmutableHashSet.Create(
typeof(LiteralExpressionSyntax));
}
}
Loading

0 comments on commit e446585

Please sign in to comment.