Skip to content

feat: add 21 validators and a sanitizers module for validator.js parity#3

Merged
divyanshub024 merged 3 commits into
mainfrom
dv/validators-sanitizers
May 15, 2026
Merged

feat: add 21 validators and a sanitizers module for validator.js parity#3
divyanshub024 merged 3 commits into
mainfrom
dv/validators-sanitizers

Conversation

@divyanshub024
Copy link
Copy Markdown
Member

@divyanshub024 divyanshub024 commented May 15, 2026

Summary

Expands flutter_validators toward validator.js coverage. The package went from 19 validators / 0 sanitizers to 40 validators / 13 sanitizers.

New validators (21)

isLowercase, isUppercase, isHexadecimal, isOctal, isDecimal, isFloat, isMongoId, isMD5, isPort, isSemVer, isSlug, isMACAddress, isLatLong, isJWT, isFQDN, isBase64, isByteLength, isStrongPassword, isIn, matches, contains.

Each is wired into all three APIs — top-level function, String extension, and a Validator.* form method with a default error message. (contains is top-level only, since String.contains already exists.)

New sanitizers module

A new package:flutter_validators/sanitizers.dart import surface: trim, ltrim, rtrim, escape, unescape, blacklist, whitelist, stripLow, normalizeEmail, toBoolean, toInt, toFloat, toDate.

Tests

  • A dedicated test file for every new validator and sanitizer.
  • Expanded form_validator_test.dart to cover all 41 Validator.* methods — 38 of them were previously untested (only required, email, length had coverage).
  • Full suite: 152 tests passing (was 113).

Other

  • README updated with new validator/sanitizer tables and form API reference.
  • CHANGELOG entry and version bump 1.1.01.2.0.

Scope deliberately excludes locale-heavy validators (isPostalCode, isMobilePhone 100+ locales, isIdentityCard, isTaxID, IBAN/BIC, crypto addresses).

Test plan

  • dart analyze — no issues
  • dart test — all 152 tests pass
  • dart format — clean
  • dart pub publish --dry-run — validates

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added 40+ validators (numbers, formats, encodings, IDs, case checks, slug/FQDN/MAC/lat-long/port/semver, hashes, JWT, strong-password, contains/matches/in-list, byte-length, etc.).
    • Added a sanitizers module (blacklist/whitelist, escape/unescape, normalize email, strip low/control chars, toBoolean/toNumber/toDate, trim variants).
    • Exposed corresponding Validator API methods and updated public exports; bumped package to 1.2.0.
  • Documentation

    • Expanded README (validator/sanitizer reference, examples, behavior notes) and updated CHANGELOG.
  • Tests

    • Added/expanded test suites covering the new validators and sanitizers.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 15, 2026

📝 Walkthrough

Walkthrough

This PR releases version 1.2.0: adds 21 validators, a sanitizers module (7 utilities) with a barrel, extends the Validator form class with matching factory methods, updates barrel exports and README/CHANGELOG, and adds comprehensive tests for validators and sanitizers.

Changes

Version 1.2.0 Feature Release: Validators, Sanitizers, and Form Integration

Layer / File(s) Summary
Release metadata and documentation
pubspec.yaml, CHANGELOG.md, README.md
Version bumped to 1.2.0, changelog entry added, README expanded with validator/sanitizer tables, Quick Start, API reference, and behavior notes.
Core string validators (16 modules)
lib/validators/base64.dart, lib/validators/hexadecimal.dart, lib/validators/octal.dart, lib/validators/decimal.dart, lib/validators/float.dart, lib/validators/lowercase.dart, lib/validators/uppercase.dart, lib/validators/slug.dart, lib/validators/fqdn.dart, lib/validators/mac_address.dart, lib/validators/lat_long.dart, lib/validators/port.dart, lib/validators/semver.dart, lib/validators/mongo_id.dart, lib/validators/md5.dart, lib/validators/jwt.dart
Format-specific validators each expose a top-level function and a String extension; implementations use RegExp or parsing checks for correctness and edge handling.
Complex validators (5 modules)
lib/validators/contains.dart, lib/validators/matches.dart, lib/validators/in.dart, lib/validators/strong_password.dart, lib/validators/byte_length.dart
Substring occurrence counting, Pattern matching (contains/matches), membership check (isIn), configurable strong-password checks, and UTF-8 byte-length validation.
Sanitizers module (7 utilities + barrel)
lib/sanitizers.dart, lib/validators/sanitizers/blacklist.dart, lib/validators/sanitizers/escape.dart, lib/validators/sanitizers/normalize_email.dart, lib/validators/sanitizers/strip_low.dart, lib/validators/sanitizers/to_boolean.dart, lib/validators/sanitizers/to_number.dart, lib/validators/sanitizers/trim.dart
Character blacklist/whitelist, HTML escape/unescape, Gmail-aware email normalization, control-character stripping, loose/strict boolean conversion, trimmed numeric/date parsing, and customizable trimming. Barrel lib/sanitizers.dart re-exports implementations.
Validator form class extensions
lib/form_validator.dart
21 new static Validator.* factory methods added, each returning String? Function(String?) closures that delegate to underlying validators and preserve _build behavior for empty/null inputs.
Barrel export reorganization
lib/flutter_validators.dart
Export list expanded and reordered to include the new validator modules and sanitizers.dart; existing exported validators remain available.
Minor reformatting/refactors
lib/validators/ip.dart, lib/validators/url.dart, lib/validators/uuid.dart
Formatting and regex-expression layout changes for readability; no behavior changes.
Comprehensive test coverage
test/form_validator_test.dart, many test/validators/* and test/validators/sanitizers/* files
Form validator tests adjusted to avoid name collisions; individual test suites added/expanded for all new validators and sanitizers verifying valid, invalid, and edge cases.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

A rabbit hops through validator fields,
With slugs and ports and FQDN shields,
Base64 bytes dance in the air,
While sanitizers trim with gentle care—
Twenty-one new friends, hopping true. 🐰

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main change: adding 21 validators and a sanitizers module to achieve validator.js parity, which is the primary objective of this changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dv/validators-sanitizers

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

Expands the package toward validator.js coverage by adding 21 new
validators (isLowercase, isUppercase, isHexadecimal, isOctal, isDecimal,
isFloat, isMongoId, isMD5, isPort, isSemVer, isSlug, isMACAddress,
isLatLong, isJWT, isFQDN, isBase64, isByteLength, isStrongPassword, isIn,
matches, contains) and a new sanitizers module (trim, ltrim, rtrim,
escape, unescape, blacklist, whitelist, stripLow, normalizeEmail,
toBoolean, toInt, toFloat, toDate).

Each new validator is wired into all three APIs (top-level function,
String extension, Validator form method). Adds test coverage for every
new validator and sanitizer, plus the 38 previously-untested Validator
form methods.
@divyanshub024 divyanshub024 force-pushed the dv/validators-sanitizers branch from ffcac2c to e2a5164 Compare May 15, 2026 10:07
Copy link
Copy Markdown

@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 (4)
lib/validators/fqdn.dart (1)

26-43: ⚡ Quick win

Consider adding total domain length validation.

The FQDN specification (RFC 1035) limits the total domain name length to 253 characters. While the current implementation correctly validates per-label constraints (max 63 chars), it doesn't enforce the overall length limit. This is a minor spec deviation that's unlikely to cause practical issues, as overly long domains would fail at DNS resolution anyway.

📏 Proposed addition for total length check
 bool _isFQDN(String str) {
   if (str.isEmpty) return false;
   var domain = str;
   if (domain.endsWith('.')) {
     domain = domain.substring(0, domain.length - 1);
   }
+  if (domain.length > 253) return false;
   final parts = domain.split('.');
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/validators/fqdn.dart` around lines 26 - 43, The _isFQDN function
currently validates labels but not the overall domain length; add a check that
the reconstructed domain (after removing an optional trailing '.') has a total
length of at most 253 characters and return false if it exceeds that limit.
Place this check in _isFQDN after the trailing-dot trim (i.e., using the
variable domain) and before proceeding to split into parts or validating labels
(parts/ tld / _fqdnTld / _fqdnLabel).
lib/validators/float.dart (1)

24-30: ⚡ Quick win

Add .trim() to maintain consistency with other validators in this codebase.

The _isFloat function should trim whitespace before parsing, consistent with how toFloat sanitizer (lib/validators/sanitizers/to_number.dart line 23) and lat_long validator (lines 25-26) handle floating-point parsing. Update line 25 to:

final value = double.tryParse(str.trim());

Additionally, consider adding test cases for whitespace handling to prevent regression.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/validators/float.dart` around lines 24 - 30, The _isFloat function
currently parses the raw input string; update it to trim surrounding whitespace
before parsing by calling str.trim() when creating the double (i.e., use
double.tryParse on the trimmed string) to match the behavior of the toFloat
sanitizer and lat_long validator, and add unit tests that pass strings with
leading/trailing whitespace to _isFloat (and any public validators that call it)
to prevent regressions.
lib/validators/strong_password.dart (1)

50-53: ⚡ Quick win

Document that non-ASCII characters are counted as symbols.

The _symbols regex [^a-zA-Z0-9] matches any character outside the ASCII alphanumeric range, including non-English letters like é, ñ, or 中. Users with non-ASCII passwords should be aware that these characters contribute to minSymbols, not minLowercase or minUppercase.

Consider adding a doc comment clarifying this behavior, especially since many users have non-English names or prefer international characters in passwords.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/validators/strong_password.dart` around lines 50 - 53, Add a doc comment
above the regex declarations explaining that the character classes use ASCII
ranges: _lowercase = RegExp(r'[a-z]'), _uppercase = RegExp(r'[A-Z]'), and
_numbers = RegExp(r'[0-9]') match only ASCII letters/digits, while _symbols =
RegExp(r'[^a-zA-Z0-9]') treats any non-ASCII character (e.g., é, ñ, 中) as a
symbol; update the comment to state that international/non-ASCII letters will
count toward minSymbols rather than minLowercase/minUppercase so callers
understand validation behavior.
lib/validators/sanitizers/blacklist.dart (1)

26-35: ⚖️ Poor tradeoff

Unicode grapheme clusters are not supported; complex emoji will be corrupted.

This implementation uses split('') which splits by UTF-16 code units rather than grapheme clusters. Complex emoji (e.g., 👨‍👩‍👧‍👦, which expands to 11 UTF-16 code units) will be split into fragments, and blacklist/whitelist operations may inadvertently corrupt them. This matches the behavior of validator.js (the original library this package mirrors), but is worth noting if your application needs to handle complex Unicode.

If Unicode support is required, use the characters package:

Example using characters package
import 'package:characters/characters.dart';

String _blacklist(String str, String chars) {
  if (chars.isEmpty) return str;
  final set = chars.characters.toSet();
  return str.characters.where((c) => !set.contains(c)).join();
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/validators/sanitizers/blacklist.dart` around lines 26 - 35, The current
_blacklist and _whitelist implementations use split('') which splits UTF-16 code
units and will corrupt complex Unicode grapheme clusters; replace usage of
split('') with the Characters API from the characters package so both functions
operate on grapheme clusters: import package:characters, use
chars.characters.toSet() and str.characters.where(...) (keep the existing
early-return in _blacklist for empty chars), and update both _blacklist and
_whitelist to build and compare sets and iterate using Characters instead of
split('').
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/validators/base64.dart`:
- Around line 25-30: The URL-safe Base64 validation in _isBase64 is too
permissive; update _isBase64 and its use of _base64UrlSafe so that after
confirming the character set you also validate the length modulo 4 (reject when
str.length % 4 == 1) to prevent impossible Base64 lengths; concretely, inside
_isBase64 when urlSafe is true keep the _base64UrlSafe.hasMatch(str) check but
add a length check returning false if str.length % 4 == 1, ensuring only
remainders 0, 2, or 3 are accepted (and keep existing empty-string check).

In `@lib/validators/sanitizers/to_boolean.dart`:
- Around line 21-25: The comparisons in _toBoolean are case-sensitive causing
values like "TRUE"/"FALSE" to be mis-parsed; update _toBoolean to normalize the
input (e.g., call toLowerCase() on str or otherwise compare lowercase) before
doing both the strict checks (str == '1' || str == 'true') and the non-strict
checks (str != '0' && str != 'false' && str != '') so keyword matching is
case-insensitive while preserving existing logic in function _toBoolean.

In `@lib/validators/sanitizers/to_number.dart`:
- Line 12: Guard against invalid radix values before calling int.tryParse in the
toInt sanitizer: check that the optional radix (if provided) is within 2..36 and
return null immediately when out of range, then call int.tryParse(str.trim(),
radix: radix) only for valid radices; apply the same guard to the other
sanitizer that calls int.tryParse (the sanitizer around line 39) so both
functions preserve the documented null-on-failure contract.

In `@test/validators/base64_test.dart`:
- Around line 17-21: The Base64 validator currently returns false for the empty
string; update the isBase64() implementation in lib/validators/base64.dart (the
isBase64 function/extension) to treat '' as valid per RFC 4648 by returning true
for empty input instead of false, and update the test in
test/validators/base64_test.dart to expect ''.isBase64() to be true; keep all
other validation logic (e.g., bad padding and invalid characters) unchanged so
'aGVsbG8'.isBase64() and '@@@@'.isBase64() still return false.

In `@test/validators/contains_test.dart`:
- Around line 21-24: The test 'Substring absent' incorrectly expects
contains('hello', '') to be false; update the assertion to expect true so the
contains function's behavior matches validator.js/JS String.prototype.includes
for an empty seed. Locate the test case in the 'Substring absent' block in
test/validators/contains_test.dart and change the expectation for
contains('hello', '') from isFalse to isTrue, ensuring the contains function's
semantics for an empty seed are represented correctly.

In `@test/validators/fqdn_test.dart`:
- Around line 6-11: The test and implementation disagree on trailing-dot
handling: update the FQDN validator (isFQDN in lib/validators/fqdn.dart) to stop
unconditionally stripping trailing dots and instead accept an optional parameter
(e.g., allowTrailingDot defaulting to false) to match validator.js behavior;
then adjust tests in test/validators/fqdn_test.dart to pass allowTrailingDot:
true when asserting that "example.com." is valid, or expect false with the
default option. Ensure the code path that currently strips the trailing dot is
gated by the new allowTrailingDot flag and that the isFQDN API signature is
updated accordingly wherever it is called.

In `@test/validators/jwt_test.dart`:
- Line 8: The test currently asserts that 'aaa.bbb.' is valid (empty signature
allowed) which is a security-sensitive behavior; decide whether unsigned JWTs
should be permitted and either update the test or the validator accordingly: if
unsigned tokens must be rejected, change the validator (the isJWT check in
lib/validators/jwt.dart — e.g., the isJWT function/extension or
JwtValidator.validate method) to treat a JWT with an empty third segment as
invalid (return false) unless an explicit allowUnsigned/allowNone option is
provided, add a corresponding unit test that expects isJWT to be false for
'aaa.bbb.', and document the new behavior; if unsigned tokens are intentionally
allowed, add an explanatory comment in jwt_test.dart and lib/validators/jwt.dart
referencing the allow-none rationale and add a test that proves the explicit
allowUnsigned flag/path is used.

---

Nitpick comments:
In `@lib/validators/float.dart`:
- Around line 24-30: The _isFloat function currently parses the raw input
string; update it to trim surrounding whitespace before parsing by calling
str.trim() when creating the double (i.e., use double.tryParse on the trimmed
string) to match the behavior of the toFloat sanitizer and lat_long validator,
and add unit tests that pass strings with leading/trailing whitespace to
_isFloat (and any public validators that call it) to prevent regressions.

In `@lib/validators/fqdn.dart`:
- Around line 26-43: The _isFQDN function currently validates labels but not the
overall domain length; add a check that the reconstructed domain (after removing
an optional trailing '.') has a total length of at most 253 characters and
return false if it exceeds that limit. Place this check in _isFQDN after the
trailing-dot trim (i.e., using the variable domain) and before proceeding to
split into parts or validating labels (parts/ tld / _fqdnTld / _fqdnLabel).

In `@lib/validators/sanitizers/blacklist.dart`:
- Around line 26-35: The current _blacklist and _whitelist implementations use
split('') which splits UTF-16 code units and will corrupt complex Unicode
grapheme clusters; replace usage of split('') with the Characters API from the
characters package so both functions operate on grapheme clusters: import
package:characters, use chars.characters.toSet() and str.characters.where(...)
(keep the existing early-return in _blacklist for empty chars), and update both
_blacklist and _whitelist to build and compare sets and iterate using Characters
instead of split('').

In `@lib/validators/strong_password.dart`:
- Around line 50-53: Add a doc comment above the regex declarations explaining
that the character classes use ASCII ranges: _lowercase = RegExp(r'[a-z]'),
_uppercase = RegExp(r'[A-Z]'), and _numbers = RegExp(r'[0-9]') match only ASCII
letters/digits, while _symbols = RegExp(r'[^a-zA-Z0-9]') treats any non-ASCII
character (e.g., é, ñ, 中) as a symbol; update the comment to state that
international/non-ASCII letters will count toward minSymbols rather than
minLowercase/minUppercase so callers understand validation behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c5dbb3a0-1dfe-4c39-a9db-fc9827b57f95

📥 Commits

Reviewing files that changed from the base of the PR and between 7736385 and ffcac2c.

📒 Files selected for processing (69)
  • CHANGELOG.md
  • README.md
  • lib/flutter_validators.dart
  • lib/form_validator.dart
  • lib/sanitizers.dart
  • lib/validators/base64.dart
  • lib/validators/byte_length.dart
  • lib/validators/contains.dart
  • lib/validators/decimal.dart
  • lib/validators/float.dart
  • lib/validators/fqdn.dart
  • lib/validators/hexadecimal.dart
  • lib/validators/in.dart
  • lib/validators/ip.dart
  • lib/validators/jwt.dart
  • lib/validators/lat_long.dart
  • lib/validators/lowercase.dart
  • lib/validators/mac_address.dart
  • lib/validators/matches.dart
  • lib/validators/md5.dart
  • lib/validators/mongo_id.dart
  • lib/validators/octal.dart
  • lib/validators/port.dart
  • lib/validators/sanitizers/blacklist.dart
  • lib/validators/sanitizers/escape.dart
  • lib/validators/sanitizers/normalize_email.dart
  • lib/validators/sanitizers/strip_low.dart
  • lib/validators/sanitizers/to_boolean.dart
  • lib/validators/sanitizers/to_number.dart
  • lib/validators/sanitizers/trim.dart
  • lib/validators/semver.dart
  • lib/validators/slug.dart
  • lib/validators/strong_password.dart
  • lib/validators/uppercase.dart
  • lib/validators/url.dart
  • lib/validators/uuid.dart
  • pubspec.yaml
  • test/form_validator_test.dart
  • test/validators/base32_test.dart
  • test/validators/base58_test.dart
  • test/validators/base64_test.dart
  • test/validators/byte_length_test.dart
  • test/validators/contains_test.dart
  • test/validators/decimal_test.dart
  • test/validators/float_test.dart
  • test/validators/fqdn_test.dart
  • test/validators/hexadecimal_test.dart
  • test/validators/in_test.dart
  • test/validators/jwt_test.dart
  • test/validators/lat_long_test.dart
  • test/validators/lowercase_test.dart
  • test/validators/mac_address_test.dart
  • test/validators/matches_test.dart
  • test/validators/md5_test.dart
  • test/validators/mongo_id_test.dart
  • test/validators/octal_test.dart
  • test/validators/port_test.dart
  • test/validators/sanitizers/blacklist_test.dart
  • test/validators/sanitizers/escape_test.dart
  • test/validators/sanitizers/normalize_email_test.dart
  • test/validators/sanitizers/strip_low_test.dart
  • test/validators/sanitizers/to_boolean_test.dart
  • test/validators/sanitizers/to_number_test.dart
  • test/validators/sanitizers/trim_test.dart
  • test/validators/semver_test.dart
  • test/validators/slug_test.dart
  • test/validators/strong_password_test.dart
  • test/validators/uppercase_test.dart
  • test/validators/uuid_test.dart

Comment thread lib/validators/base64.dart
Comment thread lib/validators/sanitizers/to_boolean.dart Outdated
/// toInt('ff', radix: 16); // 255
/// toInt('abc'); // null
/// ```
int? toInt(String str, {int? radix}) => int.tryParse(str.trim(), radix: radix);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

In Dart, does int.tryParse throw for radix values outside 2..36, or return null?

💡 Result:

In Dart, the int.tryParse method throws a RangeError if the provided radix is outside the valid range of 2 to 36. While int.tryParse is designed to return null instead of throwing a FormatException when the input string cannot be parsed, this behavior does not extend to invalid arguments like an out-of-range radix. Because the radix must be a valid base for the integer conversion, providing a value outside the 2..36 range is considered an invalid argument, resulting in a runtime exception.

Citations:


🏁 Script executed:

cat -n lib/validators/sanitizers/to_number.dart

Repository: divyanshub024/flutter_validators

Length of output: 1778


Guard invalid radix values before calling int.tryParse.

Lines 12 and 39 can throw a RangeError for out-of-range radix values (outside 2..36), breaking the sanitizer's null-on-failure contract documented in the function comments.

Proposed fix
-int? toInt(String str, {int? radix}) => int.tryParse(str.trim(), radix: radix);
+int? toInt(String str, {int? radix}) {
+  if (radix != null && (radix < 2 || radix > 36)) return null;
+  return int.tryParse(str.trim(), radix: radix);
+}
@@
-  int? toInt({int? radix}) => int.tryParse(trim(), radix: radix);
+  int? toInt({int? radix}) {
+    if (radix != null && (radix < 2 || radix > 36)) return null;
+    return int.tryParse(trim(), radix: radix);
+  }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/validators/sanitizers/to_number.dart` at line 12, Guard against invalid
radix values before calling int.tryParse in the toInt sanitizer: check that the
optional radix (if provided) is within 2..36 and return null immediately when
out of range, then call int.tryParse(str.trim(), radix: radix) only for valid
radices; apply the same guard to the other sanitizer that calls int.tryParse
(the sanitizer around line 39) so both functions preserve the documented
null-on-failure contract.

Comment on lines +17 to +21
test('Invalid Base64 strings', () {
expect('aGVsbG8'.isBase64(), isFalse); // bad padding
expect('@@@@'.isBase64(), isFalse);
expect(''.isBase64(), isFalse);
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

git ls-files | head -20

Repository: divyanshub024/flutter_validators

Length of output: 658


🏁 Script executed:

find . -type f -name "*base64*" | head -20

Repository: divyanshub024/flutter_validators

Length of output: 140


🏁 Script executed:

find . -type f -name "*.dart" | grep -E "(validator|base64)" | head -20

Repository: divyanshub024/flutter_validators

Length of output: 664


🏁 Script executed:

cat -n lib/validators/base64.dart

Repository: divyanshub024/flutter_validators

Length of output: 1373


🏁 Script executed:

cat -n test/validators/base64_test.dart

Repository: divyanshub024/flutter_validators

Length of output: 935


🌐 Web query:

validator.js isBase64 empty string behavior documentation

💡 Result:

In the validator.js library, the isBase64 function correctly identifies an empty string ("") as a valid Base64-encoded value [1]. Historically, earlier versions of validator.js incorrectly returned false for empty strings [2][3]. This behavior was identified as a bug because, according to RFC 4648 (the standard for Base64 encoding), an empty string is the valid Base64 representation of zero bytes of data [4][5]. This issue was resolved in pull request #1419, which updated the library to ensure that isBase64('') returns true [1]. If you are using a version of validator.js that includes this fix, you can expect the following behavior: - isBase64("") returns true [1]. This aligns with the RFC 4648 test vectors, which explicitly state: BASE64("") = "" [5][6].

Citations:


🏁 Script executed:

head -50 README.md

Repository: divyanshub024/flutter_validators

Length of output: 1841


🏁 Script executed:

rg -i "parity\|validator\.js\|port" README.md | head -20

Repository: divyanshub024/flutter_validators

Length of output: 58


Update Base64 validator to treat empty strings as valid per RFC 4648.

The current implementation and test treat empty strings as invalid Base64. However, validator.js (which this package is inspired by) returns true for isBase64('') to align with RFC 4648, which specifies that an empty string is the valid Base64 representation of zero bytes. The test at line 20 needs updating, and the implementation in lib/validators/base64.dart (line 28) should also be modified to return true for empty strings instead of false.

Required changes

In lib/validators/base64.dart, line 28:

-  if (str.isEmpty) return false;
+  if (str.isEmpty) return true;

In test/validators/base64_test.dart, line 20:

-      expect(''.isBase64(), isFalse);
+      expect(''.isBase64(), isTrue);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
test('Invalid Base64 strings', () {
expect('aGVsbG8'.isBase64(), isFalse); // bad padding
expect('@@@@'.isBase64(), isFalse);
expect(''.isBase64(), isFalse);
});
test('Invalid Base64 strings', () {
expect('aGVsbG8'.isBase64(), isFalse); // bad padding
expect('@@@@'.isBase64(), isFalse);
expect(''.isBase64(), isTrue);
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/validators/base64_test.dart` around lines 17 - 21, The Base64 validator
currently returns false for the empty string; update the isBase64()
implementation in lib/validators/base64.dart (the isBase64 function/extension)
to treat '' as valid per RFC 4648 by returning true for empty input instead of
false, and update the test in test/validators/base64_test.dart to expect
''.isBase64() to be true; keep all other validation logic (e.g., bad padding and
invalid characters) unchanged so 'aGVsbG8'.isBase64() and '@@@@'.isBase64()
still return false.

Comment on lines +21 to +24
test('Substring absent', () {
expect(contains('hello world', 'xyz'), isFalse);
expect(contains('hello', ''), isFalse);
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

# Find the contains validator implementation
fd -t f "contains" --extension dart | head -20

Repository: divyanshub024/flutter_validators

Length of output: 140


🏁 Script executed:

# Look for the contains implementation in the validators directory
find . -type f -name "*.dart" | xargs grep -l "contains" | head -20

Repository: divyanshub024/flutter_validators

Length of output: 627


🏁 Script executed:

# Search for the contains function implementation
rg "^\s*(String\s+)?contains\s*\(" --type dart -A 10 | head -50

Repository: divyanshub024/flutter_validators

Length of output: 58


🏁 Script executed:

# Check the test file to understand context
cat -n test/validators/contains_test.dart

Repository: divyanshub024/flutter_validators

Length of output: 1086


🏁 Script executed:

# Examine the contains implementation
cat -n lib/validators/contains.dart

Repository: divyanshub024/flutter_validators

Length of output: 1095


🏁 Script executed:

# Check validator.js source or documentation for contains behavior
web_search

Repository: divyanshub024/flutter_validators

Length of output: 127


🌐 Web query:

validator.js contains function empty string behavior

💡 Result:

In the validator.js library, the behavior of empty strings varies depending on the specific function being used. The library provides a dedicated function, isEmpty(str [, options]), which is designed to check if a string has a length of zero [1][2]. By default, this function returns true for an empty string ('') and false for any string containing characters [1][3]. For other validation functions (such as isNumeric, isAlpha, or isAlphanumeric), an empty string typically returns false [4]. This is because these functions evaluate whether the provided string conforms to a specific pattern, and an empty string does not contain the required characters to satisfy those patterns [4]. If you need to allow empty strings in your validation logic, you must explicitly handle them, as the library does not provide a universal "allow empty" option for all validation methods [4][5]. Common approaches include: 1. Using a logical OR operator to permit empty strings: validator.isAlpha(str) || str === '' [5]. 2. Checking the string length manually before running the validator [4]. Note that there are multiple libraries with similar names (e.g., validator.js and validatorjs). In validatorjs (a library inspired by Laravel's validation), string length rules like min or max may pass on empty strings unless a required rule is also applied [6][7]. Always verify which specific package you are using, as their handling of empty inputs can differ significantly [8][7].

Citations:


🌐 Web query:

validator.js contains function documentation API

💡 Result:

validator.js contains function documentation API? Yes—validator.js’s own repository documents the available string validator functions and their options directly in its README. Evidence: - The validator.js GitHub repository describes itself as “A library of string validators and sanitizers,” and its README includes “Here is a list of the validators currently available” with per-function documentation (e.g., signature and options for functions like contains, isEmail, etc.) [1][2].

Citations:


🏁 Script executed:

# Check if there's any reference to validator.js in the repository
rg "validator\.js" . --type dart --type md

Repository: divyanshub024/flutter_validators

Length of output: 404


🏁 Script executed:

# Look for any comments or documentation about parity with validator.js
rg "validator" . --type dart --type md -i | grep -i "parity\|contains" | head -20

Repository: divyanshub024/flutter_validators

Length of output: 788


🌐 Web query:

validator.js contains function empty string seed behavior github

💡 Result:

In the validator.js library, the behavior of functions when encountering an empty string is generally to return false [1][2]. This design choice has been a subject of discussion in the project's history, with maintainers opting to maintain this behavior rather than treating empty strings as valid for functions like isAlpha, isNumeric, or isAlphanumeric [1][2]. Regarding the specific function validator.contains(str, seed), it checks if the provided string contains the specified seed [3][4]. If the seed itself is an empty string, the behavior is consistent with standard JavaScript string methods (like String.prototype.includes), which typically return true because an empty string is considered to be contained within any other string. However, if you are validating whether a field is empty, you should use the dedicated isEmpty(str) function [3][5]. If your application logic requires empty strings to pass validation, the recommended approach is to explicitly handle the empty string case in your code rather than relying on the validator functions to return true [1][6]. For example: validator.isAlpha(str) || str === '' [6] This pattern ensures clarity and avoids unexpected results from the library's default validation logic [1][2].

Citations:


🏁 Script executed:

# Check validator.js GitHub repo directly for contains function documentation
# Look for any additional context about validator.js compatibility or parity goals
rg "contains" lib/ --type dart -B 3 -A 8

Repository: divyanshub024/flutter_validators

Length of output: 14358


🏁 Script executed:

# Check if there's a package.json or reference to validator.js version/documentation
find . -name "package.json" -o -name "pubspec.yaml" | head -5

Repository: divyanshub024/flutter_validators

Length of output: 114


🏁 Script executed:

# Verify validator.js source code to confirm empty string behavior
# Check if there's any additional context about this design decision in the repo
rg "seed\.isEmpty\|seed is empty\|empty seed" . --type dart -B 2 -A 2

Repository: divyanshub024/flutter_validators

Length of output: 58


Update contains test to match validator.js behavior for empty seed.

Line 23 expects contains('hello', '') to be false, but validator.js returns true when the seed is an empty string (consistent with JavaScript's String.prototype.includes). Since this library is inspired by validator.js, the test should reflect that parity.

Suggested test update
-      expect(contains('hello', ''), isFalse);
+      expect(contains('hello', ''), isTrue);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
test('Substring absent', () {
expect(contains('hello world', 'xyz'), isFalse);
expect(contains('hello', ''), isFalse);
});
test('Substring absent', () {
expect(contains('hello world', 'xyz'), isFalse);
expect(contains('hello', ''), isTrue);
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/validators/contains_test.dart` around lines 21 - 24, The test 'Substring
absent' incorrectly expects contains('hello', '') to be false; update the
assertion to expect true so the contains function's behavior matches
validator.js/JS String.prototype.includes for an empty seed. Locate the test
case in the 'Substring absent' block in test/validators/contains_test.dart and
change the expectation for contains('hello', '') from isFalse to isTrue,
ensuring the contains function's semantics for an empty seed are represented
correctly.

Comment on lines +6 to +11
test('Valid domain names', () {
expect('example.com'.isFQDN, isTrue);
expect('sub.example.co.uk'.isFQDN, isTrue);
expect('my-site.org'.isFQDN, isTrue);
expect(isFQDN('example.com.'), isTrue); // trailing dot allowed
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

git ls-files | grep -E "(fqdn|validator)" | head -20

Repository: divyanshub024/flutter_validators

Length of output: 624


🏁 Script executed:

find . -type f -name "*.dart" | grep -i fqdn

Repository: divyanshub024/flutter_validators

Length of output: 136


🏁 Script executed:

find . -type f -name "*fqdn*"

Repository: divyanshub024/flutter_validators

Length of output: 136


🏁 Script executed:

cat -n test/validators/fqdn_test.dart

Repository: divyanshub024/flutter_validators

Length of output: 897


🏁 Script executed:

cat -n lib/validators/fqdn.dart

Repository: divyanshub024/flutter_validators

Length of output: 1637


🌐 Web query:

validator.js isFQDN allow_trailing_dot default option

💡 Result:

In the validator.js library, the default value for the allow_trailing_dot option in the isFQDN function is false [1][2]. When using isFQDN, you can override this default by passing an options object. If you set allow_trailing_dot to true, the validator will permit the domain string to end with a dot (.) character [1][2]. The full set of default options for isFQDN is: { require_tld: true, allow_underscores: false, allow_trailing_dot: false, allow_numeric_tld: false, allow_wildcard: false, ignore_max_length: false } [1][2]

Citations:


Trailing-dot acceptance diverges from validator.js default behavior.

Line 10 expects isFQDN('example.com.') to return true without options. However, validator.js defaults allow_trailing_dot to false. The Flutter implementation unconditionally strips trailing dots (lines 29-31 in lib/validators/fqdn.dart), accepting them by default, whereas validator.js rejects them. Additionally, the implementation has no options parameter to control this behavior, making alignment with validator.js impossible. Either reject trailing dots by default to match validator.js, or add an allowTrailingDot option parameter.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/validators/fqdn_test.dart` around lines 6 - 11, The test and
implementation disagree on trailing-dot handling: update the FQDN validator
(isFQDN in lib/validators/fqdn.dart) to stop unconditionally stripping trailing
dots and instead accept an optional parameter (e.g., allowTrailingDot defaulting
to false) to match validator.js behavior; then adjust tests in
test/validators/fqdn_test.dart to pass allowTrailingDot: true when asserting
that "example.com." is valid, or expect false with the default option. Ensure
the code path that currently strips the trailing dot is gated by the new
allowTrailingDot flag and that the isFQDN API signature is updated accordingly
wherever it is called.

Comment thread test/validators/jwt_test.dart
Restructures the README into a full documentation page: table of
contents, a features list, categorized validator and sanitizer sections
each with runnable examples, a complete Flutter form example, the full
Validator API reference, and a behavior notes / FAQ section.
isBase64 with urlSafe now rejects strings of length 4n+1, which can
never be valid Base64 (a single base64 char cannot encode any byte).

toBoolean now matches boolean keywords case-insensitively, so values
like 'FALSE' and 'TRUE' are coerced correctly in both loose and strict
modes.
Copy link
Copy Markdown

@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.

♻️ Duplicate comments (1)
test/validators/base64_test.dart (1)

20-20: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Empty string should be valid Base64 per RFC 4648 and validator.js.

As noted in the previous review, the current test expects empty strings to be invalid, but RFC 4648 specifies that an empty string is the valid Base64 representation of zero bytes. Since this PR aims for validator.js parity, the implementation and test should treat '' as valid.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/validators/base64_test.dart` at line 20, The test currently asserts that
''.isBase64() is false, but per RFC 4648 and validator.js an empty string is a
valid Base64 encoding; update the test to expect true (expect(''.isBase64(),
isTrue)) and, if needed, update the isBase64() validation logic to return true
for the empty string path (ensure the isBase64() function treats zero-length
input as valid before applying any regex/length checks).
🧹 Nitpick comments (1)
test/validators/uppercase_test.dart (1)

6-18: ⚡ Quick win

Consider adding edge case coverage.

The test suite covers core scenarios well but could benefit from a few additional cases to document expected behavior and increase robustness:

  • Strings with special characters: "HELLO!", "!@#", "HELLO WORLD"
  • Whitespace-only strings: " ", "\n", "\t"
  • Mixed special characters and letters: "Hello!"
  • Unicode uppercase characters (if supported): "CAFÉ"
📋 Example edge case additions
     test('Valid uppercase strings', () {
       expect('HELLO'.isUppercase, isTrue);
       expect('HELLO123'.isUppercase, isTrue);
       expect('123'.isUppercase, isTrue);
       expect(''.isUppercase, isTrue);
       expect(isUppercase('ABC'), isTrue);
+      expect('HELLO!'.isUppercase, isTrue);
+      expect('HELLO WORLD'.isUppercase, isTrue);
+      expect('!@#'.isUppercase, isTrue);
+      expect('  '.isUppercase, isTrue);
     });

     test('Invalid uppercase strings', () {
       expect('Hello'.isUppercase, isFalse);
       expect('hello'.isUppercase, isFalse);
       expect(isUppercase('AbC'), isFalse);
+      expect('Hello!'.isUppercase, isFalse);
+      expect('hello world'.isUppercase, isFalse);
     });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/validators/uppercase_test.dart` around lines 6 - 18, Add tests to cover
the suggested edge cases for the isUppercase extension/property and isUppercase
function: include strings with special characters ("HELLO!", "!@#", "HELLO
WORLD"), whitespace-only strings ("  ", "\n", "\t"), mixed special+letters
("Hello!"), and a Unicode uppercase example ("CAFÉ") to the existing test groups
(referencing the isUppercase property used on string literals and the
isUppercase('...') function call) so the suite documents and asserts expected
behavior for these inputs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@test/validators/base64_test.dart`:
- Line 20: The test currently asserts that ''.isBase64() is false, but per RFC
4648 and validator.js an empty string is a valid Base64 encoding; update the
test to expect true (expect(''.isBase64(), isTrue)) and, if needed, update the
isBase64() validation logic to return true for the empty string path (ensure the
isBase64() function treats zero-length input as valid before applying any
regex/length checks).

---

Nitpick comments:
In `@test/validators/uppercase_test.dart`:
- Around line 6-18: Add tests to cover the suggested edge cases for the
isUppercase extension/property and isUppercase function: include strings with
special characters ("HELLO!", "!@#", "HELLO WORLD"), whitespace-only strings (" 
", "\n", "\t"), mixed special+letters ("Hello!"), and a Unicode uppercase
example ("CAFÉ") to the existing test groups (referencing the isUppercase
property used on string literals and the isUppercase('...') function call) so
the suite documents and asserts expected behavior for these inputs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9aefef52-7f79-413c-9402-142fb4630c3a

📥 Commits

Reviewing files that changed from the base of the PR and between ffcac2c and e01704f.

📒 Files selected for processing (69)
  • CHANGELOG.md
  • README.md
  • lib/flutter_validators.dart
  • lib/form_validator.dart
  • lib/sanitizers.dart
  • lib/validators/base64.dart
  • lib/validators/byte_length.dart
  • lib/validators/contains.dart
  • lib/validators/decimal.dart
  • lib/validators/float.dart
  • lib/validators/fqdn.dart
  • lib/validators/hexadecimal.dart
  • lib/validators/in.dart
  • lib/validators/ip.dart
  • lib/validators/jwt.dart
  • lib/validators/lat_long.dart
  • lib/validators/lowercase.dart
  • lib/validators/mac_address.dart
  • lib/validators/matches.dart
  • lib/validators/md5.dart
  • lib/validators/mongo_id.dart
  • lib/validators/octal.dart
  • lib/validators/port.dart
  • lib/validators/sanitizers/blacklist.dart
  • lib/validators/sanitizers/escape.dart
  • lib/validators/sanitizers/normalize_email.dart
  • lib/validators/sanitizers/strip_low.dart
  • lib/validators/sanitizers/to_boolean.dart
  • lib/validators/sanitizers/to_number.dart
  • lib/validators/sanitizers/trim.dart
  • lib/validators/semver.dart
  • lib/validators/slug.dart
  • lib/validators/strong_password.dart
  • lib/validators/uppercase.dart
  • lib/validators/url.dart
  • lib/validators/uuid.dart
  • pubspec.yaml
  • test/form_validator_test.dart
  • test/validators/base32_test.dart
  • test/validators/base58_test.dart
  • test/validators/base64_test.dart
  • test/validators/byte_length_test.dart
  • test/validators/contains_test.dart
  • test/validators/decimal_test.dart
  • test/validators/float_test.dart
  • test/validators/fqdn_test.dart
  • test/validators/hexadecimal_test.dart
  • test/validators/in_test.dart
  • test/validators/jwt_test.dart
  • test/validators/lat_long_test.dart
  • test/validators/lowercase_test.dart
  • test/validators/mac_address_test.dart
  • test/validators/matches_test.dart
  • test/validators/md5_test.dart
  • test/validators/mongo_id_test.dart
  • test/validators/octal_test.dart
  • test/validators/port_test.dart
  • test/validators/sanitizers/blacklist_test.dart
  • test/validators/sanitizers/escape_test.dart
  • test/validators/sanitizers/normalize_email_test.dart
  • test/validators/sanitizers/strip_low_test.dart
  • test/validators/sanitizers/to_boolean_test.dart
  • test/validators/sanitizers/to_number_test.dart
  • test/validators/sanitizers/trim_test.dart
  • test/validators/semver_test.dart
  • test/validators/slug_test.dart
  • test/validators/strong_password_test.dart
  • test/validators/uppercase_test.dart
  • test/validators/uuid_test.dart
✅ Files skipped from review due to trivial changes (10)
  • pubspec.yaml
  • lib/validators/uuid.dart
  • test/validators/mac_address_test.dart
  • lib/sanitizers.dart
  • lib/validators/url.dart
  • test/validators/base58_test.dart
  • CHANGELOG.md
  • test/validators/base32_test.dart
  • test/validators/uuid_test.dart
  • README.md
🚧 Files skipped from review as they are similar to previous changes (49)
  • test/validators/fqdn_test.dart
  • test/validators/decimal_test.dart
  • test/validators/lat_long_test.dart
  • lib/validators/contains.dart
  • lib/validators/sanitizers/to_boolean.dart
  • test/validators/octal_test.dart
  • test/validators/float_test.dart
  • test/validators/slug_test.dart
  • test/validators/port_test.dart
  • test/validators/jwt_test.dart
  • lib/validators/matches.dart
  • test/validators/semver_test.dart
  • lib/validators/float.dart
  • lib/validators/sanitizers/to_number.dart
  • test/validators/contains_test.dart
  • lib/validators/port.dart
  • lib/validators/lowercase.dart
  • lib/validators/jwt.dart
  • lib/validators/byte_length.dart
  • lib/validators/semver.dart
  • test/validators/sanitizers/to_boolean_test.dart
  • lib/validators/strong_password.dart
  • lib/validators/mac_address.dart
  • test/validators/sanitizers/normalize_email_test.dart
  • lib/validators/sanitizers/escape.dart
  • test/validators/sanitizers/blacklist_test.dart
  • test/validators/sanitizers/trim_test.dart
  • test/validators/in_test.dart
  • lib/validators/sanitizers/blacklist.dart
  • lib/validators/slug.dart
  • test/validators/lowercase_test.dart
  • test/validators/strong_password_test.dart
  • lib/validators/hexadecimal.dart
  • lib/validators/sanitizers/normalize_email.dart
  • lib/validators/sanitizers/strip_low.dart
  • test/validators/byte_length_test.dart
  • lib/validators/fqdn.dart
  • lib/validators/lat_long.dart
  • test/validators/sanitizers/to_number_test.dart
  • lib/validators/uppercase.dart
  • lib/form_validator.dart
  • lib/validators/decimal.dart
  • test/form_validator_test.dart
  • lib/validators/sanitizers/trim.dart
  • lib/validators/octal.dart
  • lib/validators/mongo_id.dart
  • test/validators/mongo_id_test.dart
  • lib/validators/md5.dart
  • lib/validators/base64.dart

@divyanshub024 divyanshub024 merged commit 0265042 into main May 15, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant