Skip to content

Commit

Permalink
[GH-38] - CSharp implementation for code fix provider for exposing
Browse files Browse the repository at this point in the history
internal type to proxy
  • Loading branch information
tpodolak committed Sep 29, 2018
1 parent a30dcc5 commit 25ba3b3
Show file tree
Hide file tree
Showing 4 changed files with 203 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using Microsoft.CodeAnalysis;
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
{
internal class SubstituteForInternalMemberCodeFixProvider : AbstractSubstituteForInternalMemberCodeFixProvider<InvocationExpressionSyntax, ExpressionSyntax, CompilationUnitSyntax>
{
protected override AbstractSubstituteProxyAnalysis<InvocationExpressionSyntax, ExpressionSyntax> GetSubstituteProxyAnalysis()
{
return new SubstituteProxyAnalysis();
}

protected override ICompilationUnitSyntax Annotate(CompilationUnitSyntax node)
{
var classDeclarationSyntax = node as ClassDeclarationSyntax;
var compilationUnitSyntax = classDeclarationSyntax.Parent.Parent as CompilationUnitSyntax;

return compilationUnitSyntax.WithAttributeLists(
SingletonList(
AttributeList(
SingletonSeparatedList(
Attribute(
IdentifierName("InternalsVisibleTo"))
.WithArgumentList(
AttributeArgumentList(
SingletonSeparatedList(
AttributeArgument(
LiteralExpression(
SyntaxKind.StringLiteralExpression,
Literal("DynamicProxyGenAssembly2"))))))))
.WithTrailingTrivia(CarriageReturn)
.WithTarget(
AttributeTargetSpecifier(
Token(SyntaxKind.AssemblyKeyword)))));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
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);

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

var syntaxReference = await GetDeclaringSyntaxReference(context, invocationExpression);

if (syntaxReference == null)
{
return;
}

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

if (compilationUnitSyntax == null)
{
return;
}

var codeAction = CodeAction.Create("my name", 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 Task<Document> CreateChangedDocument(CancellationToken cancellationToken, TCompilationUnitSyntax compilationUnitSyntax, Document document)
{
var withLeadingTrivia = AppendInternalsVisibleToAttribute(compilationUnitSyntax);
return Task.FromResult(document.WithSyntaxRoot(withLeadingTrivia.SyntaxTree.GetRoot()));
}

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)
{
var parent = syntaxNode.Parent;
while (parent != null && !(parent is TCompilationUnitSyntax))
{
parent = parent.Parent;
}

return parent as TCompilationUnitSyntax;
}
}
}
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
@@ -0,0 +1,59 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Diagnostics;
using NSubstitute.Analyzers.CSharp.CodeFixProviders;
using NSubstitute.Analyzers.CSharp.DiagnosticAnalyzers;
using Xunit;

namespace NSubstitute.Analyzers.Tests.CSharp.CodeFixProviderTests.SubstituteForInternalMemberCodeFixProviderTests
{
public class SubstituteForInternalMemberCodeFixProviderTests : CSharpCodeFixVerifier
{
[Fact]
public async Task Foo()
{
var oldSource = @"using NSubstitute;
namespace MyNamespace
{
internal class Foo
{
}
public class FooTests
{
public void Test()
{
var substitute = NSubstitute.Substitute.For(new [] { typeof(Foo) }, null);
}
}
}";
var newSource = @"using NSubstitute;
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo(""DynamicProxyGenAssembly2"")]
namespace MyNamespace
{
internal class Foo
{
}
public class FooTests
{
public void Test()
{
var substitute = NSubstitute.Substitute.For(new [] { typeof(Foo) }, null);
}
}
}";
await VerifyFix(oldSource, newSource);
}

protected override DiagnosticAnalyzer GetDiagnosticAnalyzer()
{
return new SubstituteAnalyzer();
}

protected override CodeFixProvider GetCodeFixProvider()
{
return new SubstituteForInternalMemberCodeFixProvider();
}
}
}

0 comments on commit 25ba3b3

Please sign in to comment.