Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add analyzer and fix provider RCS1265: Remove redundant catch statement #1364

Merged
merged 10 commits into from
Jan 21, 2024
4 changes: 4 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Fix analyzer [RCS1055](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1055) ([PR](https://github.com/dotnet/roslynator/pull/1361))

### Added

- Added analzer [RCS1265](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1265) ([PR](https://github.com/dotnet/roslynator/pull/1361))
jakubreznak marked this conversation as resolved.
Show resolved Hide resolved

## [4.9.0] - 2024-01-10

### Added
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright (c) .NET Foundation and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Roslynator.CodeFixes;

namespace Roslynator.CSharp.CSharp.CodeFixes;

[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(RemoveRedundantCatchBlockCodeFixProvider))]
[Shared]
public class RemoveRedundantCatchBlockCodeFixProvider : BaseCodeFixProvider
{
public sealed override ImmutableArray<string> FixableDiagnosticIds
=> ImmutableArray.Create(DiagnosticRules.RemoveRedundantCatchBlock.Id);

public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
SyntaxNode root = await context.Document.GetSyntaxRootAsync().ConfigureAwait(false);

if (!TryFindFirstAncestorOrSelf(root, context.Span, out CatchClauseSyntax catchClause))
return;

context.RegisterCodeFix(
CodeAction.Create(
title: "Remove redundant catch block",
createChangedDocument: c => RemoveRedundantCatchAsync(context.Document, catchClause, c),
equivalenceKey: nameof(RemoveRedundantCatchBlockCodeFixProvider)),
context.Diagnostics[0]);
}

private static async Task<Document> RemoveRedundantCatchAsync(Document document, CatchClauseSyntax catchClause, CancellationToken cancellationToken)
{
var tryStatement = (TryStatementSyntax)catchClause.Parent;

SyntaxList<CatchClauseSyntax> catchClauses = tryStatement.Catches;
SyntaxList<CatchClauseSyntax> newCatchClauses = SyntaxFactory.List(catchClauses.Take(catchClauses.Count - 1));
josefpihrt marked this conversation as resolved.
Show resolved Hide resolved

if (!newCatchClauses.Any() && tryStatement.Finally is null)
{
IEnumerable<StatementSyntax> newNodes = tryStatement
.Block
.Statements
.Select(f => f.WithFormatterAnnotation());

return await document.ReplaceNodeAsync(tryStatement, newNodes, cancellationToken).ConfigureAwait(false);
}
else
{
TryStatementSyntax newTryStatement = tryStatement.WithCatches(newCatchClauses);
return await document.ReplaceNodeAsync(tryStatement, newTryStatement, cancellationToken).ConfigureAwait(false);
}
}
}
35 changes: 35 additions & 0 deletions src/Analyzers.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7656,6 +7656,41 @@ public string Foo(string bar)
<Option Key="use_var" IsRequired="true" />
</ConfigOptions>
</Analyzer>
<Analyzer>
<Id>RCS1265</Id>
<Identifier>RemoveRedundantCatchBlock</Identifier>
<Title>Remove redundant catch block</Title>
<DefaultSeverity>Info</DefaultSeverity>
<IsEnabledByDefault>true</IsEnabledByDefault>
<Samples>
<Sample>
<Before><![CDATA[
try
{
DoSomething();
}
catch
{
throw;
}
finally
{
DoSomethingElse();
}
]]></Before>
<After><![CDATA[
try
{
DoSomething();
}
finally
{
DoSomethingElse();
}
]]></After>
</Sample>
</Samples>
</Analyzer>
<Analyzer>
<Id>RCS9001</Id>
<Identifier>UsePatternMatching</Identifier>
Expand Down
79 changes: 79 additions & 0 deletions src/Analyzers/CSharp/Analysis/RemoveRedundantCatchBlockAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright (c) .NET Foundation and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Roslynator.CSharp.Analysis;

namespace Roslynator.CSharp.CSharp.Analysis;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class RemoveRedundantCatchBlockAnalyzer : BaseDiagnosticAnalyzer
{
private static ImmutableArray<DiagnosticDescriptor> _supportedDiagnostics;

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
{
get
{
if (_supportedDiagnostics.IsDefault)
{
Immutable.InterlockedInitialize(ref _supportedDiagnostics, DiagnosticRules.RemoveRedundantCatchBlock);
}

return _supportedDiagnostics;
}
}

public override void Initialize(AnalysisContext context)
{
base.Initialize(context);

context.RegisterSyntaxNodeAction(f => AnalyzeCatchClause(f), SyntaxKind.CatchClause);
}

private static void AnalyzeCatchClause(SyntaxNodeAnalysisContext context)
josefpihrt marked this conversation as resolved.
Show resolved Hide resolved
{
var catchClause = (CatchClauseSyntax)context.Node;

if (catchClause.Parent is not TryStatementSyntax tryStatement)
return;

if (catchClause.Declaration is not null)
return;

if (catchClause.Block?.Statements.Count != 1)
return;

if (catchClause.Block?.Statements[0] is not ThrowStatementSyntax)
josefpihrt marked this conversation as resolved.
Show resolved Hide resolved
return;

if (tryStatement.Catches.Count > 1 || tryStatement.Finally is not null)
{
DiagnosticHelpers.ReportDiagnostic(context, DiagnosticRules.RemoveRedundantCatchBlock, catchClause);
}
else
{
BlockSyntax tryBlock = tryStatement.Block;

if (tryBlock?.Statements.Any() != true)
return;

if (!SyntaxTriviaAnalysis.IsExteriorTriviaEmptyOrWhitespace(tryStatement.TryKeyword))
josefpihrt marked this conversation as resolved.
Show resolved Hide resolved
return;

if (!SyntaxTriviaAnalysis.IsExteriorTriviaEmptyOrWhitespace(tryBlock.OpenBraceToken))
return;

if (!SyntaxTriviaAnalysis.IsExteriorTriviaEmptyOrWhitespace(tryBlock.CloseBraceToken))
return;

if (!catchClause.CatchKeyword.LeadingTrivia.IsEmptyOrWhitespace())
return;

DiagnosticHelpers.ReportDiagnostic(context, DiagnosticRules.RemoveRedundantCatchBlock, catchClause);
}
}
}
1 change: 1 addition & 0 deletions src/Analyzers/CSharp/DiagnosticIdentifiers.Generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -221,5 +221,6 @@ public static partial class DiagnosticIdentifiers
public const string UnnecessaryRawStringLiteral = "RCS1262";
public const string InvalidReferenceInDocumentationComment = "RCS1263";
public const string UseVarOrExplicitType = "RCS1264";
public const string RemoveRedundantCatchBlock = "RCS1265";
}
}
12 changes: 12 additions & 0 deletions src/Analyzers/CSharp/DiagnosticRules.Generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2615,5 +2615,17 @@ public static partial class DiagnosticRules
helpLinkUri: DiagnosticIdentifiers.UseVarOrExplicitType,
customTags: Array.Empty<string>());

/// <summary>RCS1265</summary>
public static readonly DiagnosticDescriptor RemoveRedundantCatchBlock = DiagnosticDescriptorFactory.Create(
id: DiagnosticIdentifiers.RemoveRedundantCatchBlock,
title: "Remove redundant catch block",
messageFormat: "Remove redundant catch block",
category: DiagnosticCategories.Roslynator,
defaultSeverity: DiagnosticSeverity.Info,
isEnabledByDefault: true,
description: null,
helpLinkUri: DiagnosticIdentifiers.RemoveRedundantCatchBlock,
customTags: Array.Empty<string>());

}
}
152 changes: 152 additions & 0 deletions src/Tests/Analyzers.Tests/RCS1265RemoveRedundantCantchBlockTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
// Copyright (c) .NET Foundation and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Roslynator.CSharp.CSharp.Analysis;
using Roslynator.CSharp.CSharp.CodeFixes;
using Roslynator.Testing.CSharp;
using Xunit;

namespace Roslynator.CSharp.Analysis.Tests;

public class RCS1265RemoveRedundantCatchBlockTests : AbstractCSharpDiagnosticVerifier<RemoveRedundantCatchBlockAnalyzer, RemoveRedundantCatchBlockCodeFixProvider>
{
public override DiagnosticDescriptor Descriptor { get; } = DiagnosticRules.RemoveRedundantCatchBlock;

[Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.RemoveRedundantCatchBlock)]
public async Task Test_TryCatchFinally()
{
await VerifyDiagnosticAndFixAsync(@"
class C
{
void M()
{
try
{
DoSomething();
}
[|catch
{
throw;
}|]
finally
{
DoSomething();
}
}

void DoSomething()
{
}
}
", @"
class C
{
void M()
{
try
{
DoSomething();
}
finally
{
DoSomething();
}
}

void DoSomething()
{
}
}
");
}

[Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.RemoveRedundantCatchBlock)]
public async Task Test_TryMultipleCatches()
{
await VerifyDiagnosticAndFixAsync(@"
using System;

class C
{
void M()
{
try
{
DoSomething();
}
catch (SystemException ex)
{
DoSomething();
}
[|catch
{
throw;
}|]
}

void DoSomething()
{
}
}
", @"
using System;

class C
{
void M()
{
try
{
DoSomething();
}
catch (SystemException ex)
{
DoSomething();
}
}

void DoSomething()
{
}
}
");
}

[Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.RemoveRedundantCatchBlock)]
public async Task Test_TryCatch()
{
await VerifyDiagnosticAndFixAsync(@"
class C
{
void M()
{
try
{
DoSomething();
}
[|catch
{
throw;
}|]
}

void DoSomething()
{
}
}
", @"
class C
{
void M()
{
DoSomething();
}

void DoSomething()
{
}
}
");
}
}