Skip to content

Commit

Permalink
GH-186 - SyncOverAsyncThrowsAnalyzer support for ThrowsAsync
Browse files Browse the repository at this point in the history
  • Loading branch information
tpodolak committed Jul 22, 2022
1 parent 97906f9 commit 2166298
Show file tree
Hide file tree
Showing 16 changed files with 414 additions and 7 deletions.
2 changes: 1 addition & 1 deletion global.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"sdk": {
"version": "6.0.200"
"version": "6.0.300"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
return;
}

var replacementMethod = methodSymbol.IsThrowsForAnyArgsMethod()
var replacementMethod = methodSymbol.IsThrowsSyncForAnyArgsMethod()
? "ReturnsForAnyArgs"
: "Returns";

Expand Down Expand Up @@ -93,7 +93,7 @@ private async Task<SyntaxNode> CreateUpdatedInvocationExpression(
CreateFromExceptionInvocationExpression(syntaxGenerator, invocationOperation);

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

if (invocationSymbol.MethodKind == MethodKind.Ordinary)
{
Expand Down
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,7 +42,12 @@ 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,
Expand Down
10 changes: 10 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
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ public static IEnumerable<object[]> ThrowsTestCases
}
}

public static IEnumerable<object[]> ThrowsAsyncTestCases
{
get
{
yield return new object[] { "ThrowsAsync" };
yield return new object[] { "ThrowsAsyncForAnyArgs" };
}
}

protected DiagnosticDescriptor SyncOverAsyncThrowsDescriptor => DiagnosticDescriptors<DiagnosticDescriptorsProvider>.SyncOverAsyncThrows;

protected override DiagnosticAnalyzer DiagnosticAnalyzer { get; } = new SyncOverAsyncThrowsAnalyzer();
Expand All @@ -40,4 +49,8 @@ public static IEnumerable<object[]> ThrowsTestCases
[Theory]
[MemberData(nameof(ThrowsTestCases))]
public abstract Task ReportsNoDiagnostic_WhenUsedWithSyncMember(string method);

[Theory]
[MemberData(nameof(ThrowsAsyncTestCases))]
public abstract Task ReportsNoDiagnostic_WhenThrowsAsyncUsed(string method);
}
Original file line number Diff line number Diff line change
Expand Up @@ -133,4 +133,51 @@ public void Test()

await VerifyNoDiagnostic(source);
}

public override async Task ReportsNoDiagnostic_WhenThrowsAsyncUsed(string method)
{
var source = $@"using System;
using System.Threading.Tasks;
using NSubstitute;
using NSubstitute.ExceptionExtensions;
namespace MyNamespace
{{
public interface IFoo
{{
Task Bar();
Task<object> FooBar();
Task Foo {{ get; set; }}
Task<object> FooBarBar {{ get; set; }}
Task this[int x] {{ get; set; }}
Task<object> this[int x, int y] {{ get; set; }}
}}
public class FooTests
{{
public void Test()
{{
var substitute = NSubstitute.Substitute.For<IFoo>();
substitute.Bar().{method}(new Exception());
substitute.Bar().{method}(ex: new Exception());
substitute.FooBar().{method}(new Exception());
substitute.FooBar().{method}(ex: new Exception());
substitute.Foo.{method}(new Exception());
substitute.Foo.{method}(ex: new Exception());
substitute.FooBarBar.{method}(new Exception());
substitute.FooBarBar.{method}(ex: new Exception());
substitute[0].{method}(new Exception());
substitute[0].{method}(ex: new Exception());
substitute[0, 0].{method}(new Exception());
substitute[0, 0].{method}(ex: new Exception());
}}
}}
}}";

await VerifyNoDiagnostic(source);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,51 @@ public void Test()

await VerifyNoDiagnostic(source);
}

public override async Task ReportsNoDiagnostic_WhenThrowsAsyncUsed(string method)
{
var source = $@"using System;
using System.Threading.Tasks;
using NSubstitute;
using NSubstitute.ExceptionExtensions;
namespace MyNamespace
{{
public interface IFoo
{{
Task Bar();
Task<object> FooBar();
Task Foo {{ get; set; }}
Task<object> FooBarBar {{ get; set; }}
Task this[int x] {{ get; set; }}
Task<object> this[int x, int y] {{ get; set; }}
}}
public class FooTests
{{
public void Test()
{{
var substitute = NSubstitute.Substitute.For<IFoo>();
substitute.Bar().{method}<Exception>();
substitute.Bar().{method}<Exception>();
substitute.FooBar().{method}<Exception>();
substitute.FooBar().{method}<Exception>();
substitute.Foo.{method}<Exception>();
substitute.Foo.{method}<Exception>();
substitute.FooBarBar.{method}<Exception>();
substitute.FooBarBar.{method}<Exception>();
substitute[0].{method}<Exception>();
substitute[0].{method}<Exception>();
substitute[0, 0].{method}<Exception>();
substitute[0, 0].{method}<Exception>();
}}
}}
}}";

await VerifyNoDiagnostic(source);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -142,4 +142,57 @@ public void Test()

await VerifyNoDiagnostic(source);
}

public override async Task ReportsNoDiagnostic_WhenThrowsAsyncUsed(string method)
{
var source = $@"using System;
using System.Threading.Tasks;
using NSubstitute;
using NSubstitute.ExceptionExtensions;
namespace MyNamespace
{{
public interface IFoo
{{
Task Bar();
Task<object> FooBar();
Task Foo {{ get; set; }}
Task<object> FooBarBar {{ get; set; }}
Task this[int x] {{ get; set; }}
Task<object> this[int x, int y] {{ get; set; }}
}}
public class FooTests
{{
public void Test()
{{
var substitute = NSubstitute.Substitute.For<IFoo>();
ExceptionExtensions.{method}(substitute.Bar(), new Exception());
ExceptionExtensions.{method}(value: substitute.Bar(), ex: new Exception());
ExceptionExtensions.{method}(ex: new Exception(), value: substitute.Bar());
ExceptionExtensions.{method}(substitute.FooBar(), new Exception());
ExceptionExtensions.{method}(value: substitute.FooBar(), ex: new Exception());
ExceptionExtensions.{method}(ex: new Exception(), value: substitute.FooBar());
ExceptionExtensions.{method}(substitute.Foo, new Exception());
ExceptionExtensions.{method}(value: substitute.Foo, ex: new Exception());
ExceptionExtensions.{method}(ex: new Exception(), value: substitute.Foo);
ExceptionExtensions.{method}(substitute.FooBarBar, new Exception());
ExceptionExtensions.{method}(value: substitute.FooBarBar, ex: new Exception());
ExceptionExtensions.{method}(ex: new Exception(), value: substitute.FooBarBar);
ExceptionExtensions.{method}(substitute[0], new Exception());
ExceptionExtensions.{method}(value: substitute[0], ex: new Exception());
ExceptionExtensions.{method}(ex: new Exception(), value: substitute[0]);
ExceptionExtensions.{method}(substitute[0, 0], new Exception());
ExceptionExtensions.{method}(value: substitute[0, 0], ex: new Exception());
ExceptionExtensions.{method}(ex: new Exception(), value: substitute[0, 0]);
}}
}}
}}";

await VerifyNoDiagnostic(source);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -133,4 +133,51 @@ public void Test()

await VerifyNoDiagnostic(source);
}

public override async Task ReportsNoDiagnostic_WhenThrowsAsyncUsed(string method)
{
var source = $@"using System;
using System.Threading.Tasks;
using NSubstitute;
using NSubstitute.ExceptionExtensions;
namespace MyNamespace
{{
public interface IFoo
{{
Task Bar();
Task<object> FooBar();
Task Foo {{ get; set; }}
Task<object> FooBarBar {{ get; set; }}
Task this[int x] {{ get; set; }}
Task<object> this[int x, int y] {{ get; set; }}
}}
public class FooTests
{{
public void Test()
{{
var substitute = NSubstitute.Substitute.For<IFoo>();
ExceptionExtensions.{method}<Exception>(substitute.Bar());
ExceptionExtensions.{method}<Exception>(value: substitute.Bar());
ExceptionExtensions.{method}<Exception>(substitute.FooBar());
ExceptionExtensions.{method}<Exception>(value: substitute.FooBar());
ExceptionExtensions.{method}<Exception>(substitute.Foo);
ExceptionExtensions.{method}<Exception>(value: substitute.Foo);
ExceptionExtensions.{method}<Exception>(substitute.FooBarBar);
ExceptionExtensions.{method}<Exception>(value: substitute.FooBarBar);
ExceptionExtensions.{method}<Exception>(substitute[0]);
ExceptionExtensions.{method}<Exception>(value: substitute[0]);
ExceptionExtensions.{method}<Exception>(substitute[0, 0]);
ExceptionExtensions.{method}<Exception>(value: substitute[0, 0]);
}}
}}
}}";

await VerifyNoDiagnostic(source);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ public interface ISyncOverAsyncThrowsDiagnosticVerifier
Task ReportsDiagnostic_WhenUsedInTaskReturningIndexer(string method);

Task ReportsNoDiagnostic_WhenUsedWithSyncMember(string method);

Task ReportsNoDiagnostic_WhenThrowsAsyncUsed(string method);
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ public static IEnumerable<object[]> ThrowsTestCases
}
}

public static IEnumerable<object[]> ThrowsAsyncTestCases
{
get
{
yield return new object[] { "ThrowsAsync" };
yield return new object[] { "ThrowsAsyncForAnyArgs" };
}
}

protected DiagnosticDescriptor SyncOverAsyncThrowsDescriptor => DiagnosticDescriptors<DiagnosticDescriptorsProvider>.SyncOverAsyncThrows;

protected override DiagnosticAnalyzer DiagnosticAnalyzer { get; } = new SyncOverAsyncThrowsAnalyzer();
Expand All @@ -40,4 +49,8 @@ public static IEnumerable<object[]> ThrowsTestCases
[Theory]
[MemberData(nameof(ThrowsTestCases))]
public abstract Task ReportsNoDiagnostic_WhenUsedWithSyncMember(string method);

[Theory]
[MemberData(nameof(ThrowsAsyncTestCases))]
public abstract Task ReportsNoDiagnostic_WhenThrowsAsyncUsed(string method);
}
Loading

0 comments on commit 2166298

Please sign in to comment.