Skip to content

Conversation

@samtrion
Copy link
Member

@samtrion samtrion commented Dec 30, 2025

Refactor to provide polyfills for modern argument validation APIs (ArgumentNullException.ThrowIfNull, ArgumentException.ThrowIfNullOrEmpty/WhiteSpace, ArgumentOutOfRangeException.ThrowIf*) across all supported .NET targets, including .NET Standard 2.0 and .NET Framework. Mark legacy Argument.ThrowIf* methods as obsolete and delegate to new polyfills. Expand target frameworks, update CI for matrix builds, and add comprehensive unit tests. Remove legacy helpers and update package metadata to reflect universal polyfill focus.

Summary by CodeRabbit

  • New Features

    • Public ThrowIf* polyfills added to provide consistent argument validation across more target frameworks and expanded multi-targeting.
  • Deprecated

    • Legacy Argument.ThrowIf* helpers marked Obsolete in favor of exception-type ThrowIf* methods.
  • Documentation

    • README rewritten with polyfill architecture, updated API names, usage examples, and detailed framework compatibility.
  • CI / Chores

    • CI and editor settings updated (including increased XML line length) for broader platform coverage.
  • Tests

    • New unit tests added for polyfills; obsolete/placeholder tests removed.

✏️ Tip: You can customize this high-level summary in your review settings.

Refactor to provide polyfills for modern argument validation APIs (ArgumentNullException.ThrowIfNull, ArgumentException.ThrowIfNullOrEmpty/WhiteSpace, ArgumentOutOfRangeException.ThrowIf*) across all supported .NET targets, including .NET Standard 2.0 and .NET Framework. Mark legacy Argument.ThrowIf* methods as obsolete and delegate to new polyfills. Expand target frameworks, update CI for matrix builds, and add comprehensive unit tests. Remove legacy helpers and update package metadata to reflect universal polyfill focus.
@samtrion samtrion self-assigned this Dec 30, 2025
@samtrion samtrion added state:ready for merge Indicates that a pull request has been reviewed and approved, and is ready to be merged into the mai type:feature Indicates a new feature or enhancement to be added. labels Dec 30, 2025
@coderabbitai
Copy link

coderabbitai bot commented Dec 30, 2025

Walkthrough

Moves argument validation helpers into three exception-specific polyfill classes, adds many cross-target attribute and IEnumerable polyfills, marks legacy Argument helpers obsolete and delegates to new APIs, updates CI and project target-variable configuration, and adds comprehensive unit tests for the new polyfills.

Changes

Cohort / File(s) Summary
Configuration & Builds
\.editorconfig, Directory.Build.props, .github/workflows/cicd.yml, src/NetEvolve.Arguments/NetEvolve.Arguments.csproj, tests/NetEvolve.Arguments.Tests.Unit/NetEvolve.Arguments.Tests.Unit.csproj
Increased XML max_line_length to 160; introduced target framework variables (_NetTargetFrameworks, _ClassicTargetFrameworks, _ProjectTargetFrameworks, _TestTargetFrameworks) and removed GitVersion workaround; CI workflow adds runsOnBuild runner value; projects switched to variable-based TargetFrameworks, test project adds CS0618 suppression and platform/runtime settings; removed a conditional Polyfill package reference.
New Exception-based Polyfills
src/NetEvolve.Arguments/ArgumentNullExceptionPolyfills.cs, src/NetEvolve.Arguments/ArgumentExceptionPolyfill.cs, src/NetEvolve.Arguments/ArgumentOutOfRangeExceptionPolyfills.cs
Added three public static polyfill classes (placed in/near System namespace) implementing ThrowIf* APIs for ArgumentNullException, ArgumentException, and ArgumentOutOfRangeException with extensive conditional compilation and target-aware generic constraints.
Argument Type Restructure & Removals
src/NetEvolve.Arguments/Argument.cs, src/NetEvolve.Arguments/Argument_EMPTY.cs, src/NetEvolve.Arguments/Argument_ThrowArgumentException.cs, src/NetEvolve.Arguments/Argument_ThrowArgumentNullException.cs, src/NetEvolve.Arguments/Argument_ThrowArgumentOutOfRangeException.cs
Converted Argument partial to a forward-declaration and removed several legacy helper files and an empty placeholder, eliminating prior private throw-helper implementations.
Legacy Methods Marked Obsolete & Delegating
src/NetEvolve.Arguments/Argument_ThrowIfNull.cs, src/NetEvolve.Arguments/Argument_ThrowIfNullOrEmpty.cs, src/NetEvolve.Arguments/Argument_ThrowIfNullOrWhiteSpace.cs, src/NetEvolve.Arguments/Argument_ThrowIf{Equal,NotEqual,GreaterThan,GreaterThanOrEqual,LessThan,LessThanOrEqual}.cs
Decorated legacy Argument helper methods with [Obsolete] and replaced the previous inline/conditional implementations with expression-bodied delegations to the new Argument*-based polyfill methods.
Polyfill Utilities
src/NetEvolve.Arguments/Polyfills/...
(e.g., CallerArgumentExpressionAttribute.cs, DoesNotReturnAttribute.cs, DoesNotReturnIfAttribute.cs, ModuleInitializerAttribute.cs, NotNullAttribute.cs, StackTraceHiddenAttribute.cs, IEnumerable_TryGetNonEnumeratedCount.cs)
Added conditional-polyfill implementations and assembly type-forwarding for several attributes and an IEnumerable TryGetNonEnumeratedCount helper to maintain cross-target compatibility.
Tests Added / Updated
tests/NetEvolve.Arguments.Tests.Unit/ArgumentExceptionPolyfillsTests.cs, .../ArgumentNullExceptionPolyfillsTests.cs, .../ArgumentOutOfRangeExceptionPolyfillsTests.cs, tests/.../ArgumentTests.cs
Added comprehensive unit tests for new polyfills (string, enumerable, numeric/range, comparisons); removed an empty placeholder test file; test project updated to use test-target variable and suppress CS0618.
Documentation
README.md
Rewrote documentation to describe three exception-specific polyfill classes, updated API usage examples to exception-type ThrowIf* calls, detailed framework compatibility and native delegation behavior.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐇 I hopped through code at break of day,
Split old helpers, then sent them away.
New polyfills bloom across each lane,
Tests clap softly — carrots for the brain. 🥕

Pre-merge checks

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.72% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main refactoring effort: adding universal ThrowIf* polyfill support across all .NET targets, which aligns with the extensive changes throughout the codebase.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link

codecov bot commented Dec 30, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (6d84c5c) to head (589b2a6).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@             Coverage Diff             @@
##             main      #470      +/-   ##
===========================================
+ Coverage   96.29%   100.00%   +3.70%     
===========================================
  Files          11        12       +1     
  Lines          27        92      +65     
  Branches        2        22      +20     
===========================================
+ Hits           26        92      +66     
+ Misses          1         0       -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (5)
.github/workflows/cicd.yml (1)

32-32: Consider documenting the rationale for windows-latest as the build runner.

While windows-latest works fine, using ubuntu-latest is typically faster and more cost-effective for cross-platform .NET builds. If there's a specific requirement for building on Windows (e.g., certain tooling or SDK features), consider documenting this choice in a comment for future maintainers.

src/NetEvolve.Arguments/Argument_ThrowIfNotEqual.cs (1)

4-4: Remove unused using directive.

The using System.Collections.Generic; directive is not used in this file and should be removed.

🔎 Proposed fix
 namespace NetEvolve.Arguments;
 
 using System;
-using System.Collections.Generic;
 using System.Diagnostics;
 using System.Runtime.CompilerServices;
src/NetEvolve.Arguments/Argument_ThrowIfNullOrEmpty.cs (1)

35-38: Nullability inconsistency in parameter declaration.

The parameter IEnumerable<T> argument is declared non-nullable, but it has [NotNull] attribute (used for nullable parameters). The polyfill in ArgumentExceptionPolyfill.cs (line 71) declares IEnumerable<T>? argument. Consider making this parameter nullable to match:

🔎 Suggested fix
     public static void ThrowIfNullOrEmpty<T>(
-        [NotNull] IEnumerable<T> argument,
+        [NotNull] IEnumerable<T>? argument,
         [CallerArgumentExpression(nameof(argument))] string? paramName = null
     ) => ArgumentException.ThrowIfNullOrEmpty(argument, paramName);
tests/NetEvolve.Arguments.Tests.Unit/ArgumentNullExceptionPolyfillsTests.cs (1)

78-96: Potential test redundancy to review.

The test at lines 78-86 (ThrowIfNull_ReferenceType_WhenArgumentIsNotNull_DoesNotThrow) appears to duplicate the test at lines 20-28 (ThrowIfNull_Object_WhenArgumentIsNotNull_DoesNotThrow), as both test new object().

Additionally, the test at lines 88-96 (ThrowIfNull_ValueType_WhenArgumentHasValue_DoesNotThrow) uses (object)123 which is a boxed value type. Since the argument is still of type object, this doesn't meaningfully test a different code path from the other reference type tests.

Consider consolidating or clarifying the intent of these test cases.

src/NetEvolve.Arguments/ArgumentOutOfRangeExceptionPolyfills.cs (1)

149-155: Inconsistent error message format.

The error message at line 154 includes the parameter name in the message text itself:

$"{name} ('{value}') must be a non-negative and non-zero value."

However, the parameter name is already passed as the first argument to the ArgumentOutOfRangeException constructor. This is inconsistent with the other error messages in this file (lines 45, 97, 174, etc.) which don't duplicate the parameter name in the message text.

🔎 Proposed fix for consistent error message format
 [DoesNotReturn]
 private static void ThrowNegativeOrZero<T>(T value, string? name) =>
     throw new ArgumentOutOfRangeException(
         name,
         value,
-        $"{name} ('{value}') must be a non-negative and non-zero value."
+        "Value must be non-negative and non-zero."
     );
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between 6d84c5c and 670fb46.

📒 Files selected for processing (34)
  • .editorconfig
  • .github/workflows/cicd.yml
  • Directory.Build.props
  • README.md
  • src/NetEvolve.Arguments/Argument.cs
  • src/NetEvolve.Arguments/ArgumentExceptionPolyfill.cs
  • src/NetEvolve.Arguments/ArgumentNullExceptionPolyfills.cs
  • src/NetEvolve.Arguments/ArgumentOutOfRangeExceptionPolyfills.cs
  • src/NetEvolve.Arguments/Argument_EMPTY.cs
  • src/NetEvolve.Arguments/Argument_ThrowArgumentException.cs
  • src/NetEvolve.Arguments/Argument_ThrowArgumentNullException.cs
  • src/NetEvolve.Arguments/Argument_ThrowArgumentOutOfRangeException.cs
  • src/NetEvolve.Arguments/Argument_ThrowIfEqual.cs
  • src/NetEvolve.Arguments/Argument_ThrowIfGreaterThan.cs
  • src/NetEvolve.Arguments/Argument_ThrowIfGreaterThanOrEqual.cs
  • src/NetEvolve.Arguments/Argument_ThrowIfLessThan.cs
  • src/NetEvolve.Arguments/Argument_ThrowIfLessThanOrEqual.cs
  • src/NetEvolve.Arguments/Argument_ThrowIfNotEqual.cs
  • src/NetEvolve.Arguments/Argument_ThrowIfNull.cs
  • src/NetEvolve.Arguments/Argument_ThrowIfNullOrEmpty.cs
  • src/NetEvolve.Arguments/Argument_ThrowIfNullOrWhiteSpace.cs
  • src/NetEvolve.Arguments/NetEvolve.Arguments.csproj
  • src/NetEvolve.Arguments/Polyfills/CallerArgumentExpressionAttribute.cs
  • src/NetEvolve.Arguments/Polyfills/DoesNotReturnAttribute.cs
  • src/NetEvolve.Arguments/Polyfills/DoesNotReturnIfAttribute.cs
  • src/NetEvolve.Arguments/Polyfills/IEnumerable_TryGetNonEnumeratedCount.cs
  • src/NetEvolve.Arguments/Polyfills/ModuleInitializerAttribute.cs
  • src/NetEvolve.Arguments/Polyfills/NotNullAttribute.cs
  • src/NetEvolve.Arguments/Polyfills/StackTraceHiddenAttribute.cs
  • tests/NetEvolve.Arguments.Tests.Unit/ArgumentExceptionPolyfillsTests.cs
  • tests/NetEvolve.Arguments.Tests.Unit/ArgumentNullExceptionPolyfillsTests.cs
  • tests/NetEvolve.Arguments.Tests.Unit/ArgumentOutOfRangeExceptionPolyfillsTests.cs
  • tests/NetEvolve.Arguments.Tests.Unit/ArgumentTests.cs
  • tests/NetEvolve.Arguments.Tests.Unit/NetEvolve.Arguments.Tests.Unit.csproj
💤 Files with no reviewable changes (5)
  • src/NetEvolve.Arguments/Argument_ThrowArgumentException.cs
  • src/NetEvolve.Arguments/Argument_EMPTY.cs
  • src/NetEvolve.Arguments/Argument_ThrowArgumentOutOfRangeException.cs
  • src/NetEvolve.Arguments/Argument_ThrowArgumentNullException.cs
  • tests/NetEvolve.Arguments.Tests.Unit/ArgumentTests.cs
🧰 Additional context used
🧬 Code graph analysis (13)
src/NetEvolve.Arguments/Polyfills/ModuleInitializerAttribute.cs (3)
src/NetEvolve.Arguments/Polyfills/CallerArgumentExpressionAttribute.cs (1)
  • ExcludeFromCodeCoverage (14-28)
src/NetEvolve.Arguments/Polyfills/DoesNotReturnIfAttribute.cs (1)
  • ExcludeFromCodeCoverage (12-29)
src/NetEvolve.Arguments/Polyfills/IEnumerable_TryGetNonEnumeratedCount.cs (1)
  • ExcludeFromCodeCoverage (13-38)
src/NetEvolve.Arguments/Argument_ThrowIfLessThan.cs (8)
src/NetEvolve.Arguments/Argument_ThrowIfEqual.cs (1)
  • Obsolete (13-22)
src/NetEvolve.Arguments/Argument_ThrowIfGreaterThan.cs (1)
  • Obsolete (13-22)
src/NetEvolve.Arguments/Argument_ThrowIfGreaterThanOrEqual.cs (1)
  • Obsolete (13-22)
src/NetEvolve.Arguments/Argument_ThrowIfLessThanOrEqual.cs (1)
  • Obsolete (13-22)
src/NetEvolve.Arguments/Argument_ThrowIfNotEqual.cs (1)
  • Obsolete (14-23)
src/NetEvolve.Arguments/Argument_ThrowIfNull.cs (2)
  • Obsolete (13-20)
  • Obsolete (25-34)
src/NetEvolve.Arguments/Argument_ThrowIfNullOrEmpty.cs (2)
  • Obsolete (17-24)
  • Obsolete (31-38)
src/NetEvolve.Arguments/Argument_ThrowIfNullOrWhiteSpace.cs (1)
  • Obsolete (15-22)
src/NetEvolve.Arguments/Polyfills/DoesNotReturnAttribute.cs (3)
src/NetEvolve.Arguments/Polyfills/CallerArgumentExpressionAttribute.cs (1)
  • ExcludeFromCodeCoverage (14-28)
src/NetEvolve.Arguments/Polyfills/DoesNotReturnIfAttribute.cs (1)
  • ExcludeFromCodeCoverage (12-29)
src/NetEvolve.Arguments/Polyfills/IEnumerable_TryGetNonEnumeratedCount.cs (1)
  • ExcludeFromCodeCoverage (13-38)
src/NetEvolve.Arguments/Argument_ThrowIfGreaterThan.cs (6)
src/NetEvolve.Arguments/Argument_ThrowIfEqual.cs (1)
  • Obsolete (13-22)
src/NetEvolve.Arguments/Argument_ThrowIfGreaterThanOrEqual.cs (1)
  • Obsolete (13-22)
src/NetEvolve.Arguments/Argument_ThrowIfLessThan.cs (1)
  • Obsolete (13-22)
src/NetEvolve.Arguments/Argument_ThrowIfLessThanOrEqual.cs (1)
  • Obsolete (13-22)
src/NetEvolve.Arguments/Argument_ThrowIfNotEqual.cs (1)
  • Obsolete (14-23)
src/NetEvolve.Arguments/Argument_ThrowIfNullOrWhiteSpace.cs (1)
  • Obsolete (15-22)
tests/NetEvolve.Arguments.Tests.Unit/ArgumentExceptionPolyfillsTests.cs (2)
tests/NetEvolve.Arguments.Tests.Unit/ArgumentNullExceptionPolyfillsTests.cs (8)
  • Test (7-18)
  • Test (20-28)
  • Test (30-41)
  • Test (43-52)
  • Test (54-65)
  • Test (67-76)
  • Test (78-86)
  • Test (88-96)
tests/NetEvolve.Arguments.Tests.Unit/ArgumentOutOfRangeExceptionPolyfillsTests.cs (8)
  • Test (7-18)
  • Test (20-30)
  • Test (32-43)
  • Test (45-53)
  • Test (55-66)
  • Test (68-77)
  • Test (79-90)
  • Test (92-100)
src/NetEvolve.Arguments/ArgumentExceptionPolyfill.cs (2)
src/NetEvolve.Arguments/ArgumentNullExceptionPolyfills.cs (1)
  • SuppressMessage (12-52)
src/NetEvolve.Arguments/Polyfills/IEnumerable_TryGetNonEnumeratedCount.cs (1)
  • TryGetNonEnumeratedCount (21-37)
src/NetEvolve.Arguments/Polyfills/DoesNotReturnIfAttribute.cs (2)
src/NetEvolve.Arguments/Polyfills/CallerArgumentExpressionAttribute.cs (1)
  • ExcludeFromCodeCoverage (14-28)
src/NetEvolve.Arguments/Polyfills/IEnumerable_TryGetNonEnumeratedCount.cs (1)
  • ExcludeFromCodeCoverage (13-38)
src/NetEvolve.Arguments/Polyfills/StackTraceHiddenAttribute.cs (3)
src/NetEvolve.Arguments/Polyfills/CallerArgumentExpressionAttribute.cs (1)
  • ExcludeFromCodeCoverage (14-28)
src/NetEvolve.Arguments/Polyfills/DoesNotReturnIfAttribute.cs (1)
  • ExcludeFromCodeCoverage (12-29)
src/NetEvolve.Arguments/Polyfills/IEnumerable_TryGetNonEnumeratedCount.cs (1)
  • ExcludeFromCodeCoverage (13-38)
src/NetEvolve.Arguments/Argument_ThrowIfNullOrEmpty.cs (8)
src/NetEvolve.Arguments/Argument_ThrowIfEqual.cs (1)
  • Obsolete (13-22)
src/NetEvolve.Arguments/Argument_ThrowIfGreaterThan.cs (1)
  • Obsolete (13-22)
src/NetEvolve.Arguments/Argument_ThrowIfGreaterThanOrEqual.cs (1)
  • Obsolete (13-22)
src/NetEvolve.Arguments/Argument_ThrowIfLessThan.cs (1)
  • Obsolete (13-22)
src/NetEvolve.Arguments/Argument_ThrowIfLessThanOrEqual.cs (1)
  • Obsolete (13-22)
src/NetEvolve.Arguments/Argument_ThrowIfNotEqual.cs (1)
  • Obsolete (14-23)
src/NetEvolve.Arguments/Argument_ThrowIfNull.cs (2)
  • Obsolete (13-20)
  • Obsolete (25-34)
src/NetEvolve.Arguments/Argument_ThrowIfNullOrWhiteSpace.cs (1)
  • Obsolete (15-22)
src/NetEvolve.Arguments/ArgumentNullExceptionPolyfills.cs (2)
src/NetEvolve.Arguments/ArgumentExceptionPolyfill.cs (1)
  • SuppressMessage (10-86)
src/NetEvolve.Arguments/ArgumentOutOfRangeExceptionPolyfills.cs (1)
  • SuppressMessage (9-297)
src/NetEvolve.Arguments/ArgumentOutOfRangeExceptionPolyfills.cs (2)
src/NetEvolve.Arguments/ArgumentExceptionPolyfill.cs (1)
  • SuppressMessage (10-86)
src/NetEvolve.Arguments/ArgumentNullExceptionPolyfills.cs (1)
  • SuppressMessage (12-52)
src/NetEvolve.Arguments/Argument.cs (9)
src/NetEvolve.Arguments/Argument_ThrowIfEqual.cs (1)
  • Argument (7-23)
src/NetEvolve.Arguments/Argument_ThrowIfGreaterThan.cs (1)
  • Argument (7-23)
src/NetEvolve.Arguments/Argument_ThrowIfGreaterThanOrEqual.cs (1)
  • Argument (7-23)
src/NetEvolve.Arguments/Argument_ThrowIfLessThan.cs (1)
  • Argument (7-23)
src/NetEvolve.Arguments/Argument_ThrowIfLessThanOrEqual.cs (1)
  • Argument (7-23)
src/NetEvolve.Arguments/Argument_ThrowIfNotEqual.cs (1)
  • Argument (8-24)
src/NetEvolve.Arguments/Argument_ThrowIfNull.cs (1)
  • Argument (8-35)
src/NetEvolve.Arguments/Argument_ThrowIfNullOrEmpty.cs (1)
  • Argument (10-39)
src/NetEvolve.Arguments/Argument_ThrowIfNullOrWhiteSpace.cs (1)
  • Argument (8-23)
src/NetEvolve.Arguments/Argument_ThrowIfNull.cs (2)
src/NetEvolve.Arguments/Argument_ThrowIfNullOrEmpty.cs (2)
  • Obsolete (17-24)
  • Obsolete (31-38)
src/NetEvolve.Arguments/Argument_ThrowIfNullOrWhiteSpace.cs (1)
  • Obsolete (15-22)
🔇 Additional comments (30)
.editorconfig (1)

45-47: LGTM!

The increased line length for project files accommodates verbose XML elements like package references and long property values, which aligns with the expanded multi-targeting in this PR.

src/NetEvolve.Arguments/Polyfills/CallerArgumentExpressionAttribute.cs (1)

1-34: LGTM!

The polyfill correctly guards with !NETCOREAPP3_0_OR_GREATER matching when CallerArgumentExpressionAttribute was introduced. The TypeForwardedTo pattern for newer frameworks prevents type conflicts. The attributes (ExcludeFromCodeCoverage, DebuggerNonUserCode) appropriately mark this as infrastructure code.

src/NetEvolve.Arguments/Polyfills/ModuleInitializerAttribute.cs (1)

1-25: LGTM!

The polyfill correctly targets !NET5_0_OR_GREATER which matches when ModuleInitializerAttribute was introduced. The implementation follows the same pattern as the other polyfills in this PR.

src/NetEvolve.Arguments/Polyfills/NotNullAttribute.cs (1)

1-22: LGTM!

The dual guard !NETCOREAPP3_0_OR_GREATER && !NETSTANDARD2_1_OR_GREATER correctly reflects that NotNullAttribute is available in both .NET Core 3.0+ and .NET Standard 2.1+. The AttributeUsage targets match the framework definition.

src/NetEvolve.Arguments/Polyfills/IEnumerable_TryGetNonEnumeratedCount.cs (1)

21-37: LGTM!

The polyfill correctly implements the core functionality of TryGetNonEnumeratedCount for .NET < 6.0. The check order (generic ICollection<T> before non-generic ICollection) is appropriate for type safety. The simplified implementation omits internal LINQ provider checks but covers the primary use cases (arrays, lists, and standard collections).

src/NetEvolve.Arguments/Polyfills/DoesNotReturnAttribute.cs (1)

1-19: LGTM!

The polyfill correctly implements DoesNotReturnAttribute with the appropriate conditional guard for frameworks lacking the attribute. The implementation is consistent with the other polyfills in this PR.

src/NetEvolve.Arguments/Polyfills/StackTraceHiddenAttribute.cs (1)

1-22: LGTM!

The polyfill correctly targets !NET6_0_OR_GREATER matching when StackTraceHiddenAttribute was introduced. The namespace (System.Diagnostics) and AttributeUsage targets match the framework definition.

tests/NetEvolve.Arguments.Tests.Unit/NetEvolve.Arguments.Tests.Unit.csproj (2)

17-19: LGTM!

The AssemblyAttribute for NetEvolve.Extensions.TUnit.UnitTestAttribute enables proper test discovery with the TUnit framework.


3-5: LGTM!

Using $(_TestTargetFrameworks) centralizes multi-targeting configuration across all test projects, and suppressing CS0618 is appropriate since this PR marks legacy Argument.ThrowIf* methods as obsolete while tests still need to verify their behavior.

src/NetEvolve.Arguments/NetEvolve.Arguments.csproj (1)

3-4: LGTM! Project configuration aligns with polyfill strategy.

The use of $(_ProjectTargetFrameworks) enables consistent multi-targeting across the solution, and the updated description accurately reflects the universal polyfill approach for ThrowIf* helpers.

src/NetEvolve.Arguments/Argument_ThrowIfNullOrWhiteSpace.cs (1)

15-22: LGTM! Deprecation and delegation pattern is correct.

The method is properly marked as obsolete with a clear migration path, and delegates to the framework/polyfill implementation of ArgumentException.ThrowIfNullOrWhiteSpace.

src/NetEvolve.Arguments/Argument_ThrowIfEqual.cs (1)

13-22: LGTM! Deprecation and delegation pattern is correct.

The obsolete method correctly delegates to ArgumentOutOfRangeException.ThrowIfEqual while preserving the generic constraint where T : IEquatable<T>.

src/NetEvolve.Arguments/Argument_ThrowIfNotEqual.cs (1)

14-23: LGTM! Deprecation and delegation pattern is correct.

The method is properly marked as obsolete and delegates to ArgumentOutOfRangeException.ThrowIfNotEqual while preserving the generic constraint.

src/NetEvolve.Arguments/Argument_ThrowIfGreaterThanOrEqual.cs (1)

13-22: LGTM! Deprecation and delegation pattern is correct.

The obsolete method correctly delegates to ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual while preserving the generic constraint where T : IComparable<T>.

Directory.Build.props (1)

3-13: LGTM! Multi-targeting configuration is well-structured.

The property definitions cleanly organize target frameworks into logical groups and conditionally include .NET Framework targets on Windows. The CheckEolTargetFramework=false setting is appropriate for a polyfill library supporting multiple framework versions.

Note: The configuration includes net10.0 as a target. Please ensure your build environment has an SDK version that recognizes this target framework moniker, as .NET 10 may still be in preview.

src/NetEvolve.Arguments/Argument_ThrowIfLessThanOrEqual.cs (1)

13-22: LGTM! Deprecation and delegation pattern is correct.

The obsolete method correctly delegates to ArgumentOutOfRangeException.ThrowIfLessThanOrEqual while preserving the generic constraint where T : IComparable<T>.

src/NetEvolve.Arguments/Argument.cs (1)

9-9: No concerns. The empty partial class declaration syntax is valid across all target frameworks and does not require C# 10+.

src/NetEvolve.Arguments/Polyfills/DoesNotReturnIfAttribute.cs (1)

1-34: LGTM!

The polyfill implementation follows the established pattern in the codebase (e.g., CallerArgumentExpressionAttribute). The conditional compilation correctly provides a local implementation for older frameworks and type forwards to the framework type for newer ones.

tests/NetEvolve.Arguments.Tests.Unit/ArgumentOutOfRangeExceptionPolyfillsTests.cs (1)

1-6: Good test coverage for the polyfills.

The test class provides comprehensive coverage for all ArgumentOutOfRangeException.ThrowIf* polyfill methods across multiple numeric types (int, double, nint) and strings. The parameterized tests with [Arguments] effectively cover both throwing and non-throwing scenarios.

src/NetEvolve.Arguments/Argument_ThrowIfLessThan.cs (1)

13-22: LGTM!

The deprecation pattern is consistent with other Argument.ThrowIf* methods in the codebase, correctly marking the method as obsolete and delegating to the polyfill implementation.

src/NetEvolve.Arguments/Argument_ThrowIfNull.cs (2)

13-20: LGTM!

The object overload is correctly marked obsolete and delegates to ArgumentNullException.ThrowIfNull. The pattern is consistent with other deprecated methods.


25-34: LGTM!

The unsafe pointer overload follows the same deprecation pattern. Note that MethodImpl(AggressiveInlining) is appropriate here since the method now simply delegates to the polyfill.

src/NetEvolve.Arguments/Argument_ThrowIfGreaterThan.cs (1)

13-22: LGTM!

The deprecation pattern is consistent with the other Argument.ThrowIf* methods, correctly delegating to the polyfill implementation.

src/NetEvolve.Arguments/ArgumentNullExceptionPolyfills.cs (1)

1-34: Well-structured polyfill implementation.

The object overload is correctly guarded with #if !NET6_0_OR_GREATER since ArgumentNullException.ThrowIfNull(object?, string?) was introduced in .NET 6. The extension syntax and namespace placement enable the familiar ArgumentNullException.ThrowIfNull(...) call pattern on older frameworks.

src/NetEvolve.Arguments/ArgumentExceptionPolyfill.cs (1)

15-61: LGTM!

The string validation methods (ThrowIfNullOrEmpty, ThrowIfNullOrWhiteSpace) are correctly guarded for frameworks prior to .NET 8 and properly handle null, empty, and whitespace cases with appropriate exception types.

src/NetEvolve.Arguments/Argument_ThrowIfNullOrEmpty.cs (1)

17-24: LGTM!

The string overload is correctly marked obsolete and delegates to the polyfill implementation, following the consistent pattern across all deprecated Argument.ThrowIf* methods.

tests/NetEvolve.Arguments.Tests.Unit/ArgumentExceptionPolyfillsTests.cs (1)

6-154: Comprehensive test coverage for ArgumentException polyfills!

The test suite thoroughly covers all scenarios for the ThrowIfNullOrEmpty and ThrowIfNullOrWhiteSpace polyfills:

  • Null argument validation (expecting ArgumentNullException)
  • Empty/whitespace validation (expecting ArgumentException)
  • Valid input scenarios (no exception)
  • Both string and generic enumerable overloads

The tests follow consistent patterns with the other polyfill test files and use appropriate assertion strategies.

README.md (1)

12-290: Excellent documentation for the polyfill architecture!

The README comprehensively documents:

  • The three-class polyfill architecture (ArgumentNullExceptionPolyfills, ArgumentExceptionPolyfills, ArgumentOutOfRangeExceptionPolyfills)
  • Clear usage examples demonstrating the new API patterns
  • Complete method documentation with native API references
  • Framework compatibility table with appropriate notes about delegation behavior
  • Proper attribution of Windows-only support for .NET Framework targets

The documentation provides clear guidance for users migrating to or using the polyfills across multiple .NET versions.

src/NetEvolve.Arguments/ArgumentOutOfRangeExceptionPolyfills.cs (2)

14-295: Well-structured polyfill implementation with proper conditional compilation.

The implementation correctly:

  • Uses conditional compilation to provide polyfills only for frameworks lacking native implementations (!NET8_0_OR_GREATER, etc.)
  • Leverages INumberBase<T> for .NET 7+ where available, falling back to IComparable<T> and IEquatable<T> constraints for older frameworks
  • Provides specialized overloads for nint on pre-.NET 7 frameworks
  • Uses [DoesNotReturn] attributes on helper methods
  • Includes proper XML documentation with links to official .NET API references
  • Implements consistent exception message patterns (with one minor inconsistency noted separately)

The polyfills will seamlessly delegate to native implementations on modern frameworks while providing backward compatibility for older targets.


12-12: The extension(...) syntax is the standard Polyfill library pattern and requires no changes.

This project uses the Polyfill NuGet package (v9.5.0) for source generation. The extension(ArgumentOutOfRangeException) syntax is the documented way to define polyfill extension methods; the source generator processes this syntax at compile time to create extension methods for older framework versions. The build system correctly handles this, and it generates the intended extension method implementations as designed.

@samtrion
Copy link
Member Author

@coderabbitai generate docstrings

@coderabbitai
Copy link

coderabbitai bot commented Dec 31, 2025

Warning

Docstrings generation is disabled for your repository or organization.

@samtrion
Copy link
Member Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Dec 31, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link

coderabbitai bot commented Dec 31, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Fetch the polyfill source files to review current documentation
cat src/NetEvolve.Arguments/ArgumentNullExceptionPolyfills.cs
echo "---FILE SEPARATOR---"
cat src/NetEvolve.Arguments/ArgumentExceptionPolyfill.cs
echo "---FILE SEPARATOR---"
cat src/NetEvolve.Arguments/ArgumentOutOfRangeExceptionPolyfills.cs

Length of output: 23497


@samtrion: Great news! Your polyfill classes already have comprehensive XML documentation. I've reviewed all three files and found they're well-documented with summaries, parameter descriptions, exception documentation, and links to Microsoft documentation.

However, I noticed a few minor improvements that can be made:

1. Missing seealso link in ArgumentExceptionPolyfill.cs

The ThrowIfNullOrEmpty<T> (enumerable overload) is missing a seealso reference. Here's the improved version:

/// <summary>Throws an exception if <paramref name="argument"/> is <see langword="null"/> or empty.</summary>
/// <typeparam name="T">The type of objects to enumerate.</typeparam>
/// <param name="argument">The enumerable argument to validate as non-<see langword="null"/> and non-empty.</param>
/// <param name="paramName">The name of the parameter with which <paramref name="argument"/> corresponds.</param>
/// <exception cref="ArgumentNullException"><paramref name="argument"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException"><paramref name="argument"/> is empty.</exception>
/// <remarks>The <paramref name="paramName"/> parameter is included to support the <see cref="CallerArgumentExpressionAttribute"/> attribute. It's recommended that you don't pass a value for this parameter and let the name of <paramref name="argument"/> be used instead.</remarks>
public static void ThrowIfNullOrEmpty<T>(
    [NotNull] IEnumerable<T>? argument,
    [CallerArgumentExpression(nameof(argument))] string? paramName = null
)

2. Documentation for private helper methods in ArgumentOutOfRangeExceptionPolyfills.cs

While optional, it's good practice to document private methods too:

/// <summary>Throws an <see cref="ArgumentOutOfRangeException"/> with a zero-value message.</summary>
/// <param name="paramName">The name of the parameter that caused the exception.</param>
/// <exception cref="ArgumentOutOfRangeException">Always thrown.</exception>
[DoesNotReturn]
private static void ThrowZero(string? paramName) =>
    throw new ArgumentOutOfRangeException(paramName, "Value must not be zero.");

/// <summary>Throws an <see cref="ArgumentOutOfRangeException"/> with a negative-value message.</summary>
/// <typeparam name="T">The type of the value.</typeparam>
/// <param name="value">The actual value of the argument.</param>
/// <param name="paramName">The name of the parameter that caused the exception.</param>
/// <exception cref="ArgumentOutOfRangeException">Always thrown.</exception>
[DoesNotReturn]
private static void ThrowNegative<T>(T value, string? paramName) =>
    throw new ArgumentOutOfRangeException(paramName, value, "Value must be non-negative.");

/// <summary>Throws an <see cref="ArgumentOutOfRangeException"/> with a negative-or-zero-value message.</summary>
/// <typeparam name="T">The type of the value.</typeparam>
/// <param name="value">The actual value of the argument.</param>
/// <param name="name">The name of the parameter that caused the exception.</param>
/// <exception cref="ArgumentOutOfRangeException">Always thrown.</exception>
[DoesNotReturn]
private static void ThrowNegativeOrZero<T>(T value, string? name) =>
    throw new ArgumentOutOfRangeException(
        name,
        value,
        $"{name} ('{value}') must be a non-negative and non-zero value."
    );

Overall, your documentation is already excellent and follows best practices with clear descriptions, proper XML tags, and helpful links to Microsoft's documentation. The above changes are minor polish that would make it even more complete.

Would you like me to provide the complete updated files with these improvements?

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/NetEvolve.Arguments/NetEvolve.Arguments.csproj (1)

4-4: Consider shortening the package description for better readability.

While the description is comprehensive and accurate, at 341 characters it may be verbose for package metadata. Consider a more concise version that highlights the key value proposition.

💡 Example concise description
-    <Description>A universal polyfill library that provides modern ArgumentNullException.ThrowIf* and ArgumentException.ThrowIf* helper methods across all .NET runtimes (.NET Standard 2.0+, .NET Framework 4.6.2+, .NET 6.0+), enabling consistent argument validation patterns regardless of target framework version.</Description>
+    <Description>Universal polyfill library providing modern ArgumentNullException.ThrowIf* and ArgumentException.ThrowIf* helpers across all .NET runtimes (.NET Standard 2.0+, .NET Framework 4.6.2+, .NET 6+).</Description>
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between 9b1bde6 and 837a16b.

📒 Files selected for processing (2)
  • src/NetEvolve.Arguments/NetEvolve.Arguments.csproj
  • tests/NetEvolve.Arguments.Tests.Unit/NetEvolve.Arguments.Tests.Unit.csproj
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/NetEvolve.Arguments.Tests.Unit/NetEvolve.Arguments.Tests.Unit.csproj
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build & Tests / Run Tests / Testing .NET solution
  • GitHub Check: Build & Tests / Code Formatting / Validate Code Formatting

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/NetEvolve.Arguments/ArgumentOutOfRangeExceptionPolyfills.cs (2)

174-174: Rename parameter to match other helpers.

The parameter is named name but should be paramName for consistency with the other helper methods (ThrowZero at line 54 and ThrowNegative<T> at line 118).

🔎 Proposed fix
-        private static void ThrowNegativeOrZero<T>(T value, string? name) =>
+        private static void ThrowNegativeOrZero<T>(T value, string? paramName) =>
             throw new ArgumentOutOfRangeException(
-                name,
+                paramName,
                 value,
-                $"{name} ('{value}') must be a non-negative and non-zero value."
+                $"{paramName} ('{value}') must be a non-negative and non-zero value."
             );

188-193: Consider standardizing the error message format.

The error message includes the parameter name in the message string itself ("{name} ('{value}') must be..."), which differs from all other error messages in this file. Other methods pass the parameter name as the first argument to ArgumentOutOfRangeException and keep it out of the message string (e.g., lines 212, 234, 259, 282, 307, 330).

While including the parameter name might provide additional context, the inconsistency may be unintentional.

🔎 Proposed fix for consistency
         [DoesNotReturn]
         private static void ThrowNegativeOrZero<T>(T value, string? paramName) =>
             throw new ArgumentOutOfRangeException(
                 paramName,
                 value,
-                $"{paramName} ('{value}') must be a non-negative and non-zero value."
+                "Value must be non-negative and non-zero."
             );

Note: The exception constructor already includes the parameter name and value in its formatted output, so including them in the message string may be redundant.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between 837a16b and 589b2a6.

📒 Files selected for processing (4)
  • .github/workflows/cicd.yml
  • src/NetEvolve.Arguments/ArgumentOutOfRangeExceptionPolyfills.cs
  • src/NetEvolve.Arguments/NetEvolve.Arguments.csproj
  • src/NetEvolve.Arguments/Polyfills/IEnumerable_TryGetNonEnumeratedCount.cs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/NetEvolve.Arguments/Polyfills/IEnumerable_TryGetNonEnumeratedCount.cs
🧰 Additional context used
🧬 Code graph analysis (1)
src/NetEvolve.Arguments/ArgumentOutOfRangeExceptionPolyfills.cs (2)
src/NetEvolve.Arguments/ArgumentNullExceptionPolyfills.cs (1)
  • SuppressMessage (12-52)
src/NetEvolve.Arguments/ArgumentExceptionPolyfill.cs (1)
  • SuppressMessage (10-86)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build & Tests / Run Tests / Testing .NET solution
  • GitHub Check: Build & Tests / Code Formatting / Validate Code Formatting
🔇 Additional comments (8)
.github/workflows/cicd.yml (1)

28-28: The runsOnBuild parameter is fully supported by the workflow. No action required.

src/NetEvolve.Arguments/NetEvolve.Arguments.csproj (2)

3-3: LGTM! Variable-based target framework management is a best practice.

Using the centralized $(_ProjectTargetFrameworks) variable improves maintainability and ensures consistent multi-targeting across the project.


9-9: LGTM! Typo fixed and tags appropriately expanded.

The previous typo ("clausel" → "clause") has been corrected, and the package tags have been comprehensively expanded with relevant keywords (argument-validation, polyfill, null-check, parameter-validation, etc.) that will improve package discoverability.

src/NetEvolve.Arguments/ArgumentOutOfRangeExceptionPolyfills.cs (5)

1-13: LGTM! Proper polyfill structure.

The file header, namespace declaration, and class structure follow the correct pattern for System namespace polyfills, consistent with the related ArgumentNullExceptionPolyfills and ArgumentExceptionPolyfills classes.


14-56: LGTM! Well-structured polyfill with appropriate fallbacks.

The ThrowIfZero implementation correctly uses INumberBase<T> for .NET 7+ and falls back to IEquatable<T> for older versions. The helper method is properly wrapped in the same conditional block and includes the DoesNotReturn attribute.


58-121: LGTM! Correct conditional compilation and nint handling.

The ThrowIfNegative implementation properly includes a separate nint overload for pre-.NET 7 targets, as INumberBase<T> handles nint naturally on .NET 7+. The helper method is correctly positioned within the outer conditional block.


195-237: LGTM! Correct equality comparison logic.

Both ThrowIfEqual and ThrowIfNotEqual correctly use EqualityComparer<T>.Default.Equals for robust equality comparison, with appropriate IEquatable<T>? constraints that support nullable types.


239-333: LGTM! All comparison methods implement correct logic.

The four comparison methods (ThrowIfGreaterThan, ThrowIfGreaterThanOrEqual, ThrowIfLessThan, ThrowIfLessThanOrEqual) all correctly use CompareTo with appropriate comparison operators, and their error messages accurately describe the validation constraints.

@samtrion samtrion merged commit 0b4f384 into main Dec 31, 2025
11 checks passed
@samtrion samtrion deleted the refactor/moved-to-polyfills branch December 31, 2025 03:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

state:ready for merge Indicates that a pull request has been reviewed and approved, and is ready to be merged into the mai type:feature Indicates a new feature or enhancement to be added.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants