Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for nsubstitute 4.4 api #187

Merged
merged 10 commits into from
Aug 13, 2022
Merged
2 changes: 1 addition & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ for:
test: off
build_script:
- move global.json _global.json #workaround for missing AppVeyor images
- dotnet new globaljson --sdk-version 6.0.201
- dotnet new globaljson --sdk-version 6.0.300 --roll-forward patch
- cd Build
- set IGNORE_NORMALISATION_GIT_HEAD_MOVE=1
- ps: >-
Expand Down
8 changes: 4 additions & 4 deletions build/build.cake
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

// Install tools.
#tool "dotnet:https://api.nuget.org/v3/index.json?package=GitVersion.Tool&version=5.8.1"
#tool "dotnet:https://api.nuget.org/v3/index.json?package=coveralls.net&version=1.0.0"
#tool "dotnet:https://api.nuget.org/v3/index.json?package=coveralls.net&version=4.0.1"
#tool "nuget:https://www.nuget.org/api/v2?package=ReportGenerator&version=5.0.2"
#addin "nuget:https://www.nuget.org/api/v2?package=Cake.Incubator&version=4.0.1"
#addin "nuget:https://www.nuget.org/api/v2?package=Newtonsoft.Json&version=9.0.1"
Expand Down Expand Up @@ -260,11 +260,11 @@ Task("Upload-Coverage-Report")
var pathSegments = new [] { "tools",
".store",
"coveralls.net",
"1.0.0",
"4.0.1",
"coveralls.net",
"1.0.0",
"4.0.1",
"tools",
"netcoreapp2.1",
"net6.0",
"any" };

var workingDir = pathSegments.Aggregate(Context.Environment.WorkingDirectory, (acc, seed) => acc.Combine(seed));
Expand Down
3 changes: 2 additions & 1 deletion global.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"sdk": {
"version": "6.0.200"
"version": "6.0.300",
"rollForward": "patch"
}
}
Binary file modified libs/nsubstitute-latest/NSubstitute.dll
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,21 @@
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using NSubstitute.Analyzers.Shared.CodeFixProviders;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;

namespace NSubstitute.Analyzers.CSharp.CodeFixProviders;

[ExportCodeFixProvider(LanguageNames.CSharp)]
internal sealed class SyncOverAsyncThrowsCodeFixProvider : AbstractSyncOverAsyncThrowsCodeFixProvider<InvocationExpressionSyntax>
{
protected override SyntaxNode GetExpression(InvocationExpressionSyntax invocationExpressionSyntax) => ((MemberAccessExpressionSyntax)invocationExpressionSyntax.Expression).Expression;

protected override SyntaxNode UpdateMemberExpression(InvocationExpressionSyntax invocationExpressionSyntax, SyntaxNode updatedNameSyntax)
{
var expressionSyntax = invocationExpressionSyntax.Expression;
return invocationExpressionSyntax.WithExpression(MemberAccessExpression(
expressionSyntax.Kind(),
((MemberAccessExpressionSyntax)expressionSyntax).Expression,
(SimpleNameSyntax)updatedNameSyntax));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,48 +39,75 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)

var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken);
var methodSymbol = (IMethodSymbol)semanticModel.GetSymbolInfo(invocation).Symbol;
var supportsThrowsAsync = SupportsThrowsAsync(semanticModel.Compilation);

if (methodSymbol.Parameters.Any(param => param.Type.IsCallInfoDelegate(semanticModel)))
if (!supportsThrowsAsync && methodSymbol.Parameters.Any(param => param.Type.IsCallInfoDelegate(semanticModel)))
{
return;
}

var replacementMethod = methodSymbol.IsThrowsForAnyArgsMethod()
? "ReturnsForAnyArgs"
: "Returns";
var replacementMethod = GetReplacementMethodName(methodSymbol, useModernSyntax: supportsThrowsAsync);

var codeAction = CodeAction.Create(
$"Replace with {replacementMethod}",
ct => CreateChangedDocument(context, semanticModel, invocation, methodSymbol, ct),
ct => CreateChangedDocument(context, semanticModel, invocation, methodSymbol, supportsThrowsAsync, ct),
nameof(AbstractSyncOverAsyncThrowsCodeFixProvider<TInvocationExpressionSyntax>));

context.RegisterCodeFix(codeAction, diagnostic);
}

protected abstract SyntaxNode GetExpression(TInvocationExpressionSyntax invocationExpressionSyntax);

protected abstract SyntaxNode UpdateMemberExpression(TInvocationExpressionSyntax invocationExpressionSyntax, SyntaxNode updatedNameSyntax);

private async Task<Document> CreateChangedDocument(
CodeFixContext context,
SemanticModel semanticModel,
TInvocationExpressionSyntax currentInvocationExpression,
IMethodSymbol invocationSymbol,
bool useModernSyntax,
CancellationToken cancellationToken)
{
var documentEditor = await DocumentEditor.CreateAsync(context.Document, cancellationToken);
var invocationOperation = (IInvocationOperation)semanticModel.GetOperation(currentInvocationExpression);

var updatedInvocationExpression = await CreateUpdatedInvocationExpression(
currentInvocationExpression,
invocationOperation,
invocationSymbol,
context);
var updatedInvocationExpression = useModernSyntax
? await CreateThrowsAsyncInvocationExpression(
currentInvocationExpression,
invocationSymbol,
context)
: await CreateReturnInvocationExpression(
currentInvocationExpression,
invocationOperation,
invocationSymbol,
context);

documentEditor.ReplaceNode(currentInvocationExpression, updatedInvocationExpression);

return documentEditor.GetChangedDocument();
}

private async Task<SyntaxNode> CreateUpdatedInvocationExpression(
private async Task<SyntaxNode> CreateThrowsAsyncInvocationExpression(
TInvocationExpressionSyntax currentInvocationExpression,
IMethodSymbol invocationSymbol,
CodeFixContext context)
{
var updatedMethodName =
invocationSymbol.IsThrowsSyncForAnyArgsMethod()
? MetadataNames.NSubstituteThrowsAsyncMethod
: MetadataNames.NSubstituteThrowsAsyncForAnyArgsMethod;

var documentEditor = await DocumentEditor.CreateAsync(context.Document);
var syntaxGenerator = documentEditor.Generator;

var nameSyntax = invocationSymbol.IsGenericMethod
? syntaxGenerator.GenericName(updatedMethodName, invocationSymbol.TypeArguments)
: syntaxGenerator.IdentifierName(updatedMethodName);

return UpdateMemberExpression(currentInvocationExpression, nameSyntax);
}

private async Task<SyntaxNode> CreateReturnInvocationExpression(
TInvocationExpressionSyntax currentInvocationExpression,
IInvocationOperation invocationOperation,
IMethodSymbol invocationSymbol,
Expand All @@ -93,26 +120,26 @@ private async Task<SyntaxNode> CreateUpdatedInvocationExpression(
CreateFromExceptionInvocationExpression(syntaxGenerator, invocationOperation);

var returnsMethodName =
invocationSymbol.IsThrowsForAnyArgsMethod() ? "ReturnsForAnyArgs" : "Returns";
invocationSymbol.IsThrowsSyncForAnyArgsMethod() ? "Returns" : "ReturnsForAnyArgs";

if (invocationSymbol.MethodKind == MethodKind.Ordinary)
{
return CreateUpdatedOrdinalInvocationExpression(
return CreateReturnOrdinalInvocationExpression(
currentInvocationExpression,
invocationOperation,
syntaxGenerator,
fromExceptionInvocationExpression,
returnsMethodName);
}

return CreateUpdatedExtensionInvocationExpression(
return CreateReturnExtensionInvocationExpression(
currentInvocationExpression,
syntaxGenerator,
fromExceptionInvocationExpression,
returnsMethodName);
}

private static SyntaxNode CreateUpdatedOrdinalInvocationExpression(
private static SyntaxNode CreateReturnOrdinalInvocationExpression(
TInvocationExpressionSyntax currentInvocationExpression,
IInvocationOperation invocationOperation,
SyntaxGenerator syntaxGenerator,
Expand All @@ -126,7 +153,7 @@ private static SyntaxNode CreateUpdatedOrdinalInvocationExpression(
fromExceptionInvocationExpression).WithTriviaFrom(currentInvocationExpression);
}

private SyntaxNode CreateUpdatedExtensionInvocationExpression(
private SyntaxNode CreateReturnExtensionInvocationExpression(
TInvocationExpressionSyntax currentInvocationExpression,
SyntaxGenerator syntaxGenerator,
SyntaxNode fromExceptionInvocationExpression,
Expand Down Expand Up @@ -173,4 +200,26 @@ private static SyntaxNode GetExceptionCreationExpression(
return invocationOperation.Arguments.OrderBy(arg => arg.Parameter.Ordinal)
.First(arg => arg.Parameter.Ordinal > 0).Value.Syntax;
}

private static bool SupportsThrowsAsync(Compilation compilation)
{
var exceptionExtensionsTypeSymbol = compilation.GetTypeByMetadataName("NSubstitute.ExceptionExtensions.ExceptionExtensions");

return exceptionExtensionsTypeSymbol != null &&
exceptionExtensionsTypeSymbol.GetMembers(MetadataNames.NSubstituteThrowsAsyncMethod).IsEmpty == false;
}

private static string GetReplacementMethodName(IMethodSymbol methodSymbol, bool useModernSyntax)
{
if (useModernSyntax)
{
return methodSymbol.IsThrowsSyncForAnyArgsMethod()
? MetadataNames.NSubstituteThrowsAsyncMethod
: MetadataNames.NSubstituteThrowsAsyncForAnyArgsMethod;
}

return methodSymbol.IsThrowsSyncForAnyArgsMethod()
? MetadataNames.NSubstituteReturnsMethod
: MetadataNames.NSubstituteReturnsForAnyArgsMethod;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ private void AnalyzeInvocation(SyntaxNodeAnalysisContext syntaxNodeContext)
return;
}

if (!methodSymbol.IsThrowLikeMethod())
if (!methodSymbol.IsThrowSyncLikeMethod())
{
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,16 @@ public static bool IsThrowLikeMethod(this ISymbol symbol)
return IsMember(symbol, MetadataNames.ThrowsMethodNames);
}

public static bool IsThrowsForAnyArgsMethod(this ISymbol symbol)
public static bool IsThrowSyncLikeMethod(this ISymbol symbol)
{
return IsMember(symbol, MetadataNames.ThrowsSyncMethodNames);
}

public static bool IsThrowsSyncForAnyArgsMethod(this ISymbol symbol)
{
return IsMember(
symbol,
MetadataNames.NSubstituteThrowsForAnyArgsMethod,
MetadataNames.NSubstituteThrowsMethod,
MetadataNames.NSubstituteExceptionExtensionsFullTypeName);
}

Expand Down
12 changes: 12 additions & 0 deletions src/NSubstitute.Analyzers.Shared/MetadataNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ internal class MetadataNames
public const string NSubstituteReturnsMethod = "Returns";
public const string NSubstituteReturnsForAnyArgsMethod = "ReturnsForAnyArgs";
public const string NSubstituteThrowsMethod = "Throws";
public const string NSubstituteThrowsAsyncMethod = "ThrowsAsync";
public const string NSubstituteThrowsForAnyArgsMethod = "ThrowsForAnyArgs";
public const string NSubstituteThrowsAsyncForAnyArgsMethod = "ThrowsAsyncForAnyArgs";
public const string NSubstituteAndDoesMethod = "AndDoes";
public const string NSubstituteReturnsNullMethod = "ReturnsNull";
public const string NSubstituteReturnsNullForAnyArgsMethod = "ReturnsNullForAnyArgs";
Expand Down Expand Up @@ -56,6 +58,14 @@ internal class MetadataNames
};

public static readonly IReadOnlyDictionary<string, string> ThrowsMethodNames = new Dictionary<string, string>
{
[NSubstituteThrowsMethod] = NSubstituteExceptionExtensionsFullTypeName,
[NSubstituteThrowsAsyncMethod] = NSubstituteExceptionExtensionsFullTypeName,
[NSubstituteThrowsForAnyArgsMethod] = NSubstituteExceptionExtensionsFullTypeName,
[NSubstituteThrowsAsyncForAnyArgsMethod] = NSubstituteExceptionExtensionsFullTypeName
};

public static readonly IReadOnlyDictionary<string, string> ThrowsSyncMethodNames = new Dictionary<string, string>
{
[NSubstituteThrowsMethod] = NSubstituteExceptionExtensionsFullTypeName,
[NSubstituteThrowsForAnyArgsMethod] = NSubstituteExceptionExtensionsFullTypeName
Expand Down Expand Up @@ -118,7 +128,9 @@ internal class MetadataNames
[NSubstituteReturnsMethod] = NSubstituteSubstituteExtensionsFullTypeName,
[NSubstituteReturnsForAnyArgsMethod] = NSubstituteSubstituteExtensionsFullTypeName,
[NSubstituteThrowsMethod] = NSubstituteExceptionExtensionsFullTypeName,
[NSubstituteThrowsAsyncMethod] = NSubstituteExceptionExtensionsFullTypeName,
[NSubstituteThrowsForAnyArgsMethod] = NSubstituteExceptionExtensionsFullTypeName,
[NSubstituteThrowsAsyncForAnyArgsMethod] = NSubstituteExceptionExtensionsFullTypeName,
[NSubstituteAndDoesMethod] = NSubstituteConfiguredCallFullTypeName,
[NSubstituteDoMethod] = NSubstituteWhenCalledType
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.VisualBasic;
using Microsoft.CodeAnalysis.VisualBasic.Syntax;
using NSubstitute.Analyzers.Shared.CodeFixProviders;
using static Microsoft.CodeAnalysis.VisualBasic.SyntaxFactory;

namespace NSubstitute.Analyzers.VisualBasic.CodeFixProviders;

[ExportCodeFixProvider(LanguageNames.VisualBasic)]
internal sealed class SyncOverAsyncThrowsCodeFixProvider : AbstractSyncOverAsyncThrowsCodeFixProvider<InvocationExpressionSyntax>
{
protected override SyntaxNode GetExpression(InvocationExpressionSyntax invocationExpressionSyntax) => ((MemberAccessExpressionSyntax)invocationExpressionSyntax.Expression).Expression;

protected override SyntaxNode UpdateMemberExpression(InvocationExpressionSyntax invocationExpressionSyntax, SyntaxNode updatedNameSyntax)
{
var expressionSyntax = invocationExpressionSyntax.Expression;
return invocationExpressionSyntax.WithExpression(MemberAccessExpression(
expressionSyntax.Kind(),
((MemberAccessExpressionSyntax)expressionSyntax).Expression,
Token(SyntaxKind.DotToken),
(SimpleNameSyntax)updatedNameSyntax));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ protected ReEntrantSetupCodeFixVerifier()

[Theory]
[InlineData("CreateReEntrantSubstitute(), CreateDefaultValue(), 1", "_ => CreateReEntrantSubstitute(), _ => CreateDefaultValue(), _ => 1")]
[InlineData("CreateReEntrantSubstitute(), new [] { CreateDefaultValue(), 1 }", "_ => CreateReEntrantSubstitute(), new System.Func<NSubstitute.Core.CallInfo<int>, int>[] { _ => CreateDefaultValue(), _ => 1 }")]
[InlineData("CreateReEntrantSubstitute(), new int[] { CreateDefaultValue(), 1 }", "_ => CreateReEntrantSubstitute(), new System.Func<NSubstitute.Core.CallInfo<int>, int>[] { _ => CreateDefaultValue(), _ => 1 }")]
[InlineData("CreateReEntrantSubstitute(), new [] { CreateDefaultValue(), 1 }", "_ => CreateReEntrantSubstitute(), new System.Func<NSubstitute.Core.CallInfo, int>[] { _ => CreateDefaultValue(), _ => 1 }")]
[InlineData("CreateReEntrantSubstitute(), new int[] { CreateDefaultValue(), 1 }", "_ => CreateReEntrantSubstitute(), new System.Func<NSubstitute.Core.CallInfo, int>[] { _ => CreateDefaultValue(), _ => 1 }")]
[InlineData("returnThis: CreateReEntrantSubstitute()", "returnThis: _ => CreateReEntrantSubstitute()")]
[InlineData("returnThis: CreateReEntrantSubstitute(), returnThese: new [] { CreateDefaultValue(), 1 }", "returnThis: _ => CreateReEntrantSubstitute(), returnThese: new System.Func<NSubstitute.Core.CallInfo<int>, int>[] { _ => CreateDefaultValue(), _ => 1 }")]
[InlineData("returnThis: CreateReEntrantSubstitute(), returnThese: new int[] { CreateDefaultValue(), 1 }", "returnThis: _ => CreateReEntrantSubstitute(), returnThese: new System.Func<NSubstitute.Core.CallInfo<int>, int>[] { _ => CreateDefaultValue(), _ => 1 }")]
[InlineData("returnThis: CreateReEntrantSubstitute(), returnThese: new [] { CreateDefaultValue(), 1 }", "returnThis: _ => CreateReEntrantSubstitute(), returnThese: new System.Func<NSubstitute.Core.CallInfo, int>[] { _ => CreateDefaultValue(), _ => 1 }")]
[InlineData("returnThis: CreateReEntrantSubstitute(), returnThese: new int[] { CreateDefaultValue(), 1 }", "returnThis: _ => CreateReEntrantSubstitute(), returnThese: new System.Func<NSubstitute.Core.CallInfo, int>[] { _ => CreateDefaultValue(), _ => 1 }")]
public abstract Task ReplacesArgumentExpression_WithLambda(string arguments, string rewrittenArguments);

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ public interface IFoo
public void Test()
{
var secondSubstitute = Substitute.For<IFoo>();
secondSubstitute.Id.Returns(_ => CreateReEntrantSubstitute(), new Func<CallInfo<int>, int>[] { _ => MyNamespace.FooTests.Value });
secondSubstitute.Id.Returns(_ => CreateReEntrantSubstitute(), new Func<CallInfo, int>[] { _ => MyNamespace.FooTests.Value });
}

private int CreateReEntrantSubstitute()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ public interface IFoo
public void Test()
{
var secondSubstitute = Substitute.For<IFoo>();
SubstituteExtensions.Returns(secondSubstitute.Id, _ => CreateReEntrantSubstitute(), new Func<CallInfo<int>, int>[] { _ => MyNamespace.FooTests.Value });
SubstituteExtensions.Returns(secondSubstitute.Id, _ => CreateReEntrantSubstitute(), new Func<CallInfo, int>[] { _ => MyNamespace.FooTests.Value });
}

private int CreateReEntrantSubstitute()
Expand Down
Loading