Skip to content

Commit

Permalink
[GH-1] - Attempting to analyze non generic version of Substitute.For
Browse files Browse the repository at this point in the history
  • Loading branch information
tpodolak committed May 27, 2018
1 parent 789dc71 commit 0c5aa94
Show file tree
Hide file tree
Showing 9 changed files with 1,003 additions and 90 deletions.
1 change: 1 addition & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<PropertyGroup>
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
<CodeAnalysisRuleSet>../../Analyzers.ruleset</CodeAnalysisRuleSet>
<LangVersion>7.2</LangVersion>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Include="../../StyleCop.json" />
Expand Down
30 changes: 30 additions & 0 deletions src/NSubstitute.Analyzers/Extensions/ArgumentSyntaxExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
#if CSHARP
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using CSharpExtensions = Microsoft.CodeAnalysis.CSharp.CSharpExtensions;
#endif
using Microsoft.CodeAnalysis.Diagnostics;
#if VISUAL_BASIC
using Microsoft.CodeAnalysis.VisualBasic;
using Microsoft.CodeAnalysis.VisualBasic.Syntax;
#endif

namespace NSubstitute.Analyzers.Extensions
{
public static class ArgumentSyntaxExtensions
{
public static ExpressionSyntax GetArgumentExpression(this ArgumentSyntax argumentSyntax)
{
#if CSHARP
return argumentSyntax.Expression;
#elif VISUAL_BASIC
return argumentSyntax.GetExpression();
#endif
}
}
}
52 changes: 52 additions & 0 deletions src/NSubstitute.Analyzers/Extensions/ExpressionSyntaxExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
#if CSHARP
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using CSharpExtensions = Microsoft.CodeAnalysis.CSharp.CSharpExtensions;
#endif
using Microsoft.CodeAnalysis.Diagnostics;
#if VISUAL_BASIC
using Microsoft.CodeAnalysis.VisualBasic;
using Microsoft.CodeAnalysis.VisualBasic.Syntax;
#endif

namespace NSubstitute.Analyzers.Extensions
{
public static class ExpressionSyntaxExtensions
{
private static readonly IList<ExpressionSyntax> EmptyExpressionList = new ExpressionSyntax[0];

public static IList<ExpressionSyntax> GetParameterExpressionsFromArrayArgument(this ExpressionSyntax expression)
{
#if CSHARP

InitializerExpressionSyntax initializer = null;
switch (expression.Kind())
{
case SyntaxKind.ArrayCreationExpression:
initializer = ((ArrayCreationExpressionSyntax)expression).Initializer;
break;
case SyntaxKind.ImplicitArrayCreationExpression:
initializer = ((ImplicitArrayCreationExpressionSyntax)expression).Initializer;
break;
default:
return EmptyExpressionList;
}

if (initializer == null)
{
return EmptyExpressionList;
}

return initializer.Expressions.ToList();
#elif VISUAL_BASIC

return EmptyExpressionList;
#endif
}
}
}
11 changes: 4 additions & 7 deletions src/NSubstitute.Analyzers/NSubstitute.Analyzers.CSharp.csproj
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.1</TargetFramework>
<PackageTargetFallback>portable45-net45+win8</PackageTargetFallback>
<TargetFramework>netstandard1.3</TargetFramework>
<IncludeBuildOutput>false</IncludeBuildOutput>
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
<DefineConstants>$(DefineConstants);CSHARP</DefineConstants>
<AssemblyName>NSubstitute.Analyzers.CSharp</AssemblyName>
<RootNamespace>NSubstitute.Analyzers</RootNamespace>
<AppDesignerFolder>Properties</AppDesignerFolder>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup>
<PackageId>NSubstitute.Analyzers.CSharp</PackageId>
Expand All @@ -26,11 +26,8 @@
<NoPackageAnalysis>true</NoPackageAnalysis>
</PropertyGroup>
<ItemGroup>
<PackageReference Update="NetStandard.Library" Version="$(NetStandardImplicitPackageVersion)" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="1.1.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="1.2.2" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.VisualBasic.Workspaces" Version="1.2.2" PrivateAssets="all" />
<PackageReference Include="Microsoft.Composition" Version="1.0.27" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.VisualBasic.Workspaces" Version="2.8.2" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="2.8.2" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<None Update="tools\*.ps1" CopyToOutputDirectory="Always" Pack="true" PackagePath="" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.1</TargetFramework>
<PackageTargetFallback>portable45-net45+win8</PackageTargetFallback>
<TargetFramework>netstandard1.3</TargetFramework>
<IncludeBuildOutput>false</IncludeBuildOutput>
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
<DefineConstants>$(DefineConstants);VISUAL_BASIC</DefineConstants>
Expand All @@ -26,10 +25,8 @@
<NoPackageAnalysis>true</NoPackageAnalysis>
</PropertyGroup>
<ItemGroup>
<PackageReference Update="NetStandard.Library" Version="$(NetStandardImplicitPackageVersion)" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="1.2.2" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.VisualBasic.Workspaces" Version="1.2.2" PrivateAssets="all" />
<PackageReference Include="Microsoft.Composition" Version="1.0.27" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.VisualBasic.Workspaces" Version="2.8.2" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="2.8.2" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<None Update="tools\*.ps1" CopyToOutputDirectory="Always" Pack="true" PackagePath="" />
Expand Down
142 changes: 142 additions & 0 deletions src/NSubstitute.Analyzers/SubstituteAnalysis.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Runtime.InteropServices.ComTypes;
using Microsoft.CodeAnalysis;
#if CSHARP
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using CSharpExtensions = Microsoft.CodeAnalysis.CSharp.CSharpExtensions;
#endif
using Microsoft.CodeAnalysis.Diagnostics;
using NSubstitute.Analyzers.Extensions;
#if VISUAL_BASIC
using Microsoft.CodeAnalysis.VisualBasic;
using Microsoft.CodeAnalysis.VisualBasic.Syntax;

#endif

namespace NSubstitute.Analyzers
{
// TODO remove duplication
public static class SubstituteAnalysis
{
public static InvocationInfo GetInfocationInfo(SubstituteAnalyzer.SubstituteContext substituteContext)
{
var infos = substituteContext.MethodSymbol.IsGenericMethod
? GetGenericInvocationArgumentTypes(substituteContext)
: GetNonGenericInvocationArgumentTypes(substituteContext);

return new InvocationInfo(infos);
}

private static IList<TypeInfo> GetGenericInvocationArgumentTypes(
SubstituteAnalyzer.SubstituteContext substituteContext)
{
if (substituteContext.InvocationExpression.ArgumentList == null)
{
return null;
}

var arguments = substituteContext.InvocationExpression.ArgumentList.Arguments;

if (arguments.Count == 0)
{
return new List<TypeInfo>();
}

var typeInfos = arguments.Select(arg =>
substituteContext.SyntaxNodeAnalysisContext.SemanticModel
.GetTypeInfo(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 (possibleParamsArgument.Type == null)
{
return new List<TypeInfo>();
}

var parameterExpressionsFromArrayArgument = arguments.First().GetArgumentExpression()
.GetParameterExpressionsFromArrayArgument();

var types = parameterExpressionsFromArrayArgument
.Select(exp => GetTypeInfo(substituteContext, exp))
.ToList();

return types;
}

return typeInfos;
}

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

// Substitute.For(new [] { typeof(T) }, null) // means we dont pass any arguments
var typeInfo =
substituteContext.SyntaxNodeAnalysisContext.SemanticModel.GetTypeInfo(arrayArgument.DescendantNodes()
.First());
if (typeInfo.ConvertedType != null && typeInfo.ConvertedType.TypeKind == TypeKind.Array &&
typeInfo.Type == null)
{
return new List<TypeInfo>();
}

// Substitute.For(new [] { typeof(T)}, new object[] { }); // means we dont pass any arguments
var parameterExpressionsFromArrayArgument =
arrayArgument.GetArgumentExpression().GetParameterExpressionsFromArrayArgument();
if (parameterExpressionsFromArrayArgument.Count == 0)
{
return new List<TypeInfo>();
}

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

return types;
}

private static TypeInfo GetTypeInfo(SubstituteAnalyzer.SubstituteContext substituteContext, SyntaxNode syntax)
{
return substituteContext.SyntaxNodeAnalysisContext.SemanticModel.GetTypeInfo(syntax);
}

public class InvocationInfo
{
private IList<ITypeSymbol> _typeSymbols;

public IList<ITypeSymbol> CapturedSymbols
{
get
{
return _typeSymbols = _typeSymbols ??
TypeInfos?.Select(info => info.Type).Where(type => type != null).ToList();
}
}

public IList<TypeInfo> TypeInfos { get; }

public bool AllTypesCapured => TypeInfos != null && TypeInfos.Count == CapturedSymbols.Count;

public InvocationInfo(IList<TypeInfo> typeInfos)
{
TypeInfos = typeInfos;
}
}
}
}
Loading

0 comments on commit 0c5aa94

Please sign in to comment.