diff --git a/IDisposableAnalyzers.Test/IDISP001DisposeCreatedTests/Diagnostics.Local.cs b/IDisposableAnalyzers.Test/IDISP001DisposeCreatedTests/Diagnostics.Local.cs index ad6dcfb7..ae40bb42 100644 --- a/IDisposableAnalyzers.Test/IDISP001DisposeCreatedTests/Diagnostics.Local.cs +++ b/IDisposableAnalyzers.Test/IDISP001DisposeCreatedTests/Diagnostics.Local.cs @@ -24,6 +24,22 @@ public void Dispose() } """; + private const string AsyncDisposable = """ + namespace N + { + using System; + using System.Threading.Tasks; + + public class AsyncDisposable : IAsyncDisposable + { + public ValueTask DisposeAsync() + { + return ValueTask.CompletedTask; + } + } + } + """; + [TestCase("new Disposable()")] [TestCase("new Disposable() as object")] [TestCase("(object) new Disposable()")] @@ -164,6 +180,25 @@ public static long M() RoslynAssert.Diagnostics(Analyzer, ExpectedDiagnostic, Disposable, code); } + [Test] + public static void NewAsyncDisposable() + { + var code = """ + namespace N + { + public static class C + { + public static long M() + { + ↓var disposable = new AsyncDisposable(); + return 1; + } + } + } + """; + RoslynAssert.Diagnostics(Analyzer, ExpectedDiagnostic, AsyncDisposable, code); + } + [Test] public static void MethodCreatingDisposable1() { diff --git a/IDisposableAnalyzers.Test/IDISP001DisposeCreatedTests/Valid.cs b/IDisposableAnalyzers.Test/IDISP001DisposeCreatedTests/Valid.cs index d71a83f9..49f79302 100644 --- a/IDisposableAnalyzers.Test/IDISP001DisposeCreatedTests/Valid.cs +++ b/IDisposableAnalyzers.Test/IDISP001DisposeCreatedTests/Valid.cs @@ -36,6 +36,22 @@ public void Dispose() } }"; + private const string AsyncDisposable = """ + namespace N + { + using System; + using System.Threading.Tasks; + + public class AsyncDisposable : IAsyncDisposable + { + public ValueTask DisposeAsync() + { + return ValueTask.CompletedTask; + } + } + } + """; + [TestCase("1")] [TestCase("new string(' ', 1)")] [TestCase("typeof(IDisposable)")] @@ -86,6 +102,27 @@ public void M() RoslynAssert.Valid(Analyzer, Disposable, code); } + [Test] + public static void WhenDisposingVariableAsync() + { + var code = @" +namespace N +{ + using System.Threading.Tasks; + + public class C + { + public async Task M() + { + var item = new AsyncDisposable(); + await item.DisposeAsync(); + } + } +}"; + + RoslynAssert.Valid(Analyzer, AsyncDisposable, code); + } + [Test] public static void UsingFileStream() { diff --git a/IDisposableAnalyzers/Helpers/Disposable.cs b/IDisposableAnalyzers/Helpers/Disposable.cs index 428b8279..281e2c41 100644 --- a/IDisposableAnalyzers/Helpers/Disposable.cs +++ b/IDisposableAnalyzers/Helpers/Disposable.cs @@ -42,16 +42,17 @@ internal static bool IsPotentiallyAssignableFrom(ITypeSymbol type, Compilation c return true; } - internal static bool IsAssignableFrom(ITypeSymbol type, Compilation compilation) => type switch - { - null => false, - //// https://blogs.msdn.microsoft.com/pfxteam/2012/03/25/do-i-need-to-dispose-of-tasks/ - { ContainingNamespace: { MetadataName: "Tasks", ContainingNamespace: { MetadataName: "Threading", ContainingNamespace.MetadataName: "System" } }, MetadataName: "Task" } => false, - INamedTypeSymbol { ContainingNamespace: { MetadataName: "Tasks", ContainingNamespace: { MetadataName: "Threading", ContainingNamespace.MetadataName: "System" } }, MetadataName: "Task`1", TypeArguments: { Length: 1 } arguments } - => IsAssignableFrom(arguments[0], compilation), - { IsRefLikeType: true } => DisposeMethod.IsAccessibleOn(type, compilation), - _ => type.IsAssignableTo(KnownSymbols.IDisposable, compilation), - }; + internal static bool IsAssignableFrom(ITypeSymbol type, Compilation compilation) => + type switch + { + null => false, + //// https://blogs.msdn.microsoft.com/pfxteam/2012/03/25/do-i-need-to-dispose-of-tasks/ + { ContainingNamespace: { MetadataName: "Tasks", ContainingNamespace: { MetadataName: "Threading", ContainingNamespace.MetadataName: "System" } }, MetadataName: "Task" } => false, + INamedTypeSymbol { ContainingNamespace: { MetadataName: "Tasks", ContainingNamespace: { MetadataName: "Threading", ContainingNamespace.MetadataName: "System" } }, MetadataName: "Task`1", TypeArguments: { Length: 1 } arguments } + => IsAssignableFrom(arguments[0], compilation), + { IsRefLikeType: true } => DisposeMethod.IsAccessibleOn(type, compilation), + _ => type.IsAssignableTo(KnownSymbols.IDisposable, compilation) || type.IsAssignableTo(KnownSymbols.IAsyncDisposable, compilation), + }; internal static bool IsNop(ExpressionSyntax candidate, SemanticModel semanticModel, CancellationToken cancellationToken) {