Skip to content

Commit 2e01b07

Browse files
authored
Merge pull request #90 from Concurrency-Lab/ph-s032-exclude-argumentnullexception
PH_S032: Exclude sub-types
2 parents 62e0499 + 25164df commit 2e01b07

File tree

4 files changed

+49
-11
lines changed

4 files changed

+49
-11
lines changed

doc/analyzers/PH_S032.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ Encapsulate the exceptions in a task using `Task.FromException(...)`.
1212
## Options
1313

1414
```ini
15-
# A white-space separated list of exception types to not report
15+
# A white-space separated list of exception types to not report. The sub-types of these types will be excluded too.
1616
# Format: <type-specifier1> <type-specifier2> ...
1717
dotnet_diagnostic.PH_S032.exclusions = System.ArgumentException System.NotImplementedException
1818
```

src/ParallelHelper.Test/Analyzer/Smells/ThrowsInPotentiallyAsyncMethodAnalyzerTest.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,5 +110,25 @@ public Task DoWorkAsync(int input) {
110110
.AddAnalyzerOption("dotnet_diagnostic.PH_S032.exclusions", "System.InvalidOperationException")
111111
.VerifyDiagnostic();
112112
}
113+
114+
[TestMethod]
115+
public void DoesNotReportNonAsyncMethodWithAsyncSuffixAndReturningTaskThatUsesThrowsExpressionWithSubTypeOfExcludedType() {
116+
const string source = @"
117+
using System;
118+
using System.Threading.Tasks;
119+
120+
class Test {
121+
private string value;
122+
123+
public Task<int> DoWorkAsync(string value) {
124+
this.value = value ?? throw new ArgumentNullException(nameof(value));
125+
return Task.FromResult(value.Length);
126+
}
127+
}";
128+
CreateAnalyzerCompilationBuilder()
129+
.AddSourceTexts(source)
130+
.AddAnalyzerOption("dotnet_diagnostic.PH_S032.exclusions", "System.ArgumentException")
131+
.VerifyDiagnostic();
132+
}
113133
}
114134
}

src/ParallelHelper/Analyzer/Smells/ThrowsInPotentiallyAsyncMethodAnalyzer.cs

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public class ThrowsInPotentiallyAsyncMethodAnalyzer : DiagnosticAnalyzer {
4242
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
4343

4444
private const string AsyncSuffix = "Async";
45-
private const string DefaultExcludedTypes = "System.ArgumentException System.NotImplementedException";
45+
private const string DefaultExcludedBaseTypes = "System.ArgumentException System.NotImplementedException";
4646

4747
public override void Initialize(AnalysisContext context) {
4848
context.EnableConcurrentExecution();
@@ -73,8 +73,10 @@ public override void Analyze() {
7373
}
7474
}
7575

76-
private IEnumerable<string> GetExcludedExceptionTypes() {
77-
return Context.Options.GetConfig(Rule, "exclusions", DefaultExcludedTypes).Split();
76+
private IEnumerable<ITypeSymbol> GetExcludedExceptionBaseTypes() {
77+
return Context.Options.GetConfig(Rule, "exclusions", DefaultExcludedBaseTypes)
78+
.Split()
79+
.SelectMany(SemanticModel.GetTypesByName);
7880
}
7981

8082
private bool IsPotentiallyAsyncMethod() {
@@ -89,24 +91,25 @@ private bool ReturnsTaskObject() {
8991
}
9092

9193
private IEnumerable<SyntaxNode> GetAllThrowsStatementsAndExpressionsInSameActivationFrame() {
92-
var excludedExceptionTypes = GetExcludedExceptionTypes().ToArray();
94+
var excludedBaseTypes = GetExcludedExceptionBaseTypes().ToArray();
9395
return Root.DescendantNodesInSameActivationFrame()
9496
.WithCancellation(CancellationToken)
95-
.Where(node => IsThrowsWithoutExcludedType(node, excludedExceptionTypes));
97+
.Where(node => IsThrowsWithoutExcludedType(node, excludedBaseTypes));
9698
}
9799

98-
private bool IsThrowsWithoutExcludedType(SyntaxNode node, IEnumerable<string> excludedTypes) {
100+
private bool IsThrowsWithoutExcludedType(SyntaxNode node, IEnumerable<ITypeSymbol> excludedBaseTypes) {
101+
99102
return node switch {
100-
ThrowStatementSyntax statement => !IsAnyOfType(statement.Expression, excludedTypes),
101-
ThrowExpressionSyntax expression => !IsAnyOfType(expression.Expression, excludedTypes),
103+
ThrowStatementSyntax statement => !IsAnySubTypeOf(statement.Expression, excludedBaseTypes),
104+
ThrowExpressionSyntax expression => !IsAnySubTypeOf(expression.Expression, excludedBaseTypes),
102105
_ => false
103106
};
104107
}
105108

106-
private bool IsAnyOfType(SyntaxNode node, IEnumerable<string> types) {
109+
private bool IsAnySubTypeOf(SyntaxNode node, IEnumerable<ITypeSymbol> types) {
107110
var nodeType = SemanticModel.GetTypeInfo(node, CancellationToken).Type;
108111
return nodeType != null
109-
&& types.Any(type => SemanticModel.IsEqualType(nodeType, type));
112+
&& types.Any(baseType => baseType.IsBaseTypeOf(nodeType, CancellationToken));
110113
}
111114
}
112115
}

src/ParallelHelper/Extensions/TypeSymbolExtensions.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using Microsoft.CodeAnalysis;
22
using System.Collections.Generic;
33
using System.Linq;
4+
using System.Threading;
45

56
namespace ParallelHelper.Extensions {
67
/// <summary>
@@ -77,5 +78,19 @@ public static IEnumerable<ISymbol> GetAllPublicMembers(this ITypeSymbol type) {
7778
public static IEnumerable<ISymbol> GetAllMembers(this ITypeSymbol type) {
7879
return GetAllBaseTypesAndSelf(type).SelectMany(baseType => baseType.GetMembers());
7980
}
81+
82+
/// <summary>
83+
/// Checks if the given type is a base type of the given type.
84+
/// </summary>
85+
/// <param name="baseType">The base type to check.</param>
86+
/// <param name="subType">The type to check if it's a sub type of the given base type.</param>
87+
/// <param name="cancellationToken">A token to stop the check before completion.</param>
88+
/// <returns><c>True</c> if the given type is a sub-type of the given base type.</returns>
89+
/// <remarks>This check does not include interfaces.</remarks>
90+
public static bool IsBaseTypeOf(this ITypeSymbol baseType, ITypeSymbol subType, CancellationToken cancellationToken) {
91+
return subType.GetAllBaseTypesAndSelf()
92+
.WithCancellation(cancellationToken)
93+
.Any(type => baseType.Equals(type, SymbolEqualityComparer.Default));
94+
}
8095
}
8196
}

0 commit comments

Comments
 (0)