Skip to content

Commit

Permalink
Recognize "(field as IDisposable)?.Dispose()"
Browse files Browse the repository at this point in the history
  • Loading branch information
manfred-brands committed May 29, 2024
1 parent 3e3b1c3 commit 5dee0c1
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -63,16 +63,17 @@ public void TearDownMethod()
RoslynAssert.Valid(analyzer, testCode);
}

[Test]
public void AnalyzeWhenFieldIsConditionallyDisposed()
[TestCase("IDisposable")]
[TestCase("System.IDisposable")]
public void AnalyzeWhenFieldIsConditionallyDisposedUsingIsIDisposable(string interfaceName)
{
var testCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@"
private object field = new DummyDisposable();
[OneTimeTearDown]
public void TearDownMethod()
{{
if (field is IDisposable disposable)
if (field is {interfaceName} disposable)
disposable.Dispose();
}}
Expand All @@ -82,6 +83,25 @@ public void TearDownMethod()
RoslynAssert.Valid(analyzer, testCode);
}

[TestCase("IDisposable")]
[TestCase("System.IDisposable")]
public void AnalyzeWhenFieldIsConditionallyDisposedUsingAsIDisposable(string interfaceName)
{
var testCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@"
private object field = new DummyDisposable();
[OneTimeTearDown]
public void TearDownMethod()
{{
(field as {interfaceName})?.Dispose();
}}
{DummyDisposable}
");

RoslynAssert.Valid(analyzer, testCode);
}

[Test]
public void AnalyzeWhenFieldWithInitializerIsDisposedInOneTimeTearDownMethod()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -480,8 +480,7 @@ private static void DisposedIn(Parameters parameters, HashSet<string> disposals,
// disposable.Dispose();
if (ifStatement.Condition is IsPatternExpressionSyntax isPatternExpression &&
isPatternExpression.Pattern is DeclarationPatternSyntax declarationPattern &&
declarationPattern.Type is IdentifierNameSyntax identifierName &&
identifierName.Identifier.Text.EndsWith("Disposable", StringComparison.Ordinal) &&
IsDisposable(declarationPattern.Type) &&
declarationPattern.Designation is SingleVariableDesignationSyntax singleVariableDesignation)
{
string? member = GetIdentifier(isPatternExpression.Expression);
Expand Down Expand Up @@ -555,19 +554,59 @@ private static void DisposedIn(Parameters parameters, HashSet<string> disposals,
return memberAccessExpression.Name.Identifier.Text;
}

// considering cast to IDisposable, e.g. in case of explicit interface implementation of IDisposable.Dispose()
else if (expression is ParenthesizedExpressionSyntax parenthesizedExpression &&
parenthesizedExpression.Expression is CastExpressionSyntax castExpression &&
castExpression.Expression is IdentifierNameSyntax castIdentifierName &&
castExpression.Type is IdentifierNameSyntax typeIdentifierName &&
typeIdentifierName.Identifier.Text.Equals("IDisposable", StringComparison.Ordinal))
// considering cast to I(Async)Disposable, e.g. in case of explicit interface implementation of IDisposable.Dispose()
// or in case of 'as IDisposable' or 'as IAsyncDisposable'
else if (expression is ParenthesizedExpressionSyntax parenthesizedExpression)
{
return castIdentifierName.Identifier.Text;
IdentifierNameSyntax? memberIdentifierName = null;
ExpressionSyntax? typeExpression = null;

if (parenthesizedExpression.Expression is CastExpressionSyntax castExpression)
{
memberIdentifierName = castExpression.Expression as IdentifierNameSyntax;
typeExpression = castExpression.Type;
}
else if (parenthesizedExpression.Expression is BinaryExpressionSyntax binaryExpression &&
binaryExpression.IsKind(SyntaxKind.AsExpression))
{
memberIdentifierName = binaryExpression.Left as IdentifierNameSyntax;
typeExpression = binaryExpression.Right;
}

if (memberIdentifierName is not null &&
typeExpression is not null && IsDisposable(typeExpression))
{
return memberIdentifierName.Identifier.Text;
}
}

return null;
}

private static bool IsDisposable(ExpressionSyntax typeExpression)
{
IdentifierNameSyntax? typeIdentifierName = null;

if (typeExpression is QualifiedNameSyntax qualifiedNameSyntax &&
qualifiedNameSyntax.Left is IdentifierNameSyntax systemName &&
systemName.Identifier.Text is "System")
{
typeIdentifierName = qualifiedNameSyntax.Right as IdentifierNameSyntax;
}
else if (typeExpression is IdentifierNameSyntax identifierNameSyntax)
{
typeIdentifierName = identifierNameSyntax;
}

if (typeIdentifierName is not null &&
typeIdentifierName.Identifier.Text is "IDisposable" or "IAsyncDisposable")
{
return true;
}

return false;
}

private sealed class Parameters
{
private readonly INamedTypeSymbol type;
Expand Down

0 comments on commit 5dee0c1

Please sign in to comment.