Skip to content

Commit

Permalink
[GH-153] - simplifying SubstituteConstructorAnalysis with operations api
Browse files Browse the repository at this point in the history
  • Loading branch information
tpodolak committed Dec 20, 2020
1 parent 772a7cc commit 5ee23f4
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 80 deletions.
Original file line number Diff line number Diff line change
@@ -1,28 +1,14 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using NSubstitute.Analyzers.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using NSubstitute.Analyzers.Shared.DiagnosticAnalyzers;

namespace NSubstitute.Analyzers.CSharp.DiagnosticAnalyzers
{
internal class SubstituteConstructorAnalysis : AbstractSubstituteConstructorAnalysis<InvocationExpressionSyntax, ArgumentSyntax>
internal class SubstituteConstructorAnalysis : AbstractSubstituteConstructorAnalysis<InvocationExpressionSyntax>
{
public static SubstituteConstructorAnalysis Instance { get; } = new SubstituteConstructorAnalysis();

private SubstituteConstructorAnalysis()
{
}

protected override IList<ArgumentSyntax> GetInvocationArguments(InvocationExpressionSyntax invocationExpression)
{
return invocationExpression.ArgumentList?.Arguments.ToList();
}

protected override IList<SyntaxNode> GetParameterExpressionsFromArrayArgument(ArgumentSyntax syntaxNode)
{
return syntaxNode.Expression.GetParameterExpressionsFromArrayArgument()?.Select<ExpressionSyntax, SyntaxNode>(syntax => syntax).ToList();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Operations;
using NSubstitute.Analyzers.Shared.Extensions;

namespace NSubstitute.Analyzers.Shared.DiagnosticAnalyzers
{
internal abstract class AbstractSubstituteConstructorAnalysis<TInvocationExpression, TArgumentSyntax> : ISubstituteConstructorAnalysis<TInvocationExpression> where TInvocationExpression : SyntaxNode
where TArgumentSyntax : SyntaxNode
internal abstract class AbstractSubstituteConstructorAnalysis<TInvocationExpression> : ISubstituteConstructorAnalysis<TInvocationExpression>
where TInvocationExpression : SyntaxNode
{
public ConstructorContext CollectConstructorContext(SubstituteContext<TInvocationExpression> substituteContext, ITypeSymbol proxyTypeSymbol)
{
Expand All @@ -30,10 +31,6 @@ public ConstructorContext CollectConstructorContext(SubstituteContext<TInvocatio
invocationParameterTypes);
}

protected abstract IList<TArgumentSyntax> GetInvocationArguments(TInvocationExpression invocationExpression);

protected abstract IList<SyntaxNode> GetParameterExpressionsFromArrayArgument(TArgumentSyntax syntaxNode);

private ITypeSymbol[] GetInvocationInfo(SubstituteContext<TInvocationExpression> substituteContext)
{
var infos = substituteContext.MethodSymbol.IsGenericMethod
Expand All @@ -45,70 +42,49 @@ private ITypeSymbol[] GetInvocationInfo(SubstituteContext<TInvocationExpression>

private ITypeSymbol[] GetGenericInvocationArgumentTypes(SubstituteContext<TInvocationExpression> substituteContext)
{
var arguments = GetInvocationArguments(substituteContext.InvocationExpression);
var operation = substituteContext.SyntaxNodeAnalysisContext.SemanticModel.GetOperation(substituteContext.InvocationExpression);

ImmutableArray<IArgumentOperation>? arguments = null;
if (operation is IInvocationOperation invocationOperation)
{
arguments = invocationOperation.Arguments;
}

if (arguments == null)
if (arguments.HasValue == false)
{
return null;
}

if (arguments.Count == 0)
if (arguments.Value.Length == 0)
{
return Array.Empty<ITypeSymbol>();
}

var typeInfos = arguments.Select(arg => GetTypeInfo(substituteContext, arg.DescendantNodes().First()))
.ToList();

var possibleParamsArgument = typeInfos.First();

// if passing array of objects as a sole element
if (arguments.Count == 1 &&
possibleParamsArgument.ConvertedType is IArrayTypeSymbol arrayTypeSymbol &&
arrayTypeSymbol.ElementType.Equals(substituteContext.SyntaxNodeAnalysisContext.Compilation.ObjectType))
if (arguments.Value.Length == 1 && arguments.Value.Single().Value is IArrayCreationOperation arrayTypeSymbol)
{
return GetArgumentTypeInfo(substituteContext, arguments.First());
return TypeSymbols(arrayTypeSymbol);
}

return typeInfos.Select(type => type.Type).ToArray();
return arguments.Value.SelectMany(ArgType).ToArray();
}

private ITypeSymbol[] GetNonGenericInvocationArgumentTypes(SubstituteContext<TInvocationExpression> substituteContext)
{
// Substitute.For(new [] { typeof(T) }, new object[] { 1, 2, 3}) // actual arguments reside in second arg
var arrayArgument = GetInvocationArguments(substituteContext.InvocationExpression)?.Skip(1).FirstOrDefault();
var arrayArgument = GetInvocationArguments(substituteContext, substituteContext.InvocationExpression).Skip(1).FirstOrDefault();
if (arrayArgument == null)
{
return null;
}

return GetArgumentTypeInfo(substituteContext, arrayArgument);
}

private ITypeSymbol[] GetArgumentTypeInfo(SubstituteContext<TInvocationExpression> substituteContext, TArgumentSyntax arrayArgument)
{
var typeInfo = GetTypeInfo(substituteContext, arrayArgument.DescendantNodes().First());

if (typeInfo.ConvertedType != null &&
typeInfo.ConvertedType.TypeKind == TypeKind.Array &&
typeInfo.Type == null)
// if passing array of objects as a sole element
if (arrayArgument.Value is IArrayCreationOperation arrayTypeSymbol)
{
return null;
return TypeSymbols(arrayTypeSymbol);
}

// new object[] { }; // means we dont pass any arguments
var parameterExpressionsFromArrayArgument = GetParameterExpressionsFromArrayArgument(arrayArgument);
if (parameterExpressionsFromArrayArgument == null)
{
return null;
}

// new object[] { 1, 2, 3}); // means we pass arguments
var types = parameterExpressionsFromArrayArgument
.Select(exp => GetTypeInfo(substituteContext, exp).Type)
.ToArray();

return types;
return ArgType(arrayArgument);
}

private IMethodSymbol[] GetAccessibleConstructors(ITypeSymbol genericArgument)
Expand Down Expand Up @@ -138,9 +114,32 @@ bool IsVisibleToProxy(IMethodSymbol symbol)
(IsAccessible(symbol) || IsVisibleToProxy(symbol))).ToArray();
}

private TypeInfo GetTypeInfo(SubstituteContext<TInvocationExpression> substituteContext, SyntaxNode syntax)
private ImmutableArray<IArgumentOperation> GetInvocationArguments(SubstituteContext<TInvocationExpression> substituteContext, TInvocationExpression invocationExpression)
{
var invocationOperation = (IInvocationOperation)substituteContext.SyntaxNodeAnalysisContext.SemanticModel.GetOperation(invocationExpression);

return invocationOperation.Arguments;
}

private ITypeSymbol[] TypeSymbols(IArrayCreationOperation arrayInitializerOperation)
{
return substituteContext.SyntaxNodeAnalysisContext.SemanticModel.GetTypeInfo(syntax);
return arrayInitializerOperation.Initializer.ElementValues.Select(item =>
item is IConversionOperation conversionOperation
? conversionOperation.Operand.Type
: item.Type)
.ToArray();
}

private ITypeSymbol[] ArgType(IArgumentOperation x)
{
if (x.ArgumentKind == ArgumentKind.ParamArray)
{
var arrayInitializerOperation = x.Value as IArrayCreationOperation;

return TypeSymbols(arrayInitializerOperation);
}

return Array.Empty<ITypeSymbol>();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,29 +1,14 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.VisualBasic.Syntax;
using Microsoft.CodeAnalysis.VisualBasic.Syntax;
using NSubstitute.Analyzers.Shared.DiagnosticAnalyzers;
using NSubstitute.Analyzers.VisualBasic.Extensions;

namespace NSubstitute.Analyzers.VisualBasic.DiagnosticAnalyzers
{
internal class SubstituteConstructorAnalysis : AbstractSubstituteConstructorAnalysis<InvocationExpressionSyntax, ArgumentSyntax>
internal class SubstituteConstructorAnalysis : AbstractSubstituteConstructorAnalysis<InvocationExpressionSyntax>
{
public static SubstituteConstructorAnalysis Instance { get; } = new SubstituteConstructorAnalysis();

private SubstituteConstructorAnalysis()
{
}

protected override IList<ArgumentSyntax> GetInvocationArguments(InvocationExpressionSyntax invocationExpression)
{
return invocationExpression.ArgumentList?.Arguments.ToList();
}

protected override IList<SyntaxNode> GetParameterExpressionsFromArrayArgument(ArgumentSyntax syntaxNode)
{
return syntaxNode.GetExpression().GetParameterExpressionsFromArrayArgument()?
.Select<ExpressionSyntax, SyntaxNode>(syntax => syntax).ToList();
}
}
}

0 comments on commit 5ee23f4

Please sign in to comment.