Skip to content

Commit

Permalink
[GH-35] - arg analyzer based on non-virtual calls
Browse files Browse the repository at this point in the history
  • Loading branch information
tpodolak committed Oct 27, 2019
1 parent 54b9fab commit f8b987b
Show file tree
Hide file tree
Showing 43 changed files with 1,469 additions and 3,874 deletions.
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
Expand All @@ -7,18 +8,60 @@
namespace NSubstitute.Analyzers.CSharp.DiagnosticAnalyzers
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal sealed class ArgumentMatcherAnalyzer : AbstractArgumentMatcherAnalyzer<SyntaxKind, InvocationExpressionSyntax, MemberAccessExpressionSyntax, ArgumentSyntax>
internal sealed class ArgumentMatcherAnalyzer : AbstractArgumentMatcherAnalyzer<SyntaxKind, InvocationExpressionSyntax>
{
private static ImmutableArray<ImmutableArray<int>> AllowedPaths { get; } = ImmutableArray.Create(
ImmutableArray.Create(
(int)SyntaxKind.Argument,
(int)SyntaxKind.ArgumentList,
(int)SyntaxKind.InvocationExpression),
ImmutableArray.Create(
(int)SyntaxKind.Argument,
(int)SyntaxKind.BracketedArgumentList,
(int)SyntaxKind.ElementAccessExpression),
ImmutableArray.Create(
(int)SyntaxKind.CastExpression,
(int)SyntaxKind.Argument,
(int)SyntaxKind.ArgumentList,
(int)SyntaxKind.InvocationExpression),
ImmutableArray.Create(
(int)SyntaxKind.AsExpression,
(int)SyntaxKind.Argument,
(int)SyntaxKind.ArgumentList,
(int)SyntaxKind.InvocationExpression),
ImmutableArray.Create(
(int)SyntaxKind.CastExpression,
(int)SyntaxKind.Argument,
(int)SyntaxKind.BracketedArgumentList,
(int)SyntaxKind.ElementAccessExpression),
ImmutableArray.Create(
(int)SyntaxKind.AsExpression,
(int)SyntaxKind.Argument,
(int)SyntaxKind.BracketedArgumentList,
(int)SyntaxKind.ElementAccessExpression));

private static ImmutableArray<ImmutableArray<int>> IgnoredPaths { get; } = ImmutableArray.Create(
ImmutableArray.Create(
(int)SyntaxKind.EqualsValueClause,
(int)SyntaxKind.VariableDeclarator),
ImmutableArray.Create(
(int)SyntaxKind.CastExpression,
(int)SyntaxKind.EqualsValueClause,
(int)SyntaxKind.VariableDeclarator),
ImmutableArray.Create(
(int)SyntaxKind.AsExpression,
(int)SyntaxKind.EqualsValueClause,
(int)SyntaxKind.VariableDeclarator));

public ArgumentMatcherAnalyzer()
: base(CSharp.DiagnosticDescriptorsProvider.Instance)
: base(NSubstitute.Analyzers.CSharp.DiagnosticDescriptorsProvider.Instance)
{
}

protected override SyntaxKind InvocationExpressionKind { get; } = SyntaxKind.InvocationExpression;
protected override ImmutableArray<ImmutableArray<int>> AllowedAncestorPaths { get; } = AllowedPaths;

protected override AbstractArgumentMatcherCompilationAnalyzer<InvocationExpressionSyntax, MemberAccessExpressionSyntax, ArgumentSyntax> CreateArgumentMatcherCompilationAnalyzer()
{
return new ArgumentMatcherCompilationAnalyzer(SubstitutionNodeFinder.Instance, DiagnosticDescriptorsProvider);
}
protected override ImmutableArray<ImmutableArray<int>> IgnoredAncestorPaths { get; } = IgnoredPaths;

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

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,6 @@
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<None Include="..\..\LICENSE.md" Pack="true" PackagePath="$(PackageLicenseFile)"/>
<None Include="..\..\LICENSE.md" Pack="true" PackagePath="$(PackageLicenseFile)" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,99 @@
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using NSubstitute.Analyzers.Shared.Extensions;

namespace NSubstitute.Analyzers.Shared.DiagnosticAnalyzers
{
internal abstract class AbstractArgumentMatcherAnalyzer<TSyntaxKind, TInvocationExpressionSyntax, TMemberAccessExpressionSyntax, TArgumentSyntax> : AbstractDiagnosticAnalyzer
where TSyntaxKind : struct
internal abstract class AbstractArgumentMatcherAnalyzer<TSyntaxKind, TInvocationExpressionSyntax> : AbstractDiagnosticAnalyzer
where TInvocationExpressionSyntax : SyntaxNode
where TMemberAccessExpressionSyntax : SyntaxNode
where TArgumentSyntax : SyntaxNode
where TSyntaxKind : struct
{
private readonly Action<CompilationStartAnalysisContext> _compilationStartAction;
private readonly Action<SyntaxNodeAnalysisContext> _analyzeInvocationAction;

protected abstract ImmutableArray<ImmutableArray<int>> AllowedAncestorPaths { get; }

protected abstract ImmutableArray<ImmutableArray<int>> IgnoredAncestorPaths { get; }

protected abstract TSyntaxKind InvocationExpressionKind { get; }

protected AbstractArgumentMatcherAnalyzer(IDiagnosticDescriptorsProvider diagnosticDescriptorsProvider)
: base(diagnosticDescriptorsProvider)
{
_analyzeInvocationAction = AnalyzeInvocation;
SupportedDiagnostics = ImmutableArray.Create(DiagnosticDescriptorsProvider.ArgumentMatcherUsedWithoutSpecifyingCall);
_compilationStartAction = AnalyzeCompilation;
}

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; }

protected override void InitializeAnalyzer(AnalysisContext context)
{
context.RegisterCompilationStartAction(_compilationStartAction);
context.RegisterSyntaxNodeAction(_analyzeInvocationAction, InvocationExpressionKind);
}

protected abstract AbstractArgumentMatcherCompilationAnalyzer<TInvocationExpressionSyntax, TMemberAccessExpressionSyntax, TArgumentSyntax> CreateArgumentMatcherCompilationAnalyzer();
private void AnalyzeInvocation(SyntaxNodeAnalysisContext syntaxNodeContext)
{
var invocationExpression = (TInvocationExpressionSyntax)syntaxNodeContext.Node;
var methodSymbolInfo = syntaxNodeContext.SemanticModel.GetSymbolInfo(invocationExpression);

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

var symbol = methodSymbolInfo.Symbol;

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

AnalyzeArgLikeMethod(syntaxNodeContext, invocationExpression);
}

private void AnalyzeCompilation(CompilationStartAnalysisContext compilationContext)
private void AnalyzeArgLikeMethod(SyntaxNodeAnalysisContext syntaxNodeContext, TInvocationExpressionSyntax argInvocationExpression)
{
var compilationAnalyzer = CreateArgumentMatcherCompilationAnalyzer();
// find allowed enclosing expression
var enclosingExpression = FindAllowedEnclosingExpression(argInvocationExpression);

compilationContext.RegisterSyntaxNodeAction(compilationAnalyzer.BeginAnalyzeArgMatchers, InvocationExpressionKind);
// if Arg is used with not allowed expression, find if it is used in ignored ones eg. var x = Arg.Any
// as variable might be used later on
if (enclosingExpression == null && FindIgnoredEnclosingExpression(argInvocationExpression) == null)
{
var diagnostic = Diagnostic.Create(
DiagnosticDescriptorsProvider.ArgumentMatcherUsedWithoutSpecifyingCall,
argInvocationExpression.GetLocation());

compilationContext.RegisterCompilationEndAction(compilationAnalyzer.FinishAnalyzeArgMatchers);
syntaxNodeContext.ReportDiagnostic(diagnostic);
return;
}

if (enclosingExpression == null)
{
return;
}

var symbol = syntaxNodeContext.SemanticModel.GetSymbolInfo(enclosingExpression).Symbol;
var canBeSetuped = symbol.CanBeSetuped();

if (canBeSetuped == false || symbol.MemberVisibleToProxyGenerator() == false)
{
var diagnostic = Diagnostic.Create(
DiagnosticDescriptorsProvider.ArgumentMatcherUsedWithoutSpecifyingCall,
argInvocationExpression.GetLocation());

TryReportDiagnostic(syntaxNodeContext, diagnostic, symbol);
}
}

private SyntaxNode FindAllowedEnclosingExpression(TInvocationExpressionSyntax invocationExpression)
{
return invocationExpression.GetAncestorNode(AllowedAncestorPaths);
}

private SyntaxNode FindIgnoredEnclosingExpression(TInvocationExpressionSyntax invocationExpressionSyntax)
{
return invocationExpressionSyntax.GetAncestorNode(IgnoredAncestorPaths);
}
}
}
Loading

0 comments on commit f8b987b

Please sign in to comment.