Skip to content

Commit 4e2fe19

Browse files
Legacy redundancy checks #228 (#229)
* Legacy redundancy checks #228 Add option for basic redundancy checks when control flow analysis disabled * Restore sample project * Update CHANGELOG.md * Update analyzer-specification.md
1 parent a0ccc6c commit 4e2fe19

9 files changed

Lines changed: 98 additions & 65 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111
### Added
1212

1313
- PR [#226](https://github.com/marinasundstrom/CheckedExceptions/issues/226) Enable codefix "Remove redundant catch clause" for catch all
14+
- PR [#229](https://github.com/marinasundstrom/CheckedExceptions/issues/229) Legacy redundancy checks
1415

1516
## [1.9.6] - 2025-08-05
1617

CheckedExceptions.Package/docs/README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,10 @@ Add `CheckedExceptions.settings.json`:
148148
"disableXmlDocInterop": false,
149149

150150
// If true, control flow analysis, with redundancy checks, is disabled (default: false).
151-
"disableControlFlowAnalysis": true
151+
"disableControlFlowAnalysis": false,
152+
153+
// If true, basic redundancy checks are available when control flow analysis is disabled (default: false).
154+
"enableLegacyRedundancyChecks": false
152155
}
153156
```
154157

CheckedExceptions/AnalyzerSettings.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,14 @@ public partial class AnalyzerSettings
1313
[JsonPropertyName("disableControlFlowAnalysis")]
1414
public bool DisableControlFlowAnalysis { get; set; } = false;
1515

16+
[JsonPropertyName("enableLegacyRedundancyChecks")]
17+
public bool EnableLegacyRedundancyChecks { get; set; } = false;
18+
1619
[JsonIgnore]
1720
internal bool IsControlFlowAnalysisEnabled => !DisableControlFlowAnalysis;
1821

22+
internal bool IsLegacyRedundancyChecksEnabled => EnableLegacyRedundancyChecks;
23+
1924
[JsonPropertyName("ignoredExceptions")]
2025
public IEnumerable<string> IgnoredExceptions { get; set; } = new List<string>();
2126

CheckedExceptions/CheckedExceptionsAnalyzer.cs

Lines changed: 42 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,7 @@ private static void CheckNoThrowsOnFullPropertyDecl(SyntaxNodeAnalysisContext co
358358
}
359359
}
360360

361+
// Legacy redundancy check
361362
private void AnalyzeTryStatement(SyntaxNodeAnalysisContext context)
362363
{
363364
var tryStatement = context.Node as TryStatementSyntax;
@@ -367,47 +368,57 @@ private void AnalyzeTryStatement(SyntaxNodeAnalysisContext context)
367368

368369
var settings = GetAnalyzerSettings(context.Options);
369370

370-
var semanticModel = context.SemanticModel;
371-
372-
// Check for redundant typed catch clauses
373-
foreach (var catchClause in tryStatement.Catches)
371+
if (!settings.IsControlFlowAnalysisEnabled && settings.IsLegacyRedundancyChecksEnabled)
374372
{
375-
if (catchClause.Declaration?.Type is null)
376-
{
377-
var thrownExceptions = CollectUnhandledExceptions(context, tryStatement.Block, settings);
373+
var semanticModel = context.SemanticModel;
378374

379-
if (thrownExceptions.Count > 0)
380-
continue;
375+
var thrownExceptions = CollectUnhandledExceptions(context, tryStatement.Block, settings);
381376

382-
// Report redundant catch clause
383-
/*var diagnostic = Diagnostic.Create(
384-
RuleRedundantCatchAllClause,
385-
catchClause.CatchKeyword.GetLocation());
377+
HashSet<INamespaceOrTypeSymbol> unhandledExceptions = new HashSet<INamespaceOrTypeSymbol>(thrownExceptions, SymbolEqualityComparer.Default);
386378

387-
context.ReportDiagnostic(diagnostic);*/
388-
}
389-
else
379+
// Check for redundant typed catch clauses
380+
foreach (var catchClause in tryStatement.Catches)
390381
{
391-
var catchType = semanticModel.GetTypeInfo(catchClause.Declaration.Type).Type as INamedTypeSymbol;
392-
if (catchType is null)
393-
continue;
382+
if (catchClause.Declaration?.Type is null)
383+
{
384+
if (unhandledExceptions.Count > 0)
385+
continue;
394386

395-
var thrownExceptions = CollectUnhandledExceptions(context, tryStatement.Block, settings);
387+
unhandledExceptions.Clear();
396388

397-
// Check if any thrown exception matches or derives from this catch type
398-
bool isRelevant = thrownExceptions.OfType<INamedTypeSymbol>().Any(thrown =>
399-
thrown.Equals(catchType, SymbolEqualityComparer.Default) ||
400-
thrown.InheritsFrom(catchType));
389+
// Report redundant catch clause
390+
var diagnostic = Diagnostic.Create(
391+
RuleRedundantCatchAllClause,
392+
catchClause.CatchKeyword.GetLocation());
401393

402-
if (!isRelevant)
394+
context.ReportDiagnostic(diagnostic);
395+
}
396+
else
403397
{
404-
// Report redundant catch clause
405-
/*var diagnostic = Diagnostic.Create(
406-
RuleRedundantTypedCatchClause,
407-
catchClause.Declaration.Type.GetLocation(),
408-
catchType.Name);
398+
var catchType = semanticModel.GetTypeInfo(catchClause.Declaration.Type).Type as INamedTypeSymbol;
399+
if (catchType is null)
400+
continue;
409401

410-
context.ReportDiagnostic(diagnostic);*/
402+
// Update unhandled set
403+
unhandledExceptions.RemoveWhere(thrown =>
404+
SymbolEqualityComparer.Default.Equals(thrown, catchType) ||
405+
(thrown is INamedTypeSymbol named && named.InheritsFrom(catchType)));
406+
407+
// Check if any thrown exception matches or derives from this catch type
408+
bool isRelevant = thrownExceptions.OfType<INamedTypeSymbol>().Any(thrown =>
409+
thrown.Equals(catchType, SymbolEqualityComparer.Default) ||
410+
thrown.InheritsFrom(catchType));
411+
412+
if (!isRelevant)
413+
{
414+
// Report redundant catch clause
415+
var diagnostic = Diagnostic.Create(
416+
RuleRedundantTypedCatchClause,
417+
catchClause.Declaration.Type.GetLocation(),
418+
catchType.Name);
419+
420+
context.ReportDiagnostic(diagnostic);
421+
}
411422
}
412423
}
413424
}

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,10 @@ Add `CheckedExceptions.settings.json`:
148148
"disableXmlDocInterop": false,
149149

150150
// If true, control flow analysis, with redundancy checks, is disabled (default: false).
151-
"disableControlFlowAnalysis": true
151+
"disableControlFlowAnalysis": false,
152+
153+
// If true, basic redundancy checks are available when control flow analysis is disabled (default: false).
154+
"enableLegacyRedundancyChecks": false
152155
}
153156
```
154157

SampleProject/Program.cs

Lines changed: 18 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,36 @@
11
try
22
{
3-
NewMethod();
3+
int result = ReadAndParse();
4+
Console.WriteLine(result);
45
}
5-
catch (InvalidUserInputException invalidUserInputException)
6+
catch (InvalidUserInputException ex)
67
{
8+
Console.WriteLine($"Input error: {ex.Message}");
79
}
810

9-
public class Foo
11+
[Throws(typeof(InvalidUserInputException))] // ✔️ Only the domain-specific exception is exposed
12+
static int ReadAndParse()
1013
{
14+
string input = "abc"; // Simulated input — could be user input in real scenarios
1115

12-
[Throws(typeof(InvalidUserInputException))] // ✔️ Only the domain-specific exception is exposed
13-
static int ReadAndParse()
16+
try
1417
{
15-
string input = "abc"; // Simulated input — could be user input in real scenarios
16-
17-
try
18-
{
19-
return int.Parse(input);
20-
}
21-
catch (FormatException formatException)
22-
{
23-
// Handle and rethrow as domain-specific exception
24-
throw new InvalidUserInputException("Input was not a valid number.", formatException);
25-
}
26-
catch (OverflowException overflowException)
27-
{
28-
// Handle and rethrow as domain-specific exception
29-
throw new InvalidUserInputException("Input number was too large.", overflowException);
30-
}
18+
return int.Parse(input);
3119
}
32-
33-
/// <summary>
34-
/// Test
35-
/// </summary>
36-
/// <exception cref="InvalidUserInputException" />
37-
[Throws(typeof(InvalidUserInputException))]
38-
static void NewMethod()
20+
catch (FormatException formatException)
3921
{
40-
int result = ReadAndParse();
41-
Console.WriteLine(result);
22+
// Handle and rethrow as domain-specific exception
23+
throw new InvalidUserInputException("Input was not a valid number.", formatException);
24+
}
25+
catch (OverflowException overflowException)
26+
{
27+
// Handle and rethrow as domain-specific exception
28+
throw new InvalidUserInputException("Input number was too large.", overflowException);
4229
}
4330
}
4431

4532
class InvalidUserInputException : Exception
4633
{
4734
public InvalidUserInputException(string message, Exception inner)
4835
: base(message, inner) { }
49-
}
36+
}

Test/CheckedExceptions.settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@
66
"System.TimeoutException": "Always"
77
},
88
"disableXmlDocInterop": false,
9-
"disableControlFlowAnalysis": false
9+
"disableControlFlowAnalysis": false,
10+
"enableLegacyRedundancyChecks": false
1011
}

docs/analyzer-specification.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,3 +417,20 @@ When set, the analyzer will still report **unhandled exceptions** and enforce `[
417417
* Detect redundant catch clauses (`THROW009`, `THROW013`)
418418
* Report redundant exception declarations (`THROW012`, `THROW008`)
419419
* Highlight unreachable code (IDE gray‑out support)
420+
421+
### Enable legacy redundancy checks
422+
423+
> This option enables a simplified _light mode_.
424+
425+
```json
426+
{
427+
"disableControlFlowAnalysis": true, // prerequisite
428+
"enableLegacyRedundancyChecks": true
429+
}
430+
```
431+
432+
When enabled, the analyzer performs **basic redundancy checks** without relying on full control flow analysis.
433+
434+
It provides:
435+
436+
* Detection of redundant catch clauses (`THROW009`, `THROW013`)

schemas/settings-schema.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@
1212
"default": false,
1313
"description": "Indicates whether control flow analysis is disabled."
1414
},
15+
"enableLegacyRedundancyChecks": {
16+
"type": "boolean",
17+
"default": false,
18+
"description": "Indicates whether redundancy checks are available when control flow analysis is disabled."
19+
},
1520
"ignoredExceptions": {
1621
"type": "array",
1722
"items": {

0 commit comments

Comments
 (0)