From 251ce87b4503a3d4d4678a83fd48709d89f4d0f5 Mon Sep 17 00:00:00 2001 From: Kenny Pflug Date: Sat, 19 Jul 2025 10:52:13 +0200 Subject: [PATCH 1/3] chore: add AI prompt for issue 114 Signed-off-by: Kenny Pflug --- ...-be-default-or-empty-for-immutable-array.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 Code/Plans/issue-114-must-not-be-default-or-empty-for-immutable-array.md diff --git a/Code/Plans/issue-114-must-not-be-default-or-empty-for-immutable-array.md b/Code/Plans/issue-114-must-not-be-default-or-empty-for-immutable-array.md new file mode 100644 index 0000000..e128a1e --- /dev/null +++ b/Code/Plans/issue-114-must-not-be-default-or-empty-for-immutable-array.md @@ -0,0 +1,18 @@ +# Issue 114 - MustNotBeDefaultOrEmpty for ImmutableArray + +## Context + +The .NET library Light.GuardClauses already has several assertions for collections. They often rely on `IEnumerable` or `IEnumerable`. However, these assertions would result in `ImmutableArray` being boxed - we want to avoid that by providing dedicated assertions for this type which avoids boxing. For this issue, we start out with a new assertion called `MustNotBeDefaultOrEmpty`. + +## Tasks for this issue + +- [ ] The production code should be placed in the Light.GuardClauses project. Create a file called `Check.MustNotBeDefaultOrEmpty.cs` in the root folder of the project. +- [ ] In this file, create several extension method overloads called `MustNotBeDefaultOrEmpty` for `ImmutableArray`. It should be placed in the class `Check` which is marked as `partial`. +- [ ] Each assertion in Light.GuardClauses has two overloads - the first one takes the optional `parameterName` and `message` arguments and throw the default exception (in this case the existing `EmptyCollectionException`). The actual exception in thrown in the `Throw` class, you need to create a corresponding method for it in this class. +- [ ] The other overload takes a delegate which allows the caller to provide their own custom exceptions. Pass the erroneous `ImmutableArray` instance to the delegate and throw the returned exception. +- [ ] Create unit tests for both overloads. The corresponding file should be placed in Light.GuardClauses.Tests project, in the existing subfolder 'CollectionAssertions'. Please follow conventions of the existing tests (e.g. use FluentAssertions' `Should()` for assertions). + +## Notes + +- There are already plenty of other assertions and tests in this library. All overloads are placed in the same file in the production code project. The test projects has top-level folders for different groups of assertions, like `CollectionAssertions`, `StringAssertions`, `DateTimeAssertions` and so on. Please take a look at them to follow a similar structure and code style. +- If you have any questions or suggestions, please ask me about them. From ee943e4cd83a6032d4727fe6427f16b259936c9d Mon Sep 17 00:00:00 2001 From: Kenny Pflug Date: Sat, 19 Jul 2025 11:02:49 +0200 Subject: [PATCH 2/3] feat: add MustNotBeDefaultOrEmpty for ImmutableArray Signed-off-by: Kenny Pflug --- .../MustNotBeDefaultOrEmptyTests.cs | 93 +++++++++++++++++++ .../Check.MustNotBeDefaultOrEmpty.cs | 53 +++++++++++ .../Light.GuardClauses.csproj | 9 +- 3 files changed, 151 insertions(+), 4 deletions(-) create mode 100644 Code/Light.GuardClauses.Tests/CollectionAssertions/MustNotBeDefaultOrEmptyTests.cs create mode 100644 Code/Light.GuardClauses/Check.MustNotBeDefaultOrEmpty.cs diff --git a/Code/Light.GuardClauses.Tests/CollectionAssertions/MustNotBeDefaultOrEmptyTests.cs b/Code/Light.GuardClauses.Tests/CollectionAssertions/MustNotBeDefaultOrEmptyTests.cs new file mode 100644 index 0000000..fd5b5d1 --- /dev/null +++ b/Code/Light.GuardClauses.Tests/CollectionAssertions/MustNotBeDefaultOrEmptyTests.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Immutable; +using FluentAssertions; +using Light.GuardClauses.Exceptions; +using Xunit; + +namespace Light.GuardClauses.Tests.CollectionAssertions; + +public static class MustNotBeDefaultOrEmptyTests +{ + [Fact] + public static void ImmutableArrayDefault() + { + var defaultArray = default(ImmutableArray); + + Action act = () => defaultArray.MustNotBeDefaultOrEmpty(nameof(defaultArray)); + + var assertion = act.Should().Throw().Which; + assertion.Message.Should() + .Contain($"{nameof(defaultArray)} must not be an empty collection, but it actually is."); + assertion.ParamName.Should().BeSameAs(nameof(defaultArray)); + } + + [Fact] + public static void ImmutableArrayEmpty() + { + var emptyArray = ImmutableArray.Empty; + + Action act = () => emptyArray.MustNotBeDefaultOrEmpty(nameof(emptyArray)); + + var assertion = act.Should().Throw().Which; + assertion.Message.Should() + .Contain($"{nameof(emptyArray)} must not be an empty collection, but it actually is."); + assertion.ParamName.Should().BeSameAs(nameof(emptyArray)); + } + + [Fact] + public static void ImmutableArrayNotEmpty() + { + var array = ImmutableArray.Create("Foo", "Bar", "Baz"); + + array.MustNotBeDefaultOrEmpty().Should().Equal(array); + } + + [Theory] + [MemberData(nameof(DefaultOrEmptyArrays))] + public static void CustomException(ImmutableArray array) => + Test.CustomException( + array, + (invalidArray, exceptionFactory) => invalidArray.MustNotBeDefaultOrEmpty(exceptionFactory) + ); + + [Fact] + public static void NoCustomExceptionThrown() + { + var array = ImmutableArray.Create(42, 84); + array.MustNotBeDefaultOrEmpty(_ => new ()).Should().Equal(array); + } + + [Fact] + public static void CustomMessage() => + Test.CustomMessage( + message => ImmutableArray.Empty.MustNotBeDefaultOrEmpty(message: message) + ); + + [Fact] + public static void CallerArgumentExpressionForEmptyArray() + { + var emptyArray = ImmutableArray.Empty; + + Action act = () => emptyArray.MustNotBeDefaultOrEmpty(); + + act.Should().Throw() + .WithParameterName(nameof(emptyArray)); + } + + [Fact] + public static void CallerArgumentExpressionForDefaultArray() + { + var defaultArray = default(ImmutableArray); + + Action act = () => defaultArray.MustNotBeDefaultOrEmpty(); + + act.Should().Throw() + .WithParameterName(nameof(defaultArray)); + } + + public static TheoryData> DefaultOrEmptyArrays() => new () + { + default, + ImmutableArray.Empty, + }; +} diff --git a/Code/Light.GuardClauses/Check.MustNotBeDefaultOrEmpty.cs b/Code/Light.GuardClauses/Check.MustNotBeDefaultOrEmpty.cs new file mode 100644 index 0000000..ee1d8f2 --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustNotBeDefaultOrEmpty.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Immutable; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the specified is not default or empty, or otherwise throws an . + /// + /// The to be checked. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is default or empty. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ImmutableArray MustNotBeDefaultOrEmpty( + this ImmutableArray parameter, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (parameter.IsDefaultOrEmpty) + { + Throw.EmptyCollection(parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the specified is not default or empty, or otherwise throws your custom exception. + /// + /// The to be checked. + /// The delegate that creates your custom exception. The is passed to this delegate. + /// Your custom exception thrown when is default or empty. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("exceptionFactory:null => halt")] + public static ImmutableArray MustNotBeDefaultOrEmpty( + this ImmutableArray parameter, + Func, Exception> exceptionFactory + ) + { + if (parameter.IsDefaultOrEmpty) + { + Throw.CustomException(exceptionFactory, parameter); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Light.GuardClauses.csproj b/Code/Light.GuardClauses/Light.GuardClauses.csproj index 0f55b64..7780ff0 100644 --- a/Code/Light.GuardClauses/Light.GuardClauses.csproj +++ b/Code/Light.GuardClauses/Light.GuardClauses.csproj @@ -1,4 +1,4 @@ - + @@ -39,10 +39,11 @@ Light.GuardClauses 13.0.0 - - - + + + + From f404273ecf496d4b250b8023b24188bcf884692a Mon Sep 17 00:00:00 2001 From: Kenny Pflug Date: Sat, 19 Jul 2025 16:13:26 +0200 Subject: [PATCH 3/3] chore: add plan to solution file Signed-off-by: Kenny Pflug --- Code/Light.GuardClauses.AllProjects.sln | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Code/Light.GuardClauses.AllProjects.sln b/Code/Light.GuardClauses.AllProjects.sln index 6b5c1ad..461effb 100644 --- a/Code/Light.GuardClauses.AllProjects.sln +++ b/Code/Light.GuardClauses.AllProjects.sln @@ -30,6 +30,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Versioning", "Versioning", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Light.GuardClauses.SourceCodeTransformation.Tests", "Light.GuardClauses.SourceCodeTransformation.Tests\Light.GuardClauses.SourceCodeTransformation.Tests.csproj", "{A2067796-F167-47BE-B367-191152DCE230}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Plans", "Plans", "{664661A0-3861-486B-87B5-A35A5CC5F7B7}" + ProjectSection(SolutionItems) = preProject + Plans\issue-114-must-not-be-default-or-empty-for-immutable-array.md = Plans\issue-114-must-not-be-default-or-empty-for-immutable-array.md + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU