Repository: validatorjs/validator.js
Pull request #1594 (2021-02-05 10:45:30)

Add support for IP version 4 or 6 for isIPRange function
<!--
Add a descriptive title textbox above, e.g.
feat(validatorName): brief title of what has been done
-->

At the moment, `isIPRange` function only supports IP v4, this PR allows to choose v4 or v6 or both (like `isIP` function)

## Checklist

- [x] PR contains only changes related; no stray files, etc.
- [x] README updated (where applicable)
- [x] Tests written (where applicable)


codecov[bot]: # [Codecov](https://codecov.io/gh/validatorjs/validator.js/pull/1594?src=pr&el=h1) Report
> Merging [#1594](https://codecov.io/gh/validatorjs/validator.js/pull/1594?src=pr&el=desc) (dbd23e9) into [master](https://codecov.io/gh/validatorjs/validator.js/commit/8831db31e7d5de03cb9744f811ffd256494f97f6?el=desc) (8831db3) will **not change** coverage.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/validatorjs/validator.js/pull/1594/graphs/tree.svg?width=650&height=150&src=pr&token=YzR3kbN1m2)](https://codecov.io/gh/validatorjs/validator.js/pull/1594?src=pr&el=tree)

```diff
@@            Coverage Diff            @@
##            master     #1594   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          100       100           
  Lines         1796      1809   +13     
=========================================
+ Hits          1796      1809   +13     
```


| [Impacted Files](https://codecov.io/gh/validatorjs/validator.js/pull/1594?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/lib/isMobilePhone.js](https://codecov.io/gh/validatorjs/validator.js/pull/1594/diff?src=pr&el=tree#diff-c3JjL2xpYi9pc01vYmlsZVBob25lLmpz) | `100.00% <ø> (ø)` | |
| [src/lib/isIPRange.js](https://codecov.io/gh/validatorjs/validator.js/pull/1594/diff?src=pr&el=tree#diff-c3JjL2xpYi9pc0lQUmFuZ2UuanM=) | `100.00% <100.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/validatorjs/validator.js/pull/1594?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/validatorjs/validator.js/pull/1594?src=pr&el=footer). Last update [8831db3...3dfd8fc](https://codecov.io/gh/validatorjs/validator.js/pull/1594?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

fredleger: Nice one ! Need this too
renanmontebelo: Would this be a duplicate of #1568? Yours looks like it's a more complete solution though.
Maybe a discussion should be setup and close one of the PRs in favor of the other.
neilime: @tux-tn I have rebased with master, let me know if everything looks good to you
tux-tn: @neilime LGTM ! thank you for making the necessary changes 🎉 

Patch:
diff --git a/README.md b/README.md
index ab5609a..6b471fc 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,7 @@
 [![Downloads][downloads-image]][npm-url]
 [![Backers on Open Collective](https://opencollective.com/validatorjs/backers/badge.svg)](#backers)
 [![Sponsors on Open Collective](https://opencollective.com/validatorjs/sponsors/badge.svg)](#sponsors)
+[![Gitter](https://badges.gitter.im/validatorjs/community.svg)](https://gitter.im/validatorjs/community)
 
 A library of string validators and sanitizers.
 
@@ -119,7 +120,7 @@ Validator                               | Description
 **isIn(str, values)**                   | check if the string is in a array of allowed values.
 **isInt(str [, options])**              | check if the string is an integer.<br/><br/>`options` is an object which can contain the keys `min` and/or `max` to check the integer is within boundaries (e.g. `{ min: 10, max: 99 }`). `options` can also contain the key `allow_leading_zeroes`, which when set to false will disallow integer values with leading zeroes (e.g. `{ allow_leading_zeroes: false }`). Finally, `options` can contain the keys `gt` and/or `lt` which will enforce integers being greater than or less than, respectively, the value provided (e.g. `{gt: 1, lt: 4}` for a number between 1 and 4).
 **isIP(str [, version])**               | check if the string is an IP (version 4 or 6).
-**isIPRange(str)**                      | check if the string is an IP Range(version 4 only).
+**isIPRange(str [, version])**          | check if the string is an IP Range (version 4 or 6).
 **isISBN(str [, version])**             | check if the string is an ISBN (version 10 or 13).
 **isISIN(str)**                         | check if the string is an [ISIN][ISIN] (stock/security identifier).
 **isISO8601(str)**                      | check if the string is a valid [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) date. <br/>`options` is an object which defaults to `{ strict: false, strictSeparator: false }`. If `strict` is true, date strings with invalid dates like `2009-02-29` will be invalid. If `strictSeparator` is true, date strings with date and time separated by anything other than a T will be invalid.
@@ -131,7 +132,7 @@ Validator                               | Description
 **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.
-**isLicensePlate(str [, locale])**     | check if string matches the format of a country's license plate.<br/><br/>(locale is one of `['de-DE', 'de-LI', 'pt-PT', 'sq-AL']` or `any`)
+**isLicensePlate(str [, locale])**     | check if string matches the format of a country's license plate.<br/><br/>(locale is one of `['de-DE', 'de-LI', 'pt-PT', 'sq-AL', 'pt-BR'']` or `any`). 
 **isLocale(str)**                       | check if the string is a locale
 **isLowercase(str)**                    | check if the string is lowercase.
 **isMACAddress(str)**                   | check if the string is a MAC address.<br/><br/>`options` is an object which defaults to `{no_separators: false}`. If `no_separators` is true, the validator will allow MAC addresses without separators. Also, it allows the use of hyphens, spaces or dots e.g  '01 02 03 04 05 ab', '01-02-03-04-05-ab' or '0102.0304.05ab'.
@@ -143,7 +144,7 @@ Validator                               | Description
 **isMultibyte(str)**                    | check if the string contains one or more multibyte chars.
 **isNumeric(str [, options])**                      | check if the string contains only numbers.<br/><br/>`options` is an object which defaults to `{no_symbols: false}` it also has locale as an option. If `no_symbols` is true, the validator will reject numeric strings that feature a symbol (e.g. `+`, `-`, or `.`).<br/><br/>`locale` determine the decimal separator and is one of `['ar', 'ar-AE', 'ar-BH', 'ar-DZ', 'ar-EG', 'ar-IQ', 'ar-JO', 'ar-KW', 'ar-LB', 'ar-LY', 'ar-MA', 'ar-QA', 'ar-QM', 'ar-SA', 'ar-SD', 'ar-SY', 'ar-TN', 'ar-YE', 'bg-BG', 'cs-CZ', 'da-DK', 'de-DE', 'en-AU', 'en-GB', 'en-HK', 'en-IN', 'en-NZ', 'en-US', 'en-ZA', 'en-ZM', 'es-ES', 'fr-FR', 'fr-CA', 'hu-HU', 'it-IT', 'nb-NO', 'nl-NL', 'nn-NO', 'pl-PL', 'pt-BR', 'pt-PT', 'ru-RU', 'sl-SI', 'sr-RS', 'sr-RS@latin', 'sv-SE', 'tr-TR', 'uk-UA']`.
 **isOctal(str)**                        | check if the string is a valid octal number.
-**isPassportNumber(str, countryCode)**    | check if the string is a valid passport number.<br/><br/>(countryCode is one of `[ 'AM', 'AR', 'AT', 'AU', 'BE', 'BG', 'BY', 'CA', 'CH', 'CN', 'CY', 'CZ', 'DE', 'DK', 'DZ', 'EE', 'ES', 'FI', 'FR', 'GB', 'GR', 'HR', 'HU', 'IE' 'IN', 'IS', 'IT', 'JP', 'KR', 'LT', 'LU', 'LV', 'LY', 'MT', 'NL', 'PO', 'PT', 'RO', 'RU', 'SE', 'SL', 'SK', 'TR', 'UA', 'US' ]`.
+**isPassportNumber(str, countryCode)**    | check if the string is a valid passport number.<br/><br/>(countryCode is one of `[ 'AM', 'AR', 'AT', 'AU', 'BE', 'BG', 'BY', 'BR', 'CA', 'CH', 'CN', 'CY', 'CZ', 'DE', 'DK', 'DZ', 'EE', 'ES', 'FI', 'FR', 'GB', 'GR', 'HR', 'HU', 'IE' 'IN', 'IS', 'IT', 'JP', 'KR', 'LT', 'LU', 'LV', 'LY', 'MT', 'MY', 'NL', 'PO', 'PT', 'RO', 'RU', 'SE', 'SL', 'SK', 'TR', 'UA', 'US' ]`.
 **isPort(str)**                         | check if the string is a valid port number.
 **isPostalCode(str, locale)**           | check if the string is a postal code,<br/><br/>(locale is one of `[ 'AD', 'AT', 'AU', 'AZ', 'BE', 'BG', 'BR', 'BY', 'CA', 'CH', 'CN', 'CZ', 'DE', 'DK', 'DO', 'DZ', 'EE', 'ES', 'FI', 'FR', 'GB', 'GR', 'HR', 'HT', 'HU', 'ID', 'IE' 'IL', 'IN', 'IR', 'IS', 'IT', 'JP', 'KE', 'LI', 'LT', 'LU', 'LV', 'MT', 'MX', 'MY', 'NL', 'NO', 'NP', 'NZ', 'PL', 'PR', 'PT', 'RO', 'RU', 'SA', 'SE', 'SG', 'SI', 'TH', 'TN', 'TW', 'UA', 'US', 'ZA', 'ZM' ]` OR 'any'. If 'any' is used, function will check if any of the locals match. Locale list is `validator.isPostalCodeLocales`.).
 **isRFC3339(str)**                      | check if the string is a valid [RFC 3339](https://tools.ietf.org/html/rfc3339) date.
@@ -153,7 +154,7 @@ Validator                               | Description
 **isUppercase(str)**                    | check if the string is uppercase.
 **isSlug**                              | Check if the string is of type slug. `Options` allow a single hyphen between string. e.g. [`cn-cn`, `cn-c-c`]
 **isStrongPassword(str [, options])**   | Check if a password is strong or not. Allows for custom requirements or scoring rules. If `returnScore` is true, then the function returns an integer score for the password rather than a boolean.<br/>Default options: <br/>`{ minLength: 8, minLowercase: 1, minUppercase: 1, minNumbers: 1, minSymbols: 1, returnScore: false, pointsPerUnique: 1, pointsPerRepeat: 0.5, pointsForContainingLower: 10, pointsForContainingUpper: 10, pointsForContainingNumber: 10, pointsForContainingSymbol: 10 }`
-**isTaxID(str, locale)**                | Check if the given value is a valid Tax Identification    Number. Default locale is `en-US`.<br/><br/>More info about exact TIN support can be found in `src/lib/isTaxID.js`<br/><br/>Supported locales: `[ 'bg-BG', 'cs-CZ', 'de-AT', 'de-DE', 'dk-DK', 'el-CY', 'el-GR', 'en-GB', 'en-IE', 'en-US', 'es-ES', 'et-EE', 'fi-FI', 'fr-BE', 'fr-FR', 'fr-LU', 'hr-HR', 'hu-HU', 'it-IT', 'lb-LU', 'lt-LT', 'lv-LV' 'mt-MT', 'nl-BE', 'nl-NL', 'pl-PL', 'pt-PT', 'ro-RO', 'sk-SK', 'sl-SI', 'sv-SE' ]`
+**isTaxID(str, locale)**                | Check if the given value is a valid Tax Identification    Number. Default locale is `en-US`.<br/><br/>More info about exact TIN support can be found in `src/lib/isTaxID.js`<br/><br/>Supported locales: `[ 'bg-BG', 'cs-CZ', 'de-AT', 'de-DE', 'dk-DK', 'el-CY', 'el-GR', 'en-GB', 'en-IE', 'en-US', 'es-ES', 'et-EE', 'fi-FI', 'fr-BE', 'fr-FR', 'fr-LU', 'hr-HR', 'hu-HU', 'it-IT', 'lb-LU', 'lt-LT', 'lv-LV' 'mt-MT', 'nl-BE', 'nl-NL', 'pl-PL', 'pt-BR', 'pt-PT', 'ro-RO', 'sk-SK', 'sl-SI', 'sv-SE' ]`
 **isURL(str [, options])**              | check if the string is an URL.<br/><br/>`options` is an object which defaults to `{ protocols: ['http','https','ftp'], require_tld: true, require_protocol: false, require_host: true, require_valid_protocol: true, allow_underscores: false, host_whitelist: false, host_blacklist: false, allow_trailing_dot: false, allow_protocol_relative_urls: false, disallow_auth: false }`.<br/><br/>require_protocol - if set as true isURL will return false if protocol is not present in the URL.<br/>require_valid_protocol - isURL will check if the URL's protocol is present in the protocols option.<br/>protocols - valid protocols can be modified with this option.<br/>require_host - if set as false isURL will not check if host is present in the URL.<br/>require_port - if set as true isURL will check if port is present in the URL.<br/>allow_protocol_relative_urls - if set as true protocol relative URLs will be allowed.<br/>validate_length - if set as false isURL will skip string length validation (2083 characters is IE max URL length).
 **isUUID(str [, version])**             | check if the string is a UUID (version 3, 4 or 5).
 **isVariableWidth(str)**                | check if the string contains a mixture of full and half-width chars.
diff --git a/src/lib/isIPRange.js b/src/lib/isIPRange.js
index ffb4e1a..5bb6916 100644
--- a/src/lib/isIPRange.js
+++ b/src/lib/isIPRange.js
@@ -1,9 +1,11 @@
 import assertString from './util/assertString';
 import isIP from './isIP';
 
-const subnetMaybe = /^\d{1,2}$/;
+const subnetMaybe = /^\d{1,3}$/;
+const v4Subnet = 32;
+const v6Subnet = 128;
 
-export default function isIPRange(str) {
+export default function isIPRange(str, version = '') {
   assertString(str);
   const parts = str.split('/');
 
@@ -21,5 +23,25 @@ export default function isIPRange(str) {
     return false;
   }
 
-  return isIP(parts[0], 4) && parts[1] <= 32 && parts[1] >= 0;
+  const isValidIP = isIP(parts[0], version);
+  if (!isValidIP) {
+    return false;
+  }
+
+  // Define valid subnet according to IP's version
+  let expectedSubnet = null;
+  switch (String(version)) {
+    case '4':
+      expectedSubnet = v4Subnet;
+      break;
+
+    case '6':
+      expectedSubnet = v6Subnet;
+      break;
+
+    default:
+      expectedSubnet = isIP(parts[0], '6') ? v6Subnet : v4Subnet;
+  }
+
+  return parts[1] <= expectedSubnet && parts[1] >= 0;
 }
diff --git a/src/lib/isLicensePlate.js b/src/lib/isLicensePlate.js
index c4b010d..a28d66d 100644
--- a/src/lib/isLicensePlate.js
+++ b/src/lib/isLicensePlate.js
@@ -8,6 +8,8 @@ const validators = {
     /^([A-Z]{2}|[0-9]{2})[ -·]?([A-Z]{2}|[0-9]{2})[ -·]?([A-Z]{2}|[0-9]{2})$/.test(str),
   'sq-AL': str =>
     /^[A-Z]{2}[- ]?((\d{3}[- ]?(([A-Z]{2})|T))|(R[- ]?\d{3}))$/.test(str),
+  'pt-BR': str =>
+    /^[A-Z]{3}[ -]?[0-9][A-Z][0-9]{2}|[A-Z]{3}[ -]?[0-9]{4}$/.test(str),
 };
 
 export default function isLicensePlate(str, locale) {
@@ -16,11 +18,10 @@ export default function isLicensePlate(str, locale) {
     return validators[locale](str);
   } else if (locale === 'any') {
     for (const key in validators) {
-      if (validators.hasOwnProperty(key)) {
-        const validator = validators[key];
-        if (validator(str)) {
-          return true;
-        }
+      /* eslint guard-for-in: 0 */
+      const validator = validators[key];
+      if (validator(str)) {
+        return true;
       }
     }
     return false;
diff --git a/src/lib/isMobilePhone.js b/src/lib/isMobilePhone.js
index 68a1992..ed1f6d2 100644
--- a/src/lib/isMobilePhone.js
+++ b/src/lib/isMobilePhone.js
@@ -46,7 +46,7 @@ const phones = {
   'en-PK': /^((\+92)|(0092))-{0,1}\d{3}-{0,1}\d{7}$|^\d{11}$|^\d{4}-\d{7}$/,
   'en-PH': /^(09|\+639)\d{9}$/,
   'en-RW': /^(\+?250|0)?[7]\d{8}$/,
-  'en-SG': /^(\+65)?[689]\d{7}$/,
+  'en-SG': /^(\+65)?[3689]\d{7}$/,
   'en-SL': /^(?:0|94|\+94)?(7(0|1|2|5|6|7|8)( |-)?\d)\d{6}$/,
   'en-TZ': /^(\+?255|0)?[67]\d{8}$/,
   'en-UG': /^(\+?256|0)?[7]\d{8}$/,
@@ -56,7 +56,7 @@ const phones = {
   'en-ZW': /^(\+263)[0-9]{9}$/,
   'es-AR': /^\+?549(11|[2368]\d)\d{8}$/,
   'es-BO': /^(\+?591)?(6|7)\d{7}$/,
-  'es-CO': /^(\+?57)?([1-8]{1}|3[0-9]{2})?[0-9]{1}\d{6}$/,
+  'es-CO': /^(\+?57)?3(0(0|1|2|4|5)|1\d|2[0-4]|5(0|1))\d{7}$/,
   'es-CL': /^(\+?56|0)[2-9]\d{1}\d{7}$/,
   'es-CR': /^(\+506)?[2-8]\d{7}$/,
   'es-DO': /^(\+?1)?8[024]9\d{7}$/,
diff --git a/src/lib/isPassportNumber.js b/src/lib/isPassportNumber.js
index 76fd082..3572abd 100644
--- a/src/lib/isPassportNumber.js
+++ b/src/lib/isPassportNumber.js
@@ -13,6 +13,7 @@ const passportRegexByCountryCode = {
   AU: /^[A-Z]\d{7}$/, // AUSTRALIA
   BE: /^[A-Z]{2}\d{6}$/, // BELGIUM
   BG: /^\d{9}$/, // BULGARIA
+  BR: /^[A-Z]{2}\d{6}$/, // BRAZIL
   BY: /^[A-Z]{2}\d{7}$/, // BELARUS
   CA: /^[A-Z]{2}\d{6}$/, // CANADA
   CH: /^[A-Z]\d{7}$/, // SWITZERLAND
@@ -41,6 +42,7 @@ const passportRegexByCountryCode = {
   LV: /^[A-Z0-9]{2}\d{7}$/, // LATVIA
   LY: /^[A-Z0-9]{8}$/, // LIBYA
   MT: /^\d{7}$/, // MALTA
+  MY: /^[AHK]\d{8}$/, // MALAYSIA
   NL: /^[A-Z]{2}[A-Z0-9]{6}\d$/, // NETHERLANDS
   PO: /^[A-Z]{2}\d{7}$/, // POLAND
   PT: /^[A-Z]\d{6}$/, // PORTUGAL
diff --git a/src/lib/isStrongPassword.js b/src/lib/isStrongPassword.js
index e18a759..28bb063 100644
--- a/src/lib/isStrongPassword.js
+++ b/src/lib/isStrongPassword.js
@@ -49,6 +49,7 @@ function analyzePassword(password) {
     symbolCount: 0,
   };
   Object.keys(charMap).forEach((char) => {
+    /* istanbul ignore else */
     if (upperCaseRegex.test(char)) {
       analysis.uppercaseCount += charMap[char];
     } else if (lowerCaseRegex.test(char)) {
diff --git a/src/lib/isTaxID.js b/src/lib/isTaxID.js
index 90a0c2a..f1ebae6 100644
--- a/src/lib/isTaxID.js
+++ b/src/lib/isTaxID.js
@@ -852,6 +852,93 @@ function plPlCheck(tin) {
   return checksum === parseInt(tin[10], 10);
 }
 
+/*
+* pt-BR validation function
+* (Cadastro de Pessoas Físicas (CPF, persons)
+* Cadastro Nacional de Pessoas Jurídicas (CNPJ, entities)
+* Both inputs will be validated
+*/
+
+function ptBrCheck(tin) {
+  tin = tin.replace(/[^\d]+/g, '');
+  if (tin === '') return false;
+
+  if (tin.length === 11) {
+    let sum;
+    let ramainder;
+    sum = 0;
+    tin = tin.replace(/[^\d]+/g, '');
+
+    if ( // Reject known invalid CPFs
+      tin === '11111111111' ||
+      tin === '22222222222' ||
+      tin === '33333333333' ||
+      tin === '44444444444' ||
+      tin === '55555555555' ||
+      tin === '66666666666' ||
+      tin === '77777777777' ||
+      tin === '88888888888' ||
+      tin === '99999999999' ||
+      tin === '00000000000'
+    ) return false;
+
+    for (let i = 1; i <= 9; i++) sum += parseInt(tin.substring(i - 1, i), 10) * (11 - i);
+    ramainder = (sum * 10) % 11;
+    if ((ramainder === 10) || (ramainder === 11)) ramainder = 0;
+    if (ramainder !== parseInt(tin.substring(9, 10), 10)) return false;
+    sum = 0;
+
+    for (let i = 1; i <= 10; i++) sum += parseInt(tin.substring(i - 1, i), 10) * (12 - i);
+    ramainder = (sum * 10) % 11;
+    if ((ramainder === 10) || (ramainder === 11)) ramainder = 0;
+    if (ramainder !== parseInt(tin.substring(10, 11), 10)) return false;
+
+    return true;
+  }
+
+  if (tin.length !== 14) { return false; }
+
+  if ( // Reject know invalid CNPJs
+    tin === '00000000000000' ||
+    tin === '11111111111111' ||
+    tin === '22222222222222' ||
+    tin === '33333333333333' ||
+    tin === '44444444444444' ||
+    tin === '55555555555555' ||
+    tin === '66666666666666' ||
+    tin === '77777777777777' ||
+    tin === '88888888888888' ||
+    tin === '99999999999999') { return false; }
+
+  let length = tin.length - 2;
+  let identifiers = tin.substring(0, length);
+  let verificators = tin.substring(length);
+  let sum = 0;
+  let pos = length - 7;
+
+  for (let i = length; i >= 1; i--) {
+    sum += identifiers.charAt(length - i) * pos;
+    pos -= 1;
+    if (pos < 2) { pos = 9; }
+  }
+  let result = sum % 11 < 2 ? 0 : 11 - (sum % 11);
+  if (result !== parseInt(verificators.charAt(0), 10)) { return false; }
+
+  length += 1;
+  identifiers = tin.substring(0, length);
+  sum = 0;
+  pos = length - 7;
+  for (let i = length; i >= 1; i--) {
+    sum += identifiers.charAt(length - i) * pos;
+    pos -= 1;
+    if (pos < 2) { pos = 9; }
+  }
+  result = sum % 11 < 2 ? 0 : 11 - (sum % 11);
+  if (result !== parseInt(verificators.charAt(1), 10)) { return false; }
+
+  return true;
+}
+
 /*
  * pt-PT validation function
  * (Número de identificação fiscal (NIF), persons/entities)
@@ -1039,6 +1126,7 @@ const taxIdFormat = {
   'mt-MT': /^\d{3,7}[APMGLHBZ]$|^([1-8])\1\d{7}$/i,
   'nl-NL': /^\d{9}$/,
   'pl-PL': /^\d{10,11}$/,
+  'pt-BR': /^\d{11,14}$/,
   'pt-PT': /^\d{9}$/,
   'ro-RO': /^\d{13}$/,
   'sk-SK': /^\d{6}\/{0,1}\d{3,4}$/,
@@ -1076,6 +1164,7 @@ const taxIdCheck = {
   'mt-MT': mtMtCheck,
   'nl-NL': nlNlCheck,
   'pl-PL': plPlCheck,
+  'pt-BR': ptBrCheck,
   'pt-PT': ptPtCheck,
   'ro-RO': roRoCheck,
   'sk-SK': skSkCheck,


Test patch:
diff --git a/test/validators.js b/test/validators.js
index 34b6474..962a4bb 100644
--- a/test/validators.js
+++ b/test/validators.js
@@ -872,18 +872,88 @@ describe('Validators', () => {
         '127.0.0.1/24',
         '0.0.0.0/0',
         '255.255.255.0/32',
+        '::/0',
+        '::/128',
+        '2001::/128',
+        '2001:800::/128',
+        '::ffff:127.0.0.1/128',
       ],
       invalid: [
+        'abc',
         '127.200.230.1/35',
         '127.200.230.1/-1',
         '1.1.1.1/011',
-        '::1/64',
         '1.1.1/24.1',
         '1.1.1.1/01',
         '1.1.1.1/1.1',
         '1.1.1.1/1.',
         '1.1.1.1/1/1',
         '1.1.1.1',
+        '::1',
+        '::1/164',
+        '2001::/240',
+        '2001::/-1',
+        '2001::/001',
+        '2001::/24.1',
+        '2001:db8:0000:1:1:1:1:1',
+        '::ffff:127.0.0.1',
+      ],
+    });
+    test({
+      validator: 'isIPRange',
+      args: [4],
+      valid: [
+        '127.0.0.1/1',
+        '0.0.0.0/1',
+        '255.255.255.255/1',
+        '1.2.3.4/1',
+        '255.0.0.1/1',
+        '0.0.1.1/1',
+      ],
+      invalid: [
+        'abc',
+        '::1',
+        '2001:db8:0000:1:1:1:1:1',
+        '::ffff:127.0.0.1',
+        '137.132.10.01',
+        '0.256.0.256',
+        '255.256.255.256',
+      ],
+    });
+    test({
+      validator: 'isIPRange',
+      args: [6],
+      valid: [
+        '::1/1',
+        '2001:db8:0000:1:1:1:1:1/1',
+        '::ffff:127.0.0.1/1',
+      ],
+      invalid: [
+        'abc',
+        '127.0.0.1',
+        '0.0.0.0',
+        '255.255.255.255',
+        '1.2.3.4',
+        '::ffff:287.0.0.1',
+        '::ffff:287.0.0.1/254',
+        '%',
+        'fe80::1234%',
+        'fe80::1234%1%3%4',
+        'fe80%fe80%',
+      ],
+    });
+    test({
+      validator: 'isIPRange',
+      args: [10],
+      valid: [],
+      invalid: [
+        'abc',
+        '127.0.0.1/1',
+        '0.0.0.0/1',
+        '255.255.255.255/1',
+        '1.2.3.4/1',
+        '::1/1',
+        '2001:db8:0000:1:1:1:1:1/1',
       ],
     });
   });
@@ -2289,6 +2359,18 @@ describe('Validators', () => {
       ],
     });
 
+    test({
+      validator: 'isPassportNumber',
+      args: ['BR'],
+      valid: [
+        'FZ973689',
+        'GH231233',
+      ],
+      invalid: [
+        'ABX29332',
+      ],
+    });
+
     test({
       validator: 'isPassportNumber',
       args: ['BY'],
@@ -2658,6 +2740,20 @@ describe('Validators', () => {
       ],
     });
 
+    test({
+      validator: 'isPassportNumber',
+      args: ['MY'],
+      valid: [
+        'A00000000',
+        'H12345678',
+        'K43143233',
+      ],
+      invalid: [
+        'A1234567',
+        'C01234567',
+      ],
+    });
+
     test({
       validator: 'isPassportNumber',
       args: ['NL'],
@@ -6237,6 +6333,7 @@ describe('Validators', () => {
       {
         locale: 'en-SG',
         valid: [
+          '32891278',
           '87654321',
           '98765432',
           '+6587654321',
@@ -6244,6 +6341,7 @@ describe('Validators', () => {
           '+6565241234',
         ],
         invalid: [
+          '332891231',
           '987654321',
           '876543219',
           '8765432',
@@ -6506,18 +6604,11 @@ describe('Validators', () => {
         valid: [
           '+573003321235',
           '573003321235',
-          '579871235',
           '3003321235',
           '3213321235',
           '3103321235',
-          '3253321235',
-          '3321235',
-          '574321235',
-          '5784321235',
-          '5784321235',
-          '9821235',
+          '3243321235',
           '573011140876',
-          '0698345',
         ],
         invalid: [
           '1234',
@@ -6530,6 +6621,13 @@ describe('Validators', () => {
           '5703013347567',
           '069834567',
           '969834567',
+          '579871235',
+          '574321235',
+          '5784321235',
+          '5784321235',
+          '9821235',
+          '0698345',
+          '3321235',
         ],
       },
       {
@@ -9965,6 +10063,22 @@ describe('Validators', () => {
         '2234567855',
         '02223013623'],
     });
+    test({
+      validator: 'isTaxID',
+      args: ['pt-BR'],
+      valid: [
+        '35161990910',
+        '74407265027',
+        '05423994000172',
+        '11867044000130'],
+      invalid: [
+        '170.691.440-72',
+        '01494282042',
+        '11111111111',
+        '94.592.973/0001-82',
+        '28592361000192',
+        '11111111111111'],
+    });
     test({
       validator: 'isTaxID',
       args: ['pt-PT'],
@@ -10375,6 +10489,28 @@ describe('Validators', () => {
         'AAA 00 AAA',
       ],
     });
+    test({
+      validator: 'isLicensePlate',
+      args: ['pt-BR'],
+      valid: [
+        'ABC1234',
+        'ABC 1234',
+        'ABC-1234',
+        'ABC1D23',
+        'ABC1K23',
+        'ABC1Z23',
+        'ABC 1D23',
+        'ABC-1D23',
+      ],
+      invalid: [
+        '',
+        'AA 0 A',
+        'AAA 00 AAA',
+        'ABCD123',
+        'AB12345',
+        'AB123DC',
+      ],
+    });
     test({
       validator: 'isLicensePlate',
       args: ['any'],