Skip to content

Commit

Permalink
Merge pull request #75 from nsubstitute/GH-70-fix-providers-for-inter…
Browse files Browse the repository at this point in the history
…nal-member-substitution

Gh 70 fix providers for internal member substitution
  • Loading branch information
tpodolak authored Mar 1, 2019
2 parents d388035 + 4141bcc commit 31970dd
Show file tree
Hide file tree
Showing 34 changed files with 5,278 additions and 49 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using NSubstitute.Analyzers.CSharp.Refactorings;
using NSubstitute.Analyzers.Shared.CodeFixProviders;

namespace NSubstitute.Analyzers.CSharp.CodeFixProviders
{
[ExportCodeFixProvider(LanguageNames.CSharp)]
internal class InternalSetupSpecificationCodeFixProvider : AbstractInternalSetupSpecificationCodeFixProvider<CompilationUnitSyntax>
{
protected override string ReplaceModifierCodeFixTitle { get; } = "Replace internal with public modifier";

protected override Task<Document> AddModifierRefactoring(Document document, SyntaxNode node, Accessibility accessibility)
{
return Refactorings.AddModifierRefactoring.RefactorAsync(document, node, accessibility);
}

protected override Task<Document> ReplaceModifierRefactoring(Document document, SyntaxNode node, Accessibility fromAccessibility, Accessibility toAccessibility)
{
return Refactorings.ReplaceModifierRefactoring.RefactorAsync(document, node, fromAccessibility, toAccessibility);
}

protected override void RegisterAddInternalsVisibleToAttributeCodeFix(CodeFixContext context, Diagnostic diagnostic, CompilationUnitSyntax compilationUnitSyntax)
{
AddInternalsVisibleToAttributeRefactoring.RegisterCodeFix(context, diagnostic, compilationUnitSyntax);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using NSubstitute.Analyzers.CSharp.DiagnosticAnalyzers;
using NSubstitute.Analyzers.CSharp.Refactorings;
using NSubstitute.Analyzers.Shared.CodeFixProviders;
using NSubstitute.Analyzers.Shared.DiagnosticAnalyzers;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
Expand All @@ -17,27 +18,9 @@ protected override AbstractSubstituteProxyAnalysis<InvocationExpressionSyntax, E
return new SubstituteProxyAnalysis();
}

protected override CompilationUnitSyntax AppendInternalsVisibleToAttribute(CompilationUnitSyntax compilationUnitSyntax)
protected override void RegisterCodeFix(CodeFixContext context, Diagnostic diagnostic, 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")))))))));
AddInternalsVisibleToAttributeRefactoring.RegisterCodeFix(context, diagnostic, compilationUnitSyntax);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
<AppDesignerFolder>Properties</AppDesignerFolder>
<Version>$(VersionSuffix)</Version>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup>
<PackageId>NSubstitute.Analyzers.CSharp</PackageId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using NSubstitute.Analyzers.Shared.Extensions;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;

namespace NSubstitute.Analyzers.CSharp.Refactorings
{
internal static class AddInternalsVisibleToAttributeRefactoring
{
public static Task<Document> RefactorAsync(Document document, CompilationUnitSyntax compilationUnitSyntax, CancellationToken cancellationToken = default)
{
var addAttributeLists = 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")))))))));

return document.ReplaceNodeAsync(compilationUnitSyntax, addAttributeLists, CancellationToken.None);
}

public static void RegisterCodeFix(CodeFixContext context, Diagnostic diagnostic, CompilationUnitSyntax compilationUnitSyntax)
{
var codeAction = CodeAction.Create(
"Add InternalsVisibleTo attribute",
cancellationToken => RefactorAsync(context.Document, compilationUnitSyntax, cancellationToken),
diagnostic.Id);

context.RegisterCodeFix(codeAction, context.Diagnostics);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using NSubstitute.Analyzers.Shared.Extensions;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;

namespace NSubstitute.Analyzers.CSharp.Refactorings
{
internal static class AddModifierRefactoring
{
public static Task<Document> RefactorAsync(Document document, SyntaxNode node, Accessibility accessibility)
{
SyntaxKind syntaxKind;

switch (accessibility)
{
case Accessibility.Protected:
syntaxKind = SyntaxKind.ProtectedKeyword;
break;
default:
throw new NotSupportedException($"Adding {accessibility} modifier is not supported");
}

var newNode = Insert(node, syntaxKind);

return document.ReplaceNodeAsync(node, newNode);
}

private static SyntaxNode Insert(SyntaxNode node, SyntaxKind syntaxKind)
{
switch (node)
{
case MethodDeclarationSyntax methodDeclarationSyntax:
return methodDeclarationSyntax.WithModifiers(UpdateModifiers(methodDeclarationSyntax.Modifiers, syntaxKind));
case PropertyDeclarationSyntax propertyDeclarationSyntax:
return propertyDeclarationSyntax.WithModifiers(UpdateModifiers(propertyDeclarationSyntax.Modifiers, syntaxKind));
case IndexerDeclarationSyntax indexerDeclarationSyntax:
return indexerDeclarationSyntax.WithModifiers(UpdateModifiers(indexerDeclarationSyntax.Modifiers, syntaxKind));
default:
throw new NotSupportedException($"Adding {syntaxKind} to {node.Kind()} is not supported");
}
}

private static SyntaxTokenList UpdateModifiers(SyntaxTokenList modifiers, SyntaxKind modifier)
{
return modifiers.Any(modifier) ? modifiers : modifiers.Insert(0, Token(modifier));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using NSubstitute.Analyzers.Shared.Extensions;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;

namespace NSubstitute.Analyzers.CSharp.Refactorings
{
internal class ReplaceModifierRefactoring
{
public static Task<Document> RefactorAsync(Document document, SyntaxNode node, Accessibility fromAccessibility, Accessibility toAccessibility)
{
var fromSyntaxKind = InferSyntaxKind(fromAccessibility);
var toSyntaxKind = InferSyntaxKind(toAccessibility);

var newNode = ReplaceModifier(node, fromSyntaxKind, toSyntaxKind);

return document.ReplaceNodeAsync(node, newNode);
}

private static SyntaxKind InferSyntaxKind(Accessibility fromAccessibility)
{
SyntaxKind syntaxKind;
switch (fromAccessibility)
{
case Accessibility.Internal:
syntaxKind = SyntaxKind.InternalKeyword;
break;
case Accessibility.Public:
syntaxKind = SyntaxKind.PublicKeyword;
break;
default:
throw new NotSupportedException($"Replacing {fromAccessibility} modifier is not supported");
}

return syntaxKind;
}

private static SyntaxNode ReplaceModifier(SyntaxNode node, SyntaxKind fromSyntaxKind, SyntaxKind toSyntaxKind)
{
switch (node)
{
case MethodDeclarationSyntax methodDeclarationSyntax:
return methodDeclarationSyntax.WithModifiers(ReplaceModifier(methodDeclarationSyntax.Modifiers, fromSyntaxKind, toSyntaxKind));
case PropertyDeclarationSyntax propertyDeclarationSyntax:
return propertyDeclarationSyntax.WithModifiers(ReplaceModifier(propertyDeclarationSyntax.Modifiers, fromSyntaxKind, toSyntaxKind));
case IndexerDeclarationSyntax indexerDeclarationSyntax:
return indexerDeclarationSyntax.WithModifiers(ReplaceModifier(indexerDeclarationSyntax.Modifiers, fromSyntaxKind, toSyntaxKind));
default:
throw new NotSupportedException($"Replacing {fromSyntaxKind} in {node.Kind()} is not supported");
}
}

private static SyntaxTokenList ReplaceModifier(SyntaxTokenList modifiers, SyntaxKind fromModifier, SyntaxKind toModifier)
{
var modifier = modifiers.First(mod => mod.IsKind(fromModifier));
return modifiers.Replace(modifier, Token(toModifier));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;

namespace NSubstitute.Analyzers.Shared.CodeFixProviders
{
internal abstract class AbstractInternalSetupSpecificationCodeFixProvider<TCompilationUnitSyntax> : CodeFixProvider
where TCompilationUnitSyntax : SyntaxNode
{
public override ImmutableArray<string> FixableDiagnosticIds { get; } = ImmutableArray.Create(DiagnosticIdentifiers.InternalSetupSpecification);

protected abstract string ReplaceModifierCodeFixTitle { get; }

public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var diagnostic =
context.Diagnostics.FirstOrDefault(diag => diag.Id == DiagnosticIdentifiers.InternalSetupSpecification);

if (diagnostic == null)
{
return;
}

var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
var invocationExpression = root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true);
var syntaxReference = await GetDeclaringSyntaxReference(context, invocationExpression);

if (syntaxReference == null)
{
return;
}

var syntaxNode = await syntaxReference.GetSyntaxAsync();

if (syntaxNode == null)
{
return;
}

context.RegisterCodeFix(CodeAction.Create("Add protected modifier", token => AddModifierRefactoring(context.Document, syntaxNode, Accessibility.Protected)), diagnostic);
context.RegisterCodeFix(CodeAction.Create(ReplaceModifierCodeFixTitle, token => ReplaceModifierRefactoring(context.Document, syntaxNode, Accessibility.Internal, Accessibility.Public)), diagnostic);

var compilationUnitSyntax = FindCompilationUnitSyntax(syntaxNode);

if (compilationUnitSyntax == null)
{
return;
}

RegisterAddInternalsVisibleToAttributeCodeFix(context, diagnostic, compilationUnitSyntax);
}

protected abstract Task<Document> AddModifierRefactoring(Document document, SyntaxNode node, Accessibility accessibility);

protected abstract Task<Document> ReplaceModifierRefactoring(Document document, SyntaxNode node, Accessibility fromAccessibility, Accessibility toAccessibility);

protected abstract void RegisterAddInternalsVisibleToAttributeCodeFix(CodeFixContext context, Diagnostic diagnostic, TCompilationUnitSyntax compilationUnitSyntax);

private async Task<SyntaxReference> GetDeclaringSyntaxReference(CodeFixContext context, SyntaxNode invocationExpression)
{
var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken);
var symbol = semanticModel.GetSymbolInfo(invocationExpression).Symbol;

var firstOrDefault = symbol.DeclaringSyntaxReferences.FirstOrDefault();

return firstOrDefault;
}

private TCompilationUnitSyntax FindCompilationUnitSyntax(SyntaxNode syntaxNode)
{
return syntaxNode.Parent.Ancestors().OfType<TCompilationUnitSyntax>().LastOrDefault();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,29 +40,19 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
}

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);
RegisterCodeFix(context, diagnostic, compilationUnitSyntax);
}

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);
}
protected abstract void RegisterCodeFix(CodeFixContext context, Diagnostic diagnostic, TCompilationUnitSyntax compilationUnitSyntax);

private async Task<SyntaxReference> GetDeclaringSyntaxReference(CodeFixContext context, TInvocationExpressionSyntax invocationExpression)
{
Expand Down
22 changes: 22 additions & 0 deletions src/NSubstitute.Analyzers.Shared/Extensions/DocumentExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;

namespace NSubstitute.Analyzers.Shared.Extensions
{
internal static class DocumentExtensions
{
public static async Task<Document> ReplaceNodeAsync(
this Document document,
SyntaxNode oldNode,
SyntaxNode newNode,
CancellationToken cancellationToken = default)
{
var root = await document.GetSyntaxRootAsync(cancellationToken);

var newRoot = root.ReplaceNode(oldNode, newNode);

return document.WithSyntaxRoot(newRoot);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<GenerateAssemblyInfo>True</GenerateAssemblyInfo>
<IsPackable>false</IsPackable>
<Version>$(VersionSuffix)</Version>
<LangVersion>7.2</LangVersion>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Common" Version="1.3.2" PrivateAssets="all" />
Expand Down
Loading

0 comments on commit 31970dd

Please sign in to comment.