diff --git a/IDisposableAnalyzers.Test/IDISP006ImplementIDisposableTests/ValidCode.WhenDisposing.cs b/IDisposableAnalyzers.Test/IDISP006ImplementIDisposableTests/ValidCode.WhenDisposing.cs index c5a782dd..5e5a2326 100644 --- a/IDisposableAnalyzers.Test/IDISP006ImplementIDisposableTests/ValidCode.WhenDisposing.cs +++ b/IDisposableAnalyzers.Test/IDISP006ImplementIDisposableTests/ValidCode.WhenDisposing.cs @@ -561,6 +561,29 @@ void IDisposable.Dispose() this.disposable.Dispose(); } } +}"; + RoslynAssert.Valid(Analyzer, Disposable, code); + } + + [Test] + public static void AsyncExplicitImplementation() + { + var code = @" +namespace N +{ + using System; + using System.Threading.Tasks; + + public sealed class C : IAsyncDisposable + { + private readonly Disposable disposable = new Disposable(); + + ValueTask IAsyncDisposable.DisposeAsync() + { + this.disposable.Dispose(); + return ValueTask.CompletedTask; + } + } }"; RoslynAssert.Valid(Analyzer, Disposable, code); } diff --git a/IDisposableAnalyzers/Helpers/DisposeMethod.cs b/IDisposableAnalyzers/Helpers/DisposeMethod.cs index 1b5c0803..d9dde50d 100644 --- a/IDisposableAnalyzers/Helpers/DisposeMethod.cs +++ b/IDisposableAnalyzers/Helpers/DisposeMethod.cs @@ -27,15 +27,20 @@ internal static class DisposeMethod { return topLevel; } - else - { - return null; - } + + return null; } - return type.TryFindFirstMethodRecursive("Dispose", x => IsMatch(x), out var recursive) - ? recursive - : null; + if (type.TryFindFirstMethodRecursive("Dispose", x => IsMatch(x), out var recursive)) + { + return recursive; + } + else if (type.TryFindFirstMethodRecursive("System.IDisposable.Dispose", out recursive)) + { + return recursive; + } + + return null; static bool IsMatch(IMethodSymbol candidate) { @@ -101,14 +106,28 @@ static bool IsMatch(IMethodSymbol candidate) if (search == Search.TopLevel) { - return type.TryFindFirstMethod("DisposeAsync", x => IsMatch(x), out var topLevel) - ? topLevel - : null; + if (type.TryFindFirstMethod("DisposeAsync", x => IsMatch(x), out var topLevel)) + { + return topLevel; + } + else if (type.TryFindFirstMethod("System.IAsyncDisposable.DisposeAsync", out topLevel)) + { + return topLevel; + } + + return null; } - return type.TryFindFirstMethodRecursive("DisposeAsync", x => IsMatch(x), out var recursive) - ? recursive - : null; + if (type.TryFindFirstMethodRecursive("DisposeAsync", x => IsMatch(x), out var recursive)) + { + return recursive; + } + else if (type.TryFindFirstMethodRecursive("System.IAsyncDisposable.DisposeAsync", out recursive)) + { + return recursive; + } + + return null; static bool IsMatch(IMethodSymbol candidate) { diff --git a/IDisposableAnalyzers/Helpers/Walkers/DisposeWalker.cs b/IDisposableAnalyzers/Helpers/Walkers/DisposeWalker.cs index 8b8fb951..7fe384a6 100644 --- a/IDisposableAnalyzers/Helpers/Walkers/DisposeWalker.cs +++ b/IDisposableAnalyzers/Helpers/Walkers/DisposeWalker.cs @@ -46,10 +46,10 @@ internal static DisposeWalker Borrow(INamedTypeSymbol type, SemanticModel semant } if (type.IsAssignableTo(KnownSymbols.IAsyncDisposable, semanticModel.Compilation) && - type.TryFindFirstMethod(x => x is { Parameters.Length: 0 } && x == KnownSymbols.IAsyncDisposable.DisposeAsync, out var disposeAsync) && - disposeAsync.TrySingleDeclaration(cancellationToken, out declaration)) + DisposeMethod.FindDisposeAsync(type, semanticModel.Compilation, Search.Recursive) is { } asyncDisposeMethod && + asyncDisposeMethod.TrySingleDeclaration(cancellationToken, out MethodDeclarationSyntax? asyncDeclaration)) { - return BorrowAndVisit(declaration, SearchScope.Instance, type, semanticModel, () => new DisposeWalker(), cancellationToken); + return BorrowAndVisit(asyncDeclaration, SearchScope.Instance, type, semanticModel, () => new DisposeWalker(), cancellationToken); } return Borrow(() => new DisposeWalker());