From 2ae2709e67cabc09d56a6fbeb5e21ab494e1e5d7 Mon Sep 17 00:00:00 2001 From: tpodolak Date: Sun, 11 Oct 2020 18:33:56 +0200 Subject: [PATCH] GH-153 - simplifing PartialSubstituteUsedForUnsupportedTypeCodeFixProvider --- ...teUsedForUnsupportedTypeCodeFixProvider.cs | 17 ++-- ...teUsedForUnsupportedTypeCodeFixProvider.cs | 85 +++++++++---------- ...stituteForInternalMemberCodeFixProvider.cs | 5 +- ...teUsedForUnsupportedTypeCodeFixProvider.cs | 17 ++-- 4 files changed, 59 insertions(+), 65 deletions(-) diff --git a/src/NSubstitute.Analyzers.CSharp/CodeFixProviders/PartialSubstituteUsedForUnsupportedTypeCodeFixProvider.cs b/src/NSubstitute.Analyzers.CSharp/CodeFixProviders/PartialSubstituteUsedForUnsupportedTypeCodeFixProvider.cs index 91e86ae4..b3dc859a 100644 --- a/src/NSubstitute.Analyzers.CSharp/CodeFixProviders/PartialSubstituteUsedForUnsupportedTypeCodeFixProvider.cs +++ b/src/NSubstitute.Analyzers.CSharp/CodeFixProviders/PartialSubstituteUsedForUnsupportedTypeCodeFixProvider.cs @@ -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 +internal sealed class PartialSubstituteUsedForUnsupportedTypeCodeFixProvider : AbstractPartialSubstituteUsedForUnsupportedTypeCodeFixProvider { - protected override TInnerNameSyntax GetNameSyntax(InvocationExpressionSyntax methodInvocationNode) + protected override SyntaxNode UpdateInvocationExpression(IInvocationOperation invocationOperation, string identifierName) { - var memberAccess = (MemberAccessExpressionSyntax)methodInvocationNode.Expression; - return (TInnerNameSyntax)memberAccess.Name; - } - - protected override TInnerNameSyntax GetUpdatedNameSyntax(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)))); } } \ No newline at end of file diff --git a/src/NSubstitute.Analyzers.Shared/CodeFixProviders/AbstractPartialSubstituteUsedForUnsupportedTypeCodeFixProvider.cs b/src/NSubstitute.Analyzers.Shared/CodeFixProviders/AbstractPartialSubstituteUsedForUnsupportedTypeCodeFixProvider.cs index a49db4e4..66b50d54 100644 --- a/src/NSubstitute.Analyzers.Shared/CodeFixProviders/AbstractPartialSubstituteUsedForUnsupportedTypeCodeFixProvider.cs +++ b/src/NSubstitute.Analyzers.Shared/CodeFixProviders/AbstractPartialSubstituteUsedForUnsupportedTypeCodeFixProvider.cs @@ -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 - : 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 FixableDiagnosticIds { get; } = ImmutableArray.Create(DiagnosticIdentifiers.PartialSubstituteForUnsupportedType); + public override ImmutableArray 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)); - context.RegisterCodeFix(codeAction, diagnostic); + if (semanticModel.GetOperation(syntaxNode) is not IInvocationOperation invocationOperation) + { + return; } - } - protected abstract TInnerNameSyntax GetNameSyntax(TInvocationExpression methodInvocationNode) where TInnerNameSyntax : TNameSyntax; + var title = invocationOperation.TargetMethod.Name == MetadataNames.SubstituteFactoryCreatePartial + ? "Use SubstituteFactory.Create" + : "Use Substitute.For"; - protected abstract TInnerNameSyntax GetUpdatedNameSyntax(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 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(invocationExpression); - nameNode = genericNameSyntax; - updateNameNode = GetUpdatedNameSyntax(genericNameSyntax, MetadataNames.NSubstituteForMethod); - } - else - { - var identifierNameSyntax = GetNameSyntax(invocationExpression); - nameNode = identifierNameSyntax; - updateNameNode = GetUpdatedNameSyntax(identifierNameSyntax, MetadataNames.SubstituteFactoryCreate); - } + private async Task 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(); } } \ No newline at end of file diff --git a/src/NSubstitute.Analyzers.Shared/CodeFixProviders/AbstractSubstituteForInternalMemberCodeFixProvider.cs b/src/NSubstitute.Analyzers.Shared/CodeFixProviders/AbstractSubstituteForInternalMemberCodeFixProvider.cs index eab32bd9..ba865025 100644 --- a/src/NSubstitute.Analyzers.Shared/CodeFixProviders/AbstractSubstituteForInternalMemberCodeFixProvider.cs +++ b/src/NSubstitute.Analyzers.Shared/CodeFixProviders/AbstractSubstituteForInternalMemberCodeFixProvider.cs @@ -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; } @@ -66,6 +65,6 @@ private SyntaxReference GetDeclaringSyntaxReference(IInvocationOperation invocat private TCompilationUnitSyntax FindCompilationUnitSyntax(SyntaxNode syntaxNode) { - return syntaxNode.Parent.Ancestors().OfType().LastOrDefault(); + return syntaxNode.Ancestors().OfType().LastOrDefault(); } } \ No newline at end of file diff --git a/src/NSubstitute.Analyzers.VisualBasic/CodeFixProviders/PartialSubstituteUsedForUnsupportedTypeCodeFixProvider.cs b/src/NSubstitute.Analyzers.VisualBasic/CodeFixProviders/PartialSubstituteUsedForUnsupportedTypeCodeFixProvider.cs index 5159c2b8..43325d9b 100644 --- a/src/NSubstitute.Analyzers.VisualBasic/CodeFixProviders/PartialSubstituteUsedForUnsupportedTypeCodeFixProvider.cs +++ b/src/NSubstitute.Analyzers.VisualBasic/CodeFixProviders/PartialSubstituteUsedForUnsupportedTypeCodeFixProvider.cs @@ -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; @@ -8,16 +9,14 @@ namespace NSubstitute.Analyzers.VisualBasic.CodeFixProviders; [ExportCodeFixProvider(LanguageNames.VisualBasic)] -internal sealed class PartialSubstituteUsedForUnsupportedTypeCodeFixProvider : AbstractPartialSubstituteUsedForUnsupportedTypeCodeFixProvider +internal sealed class PartialSubstituteUsedForUnsupportedTypeCodeFixProvider : AbstractPartialSubstituteUsedForUnsupportedTypeCodeFixProvider { - protected override TInnerNameSyntax GetNameSyntax(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 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)))); } } \ No newline at end of file