-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
GH-164 - throws used in async method diagnostic
- Loading branch information
Showing
16 changed files
with
573 additions
and
0 deletions.
There are no files selected for viewing
19 changes: 19 additions & 0 deletions
19
src/NSubstitute.Analyzers.CSharp/DiagnosticAnalyzers/AsyncThrowsAnalyzer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CSharp; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
using NSubstitute.Analyzers.Shared.DiagnosticAnalyzers; | ||
|
||
namespace NSubstitute.Analyzers.CSharp.DiagnosticAnalyzers | ||
{ | ||
[DiagnosticAnalyzer(LanguageNames.CSharp)] | ||
internal class AsyncThrowsAnalyzer : AbstractAsyncThrowsAnalyzer<SyntaxKind, InvocationExpressionSyntax> | ||
{ | ||
public AsyncThrowsAnalyzer() | ||
: base(CSharp.DiagnosticDescriptorsProvider.Instance, SubstitutionNodeFinder.Instance) | ||
{ | ||
} | ||
|
||
protected override SyntaxKind InvocationExpressionKind => SyntaxKind.InvocationExpression; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
87 changes: 87 additions & 0 deletions
87
src/NSubstitute.Analyzers.Shared/DiagnosticAnalyzers/AbstractAsyncThrowsAnalyzer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
using System; | ||
using System.Collections.Immutable; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
using NSubstitute.Analyzers.Shared.Extensions; | ||
|
||
namespace NSubstitute.Analyzers.Shared.DiagnosticAnalyzers | ||
{ | ||
internal abstract class | ||
AbstractAsyncThrowsAnalyzer<TSyntaxKind, TInvocationExpressionSyntax> : AbstractDiagnosticAnalyzer | ||
where TInvocationExpressionSyntax : SyntaxNode | ||
where TSyntaxKind : struct | ||
{ | ||
private readonly ISubstitutionNodeFinder<TInvocationExpressionSyntax> _substitutionNodeFinder; | ||
private readonly Action<SyntaxNodeAnalysisContext> _analyzeInvocationAction; | ||
|
||
protected abstract TSyntaxKind InvocationExpressionKind { get; } | ||
|
||
protected AbstractAsyncThrowsAnalyzer( | ||
IDiagnosticDescriptorsProvider diagnosticDescriptorsProvider, | ||
ISubstitutionNodeFinder<TInvocationExpressionSyntax> substitutionNodeFinder) | ||
: base(diagnosticDescriptorsProvider) | ||
{ | ||
_substitutionNodeFinder = substitutionNodeFinder; | ||
SupportedDiagnostics = ImmutableArray.Create(DiagnosticDescriptorsProvider.AsyncThrows); | ||
|
||
_analyzeInvocationAction = AnalyzeInvocation; | ||
} | ||
|
||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } | ||
|
||
protected override void InitializeAnalyzer(AnalysisContext context) | ||
{ | ||
context.RegisterSyntaxNodeAction(_analyzeInvocationAction, InvocationExpressionKind); | ||
} | ||
|
||
private void AnalyzeInvocation(SyntaxNodeAnalysisContext syntaxNodeContext) | ||
{ | ||
var invocationExpression = syntaxNodeContext.Node; | ||
var methodSymbolInfo = syntaxNodeContext.SemanticModel.GetSymbolInfo(invocationExpression); | ||
|
||
if (methodSymbolInfo.Symbol?.Kind != SymbolKind.Method) | ||
{ | ||
return; | ||
} | ||
|
||
var methodSymbol = (IMethodSymbol)methodSymbolInfo.Symbol; | ||
|
||
if (!methodSymbol.IsThrowLikeMethod()) | ||
{ | ||
return; | ||
} | ||
|
||
var substitutedExpression = _substitutionNodeFinder.FindForStandardExpression( | ||
(TInvocationExpressionSyntax)invocationExpression, | ||
methodSymbol); | ||
|
||
if (substitutedExpression == null) | ||
{ | ||
return; | ||
} | ||
|
||
var semanticModel = syntaxNodeContext.SemanticModel.GetSymbolInfo(substitutedExpression); | ||
|
||
if (!(semanticModel.Symbol is IMethodSymbol substituteMethodSymbol)) | ||
{ | ||
return; | ||
} | ||
|
||
var typeByMetadataName = syntaxNodeContext.Compilation.GetTypeByMetadataName("System.Threading.Tasks.Task"); | ||
|
||
if (!HasTaskReturnType(substituteMethodSymbol, typeByMetadataName)) | ||
{ | ||
return; | ||
} | ||
|
||
syntaxNodeContext.ReportDiagnostic( | ||
Diagnostic.Create(DiagnosticDescriptorsProvider.AsyncThrows, invocationExpression.GetLocation())); | ||
} | ||
|
||
private static bool HasTaskReturnType(IMethodSymbol methodSymbol, ISymbol typeByMetadataName) | ||
{ | ||
return methodSymbol.ReturnType.Equals(typeByMetadataName) || | ||
(methodSymbol.ReturnType.BaseType != null && methodSymbol.ReturnType.BaseType.Equals(typeByMetadataName)); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
19 changes: 19 additions & 0 deletions
19
src/NSubstitute.Analyzers.VisualBasic/DiagnosticAnalyzers/AsyncThrowsAnalyzer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
using Microsoft.CodeAnalysis.VisualBasic; | ||
using Microsoft.CodeAnalysis.VisualBasic.Syntax; | ||
using NSubstitute.Analyzers.Shared.DiagnosticAnalyzers; | ||
|
||
namespace NSubstitute.Analyzers.VisualBasic.DiagnosticAnalyzers | ||
{ | ||
[DiagnosticAnalyzer(LanguageNames.VisualBasic)] | ||
internal class AsyncThrowsAnalyzer : AbstractAsyncThrowsAnalyzer<SyntaxKind, InvocationExpressionSyntax> | ||
{ | ||
public AsyncThrowsAnalyzer() | ||
: base(VisualBasic.DiagnosticDescriptorsProvider.Instance, SubstitutionNodeFinder.Instance) | ||
{ | ||
} | ||
|
||
protected override SyntaxKind InvocationExpressionKind => SyntaxKind.InvocationExpression; | ||
} | ||
} |
33 changes: 33 additions & 0 deletions
33
....CSharp/DiagnosticAnalyzerTests/AsyncThrowsAnalyzerTests/AsyncThrowsDiagnosticVerifier.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
using System.Threading.Tasks; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
using NSubstitute.Analyzers.CSharp; | ||
using NSubstitute.Analyzers.CSharp.DiagnosticAnalyzers; | ||
using NSubstitute.Analyzers.Shared; | ||
using NSubstitute.Analyzers.Tests.Shared.DiagnosticAnalyzers; | ||
using Xunit; | ||
|
||
namespace NSubstitute.Analyzers.Tests.CSharp.DiagnosticAnalyzerTests.AsyncThrowsAnalyzerTests | ||
{ | ||
public abstract class AsyncThrowsDiagnosticVerifier : CSharpDiagnosticVerifier, IAsyncThrowsDiagnosticVerifier | ||
{ | ||
protected DiagnosticDescriptor AsyncThrowsDescriptor => DiagnosticDescriptors<DiagnosticDescriptorsProvider>.AsyncThrows; | ||
|
||
protected override DiagnosticAnalyzer DiagnosticAnalyzer { get; } = new AsyncThrowsAnalyzer(); | ||
|
||
[Theory] | ||
[InlineData("Throws")] | ||
[InlineData("ThrowsForAnyArgs")] | ||
public abstract Task ReportsDiagnostic_WhenUsedWithNonGenericAsyncMethod(string method); | ||
|
||
[Theory] | ||
[InlineData("Throws")] | ||
[InlineData("ThrowsForAnyArgs")] | ||
public abstract Task ReportsDiagnostic_WhenUsedWithGenericAsyncMethod(string method); | ||
|
||
[Theory] | ||
[InlineData("Throws")] | ||
[InlineData("ThrowsForAnyArgs")] | ||
public abstract Task ReportsNoDiagnostic_WhenUsedWithSyncMethod(string method); | ||
} | ||
} |
88 changes: 88 additions & 0 deletions
88
...s.CSharp/DiagnosticAnalyzerTests/AsyncThrowsAnalyzerTests/ThrowsAsExtensionMethodTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
using System.Threading.Tasks; | ||
|
||
namespace NSubstitute.Analyzers.Tests.CSharp.DiagnosticAnalyzerTests.AsyncThrowsAnalyzerTests | ||
{ | ||
public class ThrowsAsExtensionMethodTests : AsyncThrowsDiagnosticVerifier | ||
{ | ||
public override async Task ReportsDiagnostic_WhenUsedWithNonGenericAsyncMethod(string method) | ||
{ | ||
var source = $@"using System; | ||
using System.Threading.Tasks; | ||
using NSubstitute; | ||
using NSubstitute.ExceptionExtensions; | ||
namespace MyNamespace | ||
{{ | ||
public interface IFoo | ||
{{ | ||
Task Bar(); | ||
}} | ||
public class FooTests | ||
{{ | ||
public void Test() | ||
{{ | ||
var substitute = NSubstitute.Substitute.For<IFoo>(); | ||
[|substitute.Bar().{method}(new Exception())|]; | ||
}} | ||
}} | ||
}}"; | ||
|
||
await VerifyDiagnostic(source, AsyncThrowsDescriptor); | ||
} | ||
|
||
public override async Task ReportsDiagnostic_WhenUsedWithGenericAsyncMethod(string method) | ||
{ | ||
var source = $@"using System; | ||
using System.Threading.Tasks; | ||
using NSubstitute; | ||
using NSubstitute.ExceptionExtensions; | ||
namespace MyNamespace | ||
{{ | ||
public interface IFoo | ||
{{ | ||
Task<object> Bar(); | ||
}} | ||
public class FooTests | ||
{{ | ||
public void Test() | ||
{{ | ||
var substitute = NSubstitute.Substitute.For<IFoo>(); | ||
[|substitute.Bar().{method}(new Exception())|]; | ||
}} | ||
}} | ||
}}"; | ||
|
||
await VerifyDiagnostic(source, AsyncThrowsDescriptor); | ||
} | ||
|
||
public override async Task ReportsNoDiagnostic_WhenUsedWithSyncMethod(string method) | ||
{ | ||
var source = $@"using System; | ||
using System.Threading.Tasks; | ||
using NSubstitute; | ||
using NSubstitute.ExceptionExtensions; | ||
namespace MyNamespace | ||
{{ | ||
public interface IFoo | ||
{{ | ||
object Bar(); | ||
}} | ||
public class FooTests | ||
{{ | ||
public void Test() | ||
{{ | ||
var substitute = NSubstitute.Substitute.For<IFoo>(); | ||
substitute.Bar().{method}(new Exception()); | ||
}} | ||
}} | ||
}}"; | ||
|
||
await VerifyNoDiagnostic(source); | ||
} | ||
} | ||
} |
Oops, something went wrong.