Skip to content

Commit

Permalink
Merge pull request #90 from Concurrency-Lab/ph-s032-exclude-argumentn…
Browse files Browse the repository at this point in the history
…ullexception

PH_S032: Exclude sub-types
  • Loading branch information
camrein authored Nov 19, 2021
2 parents 62e0499 + 25164df commit 2e01b07
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 11 deletions.
2 changes: 1 addition & 1 deletion doc/analyzers/PH_S032.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Encapsulate the exceptions in a task using `Task.FromException(...)`.
## Options

```ini
# A white-space separated list of exception types to not report
# A white-space separated list of exception types to not report. The sub-types of these types will be excluded too.
# Format: <type-specifier1> <type-specifier2> ...
dotnet_diagnostic.PH_S032.exclusions = System.ArgumentException System.NotImplementedException
```
Original file line number Diff line number Diff line change
Expand Up @@ -110,5 +110,25 @@ public Task DoWorkAsync(int input) {
.AddAnalyzerOption("dotnet_diagnostic.PH_S032.exclusions", "System.InvalidOperationException")
.VerifyDiagnostic();
}

[TestMethod]
public void DoesNotReportNonAsyncMethodWithAsyncSuffixAndReturningTaskThatUsesThrowsExpressionWithSubTypeOfExcludedType() {
const string source = @"
using System;
using System.Threading.Tasks;
class Test {
private string value;
public Task<int> DoWorkAsync(string value) {
this.value = value ?? throw new ArgumentNullException(nameof(value));
return Task.FromResult(value.Length);
}
}";
CreateAnalyzerCompilationBuilder()
.AddSourceTexts(source)
.AddAnalyzerOption("dotnet_diagnostic.PH_S032.exclusions", "System.ArgumentException")
.VerifyDiagnostic();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public class ThrowsInPotentiallyAsyncMethodAnalyzer : DiagnosticAnalyzer {
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);

private const string AsyncSuffix = "Async";
private const string DefaultExcludedTypes = "System.ArgumentException System.NotImplementedException";
private const string DefaultExcludedBaseTypes = "System.ArgumentException System.NotImplementedException";

public override void Initialize(AnalysisContext context) {
context.EnableConcurrentExecution();
Expand Down Expand Up @@ -73,8 +73,10 @@ public override void Analyze() {
}
}

private IEnumerable<string> GetExcludedExceptionTypes() {
return Context.Options.GetConfig(Rule, "exclusions", DefaultExcludedTypes).Split();
private IEnumerable<ITypeSymbol> GetExcludedExceptionBaseTypes() {
return Context.Options.GetConfig(Rule, "exclusions", DefaultExcludedBaseTypes)
.Split()
.SelectMany(SemanticModel.GetTypesByName);
}

private bool IsPotentiallyAsyncMethod() {
Expand All @@ -89,24 +91,25 @@ private bool ReturnsTaskObject() {
}

private IEnumerable<SyntaxNode> GetAllThrowsStatementsAndExpressionsInSameActivationFrame() {
var excludedExceptionTypes = GetExcludedExceptionTypes().ToArray();
var excludedBaseTypes = GetExcludedExceptionBaseTypes().ToArray();
return Root.DescendantNodesInSameActivationFrame()
.WithCancellation(CancellationToken)
.Where(node => IsThrowsWithoutExcludedType(node, excludedExceptionTypes));
.Where(node => IsThrowsWithoutExcludedType(node, excludedBaseTypes));
}

private bool IsThrowsWithoutExcludedType(SyntaxNode node, IEnumerable<string> excludedTypes) {
private bool IsThrowsWithoutExcludedType(SyntaxNode node, IEnumerable<ITypeSymbol> excludedBaseTypes) {

return node switch {
ThrowStatementSyntax statement => !IsAnyOfType(statement.Expression, excludedTypes),
ThrowExpressionSyntax expression => !IsAnyOfType(expression.Expression, excludedTypes),
ThrowStatementSyntax statement => !IsAnySubTypeOf(statement.Expression, excludedBaseTypes),
ThrowExpressionSyntax expression => !IsAnySubTypeOf(expression.Expression, excludedBaseTypes),
_ => false
};
}

private bool IsAnyOfType(SyntaxNode node, IEnumerable<string> types) {
private bool IsAnySubTypeOf(SyntaxNode node, IEnumerable<ITypeSymbol> types) {
var nodeType = SemanticModel.GetTypeInfo(node, CancellationToken).Type;
return nodeType != null
&& types.Any(type => SemanticModel.IsEqualType(nodeType, type));
&& types.Any(baseType => baseType.IsBaseTypeOf(nodeType, CancellationToken));
}
}
}
Expand Down
15 changes: 15 additions & 0 deletions src/ParallelHelper/Extensions/TypeSymbolExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Microsoft.CodeAnalysis;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

namespace ParallelHelper.Extensions {
/// <summary>
Expand Down Expand Up @@ -77,5 +78,19 @@ public static IEnumerable<ISymbol> GetAllPublicMembers(this ITypeSymbol type) {
public static IEnumerable<ISymbol> GetAllMembers(this ITypeSymbol type) {
return GetAllBaseTypesAndSelf(type).SelectMany(baseType => baseType.GetMembers());
}

/// <summary>
/// Checks if the given type is a base type of the given type.
/// </summary>
/// <param name="baseType">The base type to check.</param>
/// <param name="subType">The type to check if it's a sub type of the given base type.</param>
/// <param name="cancellationToken">A token to stop the check before completion.</param>
/// <returns><c>True</c> if the given type is a sub-type of the given base type.</returns>
/// <remarks>This check does not include interfaces.</remarks>
public static bool IsBaseTypeOf(this ITypeSymbol baseType, ITypeSymbol subType, CancellationToken cancellationToken) {
return subType.GetAllBaseTypesAndSelf()
.WithCancellation(cancellationToken)
.Any(type => baseType.Equals(type, SymbolEqualityComparer.Default));
}
}
}

0 comments on commit 2e01b07

Please sign in to comment.