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 @@ -16,6 +16,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

- Add analyzer "Remove redundant catch block" [RCS1265](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1265) ([PR](https://github.com/dotnet/roslynator/pull/1364))

## [4.9.0] - 2024-01-10

### Added
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// 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());

newNodes = new[] { newNodes.First().WithLeadingTrivia(tryStatement.GetLeadingTrivia()) }.Concat(newNodes.Skip(1));

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
81 changes: 81 additions & 0 deletions src/Analyzers/CSharp/Analysis/RemoveRedundantCatchBlockAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// 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;

CatchClauseSyntax lastCatchClause = tryStatement.Catches.Last();

if (!catchClause.Equals(lastCatchClause))
return;

if (catchClause.Declaration is not null)
return;

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

if (catchClause.Block.Statements[0] is not ThrowStatementSyntax throwStatement || throwStatement.Expression is not null)
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(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>());

}
}