Skip to content

Commit

Permalink
Merge pull request #39 from nsubstitute/GH-38-substitute-for-internal…
Browse files Browse the repository at this point in the history
…-type

Gh 38 substitute for internal type - code fix provider
  • Loading branch information
tpodolak authored Sep 30, 2018
2 parents a30dcc5 + b7229cc commit 4f463d3
Show file tree
Hide file tree
Showing 54 changed files with 2,507 additions and 125 deletions.
2 changes: 1 addition & 1 deletion Analyzers.ruleset
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<RuleSet Name="Rules for NSubstitute.Analyzers" Description="Code analysis rules for NSubstitute.Analyzers" ToolsVersion="14.0">
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.Analyzers">
<Rule Id="SA0000" Action="Error" />
<Rule Id="SA1305" Action="Error" />
<Rule Id="SA1305" Action="None" />
<Rule Id="SA1609" Action="Error" />
<Rule Id="SA1639" Action="Error" />
<Rule Id="SX1309" Action="Error" />
Expand Down
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
<AdditionalFiles Include="../../StyleCop.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" PrivateAssets="all" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.0-beta009" PrivateAssets="all" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<Import Sdk="Microsoft.NET.Sdk" Project="Sdk.props" />

<PropertyGroup>
<TargetFramework>net461</TargetFramework>
<RootNamespace>NSubstitute.Analyzers.CSharp</RootNamespace>
<AssemblyName>NSubstitute.Analyzers.CSharp.Vsix</AssemblyName>
</PropertyGroup>

<PropertyGroup>
<GeneratePkgDefFile>false</GeneratePkgDefFile>
<IncludeAssemblyInVSIXContainer>false</IncludeAssemblyInVSIXContainer>
Expand All @@ -17,35 +15,26 @@
<CopyOutputSymbolsToOutputDirectory>false</CopyOutputSymbolsToOutputDirectory>
<VSSDKTargetPlatformRegRootSuffix>Roslyn</VSSDKTargetPlatformRegRootSuffix>
</PropertyGroup>

<PropertyGroup Condition="'$(BuildingInsideVisualStudio)' != 'true'">
<!-- This property disables extension deployment for command line builds; required for AppVeyor and the build script -->
<DeployExtension>False</DeployExtension>
</PropertyGroup>

<ItemGroup>
<DotNetCliToolReference Include="Microsoft.VSSDK.BuildTools" Version="15.1.192" />
<DotNetCliToolReference Include="Microsoft.VSSDK.BuildTools" Version="15.1.192" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\NSubstitute.Analyzers.CSharp\NSubstitute.Analyzers.CSharp.csproj" />
<ProjectReference Include="..\NSubstitute.Analyzers.Shared\NSubstitute.Analyzers.Shared.csproj" />
</ItemGroup>

<ItemGroup>
<None Include="source.extension.vsixmanifest" />
</ItemGroup>

<Import Sdk="Microsoft.NET.Sdk" Project="Sdk.targets" />

<Import Project="$(VSToolsPath)\VSSDK\Microsoft.VsSDK.targets" Condition="Exists('$(VSToolsPath)\VSSDK\Microsoft.VsSDK.targets')" />

<ItemGroup>
<!-- https://github.com/dotnet/sdk/issues/433 -->
<ProjectReference Update="@(ProjectReference)" AdditionalProperties="TargetFramework=netstandard1.1" />

<!-- https://github.com/Microsoft/extendvs/issues/57 -->
<ProjectReference Update="@(ProjectReference)" Name="%(Filename)" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using NSubstitute.Analyzers.CSharp.DiagnosticAnalyzers;
using NSubstitute.Analyzers.Shared.CodeFixProviders;
using NSubstitute.Analyzers.Shared.DiagnosticAnalyzers;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;

namespace NSubstitute.Analyzers.CSharp.CodeFixProviders
{
[ExportCodeFixProvider(LanguageNames.CSharp)]
internal class SubstituteForInternalMemberCodeFixProvider : AbstractSubstituteForInternalMemberCodeFixProvider<InvocationExpressionSyntax, ExpressionSyntax, CompilationUnitSyntax>
{
protected override AbstractSubstituteProxyAnalysis<InvocationExpressionSyntax, ExpressionSyntax> GetSubstituteProxyAnalysis()
{
return new SubstituteProxyAnalysis();
}

protected override CompilationUnitSyntax AppendInternalsVisibleToAttribute(CompilationUnitSyntax compilationUnitSyntax)
{
return compilationUnitSyntax.AddAttributeLists(
AttributeList(
AttributeTargetSpecifier(
Token(SyntaxKind.AssemblyKeyword)),
SingletonSeparatedList(
Attribute(
QualifiedName(
QualifiedName(
QualifiedName(
IdentifierName("System"),
IdentifierName("Runtime")),
IdentifierName("CompilerServices")),
IdentifierName("InternalsVisibleTo")),
AttributeArgumentList(
SingletonSeparatedList(
AttributeArgument(
LiteralExpression(
SyntaxKind.StringLiteralExpression,
Literal("DynamicProxyGenAssembly2")))))))));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using NSubstitute.Analyzers.Shared.DiagnosticAnalyzers;

namespace NSubstitute.Analyzers.Shared.CodeFixProviders
{
internal abstract class AbstractSubstituteForInternalMemberCodeFixProvider<TInvocationExpressionSyntax, TExpressionSyntax, TCompilationUnitSyntax> : AbstractSuppressDiagnosticsCodeFixProvider
where TInvocationExpressionSyntax : SyntaxNode
where TExpressionSyntax : SyntaxNode
where TCompilationUnitSyntax : SyntaxNode
{
public override ImmutableArray<string> FixableDiagnosticIds { get; } = ImmutableArray.Create(DiagnosticIdentifiers.SubstituteForInternalMember);

public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var diagnostic = context.Diagnostics.FirstOrDefault(diag => diag.Descriptor.Id == DiagnosticIdentifiers.SubstituteForInternalMember);
if (diagnostic == null)
{
return;
}

var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);

var findNode = root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true);
if (!(findNode is TInvocationExpressionSyntax invocationExpression))
{
return;
}

var syntaxReference = await GetDeclaringSyntaxReference(context, invocationExpression);

if (syntaxReference == null)
{
return;
}

var syntaxNode = await syntaxReference.GetSyntaxAsync();
var document = context.Document.Project.Solution.GetDocument(syntaxNode.SyntaxTree);
var compilationUnitSyntax = FindCompilationUnitSyntax(syntaxNode);

if (compilationUnitSyntax == null)
{
return;
}

var codeAction = CodeAction.Create("Add InternalsVisibleTo attribute", token => CreateChangedDocument(token, compilationUnitSyntax, document), nameof(AbstractSubstituteForInternalMemberCodeFixProvider<TInvocationExpressionSyntax, TExpressionSyntax, TCompilationUnitSyntax>));
context.RegisterCodeFix(codeAction, diagnostic);
}

protected abstract AbstractSubstituteProxyAnalysis<TInvocationExpressionSyntax, TExpressionSyntax> GetSubstituteProxyAnalysis();

protected abstract TCompilationUnitSyntax AppendInternalsVisibleToAttribute(TCompilationUnitSyntax compilationUnitSyntax);

private async Task<Document> CreateChangedDocument(CancellationToken cancellationToken, TCompilationUnitSyntax compilationUnitSyntax, Document document)
{
var updatedCompilationUnitSyntax = AppendInternalsVisibleToAttribute(compilationUnitSyntax);
var root = await document.GetSyntaxRootAsync(cancellationToken);
var replaceNode = root.ReplaceNode(compilationUnitSyntax, updatedCompilationUnitSyntax);
return document.WithSyntaxRoot(replaceNode);
}

private async Task<SyntaxReference> GetDeclaringSyntaxReference(CodeFixContext context, TInvocationExpressionSyntax invocationExpression)
{
var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken);
var methodSymbol = semanticModel.GetSymbolInfo(invocationExpression).Symbol as IMethodSymbol;
var proxyAnalysis = GetSubstituteProxyAnalysis();
var actualProxyTypeSymbol = proxyAnalysis.GetActualProxyTypeSymbol(semanticModel, invocationExpression, methodSymbol);
var syntaxReference = actualProxyTypeSymbol.DeclaringSyntaxReferences.FirstOrDefault();
return syntaxReference;
}

private TCompilationUnitSyntax FindCompilationUnitSyntax(SyntaxNode syntaxNode)
{
return syntaxNode.Parent.Ancestors().OfType<TCompilationUnitSyntax>().LastOrDefault();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,31 @@ internal abstract class AbstractSubstituteProxyAnalysis<TInvocationExpressionSyn
{
public ITypeSymbol GetActualProxyTypeSymbol(SubstituteContext<TInvocationExpressionSyntax> substituteContext)
{
var proxies = GetProxySymbols(substituteContext).ToList();
return GetActualProxyTypeSymbol(substituteContext.SyntaxNodeAnalysisContext.SemanticModel, substituteContext.InvocationExpression, substituteContext.MethodSymbol);
}

public ImmutableArray<ITypeSymbol> GetProxySymbols(SubstituteContext<TInvocationExpressionSyntax> substituteContext)
{
return GetProxySymbols(substituteContext.SyntaxNodeAnalysisContext.SemanticModel, substituteContext.InvocationExpression, substituteContext.MethodSymbol);
}

public ITypeSymbol GetActualProxyTypeSymbol(SemanticModel semanticModel, TInvocationExpressionSyntax invocationExpressionSyntax, IMethodSymbol methodSymbol)
{
var proxies = GetProxySymbols(semanticModel, invocationExpressionSyntax, methodSymbol).ToList();

var classSymbol = proxies.FirstOrDefault(symbol => symbol.TypeKind == TypeKind.Class);

return classSymbol ?? proxies.FirstOrDefault();
}

public ImmutableArray<ITypeSymbol> GetProxySymbols(SubstituteContext<TInvocationExpressionSyntax> substituteContext)
public ImmutableArray<ITypeSymbol> GetProxySymbols(SemanticModel semanticModel, TInvocationExpressionSyntax invocationExpressionSyntax, IMethodSymbol methodSymbol)
{
if (substituteContext.MethodSymbol.IsGenericMethod)
if (methodSymbol.IsGenericMethod)
{
return substituteContext.MethodSymbol.TypeArguments;
return methodSymbol.TypeArguments;
}

var arrayParameters = GetArrayInitializerArguments(substituteContext.InvocationExpression)?.ToList();
var arrayParameters = GetArrayInitializerArguments(invocationExpressionSyntax)?.ToList();

if (arrayParameters == null)
{
Expand All @@ -34,7 +44,7 @@ public ImmutableArray<ITypeSymbol> GetProxySymbols(SubstituteContext<TInvocation

var proxyTypes = GetTypeOfLikeExpressions(arrayParameters)
.Select(exp =>
substituteContext.SyntaxNodeAnalysisContext.SemanticModel
semanticModel
.GetTypeInfo(exp.DescendantNodes().First()))
.Where(model => model.Type != null)
.Select(model => model.Type)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<Import Sdk="Microsoft.NET.Sdk" Project="Sdk.props" />

<PropertyGroup>
<TargetFramework>net461</TargetFramework>
<RootNamespace>NSubstitute.Analyzers.VisualBasic</RootNamespace>
<AssemblyName>NSubstitute.Analyzers.VisualBasic.Vsix</AssemblyName>
</PropertyGroup>

<PropertyGroup>
<GeneratePkgDefFile>false</GeneratePkgDefFile>
<IncludeAssemblyInVSIXContainer>false</IncludeAssemblyInVSIXContainer>
Expand All @@ -17,35 +15,26 @@
<CopyOutputSymbolsToOutputDirectory>false</CopyOutputSymbolsToOutputDirectory>
<VSSDKTargetPlatformRegRootSuffix>Roslyn</VSSDKTargetPlatformRegRootSuffix>
</PropertyGroup>

<PropertyGroup Condition="'$(BuildingInsideVisualStudio)' != 'true'">
<!-- This property disables extension deployment for command line builds; required for AppVeyor and the build script -->
<DeployExtension>False</DeployExtension>
</PropertyGroup>

<ItemGroup>
<DotNetCliToolReference Include="Microsoft.VSSDK.BuildTools" Version="15.1.192" />
<DotNetCliToolReference Include="Microsoft.VSSDK.BuildTools" Version="15.1.192" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\NSubstitute.Analyzers.VisualBasic\NSubstitute.Analyzers.VisualBasic.csproj" />
<ProjectReference Include="..\NSubstitute.Analyzers.Shared\NSubstitute.Analyzers.Shared.csproj" />
</ItemGroup>

<ItemGroup>
<None Include="source.extension.vsixmanifest" />
</ItemGroup>

<Import Sdk="Microsoft.NET.Sdk" Project="Sdk.targets" />

<Import Project="$(VSToolsPath)\VSSDK\Microsoft.VsSDK.targets" Condition="Exists('$(VSToolsPath)\VSSDK\Microsoft.VsSDK.targets')" />

<ItemGroup>
<!-- https://github.com/dotnet/sdk/issues/433 -->
<ProjectReference Update="@(ProjectReference)" AdditionalProperties="TargetFramework=netstandard1.1" />

<!-- https://github.com/Microsoft/extendvs/issues/57 -->
<ProjectReference Update="@(ProjectReference)" Name="%(Filename)" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.VisualBasic;
using Microsoft.CodeAnalysis.VisualBasic.Syntax;
using NSubstitute.Analyzers.Shared.CodeFixProviders;
using NSubstitute.Analyzers.Shared.DiagnosticAnalyzers;
using NSubstitute.Analyzers.VisualBasic.DiagnosticAnalyzers;
using static Microsoft.CodeAnalysis.VisualBasic.SyntaxFactory;

namespace NSubstitute.Analyzers.VisualBasic.CodeFixProviders
{
[ExportCodeFixProvider(LanguageNames.VisualBasic)]
internal class SubstituteForInternalMemberCodeFixProvider : AbstractSubstituteForInternalMemberCodeFixProvider<InvocationExpressionSyntax, ExpressionSyntax, CompilationUnitSyntax>
{
protected override AbstractSubstituteProxyAnalysis<InvocationExpressionSyntax, ExpressionSyntax> GetSubstituteProxyAnalysis()
{
return new SubstituteProxyAnalysis();
}

protected override CompilationUnitSyntax AppendInternalsVisibleToAttribute(CompilationUnitSyntax compilationUnitSyntax)
{
return compilationUnitSyntax.AddAttributes(
AttributesStatement(SingletonList(
AttributeList(SingletonSeparatedList(Attribute(
AttributeTarget(Token(SyntaxKind.AssemblyKeyword)),
QualifiedName(
QualifiedName(
QualifiedName(
IdentifierName("System"),
IdentifierName("Runtime")),
IdentifierName("CompilerServices")),
IdentifierName("InternalsVisibleTo")),
ArgumentList(SingletonSeparatedList<ArgumentSyntax>(SimpleArgument(
LiteralExpression(
SyntaxKind.StringLiteralExpression,
Literal("DynamicProxyGenAssembly2")))))))))));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,16 @@ private IEnumerable<SyntaxNode> GetExpressionsForAnalysys(SyntaxNodeAnalysisCont
}

break;
case UnaryExpressionSyntax unaryExpressionSyntax:
foreach (var syntaxNode in GetExpressionsForAnalysys(syntaxNodeContext, unaryExpressionSyntax.Operand))
case UnaryExpressionSyntax unaryExpressionSyntax:
foreach (var syntaxNode in GetExpressionsForAnalysys(syntaxNodeContext, unaryExpressionSyntax.Operand))
{
yield return syntaxNode;
}

break;
case IdentifierNameSyntax identifierNameSyntax:
var symbol = syntaxNodeContext.SemanticModel.GetSymbolInfo(identifierNameSyntax);
if (symbol.Symbol != null && symbol.Symbol.Locations.Any())
break;
case IdentifierNameSyntax identifierNameSyntax:
var symbol = syntaxNodeContext.SemanticModel.GetSymbolInfo(identifierNameSyntax);
if (symbol.Symbol != null && symbol.Symbol.Locations.Any())
{
var location = symbol.Symbol.Locations.First();
var syntaxNode = location.SourceTree.GetRoot().FindNode(location.SourceSpan);
Expand All @@ -77,7 +77,7 @@ private IEnumerable<SyntaxNode> GetExpressionsForAnalysys(SyntaxNodeAnalysisCont
}
}

break;
break;
}

if (body == null)
Expand Down
Loading

0 comments on commit 4f463d3

Please sign in to comment.