Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- PR [#301](https://github.com/marinasundstrom/CheckedExceptions/pull/301) Allow treating `Exception` in `[Throws]` as a catch-all via `treatThrowsExceptionAsCatchRest` setting (base-type diagnostic unchanged)
- PR [#PR_NUMBER](https://github.com/marinasundstrom/CheckedExceptions/pull/PR_NUMBER) Provide comprehensive baseline exception classifications in `default-settings.json`

### Changed

- PR [#PR_NUMBER](https://github.com/marinasundstrom/CheckedExceptions/pull/PR_NUMBER) Replace `ignoredExceptions` and `informationalExceptions` with explicit `exceptions` classification map
- PR [#PR_NUMBER](https://github.com/marinasundstrom/CheckedExceptions/pull/PR_NUMBER) Document explicit exception taxonomy and strict default for unlisted types in README and docs

### Deprecated

- PR [#PR_NUMBER](https://github.com/marinasundstrom/CheckedExceptions/pull/PR_NUMBER) Support legacy `ignoredExceptions` and `informationalExceptions` settings by translating them to `exceptions`

## [2.2.3] - 2025-08-24

Expand Down
16 changes: 8 additions & 8 deletions CheckedExceptions.Tests/AnazylerConfigTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,12 @@ public void TestMethod2()
}
""";

var expected1 = Verifier.Informational("IOException")
.WithSpan(9, 21, 9, 39);
var expected2 = Verifier.Diagnostic(CheckedExceptionsAnalyzer.DiagnosticIdRedundantCatchAllClause)
.WithSpan(11, 9, 11, 14);
await Verifier.VerifyAnalyzerAsync2(test, expected1, expected2);
}
var expected1 = Verifier.Informational("IOException")
.WithSpan(9, 21, 9, 39);

var expected2 = Verifier.Informational("IOException")
.WithSpan(13, 13, 13, 19);

await Verifier.VerifyAnalyzerAsync2(test, expected1, expected2);
}
}
4 changes: 2 additions & 2 deletions CheckedExceptions.Tests/CSharpAnalyzerVerifier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@ await VerifyAnalyzerAsync(source, (test) =>
"System.NotImplementedException"
],
"informationalExceptions": {
"System.IO.IOException": "Always",
"System.TimeoutException": "Always"
"System.IO.IOException": "Propagation",
"System.TimeoutException": "Propagation"
}
}
""""));
Expand Down
12 changes: 5 additions & 7 deletions CheckedExceptions.Tests/CheckedExceptions.settings.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
{
"ignoredExceptions": [
"System.ArgumentNullException"
],
"informationalExceptions": {
"System.NotImplementedException": "Propagation",
"System.IO.IOException": "Propagation",
"System.TimeoutException": "Always"
"exceptions": {
"System.ArgumentNullException": "Ignored",
"System.NotImplementedException": "Informational",
"System.IO.IOException": "Informational",
"System.TimeoutException": "Informational"
},
"disableXmlDocInterop": false,
"disableLinqSupport": false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,7 @@ await Verifier.VerifyAnalyzerAsync(test, t =>
{
t.TestState.AdditionalFiles.Add(("CheckedExceptions.settings.json", """
{
"ignoredExceptions": [],
"informationalExceptions": {},
"exceptions": {},
"treatThrowsExceptionAsCatchRest": true
}
"""));
Expand Down
47 changes: 41 additions & 6 deletions CheckedExceptions/AnalyzerSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,17 +64,52 @@ public partial class AnalyzerSettings
[JsonIgnore]
internal bool TreatThrowsExceptionAsCatchRestEnabled => TreatThrowsExceptionAsCatchRest;

[JsonPropertyName("exceptions")]
public IDictionary<string, ExceptionClassification> Exceptions { get; set; } = new Dictionary<string, ExceptionClassification>();

[JsonPropertyName("ignoredExceptions")]
public IEnumerable<string> IgnoredExceptions { get; set; } = new List<string>();
[Obsolete("Use 'exceptions' instead.")]
public IList<string>? IgnoredExceptions
{
get => null;
set
{
if (value is null)
{
return;
}

foreach (var exception in value)
{
Exceptions[exception] = ExceptionClassification.Ignored;
}
}
}

[JsonPropertyName("informationalExceptions")]
public IDictionary<string, ExceptionMode> InformationalExceptions { get; set; } = new Dictionary<string, ExceptionMode>();
[Obsolete("Use 'exceptions' instead.")]
public IDictionary<string, string>? InformationalExceptions
{
get => null;
set
{
if (value is null)
{
return;
}

foreach (var exception in value.Keys)
{
Exceptions[exception] = ExceptionClassification.Informational;
}
}
}
}

[JsonConverter(typeof(JsonStringEnumConverter))]
public enum ExceptionMode
public enum ExceptionClassification
{
Throw = 1,
Propagation = 2,
Always = Throw | Propagation
Ignored,
Informational,
Strict
}
35 changes: 15 additions & 20 deletions CheckedExceptions/CheckedExceptionsAnalyzer.Analysis.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,22 +48,17 @@ private static void AnalyzeExceptionsInTryBlock(SyntaxNodeAnalysisContext contex
// For each thrown exception, check if it is handled
foreach (var exceptionType in thrownExceptions.Distinct(SymbolEqualityComparer.Default).OfType<INamedTypeSymbol>())
{
var exceptionName = exceptionType.ToDisplayString();
var classification = GetExceptionClassification(exceptionType, settings);

if (FilterIgnored(settings, exceptionName))
if (classification is ExceptionClassification.Ignored)
{
// Completely ignore this exception
continue;
}
else if (settings.InformationalExceptions.TryGetValue(exceptionName, out var mode))
else if (classification is ExceptionClassification.Informational)
{
if (ShouldIgnore(throwStatement, mode))
{
// Report as THROW002 (Info level)
var diagnostic = Diagnostic.Create(RuleIgnoredException, GetSignificantLocation(throwStatement), exceptionType.Name);
context.ReportDiagnostic(diagnostic);
continue;
}
var diagnostic = Diagnostic.Create(RuleIgnoredException, GetSignificantLocation(throwStatement), exceptionType.Name);
context.ReportDiagnostic(diagnostic);
continue;
}

// ① handled by any typed catch BEFORE the general catch?
Expand Down Expand Up @@ -168,7 +163,7 @@ private static HashSet<INamedTypeSymbol> CollectExceptionsFromStatement(Statemen
var exceptionType = semanticModel.GetTypeInfo(throwStatement.Expression).Type as INamedTypeSymbol;
if (exceptionType is not null)
{
if (ShouldIncludeException(exceptionType, throwStatement, settings))
if (ShouldIncludeException(exceptionType, settings))
{
exceptions.Add(exceptionType);
}
Expand Down Expand Up @@ -199,7 +194,7 @@ private static void CollectExceptionsFromExpression(SyntaxNode expression, Compi
var exceptionType = semanticModel.GetTypeInfo(throwExpression.Expression).Type as INamedTypeSymbol;
if (exceptionType is not null)
{
if (ShouldIncludeException(exceptionType, throwExpression, settings))
if (ShouldIncludeException(exceptionType, settings))
{
exceptions.Add(exceptionType);
}
Expand All @@ -225,7 +220,7 @@ private static void CollectExceptionsFromExpression(SyntaxNode expression, Compi

foreach (var exceptionType in exceptionTypes)
{
if (ShouldIncludeException(exceptionType, invocation, settings))
if (ShouldIncludeException(exceptionType, settings))
{
exceptions.Add(exceptionType);
}
Expand All @@ -250,7 +245,7 @@ private static void CollectExceptionsFromExpression(SyntaxNode expression, Compi

foreach (var exceptionType in exceptionTypes)
{
if (ShouldIncludeException(exceptionType, invocation, settings))
if (ShouldIncludeException(exceptionType, settings))
{
exceptions.Add(exceptionType);
}
Expand Down Expand Up @@ -282,7 +277,7 @@ private static void CollectExceptionsFromExpression(SyntaxNode expression, Compi

foreach (var exceptionType in exceptionTypes)
{
if (ShouldIncludeException(exceptionType, objectCreation, settings))
if (ShouldIncludeException(exceptionType, settings))
{
exceptions.Add(exceptionType);
}
Expand All @@ -301,7 +296,7 @@ private static void CollectExceptionsFromExpression(SyntaxNode expression, Compi

foreach (var exceptionType in exceptionTypes)
{
if (ShouldIncludeException(exceptionType, memberAccess, settings))
if (ShouldIncludeException(exceptionType, settings))
{
exceptions.Add(exceptionType);
}
Expand All @@ -319,7 +314,7 @@ private static void CollectExceptionsFromExpression(SyntaxNode expression, Compi

foreach (var exceptionType in exceptionTypes)
{
if (ShouldIncludeException(exceptionType, elementAccess, settings))
if (ShouldIncludeException(exceptionType, settings))
{
exceptions.Add(exceptionType);
}
Expand All @@ -339,7 +334,7 @@ private static void CollectExceptionsFromExpression(SyntaxNode expression, Compi
{
if (exceptionType is not null)
{
if (ShouldIncludeException(exceptionType, identifier, settings))
if (ShouldIncludeException(exceptionType, settings))
{
exceptions.Add(exceptionType);
}
Expand All @@ -361,7 +356,7 @@ private static void CollectExceptionsFromExpression(SyntaxNode expression, Compi

if (invalidCastException is not null)
{
if (ShouldIncludeException(invalidCastException, castExpression, settings))
if (ShouldIncludeException(invalidCastException, settings))
{
exceptions.Add(invalidCastException);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using Microsoft.CodeAnalysis;

namespace Sundstrom.CheckedExceptions;

partial class CheckedExceptionsAnalyzer
{
public static ExceptionClassification GetExceptionClassification(
INamedTypeSymbol exceptionType,
AnalyzerSettings settings)
{
var exceptionName = exceptionType.ToDisplayString();

if (settings.Exceptions.TryGetValue(exceptionName, out var classification))
{
return classification;
}

return ExceptionClassification.Strict;
}

public static bool ShouldIncludeException(
INamedTypeSymbol exceptionType,
AnalyzerSettings settings)
=> GetExceptionClassification(exceptionType, settings) != ExceptionClassification.Ignored;
}

100 changes: 0 additions & 100 deletions CheckedExceptions/CheckedExceptionsAnalyzer.IgnoredExceptions.cs

This file was deleted.

Loading
Loading