-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
Showing
34 changed files
with
5,278 additions
and
49 deletions.
There are no files selected for viewing
30 changes: 30 additions & 0 deletions
30
...Substitute.Analyzers.CSharp/CodeFixProviders/InternalSetupSpecificationCodeFixProvider.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
50 changes: 50 additions & 0 deletions
50
src/NSubstitute.Analyzers.CSharp/Refactorings/AddInternalsVisibleToAttributeRefactoring.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} |
51 changes: 51 additions & 0 deletions
51
src/NSubstitute.Analyzers.CSharp/Refactorings/AddModifierRefactoring.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | ||
} | ||
} | ||
} |
63 changes: 63 additions & 0 deletions
63
src/NSubstitute.Analyzers.CSharp/Refactorings/ReplaceModifierRefactoring.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | ||
} | ||
} | ||
} |
77 changes: 77 additions & 0 deletions
77
...te.Analyzers.Shared/CodeFixProviders/AbstractInternalSetupSpecificationCodeFixProvider.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
22 changes: 22 additions & 0 deletions
22
src/NSubstitute.Analyzers.Shared/Extensions/DocumentExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.