From fc0ecc9b2f2aa736705bc76eb8dff539ff2f607d Mon Sep 17 00:00:00 2001 From: Corniel Nobel Date: Fri, 18 Oct 2024 13:42:00 +0200 Subject: [PATCH] IDisposables are always disposed after the foreach statement. --- .../Valid.CreatedWithinForEach.cs | 108 ++++++++++++++++++ .../Helpers/Disposable.Disposes.cs | 3 + 2 files changed, 111 insertions(+) create mode 100644 IDisposableAnalyzers.Test/IDISP004DoNotIgnoreCreatedTests/Valid.CreatedWithinForEach.cs diff --git a/IDisposableAnalyzers.Test/IDISP004DoNotIgnoreCreatedTests/Valid.CreatedWithinForEach.cs b/IDisposableAnalyzers.Test/IDISP004DoNotIgnoreCreatedTests/Valid.CreatedWithinForEach.cs new file mode 100644 index 00000000..5963d82c --- /dev/null +++ b/IDisposableAnalyzers.Test/IDISP004DoNotIgnoreCreatedTests/Valid.CreatedWithinForEach.cs @@ -0,0 +1,108 @@ +namespace IDisposableAnalyzers.Test.IDISP004DoNotIgnoreCreatedTests; + +using Gu.Roslyn.Asserts; +using NUnit.Framework; + +public static partial class Valid +{ + [Test] + public static void StructEnumeratorCreatedWithinForEach() + { + var code = @" +using System.Collections; +using System.Collections.Generic; + +namespace N +{ + public class C + { + public void M() + { + foreach(var n in new Enumerator()) + { + } + } + } + + public struct Enumerator : IEnumerator, IEnumerable + { + public int Current => 42; + object IEnumerator.Current => 42; + public void Dispose() { } + public bool MoveNext() => true; + public void Reset() { } + public IEnumerator GetEnumerator() => this; + IEnumerator IEnumerable.GetEnumerator() => this; + } +}"; + RoslynAssert.Valid(Analyzer, code); + } + + [Test] + public static void ClassEnumeratorCreatedWithinForEach() + { + var code = @" +using System.Collections; +using System.Collections.Generic; + +namespace N +{ + public class C + { + public void M() + { + foreach(var n in new Enumerator()) + { + } + } + } + + public class Enumerator : IEnumerator, IEnumerable + { + public int Current => 42; + object IEnumerator.Current => 42; + public void Dispose() { } + public bool MoveNext() => true; + public void Reset() { } + public IEnumerator GetEnumerator() => this; + IEnumerator IEnumerable.GetEnumerator() => this; + } +}"; + RoslynAssert.Valid(Analyzer, code); + } + + [Test] + public static void CreatedEnumerableWithinForEach() + { + var code = @" +using System.Collections; +using System.Collections.Generic; + +namespace N +{ + public class C + { + public void M() + { + foreach(var n in All()) + { + } + } + + public IEnumerable All() => new Enumerator(); + } + + public class Enumerator : IEnumerator, IEnumerable + { + public int Current => 42; + object IEnumerator.Current => 42; + public void Dispose() { } + public bool MoveNext() => true; + public void Reset() { } + public IEnumerator GetEnumerator() => this; + IEnumerator IEnumerable.GetEnumerator() => this; + } +}"; + RoslynAssert.Valid(Analyzer, code); + } +} diff --git a/IDisposableAnalyzers/Helpers/Disposable.Disposes.cs b/IDisposableAnalyzers/Helpers/Disposable.Disposes.cs index 299c46b5..089f07e2 100644 --- a/IDisposableAnalyzers/Helpers/Disposable.Disposes.cs +++ b/IDisposableAnalyzers/Helpers/Disposable.Disposes.cs @@ -90,6 +90,9 @@ private static bool Disposes(ExpressionSyntax candidate, Recursion recursion) when Identity(candidate, recursion) is { } id && Disposes(id, recursion) => true, + { Parent: ForEachStatementSyntax parent } + when parent.Expression == candidate + => true, { Parent: ConditionalAccessExpressionSyntax { WhenNotNull: InvocationExpressionSyntax invocation } } => IsDisposeOrReturnValueDisposed(invocation), { Parent: MemberAccessExpressionSyntax { Parent: InvocationExpressionSyntax invocation } }