Repository: validatorjs/validator.js
Pull request #2474 (2024-10-12 15:49:22)

feat(isLength): Fix linting and merge errors for #2019
<!--
Add a descriptive title textbox above, e.g.
feat(isLength): Fix linting and merge errors for https://github.com/validatorjs/validator.js/pull/2019
-->
feat(isLength): Fix linting and merge errors for https://github.com/validatorjs/validator.js/pull/2019

<!--- briefly describe what you have done in this PR --->

<!--- provide some (credible) references showing the structure of the data to be validated, if applicable --->

## Checklist

- [x] PR contains only changes related; no stray files, etc.
- [ ] README updated (where applicable)
- [ ] Tests written (where applicable)
- [ ] References provided in PR (where applicable)


Suven-p: Is it okay to ignore discreteLength parameter if it is not of type array?
Suven-p: Requesting review @rubiin 

rubiin: There are several commits from other contributers?  44+?
Suven-p: @rubiin How do I do that? All other commits are from the original PR.
rubiin: No worries then. I thought this was a fresh one

Patch:
diff --git a/README.md b/README.md
index 7c1cadb..c7518fd 100644
--- a/README.md
+++ b/README.md
@@ -139,7 +139,7 @@ Validator                               | Description
 **isJSON(str [, options])**             | check if the string is valid JSON (note: uses JSON.parse).<br/><br/>`options` is an object which defaults to `{ allow_primitives: false }`. If `allow_primitives` is true, the primitives 'true', 'false' and 'null' are accepted as valid JSON values.
 **isJWT(str)**                         | check if the string is valid JWT token.
 **isLatLong(str [, options])**                      | check if the string is a valid latitude-longitude coordinate in the format `lat,long` or `lat, long`.<br/><br/>`options` is an object that defaults to `{ checkDMS: false }`. Pass `checkDMS` as `true` to validate DMS(degrees, minutes, and seconds) latitude-longitude format.
-**isLength(str [, options])**              | check if the string's length falls in a range.<br/><br/>`options` is an object which defaults to `{ min: 0, max: undefined }`. Note: this function takes into account surrogate pairs.
+**isLength(str [, options])**              | check if the string's length falls in a range and equal to any of the integers of the `discreteLengths` array if provided.<br/><br/>`options` is an object which defaults to `{ min: 0, max: undefined, discreteLengths: undefined }`. Note: this function takes into account surrogate pairs.
 **isLicensePlate(str, locale)**     | check if the string matches the format of a country's license plate.<br/><br/>`locale` is one of `['cs-CZ', 'de-DE', 'de-LI', 'en-IN', 'en-SG', 'en-PK', 'es-AR', 'hu-HU', 'pt-BR', 'pt-PT', 'sq-AL', 'sv-SE']` or `'any'`.
 **isLocale(str)**                       | check if the string is a locale.
 **isLowercase(str)**                    | check if the string is lowercase.
diff --git a/src/lib/isLength.js b/src/lib/isLength.js
index 4ef8b83..4d5d525 100644
--- a/src/lib/isLength.js
+++ b/src/lib/isLength.js
@@ -5,6 +5,7 @@ export default function isLength(str, options) {
   assertString(str);
   let min;
   let max;
+
   if (typeof (options) === 'object') {
     min = options.min || 0;
     max = options.max;
@@ -12,8 +13,15 @@ export default function isLength(str, options) {
     min = arguments[1] || 0;
     max = arguments[2];
   }
+
   const presentationSequences = str.match(/(\uFE0F|\uFE0E)/g) || [];
   const surrogatePairs = str.match(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g) || [];
   const len = str.length - presentationSequences.length - surrogatePairs.length;
-  return len >= min && (typeof max === 'undefined' || len <= max);
+  const isInsideRange = len >= min && (typeof max === 'undefined' || len <= max);
+
+  if (isInsideRange && Array.isArray(options?.discreteLengths)) {
+    return options.discreteLengths.some(discreteLen => discreteLen === len);
+  }
+
+  return isInsideRange;
 }


Test patch:
diff --git a/test/validators.test.js b/test/validators.test.js
index 31a36d0..4df9139 100644
--- a/test/validators.test.js
+++ b/test/validators.test.js
@@ -5394,12 +5394,42 @@ describe('Validators', () => {
       valid: ['abc', 'de', 'a', ''],
       invalid: ['abcd'],
     });
+    test({
+      validator: 'isLength',
+      args: [{ max: 6, discreteLengths: 5 }],
+      valid: ['abcd', 'vfd', 'ff', '', 'k'],
+      invalid: ['abcdefgh', 'hfjdksks'],
+    });
+    test({
+      validator: 'isLength',
+      args: [{ min: 2, max: 6, discreteLengths: 5 }],
+      valid: ['bsa', 'vfvd', 'ff'],
+      invalid: ['', ' ', 'hfskdunvc'],
+    });
+    test({
+      validator: 'isLength',
+      args: [{ min: 1, discreteLengths: 2 }],
+      valid: [' ', 'hello', 'bsa'],
+      invalid: [''],
+    });
     test({
       validator: 'isLength',
       args: [{ max: 0 }],
       valid: [''],
       invalid: ['a', 'ab'],
     });
+    test({
+      validator: 'isLength',
+      args: [{ min: 5, max: 10, discreteLengths: [2, 6, 8, 9] }],
+      valid: ['helloguy', 'shopping', 'validator', 'length'],
+      invalid: ['abcde', 'abcdefg'],
+    });
+    test({
+      validator: 'isLength',
+      args: [{ discreteLengths: '9' }],
+      valid: ['a', 'abcd', 'abcdefghijkl'],
+      invalid: [],
+    });
     test({
       validator: 'isLength',
       valid: ['a', '', 'asds'],