Skip to content

Commit

Permalink
GH-153 - simplifying ConstructorArgumentsForInterfaceCodeFixProvider …
Browse files Browse the repository at this point in the history
…with operations API
  • Loading branch information
tpodolak committed Oct 2, 2022
1 parent f72f076 commit 464e318
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,36 @@
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 ConstructorArgumentsForInterfaceCodeFixProvider : AbstractConstructorArgumentsForInterfaceCodeFixProvider<InvocationExpressionSyntax>
internal sealed class ConstructorArgumentsForInterfaceCodeFixProvider : AbstractConstructorArgumentsForInterfaceCodeFixProvider
{
protected override InvocationExpressionSyntax GetInvocationExpressionSyntaxWithEmptyArgumentList(InvocationExpressionSyntax invocationExpressionSyntax)
protected override SyntaxNode GetInvocationExpressionSyntaxWithEmptyArgumentList(IInvocationOperation invocationOperation)
{
return invocationExpressionSyntax.WithArgumentList(ArgumentList());
var invocationExpression = (InvocationExpressionSyntax)invocationOperation.Syntax;

return invocationExpression.WithArgumentList(ArgumentList());
}

protected override InvocationExpressionSyntax GetInvocationExpressionSyntaxWithNullConstructorArgument(InvocationExpressionSyntax invocationExpressionSyntax)
protected override SyntaxNode GetInvocationExpressionSyntaxWithNullConstructorArgument(IInvocationOperation invocationOperation)
{
var nullSyntax = Argument(LiteralExpression(SyntaxKind.NullLiteralExpression));
var secondArgument = invocationExpressionSyntax.ArgumentList.Arguments.Skip(1).First();
var argumentListSyntax = invocationExpressionSyntax.ArgumentList.ReplaceNode(secondArgument, nullSyntax);
return invocationExpressionSyntax.WithArgumentList(argumentListSyntax);
var invocationExpressionSyntax = (InvocationExpressionSyntax)invocationOperation.Syntax;
var arguments = invocationOperation.Arguments.Select(argumentOperation =>
{
var argumentSyntax = (ArgumentSyntax)argumentOperation.Syntax;
if (argumentOperation.Parameter.Ordinal > 0)
{
argumentSyntax = argumentSyntax.WithExpression(LiteralExpression(SyntaxKind.NullLiteralExpression));
}
return argumentSyntax;
});

return invocationExpressionSyntax.WithArgumentList(ArgumentList(SeparatedList(arguments)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,46 +5,60 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Operations;
using Document = Microsoft.CodeAnalysis.Document;

namespace NSubstitute.Analyzers.Shared.CodeFixProviders;

internal abstract class AbstractConstructorArgumentsForInterfaceCodeFixProvider<TInvocationExpressionSyntax>
: CodeFixProvider
where TInvocationExpressionSyntax : SyntaxNode
internal abstract class AbstractConstructorArgumentsForInterfaceCodeFixProvider : CodeFixProvider
{
public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;

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

public override Task RegisterCodeFixesAsync(CodeFixContext context)
{
var diagnostic = context.Diagnostics.FirstOrDefault(diag => diag.Descriptor.Id == DiagnosticIdentifiers.SubstituteConstructorArgumentsForInterface);
if (diagnostic != null)
var diagnostic = context.Diagnostics.FirstOrDefault(diag =>
diag.Descriptor.Id == DiagnosticIdentifiers.SubstituteConstructorArgumentsForInterface);
if (diagnostic == null)
{
var codeAction = CodeAction.Create("Remove constructor arguments", ct => CreateChangedDocument(ct, context, diagnostic), nameof(AbstractConstructorArgumentsForInterfaceCodeFixProvider<TInvocationExpressionSyntax>));
context.RegisterCodeFix(codeAction, diagnostic);
return Task.CompletedTask;
}

var codeAction = CodeAction.Create(
"Remove constructor arguments",
ct => CreateChangedDocument(ct, context, diagnostic),
nameof(AbstractConstructorArgumentsForInterfaceCodeFixProvider));
context.RegisterCodeFix(codeAction, diagnostic);

return Task.CompletedTask;
}

protected abstract TInvocationExpressionSyntax GetInvocationExpressionSyntaxWithEmptyArgumentList(TInvocationExpressionSyntax invocationExpressionSyntax);
protected abstract SyntaxNode GetInvocationExpressionSyntaxWithEmptyArgumentList(IInvocationOperation invocationOperation);

protected abstract TInvocationExpressionSyntax GetInvocationExpressionSyntaxWithNullConstructorArgument(TInvocationExpressionSyntax invocationExpressionSyntax);
protected abstract SyntaxNode GetInvocationExpressionSyntaxWithNullConstructorArgument(IInvocationOperation invocationOperation);

private async Task<Document> CreateChangedDocument(CancellationToken cancellationToken, CodeFixContext context, Diagnostic diagnostic)
{
var documentEditor = await DocumentEditor.CreateAsync(context.Document, cancellationToken);

var root = await context.Document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

var invocation = (TInvocationExpressionSyntax)root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true);
var invocation = root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true);
var semanticModel = await context.Document.GetSemanticModelAsync(cancellationToken);
var updatedInvocation = semanticModel.GetSymbolInfo(invocation).Symbol is IMethodSymbol methodSymbol &&
methodSymbol.IsGenericMethod
? GetInvocationExpressionSyntaxWithEmptyArgumentList(invocation)
: GetInvocationExpressionSyntaxWithNullConstructorArgument(invocation);
if (semanticModel.GetOperation(invocation) is not IInvocationOperation invocationOperation)
{
return context.Document;
}

var updatedInvocation = invocationOperation.TargetMethod.IsGenericMethod
? GetInvocationExpressionSyntaxWithEmptyArgumentList(invocationOperation)
: GetInvocationExpressionSyntaxWithNullConstructorArgument(invocationOperation);

documentEditor.ReplaceNode(invocation, updatedInvocation);

var replacedRoot = root.ReplaceNode(invocation, updatedInvocation);
return context.Document.WithSyntaxRoot(replacedRoot);
return documentEditor.GetChangedDocument();
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Linq;
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 @@ -9,18 +10,32 @@
namespace NSubstitute.Analyzers.VisualBasic.CodeFixProviders;

[ExportCodeFixProvider(LanguageNames.VisualBasic)]
internal sealed class ConstructorArgumentsForInterfaceCodeFixProvider : AbstractConstructorArgumentsForInterfaceCodeFixProvider<InvocationExpressionSyntax>
internal sealed class ConstructorArgumentsForInterfaceCodeFixProvider : AbstractConstructorArgumentsForInterfaceCodeFixProvider
{
protected override InvocationExpressionSyntax GetInvocationExpressionSyntaxWithEmptyArgumentList(InvocationExpressionSyntax invocationExpressionSyntax)
protected override SyntaxNode GetInvocationExpressionSyntaxWithEmptyArgumentList(IInvocationOperation invocationOperation)
{
return invocationExpressionSyntax.WithArgumentList(ArgumentList());
var invocationExpression = (InvocationExpressionSyntax)invocationOperation.Syntax;

return invocationExpression.WithArgumentList(ArgumentList());
}

protected override InvocationExpressionSyntax GetInvocationExpressionSyntaxWithNullConstructorArgument(InvocationExpressionSyntax invocationExpressionSyntax)
protected override SyntaxNode GetInvocationExpressionSyntaxWithNullConstructorArgument(IInvocationOperation invocationOperation)
{
var nullSyntax = SimpleArgument(LiteralExpression(SyntaxKind.NothingLiteralExpression, Token(SyntaxKind.NothingKeyword)));
var seconArgument = invocationExpressionSyntax.ArgumentList.Arguments.Skip(1).First();
var argumentListSyntax = invocationExpressionSyntax.ArgumentList.ReplaceNode(seconArgument, nullSyntax);
return invocationExpressionSyntax.WithArgumentList(argumentListSyntax);
var invocationExpressionSyntax = (InvocationExpressionSyntax)invocationOperation.Syntax;
var arguments = invocationOperation.Arguments
.OrderBy(x => x.Syntax.GetLocation().GetLineSpan().StartLinePosition.Character)
.Select<IArgumentOperation, ArgumentSyntax>(argumentOperation =>
{
var argumentSyntax = (SimpleArgumentSyntax)argumentOperation.Syntax;
if (argumentOperation.Parameter.Ordinal > 0)
{
argumentSyntax = argumentSyntax.WithExpression(
LiteralExpression(SyntaxKind.NothingLiteralExpression, Token(SyntaxKind.NothingKeyword)));
}
return argumentSyntax;
});

return invocationExpressionSyntax.WithArgumentList(ArgumentList(SeparatedList(arguments)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ End Interface
Public Class FooTests
Public Sub Test()
Dim substitute = NSubstitute.Substitute.[For]({GetType(IFoo)}, Nothing)
Dim otherSubstitute = NSubstitute.Substitute.[For](typesToProxy:= {GetType(IFoo)}, constructorArguments:= Nothing)
Dim yetAnotherSubstitute = NSubstitute.Substitute.[For](constructorArguments:= Nothing, typesToProxy:= {GetType(IFoo)})
Dim otherSubstitute = NSubstitute.Substitute.[For](typesToProxy:= {GetType(IFoo)}, constructorArguments:=Nothing)
Dim yetAnotherSubstitute = NSubstitute.Substitute.[For](constructorArguments:=Nothing, typesToProxy:= {GetType(IFoo)})
End Sub
End Class
End Namespace
Expand Down Expand Up @@ -114,8 +114,8 @@ End Interface
Public Class FooTests
Public Sub Test()
Dim substitute = SubstitutionContext.Current.SubstituteFactory.Create({GetType(IFoo)}, Nothing)
Dim otherSubstitute = SubstitutionContext.Current.SubstituteFactory.Create(typesToProxy:= {GetType(IFoo)}, constructorArguments:= Nothing)
Dim yetAnotherSubstitute = SubstitutionContext.Current.SubstituteFactory.Create(constructorArguments:= Nothing, typesToProxy:= {GetType(IFoo)})
Dim otherSubstitute = SubstitutionContext.Current.SubstituteFactory.Create(typesToProxy:= {GetType(IFoo)}, constructorArguments:=Nothing)
Dim yetAnotherSubstitute = SubstitutionContext.Current.SubstituteFactory.Create(constructorArguments:=Nothing, typesToProxy:= {GetType(IFoo)})
End Sub
End Class
End Namespace
Expand Down

0 comments on commit 464e318

Please sign in to comment.