Skip to content

Commit 33ea27d

Browse files
Add code fix for redundant exception declarations (#295)
* Add code fix for redundant exception declarations * Update CHANGELOG.md * Fix tests
1 parent 753f6a5 commit 33ea27d

File tree

5 files changed

+175
-2
lines changed

5 files changed

+175
-2
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88

99
## Unreleased
1010

11+
### Added
12+
13+
- PR [#295](https://github.com/marinasundstrom/CheckedExceptions/pull/295) Code fix to remove redundant exception declarations
14+
1115
## [2.2.0] - 2025-08-23
1216

1317
### Added

CheckedExceptions.CodeFixes/CodeFixReleases.Unshipped.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
### New Rules
44

55
- THROW017: Materialize deferred enumeration with ToArray
6+
- THROW012: Remove redundant exception declaration
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
using System.Collections.Immutable;
2+
using System.Composition;
3+
using System.Linq;
4+
5+
using Microsoft.CodeAnalysis;
6+
using Microsoft.CodeAnalysis.CodeActions;
7+
using Microsoft.CodeAnalysis.CodeFixes;
8+
using Microsoft.CodeAnalysis.CSharp.Syntax;
9+
using Microsoft.CodeAnalysis.Editing;
10+
using Microsoft.CodeAnalysis.Formatting;
11+
12+
namespace Sundstrom.CheckedExceptions;
13+
14+
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(RemoveRedundantExceptionDeclarationCodeFixProvider)), Shared]
15+
public class RemoveRedundantExceptionDeclarationCodeFixProvider : CodeFixProvider
16+
{
17+
private const string TitleRemoveRedundantExceptionDeclaration = "Remove redundant exception declaration";
18+
19+
public sealed override ImmutableArray<string> FixableDiagnosticIds =>
20+
[CheckedExceptionsAnalyzer.DiagnosticIdDuplicateDeclarations,
21+
CheckedExceptionsAnalyzer.DiagnosticIdDuplicateThrowsByHierarchy,
22+
CheckedExceptionsAnalyzer.DiagnosticIdRedundantExceptionDeclaration];
23+
24+
public sealed override FixAllProvider GetFixAllProvider() =>
25+
WellKnownFixAllProviders.BatchFixer;
26+
27+
public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
28+
{
29+
var diagnostic = context.Diagnostics.First();
30+
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
31+
var node = root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true);
32+
var argument = node.FirstAncestorOrSelf<AttributeArgumentSyntax>();
33+
34+
if (argument is null)
35+
return;
36+
37+
context.RegisterCodeFix(
38+
CodeAction.Create(
39+
title: TitleRemoveRedundantExceptionDeclaration,
40+
createChangedDocument: c => RemoveRedundantDeclarationAsync(context.Document, argument, c),
41+
equivalenceKey: TitleRemoveRedundantExceptionDeclaration),
42+
context.Diagnostics);
43+
}
44+
45+
private static async Task<Document> RemoveRedundantDeclarationAsync(Document document, AttributeArgumentSyntax argument, CancellationToken cancellationToken)
46+
{
47+
var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false);
48+
49+
var attribute = (AttributeSyntax)argument.Parent.Parent;
50+
var list = (AttributeListSyntax)attribute.Parent;
51+
52+
if (attribute.ArgumentList?.Arguments.Count == 1)
53+
{
54+
if (list.Attributes.Count == 1)
55+
{
56+
editor.RemoveNode(list);
57+
}
58+
else
59+
{
60+
editor.RemoveNode(attribute);
61+
}
62+
}
63+
else
64+
{
65+
var newArguments = attribute.ArgumentList.Arguments.Remove(argument);
66+
var newAttribute = attribute
67+
.WithArgumentList(attribute.ArgumentList.WithArguments(newArguments))
68+
.WithAdditionalAnnotations(Formatter.Annotation);
69+
editor.ReplaceNode(attribute, newAttribute);
70+
}
71+
72+
return editor.GetChangedDocument();
73+
}
74+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
namespace Sundstrom.CheckedExceptions.Tests.CodeFixes;
2+
3+
using System.Threading.Tasks;
4+
5+
using Microsoft.CodeAnalysis.Testing;
6+
7+
using Xunit;
8+
using Xunit.Abstractions;
9+
10+
using Verifier = CSharpCodeFixVerifier<CheckedExceptionsAnalyzer, RemoveRedundantExceptionDeclarationCodeFixProvider, Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
11+
12+
public class RemoveRedundantExceptionDeclarationCodeFixProviderTests
13+
{
14+
[Fact]
15+
public async Task RemoveWholeAttribute()
16+
{
17+
var testCode = /* lang=c#-test */ """
18+
#nullable enable
19+
using System;
20+
21+
public class C
22+
{
23+
[Throws(typeof(InvalidOperationException))]
24+
public void M()
25+
{
26+
}
27+
}
28+
""";
29+
30+
var fixedCode = /* lang=c#-test */ """
31+
#nullable enable
32+
using System;
33+
34+
public class C
35+
{
36+
public void M()
37+
{
38+
}
39+
}
40+
""";
41+
42+
var expected = Verifier.Diagnostic(CheckedExceptionsAnalyzer.DiagnosticIdRedundantExceptionDeclaration)
43+
.WithArguments("InvalidOperationException")
44+
.WithSpan(6, 20, 6, 45);
45+
46+
await Verifier.VerifyCodeFixAsync(testCode, [expected], fixedCode, setup: option =>
47+
{
48+
option.DisabledDiagnostics.Remove(CheckedExceptionsAnalyzer.DiagnosticIdRedundantExceptionDeclaration);
49+
});
50+
}
51+
52+
[Fact]
53+
public async Task RemoveSingleArgumentFromAttribute()
54+
{
55+
var testCode = /* lang=c#-test */ """
56+
#nullable enable
57+
using System;
58+
59+
public class C
60+
{
61+
[Throws(typeof(InvalidOperationException), typeof(ArgumentException))]
62+
public void M()
63+
{
64+
throw new ArgumentException();
65+
}
66+
}
67+
""";
68+
69+
var fixedCode = /* lang=c#-test */ """
70+
#nullable enable
71+
using System;
72+
73+
public class C
74+
{
75+
[Throws(typeof(ArgumentException))]
76+
public void M()
77+
{
78+
throw new ArgumentException();
79+
}
80+
}
81+
""";
82+
83+
var expected = Verifier.Diagnostic(CheckedExceptionsAnalyzer.DiagnosticIdRedundantExceptionDeclaration)
84+
.WithArguments("InvalidOperationException")
85+
.WithSpan(6, 20, 6, 45);
86+
87+
await Verifier.VerifyCodeFixAsync(testCode, [expected], fixedCode, setup: option =>
88+
{
89+
option.DisabledDiagnostics.Remove(CheckedExceptionsAnalyzer.DiagnosticIdRedundantExceptionDeclaration);
90+
});
91+
}
92+
}

docs/codefix-specification.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ This document describes the available **code fixes** and which diagnostics they
1010
| ------------- | ------------------------------------- | ------------------------------------------------------------------------------------------------ |
1111
| **THROW001** | Unhandled exception type | 🔧 Add throws declaration<br>🧯 Surround with try/catch<br>➕ Add catch clause to surrounding try<br>➕ Introduce catch clause |
1212
| **THROW004** | Redundant typed catch clause | 🧹 Remove redundant catch clause |
13-
| **THROW005** | Redundant exception declaration | 🗑️ Remove redundant throws declaration |
13+
| **THROW005** | Duplicate exception declaration | 🗑️ Remove redundant throws declaration |
1414
| **THROW007** | Missing throws from base/interface | 🔧 Add throws declaration from base member |
1515
| **THROW011** | Missing throws from XML documentation | 🔧 Add throws declaration from XML doc |
16+
| **THROW012** | Redundant exception declaration | 🗑️ Remove redundant throws declaration |
1617
| **THROW013** | Redundant catch-all clause | 🧹 Remove redundant catch clause
1718
| **THROW014** | Catch clause has no remaining exceptions to handle | 🧹 Remove redundant catch clause
1819
| **THROW015** | Redundant catch clause | 🧹 Remove redundant catch clause
@@ -362,7 +363,8 @@ Func<int, int> f = x => x;
362363

363364
**Applies to:**
364365

365-
* `THROW005`*Redundant exception declaration*
366+
* `THROW005`*Duplicate exception declaration*
367+
* `THROW012`*Redundant exception declaration*
366368

367369
Removes a `[Throws]` declaration for an exception type that is **never thrown**.
368370

0 commit comments

Comments
 (0)