Skip to content

Commit

Permalink
RCS1140 - Don't require user to document exception types if caught in…
Browse files Browse the repository at this point in the history
… same method (#1524)
  • Loading branch information
Qluxzz authored Sep 27, 2024
1 parent e7ed6ad commit bde6487
Show file tree
Hide file tree
Showing 3 changed files with 186 additions and 0 deletions.
1 change: 1 addition & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Fix analyzer [RCS1202](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1202) ([PR](https://github.com/dotnet/roslynator/pull/1542))
- Fix analyzer [RCS1246](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1246) ([PR](https://github.com/dotnet/roslynator/pull/1543))
- Fix analyzer [RCS1140](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1140) ([PR](https://github.com/dotnet/roslynator/pull/1524))

## [4.12.6] - 2024-09-23

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ private static AddExceptionToDocumentationCommentAnalysisResult Analyze(
if (!InheritsFromException(typeSymbol, exceptionSymbol))
return Fail;

if (IsExceptionTypeCaughtInMethod(node, typeSymbol, semanticModel, cancellationToken))
return Fail;

ISymbol declarationSymbol = GetDeclarationSymbol(node.SpanStart, semanticModel, cancellationToken);

if (declarationSymbol?.GetSyntax(cancellationToken) is not MemberDeclarationSyntax containingMember)
Expand Down Expand Up @@ -216,4 +219,32 @@ private static bool InheritsFromException(ITypeSymbol typeSymbol, INamedTypeSymb
&& typeSymbol.BaseType?.IsObject() == false
&& typeSymbol.InheritsFrom(exceptionSymbol);
}

/// <summary>
/// Walk upwards from throw statement and find all try statements in method and see if any of them catches the thrown exception type
/// </summary>
private static bool IsExceptionTypeCaughtInMethod(SyntaxNode node, ITypeSymbol exceptionSymbol, SemanticModel semanticModel, CancellationToken cancellationToken)
{
SyntaxNode parent = node.Parent;
while (parent is not null)
{
if (parent is TryStatementSyntax tryStatement && tryStatement.Catches.Any(catchClause => SymbolEqualityComparer.Default.Equals(exceptionSymbol, semanticModel.GetTypeSymbol(catchClause.Declaration?.Type, cancellationToken))))
{
return true;
}

if (parent is MemberDeclarationSyntax or LocalFunctionStatementSyntax)
{
// We don't care if it's caught outside of the current method
// Since the exception should be documented in this method
return false;
}
else
{
parent = parent.Parent;
}
}

return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// 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.CodeFixes;
using Roslynator.Testing.CSharp;
using Xunit;

namespace Roslynator.CSharp.Analysis.Tests;

public class RCS1140AddExceptionToDocumentationCommentTests : AbstractCSharpDiagnosticVerifier<AddExceptionToDocumentationCommentAnalyzer, AddExceptionToDocumentationCommentCodeFixProvider>
{
public override DiagnosticDescriptor Descriptor { get; } = DiagnosticRules.AddExceptionToDocumentationComment;

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

class C
{
/// <summary>
/// ...
/// </summary>
/// <param name="parameter"></param>
public void Foo(object parameter)
{
if (parameter == null)
[|throw new ArgumentNullException(nameof(parameter));|]
}
}

""", """
using System;

class C
{
/// <summary>
/// ...
/// </summary>
/// <param name="parameter"></param>
/// <exception cref="ArgumentNullException"><paramref name="parameter"/> is <c>null</c>.</exception>
public void Foo(object parameter)
{
if (parameter == null)
throw new ArgumentNullException(nameof(parameter));
}
}

""");
}

[Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.AddExceptionToDocumentationComment)]
public async Task Test_No_Diagnostic_If_Exception_Is_Caught_In_Method()
{
await VerifyNoDiagnosticAsync("""
using System;

class C
{
/// <summary>
/// ...
/// </summary>
/// <param name="parameter"></param>
public void Foo(object parameter)
{
try
{
if (parameter == null)
throw new ArgumentNullException(nameof(parameter));
}
catch (ArgumentNullException) {}
}
}
""");
}

[Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.AddExceptionToDocumentationComment)]
public async Task Test_No_Diagnostic_If_Exception_Is_Caught_In_Method_Nested()
{
await VerifyNoDiagnosticAsync("""
using System;

class C
{
/// <summary>
/// ...
/// </summary>
/// <param name="parameter"></param>
public void Foo(object parameter)
{
try
{
try
{
if (parameter == null)
throw new ArgumentNullException(nameof(parameter));
}
catch (InvalidOperationException) {}
}
catch (ArgumentNullException) {}
}
}
""");
}

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

class C
{
/// <summary>
/// ...
/// </summary>
/// <param name="parameter"></param>
public void Foo(object parameter)
{
try
{
if (parameter == null)
[|throw new ArgumentNullException(nameof(parameter));|]
}
catch (InvalidOperationException) {}
}
}

""", """
using System;

class C
{
/// <summary>
/// ...
/// </summary>
/// <param name="parameter"></param>
/// <exception cref="ArgumentNullException"><paramref name="parameter"/> is <c>null</c>.</exception>
public void Foo(object parameter)
{
try
{
if (parameter == null)
throw new ArgumentNullException(nameof(parameter));
}
catch (InvalidOperationException) {}
}
}

""");
}
}

0 comments on commit bde6487

Please sign in to comment.