Skip to content

Commit

Permalink
GH-153 - simplifing PartialSubstituteUsedForUnsupportedTypeCodeFixPro…
Browse files Browse the repository at this point in the history
…vider
  • Loading branch information
tpodolak committed Oct 2, 2022
1 parent 464e318 commit 2ae2709
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,21 @@
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Operations;
using NSubstitute.Analyzers.Shared.CodeFixProviders;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;

namespace NSubstitute.Analyzers.CSharp.CodeFixProviders;

[ExportCodeFixProvider(LanguageNames.CSharp)]
internal sealed class PartialSubstituteUsedForUnsupportedTypeCodeFixProvider : AbstractPartialSubstituteUsedForUnsupportedTypeCodeFixProvider<InvocationExpressionSyntax, GenericNameSyntax, IdentifierNameSyntax, SimpleNameSyntax>
internal sealed class PartialSubstituteUsedForUnsupportedTypeCodeFixProvider : AbstractPartialSubstituteUsedForUnsupportedTypeCodeFixProvider
{
protected override TInnerNameSyntax GetNameSyntax<TInnerNameSyntax>(InvocationExpressionSyntax methodInvocationNode)
protected override SyntaxNode UpdateInvocationExpression(IInvocationOperation invocationOperation, string identifierName)
{
var memberAccess = (MemberAccessExpressionSyntax)methodInvocationNode.Expression;
return (TInnerNameSyntax)memberAccess.Name;
}

protected override TInnerNameSyntax GetUpdatedNameSyntax<TInnerNameSyntax>(TInnerNameSyntax nameSyntax, string identifierName)
{
return (TInnerNameSyntax)nameSyntax.WithIdentifier(IdentifierName(identifierName).Identifier);
var invocationExpression = (InvocationExpressionSyntax)invocationOperation.Syntax;
var memberAccessExpression = (MemberAccessExpressionSyntax)invocationExpression.Expression;
return invocationExpression.WithExpression(
memberAccessExpression.WithName(
memberAccessExpression.Name.WithIdentifier(Identifier(identifierName))));
}
}
Original file line number Diff line number Diff line change
@@ -1,72 +1,69 @@
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 Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Operations;

namespace NSubstitute.Analyzers.Shared.CodeFixProviders;

internal abstract class AbstractPartialSubstituteUsedForUnsupportedTypeCodeFixProvider<TInvocationExpression, TGenericNameSyntax, TIdentifierNameSyntax, TNameSyntax>
: CodeFixProvider
where TInvocationExpression : SyntaxNode
where TGenericNameSyntax : TNameSyntax
where TIdentifierNameSyntax : TNameSyntax
where TNameSyntax : SyntaxNode
internal abstract class AbstractPartialSubstituteUsedForUnsupportedTypeCodeFixProvider : CodeFixProvider
{
public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;

public override ImmutableArray<string> FixableDiagnosticIds { get; } = ImmutableArray.Create(DiagnosticIdentifiers.PartialSubstituteForUnsupportedType);
public override ImmutableArray<string> FixableDiagnosticIds { get; } =
ImmutableArray.Create(DiagnosticIdentifiers.PartialSubstituteForUnsupportedType);

public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var diagnostic = context.Diagnostics.FirstOrDefault(diag => diag.Descriptor.Id == DiagnosticIdentifiers.PartialSubstituteForUnsupportedType);
if (diagnostic != null)
var diagnostic = context.Diagnostics.FirstOrDefault(diag =>
diag.Descriptor.Id == DiagnosticIdentifiers.PartialSubstituteForUnsupportedType);
if (diagnostic == null)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
var invocationExpression = (TInvocationExpression)root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true);
var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken);
return;
}

if (semanticModel.GetSymbolInfo(invocationExpression).Symbol is not IMethodSymbol methodSymbol)
{
return;
}
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
var syntaxNode = root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true);
var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken);

var title = methodSymbol.Name == MetadataNames.SubstituteFactoryCreatePartial ? "Use SubstituteFactory.Create" : "Use Substitute.For";
var codeAction = CodeAction.Create(
title,
ct => CreateChangedDocument(context, root, methodSymbol, invocationExpression),
nameof(AbstractPartialSubstituteUsedForUnsupportedTypeCodeFixProvider<TInvocationExpression, TGenericNameSyntax, TIdentifierNameSyntax, TNameSyntax>));
context.RegisterCodeFix(codeAction, diagnostic);
if (semanticModel.GetOperation(syntaxNode) is not IInvocationOperation invocationOperation)
{
return;
}
}

protected abstract TInnerNameSyntax GetNameSyntax<TInnerNameSyntax>(TInvocationExpression methodInvocationNode) where TInnerNameSyntax : TNameSyntax;
var title = invocationOperation.TargetMethod.Name == MetadataNames.SubstituteFactoryCreatePartial
? "Use SubstituteFactory.Create"
: "Use Substitute.For";

protected abstract TInnerNameSyntax GetUpdatedNameSyntax<TInnerNameSyntax>(TInnerNameSyntax nameSyntax, string identifierName) where TInnerNameSyntax : TNameSyntax;
var codeAction = CodeAction.Create(
title,
ct => CreateChangedDocument(context, invocationOperation, ct),
nameof(AbstractPartialSubstituteUsedForUnsupportedTypeCodeFixProvider));
context.RegisterCodeFix(codeAction, diagnostic);
}

private Task<Document> CreateChangedDocument(CodeFixContext context, SyntaxNode root, IMethodSymbol methodSymbol, TInvocationExpression invocationExpression)
{
SyntaxNode nameNode;
SyntaxNode updateNameNode;
protected abstract SyntaxNode UpdateInvocationExpression(
IInvocationOperation invocationOperation,
string identifierName);

if (methodSymbol.IsGenericMethod)
{
var genericNameSyntax = GetNameSyntax<TGenericNameSyntax>(invocationExpression);
nameNode = genericNameSyntax;
updateNameNode = GetUpdatedNameSyntax(genericNameSyntax, MetadataNames.NSubstituteForMethod);
}
else
{
var identifierNameSyntax = GetNameSyntax<TIdentifierNameSyntax>(invocationExpression);
nameNode = identifierNameSyntax;
updateNameNode = GetUpdatedNameSyntax(identifierNameSyntax, MetadataNames.SubstituteFactoryCreate);
}
private async Task<Document> CreateChangedDocument(
CodeFixContext context,
IInvocationOperation invocationOperation,
CancellationToken cancellationToken)
{
var documentEditor = await DocumentEditor.CreateAsync(context.Document, cancellationToken);
var newIdentifierName = invocationOperation.TargetMethod.IsGenericMethod
? MetadataNames.NSubstituteForMethod
: MetadataNames.SubstituteFactoryCreate;

var forNode = invocationExpression.ReplaceNode(nameNode, updateNameNode);
var updatedInvocationExpression = UpdateInvocationExpression(invocationOperation, newIdentifierName);

var replaceNode = root.ReplaceNode(invocationExpression, forNode);
documentEditor.ReplaceNode(invocationOperation.Syntax, updatedInvocationExpression);

return Task.FromResult(context.Document.WithSyntaxRoot(replaceNode));
return documentEditor.GetChangedDocument();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
var invocationExpression = root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true);
var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken);

if (invocationExpression is not { } ||
semanticModel.GetOperation(invocationExpression) is not IInvocationOperation invocationOperation)
if (semanticModel.GetOperation(invocationExpression) is not IInvocationOperation invocationOperation)
{
return;
}
Expand Down Expand Up @@ -66,6 +65,6 @@ private SyntaxReference GetDeclaringSyntaxReference(IInvocationOperation invocat

private TCompilationUnitSyntax FindCompilationUnitSyntax(SyntaxNode syntaxNode)
{
return syntaxNode.Parent.Ancestors().OfType<TCompilationUnitSyntax>().LastOrDefault();
return syntaxNode.Ancestors().OfType<TCompilationUnitSyntax>().LastOrDefault();
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Operations;
using Microsoft.CodeAnalysis.VisualBasic;
using Microsoft.CodeAnalysis.VisualBasic.Syntax;
using NSubstitute.Analyzers.Shared.CodeFixProviders;
Expand All @@ -8,16 +9,14 @@
namespace NSubstitute.Analyzers.VisualBasic.CodeFixProviders;

[ExportCodeFixProvider(LanguageNames.VisualBasic)]
internal sealed class PartialSubstituteUsedForUnsupportedTypeCodeFixProvider : AbstractPartialSubstituteUsedForUnsupportedTypeCodeFixProvider<InvocationExpressionSyntax, GenericNameSyntax, IdentifierNameSyntax, SimpleNameSyntax>
internal sealed class PartialSubstituteUsedForUnsupportedTypeCodeFixProvider : AbstractPartialSubstituteUsedForUnsupportedTypeCodeFixProvider
{
protected override TInnerNameSyntax GetNameSyntax<TInnerNameSyntax>(InvocationExpressionSyntax methodInvocationNode)
protected override SyntaxNode UpdateInvocationExpression(IInvocationOperation invocationOperation, string identifierName)
{
var memberAccess = (MemberAccessExpressionSyntax)methodInvocationNode.Expression;
return memberAccess.Name as TInnerNameSyntax;
}

protected override TInnerNameSyntax GetUpdatedNameSyntax<TInnerNameSyntax>(TInnerNameSyntax nameSyntax, string identifierName)
{
return (TInnerNameSyntax)nameSyntax.WithIdentifier(IdentifierName(identifierName).Identifier);
var invocationExpression = (InvocationExpressionSyntax)invocationOperation.Syntax;
var memberAccessExpression = (MemberAccessExpressionSyntax)invocationExpression.Expression;
return invocationExpression.WithExpression(
memberAccessExpression.WithName(
memberAccessExpression.Name.WithIdentifier(Identifier(identifierName))));
}
}

0 comments on commit 2ae2709

Please sign in to comment.