diff --git a/src/NSubstitute.Analyzers.CSharp/CodeFixProviders/PartialSubstituteUsedForUnsupportedTypeCodeFixProvider.cs b/src/NSubstitute.Analyzers.CSharp/CodeFixProviders/PartialSubstituteUsedForUnsupportedTypeCodeFixProvider.cs index f658e192..f808392e 100644 --- a/src/NSubstitute.Analyzers.CSharp/CodeFixProviders/PartialSubstituteUsedForUnsupportedTypeCodeFixProvider.cs +++ b/src/NSubstitute.Analyzers.CSharp/CodeFixProviders/PartialSubstituteUsedForUnsupportedTypeCodeFixProvider.cs @@ -8,17 +8,17 @@ 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 SimpleNameSyntax GetNameSyntax(InvocationExpressionSyntax methodInvocationNode) { var memberAccess = (MemberAccessExpressionSyntax)methodInvocationNode.Expression; - return (TInnerNameSyntax)memberAccess.Name; + return memberAccess.Name; } - protected override TInnerNameSyntax GetUpdatedNameSyntax(TInnerNameSyntax nameSyntax, string identifierName) + protected override SimpleNameSyntax GetUpdatedNameSyntax(SimpleNameSyntax nameSyntax, string identifierName) { - return (TInnerNameSyntax)nameSyntax.WithIdentifier(IdentifierName(identifierName).Identifier); + return nameSyntax.WithIdentifier(IdentifierName(identifierName).Identifier); } } } \ 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 cd3c7857..8cc6ec34 100644 --- a/src/NSubstitute.Analyzers.Shared/CodeFixProviders/AbstractPartialSubstituteUsedForUnsupportedTypeCodeFixProvider.cs +++ b/src/NSubstitute.Analyzers.Shared/CodeFixProviders/AbstractPartialSubstituteUsedForUnsupportedTypeCodeFixProvider.cs @@ -4,15 +4,14 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Editing; namespace NSubstitute.Analyzers.Shared.CodeFixProviders { - internal abstract class AbstractPartialSubstituteUsedForUnsupportedTypeCodeFixProvider + internal abstract class AbstractPartialSubstituteUsedForUnsupportedTypeCodeFixProvider : CodeFixProvider where TInvocationExpression : SyntaxNode - where TGenericNameSyntax : TNameSyntax - where TIdentifierNameSyntax : TNameSyntax - where TNameSyntax : SyntaxNode + where TSimpleNameSyntax : SyntaxNode { public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; @@ -21,53 +20,51 @@ internal abstract class AbstractPartialSubstituteUsedForUnsupportedTypeCodeFixPr public override async Task RegisterCodeFixesAsync(CodeFixContext context) { var diagnostic = context.Diagnostics.FirstOrDefault(diag => diag.Descriptor.Id == DiagnosticIdentifiers.PartialSubstituteForUnsupportedType); - if (diagnostic != null) + 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 IMethodSymbol methodSymbol)) - { - return; - } + 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); - 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.GetSymbolInfo(invocationExpression).Symbol is IMethodSymbol methodSymbol)) + { + return; } + + var title = methodSymbol.Name == MetadataNames.SubstituteFactoryCreatePartial + ? "Use SubstituteFactory.Create" + : "Use Substitute.For"; + + var codeAction = CodeAction.Create( + title, + ct => CreateChangedDocument(context, methodSymbol, invocationExpression), + nameof(AbstractPartialSubstituteUsedForUnsupportedTypeCodeFixProvider)); + + context.RegisterCodeFix(codeAction, diagnostic); } - protected abstract TInnerNameSyntax GetNameSyntax(TInvocationExpression methodInvocationNode) where TInnerNameSyntax : TNameSyntax; + protected abstract TSimpleNameSyntax GetNameSyntax(TInvocationExpression methodInvocationNode); - protected abstract TInnerNameSyntax GetUpdatedNameSyntax(TInnerNameSyntax nameSyntax, string identifierName) where TInnerNameSyntax : TNameSyntax; + protected abstract TSimpleNameSyntax GetUpdatedNameSyntax(TSimpleNameSyntax nameSyntax, string identifierName); - private Task CreateChangedDocument(CodeFixContext context, SyntaxNode root, IMethodSymbol methodSymbol, TInvocationExpression invocationExpression) + private async Task CreateChangedDocument(CodeFixContext context, IMethodSymbol methodSymbol, TInvocationExpression invocationExpression) { - SyntaxNode nameNode; - SyntaxNode updateNameNode; + var documentEditor = await DocumentEditor.CreateAsync(context.Document); + var newIdentifierName = methodSymbol.IsGenericMethod + ? MetadataNames.NSubstituteForMethod + : MetadataNames.SubstituteFactoryCreate; - 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); - } + var nameNode = GetNameSyntax(invocationExpression); + var updateNameNode = GetUpdatedNameSyntax(nameNode, newIdentifierName); - var forNode = invocationExpression.ReplaceNode(nameNode, updateNameNode); + var updatedInvocationExpression = invocationExpression.ReplaceNode(nameNode, updateNameNode); - var replaceNode = root.ReplaceNode(invocationExpression, forNode); + documentEditor.ReplaceNode(invocationExpression, updatedInvocationExpression); - return Task.FromResult(context.Document.WithSyntaxRoot(replaceNode)); + return documentEditor.GetChangedDocument(); } } } \ No newline at end of file diff --git a/src/NSubstitute.Analyzers.Shared/Extensions/IOperationExtensions.cs b/src/NSubstitute.Analyzers.Shared/Extensions/IOperationExtensions.cs index f8e5cf8e..c7c7d2d0 100644 --- a/src/NSubstitute.Analyzers.Shared/Extensions/IOperationExtensions.cs +++ b/src/NSubstitute.Analyzers.Shared/Extensions/IOperationExtensions.cs @@ -23,7 +23,7 @@ public static bool IsEventAssignmentOperation(this IOperation operation) public static IEnumerable GetOrderedArgumentOperationsWithoutInstanceArgument(this IInvocationOperation invocationOperation) { var orderedArguments = invocationOperation.GetOrderedArgumentOperations() - .Where(arg => IsImplicitlyProvidedArrayWithValues(arg) == false); + .Where(arg => IsImplicitlyProvidedArrayWithoutValues(arg) == false); if (!invocationOperation.TargetMethod.IsExtensionMethod) { @@ -109,7 +109,7 @@ public static IOperation GetSubstituteOperation(this IInvocationOperation invoca return (int)literal.ConstantValue.Value; } - private static bool IsImplicitlyProvidedArrayWithValues(IArgumentOperation arg) + private static bool IsImplicitlyProvidedArrayWithoutValues(IArgumentOperation arg) { return arg.IsImplicit && arg.ArgumentKind == ArgumentKind.ParamArray && diff --git a/src/NSubstitute.Analyzers.VisualBasic/CodeFixProviders/PartialSubstituteUsedForUnsupportedTypeCodeFixProvider.cs b/src/NSubstitute.Analyzers.VisualBasic/CodeFixProviders/PartialSubstituteUsedForUnsupportedTypeCodeFixProvider.cs index c20c22bb..94601c3f 100644 --- a/src/NSubstitute.Analyzers.VisualBasic/CodeFixProviders/PartialSubstituteUsedForUnsupportedTypeCodeFixProvider.cs +++ b/src/NSubstitute.Analyzers.VisualBasic/CodeFixProviders/PartialSubstituteUsedForUnsupportedTypeCodeFixProvider.cs @@ -8,17 +8,17 @@ 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 SimpleNameSyntax GetNameSyntax(InvocationExpressionSyntax methodInvocationNode) { var memberAccess = (MemberAccessExpressionSyntax)methodInvocationNode.Expression; - return memberAccess.Name as TInnerNameSyntax; + return memberAccess.Name; } - protected override TInnerNameSyntax GetUpdatedNameSyntax(TInnerNameSyntax nameSyntax, string identifierName) + protected override SimpleNameSyntax GetUpdatedNameSyntax(SimpleNameSyntax nameSyntax, string identifierName) { - return (TInnerNameSyntax)nameSyntax.WithIdentifier(IdentifierName(identifierName).Identifier); + return nameSyntax.WithIdentifier(IdentifierName(identifierName).Identifier); } } } \ No newline at end of file