diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index bf7c05a0e..ba6041d49 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -5,8 +5,11 @@ feat(validatorName): brief title of what has been done + + ## Checklist - [ ] PR contains only changes related; no stray files, etc. - [ ] README updated (where applicable) - [ ] Tests written (where applicable) +- [ ] References provided in PR (where applicable) diff --git a/CHANGELOG.md b/CHANGELOG.md index edd1960f4..8d27cf979 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,145 @@ +# 13.11.0 + +### New Features / Validators + +- [#2144](https://github.com/validatorjs/validator.js/pull/2144) `isFreightContainerID`: for shipping containers IDs @songyuew +- [#2188](https://github.com/validatorjs/validator.js/pull/2188) `isMailtoURI` @uksarkar + +### Fixes, New Locales and Enhancements + +- [#2025](https://github.com/validatorjs/validator.js/pull/2025) `isIBAN` add `MA` locale @lroudge +- [#2117](https://github.com/validatorjs/validator.js/pull/2117) `isCreditCard` refactor @pano9000 +- [#2189](https://github.com/validatorjs/validator.js/pull/2189) `isLocale` add support for more language tags @kwahome +- [#2203](https://github.com/validatorjs/validator.js/pull/2203) `isVAT` for `CU` @jimmyorpheus +- [#2217](https://github.com/validatorjs/validator.js/pull/2217) `isJWT` @Prathamesh061 +- [#2222](https://github.com/validatorjs/validator.js/pull/2222) `IsFQDN` test enhancements @aalekhpatel07 +- [#2226](https://github.com/validatorjs/validator.js/pull/2226) `isAlpha`, `isAlphanumeric` for `kk-KZ` @BekStar7 +- [#2229](https://github.com/validatorjs/validator.js/pull/2229) `isEmail` support `allow_underscores` @guspower +- [#2231](https://github.com/validatorjs/validator.js/pull/2231) `isDate` enhance Date declaration compatibility across multiple environments @CiprianS +- [#2235](https://github.com/validatorjs/validator.js/pull/2235) `isIBAN` add white and blacklist options to the isIBAN validator @edilson +- [#2237](https://github.com/validatorjs/validator.js/pull/2237) `isEmail` do not allow non-breaking space in user part @jeremy21212121 +- `isMobilePhone`: + - [#2175](https://github.com/validatorjs/validator.js/pull/2175) `so-SO` @ohersi + - [#2176](https://github.com/validatorjs/validator.js/pull/2176) `fr-CF` @cheboi + - [#2197](https://github.com/validatorjs/validator.js/pull/2197) `es-CU` @klaframboise + - [#2202](https://github.com/validatorjs/validator.js/pull/2202) `pl-PL` @czerwony03 + - [#2209](https://github.com/validatorjs/validator.js/pull/2209) `fr-WF` @aidos42 + - [#2246](https://github.com/validatorjs/validator.js/pull/2246) `ar-SD` @Hussienma + + + +# 13.9.0 + +### New Features / Validators + +- [#1892](https://github.com/validatorjs/validator.js/pull/1892) `isISO6391`: add ISO 639-1 validator @braaar +- [#1974](https://github.com/validatorjs/validator.js/pull/1974) `isLuhnNumber` @ST-DDT + +### Fixes and Enhancements + +- [#1865](https://github.com/validatorjs/validator.js/pull/1865) `isMACAddress`: add EUI-validation @WikiRik @tux-tn +- [#1888](https://github.com/validatorjs/validator.js/pull/1888) `isBase32`: add option for Crockford's base32 alternative @BigOsvaap +- [#1916](https://github.com/validatorjs/validator.js/pull/1916) `isDataURI`: fix mediaType format @temoffey +- [#1920](https://github.com/validatorjs/validator.js/pull/1920) `isEmail`: add `host_whitelist` option @poor-coder +- [#1939](https://github.com/validatorjs/validator.js/pull/1939) `isFQDN`: fix `allow_numeric_tld` option @BigOsvaap +- [#1962](https://github.com/validatorjs/validator.js/pull/1962) `isIP`: refactor @UnKnoWn-Consortium +- [#1967](https://github.com/validatorjs/validator.js/pull/1967) `isLength` @ikkyu-3 +- [#1992](https://github.com/validatorjs/validator.js/pull/1992) `isMagnetURI` @Rhilip @tux-tn +- [#1995](https://github.com/validatorjs/validator.js/pull/1995) `isURL`: fix check for host @mortbauer +- [#2008](https://github.com/validatorjs/validator.js/pull/2008) `isCreditCard` @brianwhaley +- [#2075](https://github.com/validatorjs/validator.js/pull/2075) `isAfter`: allow usage of option object @WikiRik +- [#2114](https://github.com/validatorjs/validator.js/pull/2114) `isRgbColor` @pano9000 +- [#2122](https://github.com/validatorjs/validator.js/pull/2122) `isDataURI`: fix MIME types with underscores @pano9000 +- [#2148](https://github.com/validatorjs/validator.js/pull/2148) `isStrongPassword` @sandmule +- [#2157](https://github.com/validatorjs/validator.js/pull/2157) `isISBN`: allow usage of option object @WikiRik +- [#2170](https://github.com/validatorjs/validator.js/pull/2170) `isEmail`: fix `ignore_max_length` for FQDN @sakhmedbayev +- [#2020](https://github.com/validatorjs/validator.js/pull/2170) `isFloat`: fix comma(,) passing as float @frederike-ramin + +- Documentation fixes: + - [#1860](https://github.com/validatorjs/validator.js/pull/1860) @leonardovillela + - [#1861](https://github.com/validatorjs/validator.js/pull/1860) @tux-tn + - [#1957](https://github.com/validatorjs/validator.js/pull/1957) @tfilo + - [#2010](https://github.com/validatorjs/validator.js/pull/2010) @marcelozarate + - [#2107](https://github.com/validatorjs/validator.js/pull/2107) @pano9000 + - [#2160](https://github.com/validatorjs/validator.js/pull/2160) @WikiRik + +- Code Refactors: + - [#1942](https://github.com/validatorjs/validator.js/pull/1942) @CommanderRoot + - [#1975](https://github.com/validatorjs/validator.js/pull/1975) @fedeci + - [#2137](https://github.com/validatorjs/validator.js/pull/2137) [#2132](https://github.com/validatorjs/validator.js/pull/2132) @pano9000 + +### New and Improved Locales + +- `isAlpha`, `isAlphanumeric`: + - [#1678](https://github.com/validatorjs/validator.js/pull/1678) `bn-BD` @rak810 + - [#1996](https://github.com/validatorjs/validator.js/pull/1996) `si-LK` @melkorCBA + - [#2014](https://github.com/validatorjs/validator.js/pull/2014) `ja-JP` @starcharles + - [#1995](https://github.com/validatorjs/validator.js/pull/1995) `ko-KR` @Dongkyuuuu + +- `isBIC`: + - [#2046](https://github.com/validatorjs/validator.js/pull/2046) `XK` @import-brain + +- `isIdentityCard`: + - [#2142](https://github.com/validatorjs/validator.js/pull/2142) `hk-HK` @Dongkyuuuu + +- `isMobilePhone`: + - [#1813](https://github.com/validatorjs/validator.js/pull/1813) `my-MM`, @ferdousulhaque + - [#1868](https://github.com/validatorjs/validator.js/pull/1868) `de-DE`, @thomaschaaf + - [#1896](https://github.com/validatorjs/validator.js/pull/1896) `en-LS`, @DevilsAutumn + - [#1897](https://github.com/validatorjs/validator.js/pull/1897) `el-CY`, @ikerasiotis + - [#1909](https://github.com/validatorjs/validator.js/pull/1909) `es-NI`, @ajGingrich + - [#1910](https://github.com/validatorjs/validator.js/pull/1910) `az-AZ`, @shaanaliyev + - [#1922](https://github.com/validatorjs/validator.js/pull/1922) `ir-IR`, @ArashST79 + - [#1924](https://github.com/validatorjs/validator.js/pull/1924) `ky-KG`, @arsalanfiroozi + - [#1925](https://github.com/validatorjs/validator.js/pull/1925) `ar-YE`, `ar-EH`, `fa-AF`, @Mustafiz04 + - [#1932](https://github.com/validatorjs/validator.js/pull/1932) `ro-MD`, @mik7up + - [#1940](https://github.com/validatorjs/validator.js/pull/1940) `ar-YE`, `en-BS`, @savannahvaith + - [#1952](https://github.com/validatorjs/validator.js/pull/1952) `ka-GE`, @avkvak + - [#1964](https://github.com/validatorjs/validator.js/pull/1964) [#1951](https://github.com/validatorjs/validator.js/pull/1951) `pt-BR`, @jhcaiafa @matheusnascgomes + - [#1983](https://github.com/validatorjs/validator.js/pull/1983) `es-HN`, @ademyan05 + - [#1985](https://github.com/validatorjs/validator.js/pull/1985) `nl-AW`, @adida948 + - [#1986](https://github.com/validatorjs/validator.js/pull/1986) `en-JM`, @ademyan05 + - [#1993](https://github.com/validatorjs/validator.js/pull/1993) `mn-MN`, @rksp25 + - [#1997](https://github.com/validatorjs/validator.js/pull/1997) `fr-BJ`, @rkuma552 @rksp25 + - [#2001](https://github.com/validatorjs/validator.js/pull/2001) `mg-MG`, @ShivangiRai1310 + - [#2002](https://github.com/validatorjs/validator.js/pull/2002) `en-PG`, @kai2128 + - [#2004](https://github.com/validatorjs/validator.js/pull/2004) `en-AG`, @jiaweilow + - [#2007](https://github.com/validatorjs/validator.js/pull/2007) `en-AI`, @elaine1129 + - [#2011](https://github.com/validatorjs/validator.js/pull/2011) `en-KN`, @Eelyneee + - [#2041](https://github.com/validatorjs/validator.js/pull/2041) `fr-CD`, @coolbeatz71 + - [#2084](https://github.com/validatorjs/validator.js/pull/2084) `en-SS`, @cheboi + - [#2109](https://github.com/validatorjs/validator.js/pull/2109) `dv-MV`, @pano9000 + - [#2129](https://github.com/validatorjs/validator.js/pull/2129) `en-HN`, @WikiRik + - [#2148](https://github.com/validatorjs/validator.js/pull/2148) `ar-KW`, @Yazan-KE @WikiRik + - [#2112](https://github.com/validatorjs/validator.js/pull/2112) `el-GR`, @pano9000 + - [#2116](https://github.com/validatorjs/validator.js/pull/2116) `en-BM`, @pano9000 + - [#2155](https://github.com/validatorjs/validator.js/pull/2155) `ms-MY`, @pano9000 + - [#2156](https://github.com/validatorjs/validator.js/pull/2156) `ro-RO`, @pano9000 + +- `isLicensePlate`: + - [#1665](https://github.com/validatorjs/validator.js/pull/1665) `sv-SE`, @elmaxe + - [#1895](https://github.com/validatorjs/validator.js/pull/1895) `hu-HU`, @szabolcstarnai + - [#1944](https://github.com/validatorjs/validator.js/pull/1944) `en-NI`, @NishantJS + - [#1945](https://github.com/validatorjs/validator.js/pull/1945) `de-DE`, @bennetfabian + - [#1945](https://github.com/validatorjs/validator.js/pull/1945) `de-DE`, @bennetfabian + - [#2103](https://github.com/validatorjs/validator.js/pull/2103) `es-AR`, @alvarocastro + +- `isPassportNumber`: + - [#1515](https://github.com/validatorjs/validator.js/pull/1515) `JM`,`KZ`,`LI`,`NZ` @JuanFML + - [#1814](https://github.com/validatorjs/validator.js/pull/1814) `TH` @TonPC64 @braaar + - [#2061](https://github.com/validatorjs/validator.js/pull/2061) `AZ` @djeks922 + - [#2073](https://github.com/validatorjs/validator.js/pull/2073) `PH`,`PK` @digambar-t7 + +- `isPostalCode`: + - [#1951](https://github.com/validatorjs/validator.js/pull/1951) `BA`, @matheusnascgomes + - [#2134](https://github.com/validatorjs/validator.js/pull/2134) `BY`, @pano9000 + - [#2136](https://github.com/validatorjs/validator.js/pull/2136) `IR`, @pano9000 + + +- `isTaxID`: + - [#1867](https://github.com/validatorjs/validator.js/pull/1867) `en-CA`, @boonya + - [#1989](https://github.com/validatorjs/validator.js/pull/1989) `'AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR', 'DE', 'EL', 'HU', 'IE', 'LV', 'LT', 'LU', 'MT', 'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE', 'AL', 'MK', 'AU', 'BY', 'CA', 'IS', 'IN', 'ID', 'IL', 'KZ', 'NZ', 'NG', 'NO', 'PH', 'RU', 'SM', 'SA', 'RS', 'CH', 'TR', 'UA', 'UZ', 'AR', 'BO', 'BR', 'CL', 'CO', 'CR', 'EC', 'SV', 'GT', 'HN', 'MX', 'NI', 'PA', 'PY', 'PE', 'DO', 'UY', 'VE'` @Dev1lDragon + ## 13.7.0 ### New Features diff --git a/README.md b/README.md index de00deb72..e00b7cfac 100644 --- a/README.md +++ b/README.md @@ -91,8 +91,8 @@ Validator | Description **contains(str, seed [, options])** | check if the string contains the seed.

`options` is an object that defaults to `{ ignoreCase: false, minOccurrences: 1 }`.
Options:
`ignoreCase`: Ignore case when doing comparison, default false.
`minOccurences`: Minimum number of occurrences for the seed in the string. Defaults to 1. **equals(str, comparison)** | check if the string matches the comparison. **isAfter(str [, options])** | check if the string is a date that is after the specified date.

`options` is an object that defaults to `{ comparisonDate: Date().toString() }`.
**Options:**
`comparisonDate`: Date to compare to. Defaults to `Date().toString()` (now). -**isAlpha(str [, locale, options])** | check if the string contains only letters (a-zA-Z).

`locale` 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', 'bn', 'cs-CZ', 'da-DK', 'de-DE', 'el-GR', 'en-AU', 'en-GB', 'en-HK', 'en-IN', 'en-NZ', 'en-US', 'en-ZA', 'en-ZM', 'es-ES', 'fa-IR', 'fi-FI', 'fr-CA', 'fr-FR', 'he', 'hi-IN', 'hu-HU', 'it-IT', 'ko-KR', 'ja-JP', 'ku-IQ', 'nb-NO', 'nl-NL', 'nn-NO', 'pl-PL', 'pt-BR', 'pt-PT', 'ru-RU', 'si-LK', 'sl-SI', 'sk-SK', 'sr-RS', 'sr-RS@latin', 'sv-SE', 'tr-TR', 'uk-UA']` and defaults to `en-US`. Locale list is `validator.isAlphaLocales`. `options` is an optional object that can be supplied with the following key(s): `ignore` which can either be a String or RegExp of characters to be ignored e.g. " -" will ignore spaces and -'s. -**isAlphanumeric(str [, locale, options])** | check if the string contains only letters and numbers (a-zA-Z0-9).

`locale` 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', 'bn', 'bg-BG', 'cs-CZ', 'da-DK', 'de-DE', 'el-GR', 'en-AU', 'en-GB', 'en-HK', 'en-IN', 'en-NZ', 'en-US', 'en-ZA', 'en-ZM', 'es-ES', 'fa-IR', 'fi-FI', 'fr-CA', 'fr-FR', 'he', 'hi-IN', 'hu-HU', 'it-IT', 'ko-KR', 'ja-JP','ku-IQ', 'nb-NO', 'nl-NL', 'nn-NO', 'pl-PL', 'pt-BR', 'pt-PT', 'ru-RU', 'si-LK', 'sl-SI', 'sk-SK', 'sr-RS', 'sr-RS@latin', 'sv-SE', 'tr-TR', 'uk-UA']`) and defaults to `en-US`. Locale list is `validator.isAlphanumericLocales`. `options` is an optional object that can be supplied with the following key(s): `ignore` which can either be a String or RegExp of characters to be ignored e.g. " -" will ignore spaces and -'s. +**isAlpha(str [, locale, options])** | check if the string contains only letters (a-zA-Z).

`locale` 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', 'bn', 'cs-CZ', 'da-DK', 'de-DE', 'el-GR', 'en-AU', 'en-GB', 'en-HK', 'en-IN', 'en-NZ', 'en-US', 'en-ZA', 'en-ZM', 'es-ES', 'fa-IR', 'fi-FI', 'fr-CA', 'fr-FR', 'he', 'hi-IN', 'hu-HU', 'it-IT', 'kk-KZ', 'ko-KR', 'ja-JP', 'ku-IQ', 'nb-NO', 'nl-NL', 'nn-NO', 'pl-PL', 'pt-BR', 'pt-PT', 'ru-RU', 'si-LK', 'sl-SI', 'sk-SK', 'sr-RS', 'sr-RS@latin', 'sv-SE', 'th-TH', 'tr-TR', 'uk-UA']` and defaults to `en-US`. Locale list is `validator.isAlphaLocales`. `options` is an optional object that can be supplied with the following key(s): `ignore` which can either be a String or RegExp of characters to be ignored e.g. " -" will ignore spaces and -'s. +**isAlphanumeric(str [, locale, options])** | check if the string contains only letters and numbers (a-zA-Z0-9).

`locale` 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', 'bn', 'bg-BG', 'cs-CZ', 'da-DK', 'de-DE', 'el-GR', 'en-AU', 'en-GB', 'en-HK', 'en-IN', 'en-NZ', 'en-US', 'en-ZA', 'en-ZM', 'es-ES', 'fa-IR', 'fi-FI', 'fr-CA', 'fr-FR', 'he', 'hi-IN', 'hu-HU', 'it-IT', 'kk-KZ', 'ko-KR', 'ja-JP','ku-IQ', 'nb-NO', 'nl-NL', 'nn-NO', 'pl-PL', 'pt-BR', 'pt-PT', 'ru-RU', 'si-LK', 'sl-SI', 'sk-SK', 'sr-RS', 'sr-RS@latin', 'sv-SE', 'th-TH', 'tr-TR', 'uk-UA']`) and defaults to `en-US`. Locale list is `validator.isAlphanumericLocales`. `options` is an optional object that can be supplied with the following key(s): `ignore` which can either be a String or RegExp of characters to be ignored e.g. " -" will ignore spaces and -'s. **isAscii(str)** | check if the string contains ASCII chars only. **isBase32(str [, options])** | check if the string is base32 encoded. `options` is optional and defaults to `{ crockford: false }`.
When `crockford` is true it tests the given base32 encoded string using [Crockford's base32 alternative][Crockford Base32]. **isBase58(str)** | check if the string is base58 encoded. @@ -109,18 +109,19 @@ Validator | Description **isDecimal(str [, options])** | check if the string represents a decimal number, such as 0.1, .3, 1.1, 1.00003, 4.0, etc.

`options` is an object which defaults to `{force_decimal: false, decimal_digits: '1,', locale: 'en-US'}`.

`locale` determines 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', 'el-GR', 'en-AU', 'en-GB', 'en-HK', 'en-IN', 'en-NZ', 'en-US', 'en-ZA', 'en-ZM', 'es-ES', 'fa', 'fa-AF', 'fa-IR', 'fr-FR', 'fr-CA', 'hu-HU', 'id-ID', 'it-IT', 'ku-IQ', 'nb-NO', 'nl-NL', 'nn-NO', 'pl-PL', 'pl-Pl', 'pt-BR', 'pt-PT', 'ru-RU', 'sl-SI', 'sr-RS', 'sr-RS@latin', 'sv-SE', 'tr-TR', 'uk-UA', 'vi-VN']`.
**Note:** `decimal_digits` is given as a range like '1,3', a specific value like '3' or min like '1,'. **isDivisibleBy(str, number)** | check if the string is a number that is divisible by another. **isEAN(str)** | check if the string is an [EAN (European Article Number)][European Article Number]. -**isEmail(str [, options])** | check if the string is an email.

`options` is an object which defaults to `{ allow_display_name: false, require_display_name: false, allow_utf8_local_part: true, require_tld: true, allow_ip_domain: false, domain_specific_validation: false, blacklisted_chars: '', host_blacklist: [] }`. If `allow_display_name` is set to true, the validator will also match `Display Name `. If `require_display_name` is set to true, the validator will reject strings without the format `Display Name `. If `allow_utf8_local_part` is set to false, the validator will not allow any non-English UTF8 character in email address' local part. If `require_tld` is set to false, email addresses without a TLD in their domain will also be matched. If `ignore_max_length` is set to true, the validator will not check for the standard max length of an email. If `allow_ip_domain` is set to true, the validator will allow IP addresses in the host part. If `domain_specific_validation` is true, some additional validation will be enabled, e.g. disallowing certain syntactically valid email addresses that are rejected by Gmail. If `blacklisted_chars` receives a string, then the validator will reject emails that include any of the characters in the string, in the name part. If `host_blacklist` is set to an array of strings and the part of the email after the `@` symbol matches one of the strings defined in it, the validation fails. If `host_whitelist` is set to an array of strings and the part of the email after the `@` symbol matches none of the strings defined in it, the validation fails. +**isEmail(str [, options])** | check if the string is an email.

`options` is an object which defaults to `{ allow_display_name: false, require_display_name: false, allow_utf8_local_part: true, require_tld: true, allow_ip_domain: false, allow_underscores: false, domain_specific_validation: false, blacklisted_chars: '', host_blacklist: [] }`. If `allow_display_name` is set to true, the validator will also match `Display Name `. If `require_display_name` is set to true, the validator will reject strings without the format `Display Name `. If `allow_utf8_local_part` is set to false, the validator will not allow any non-English UTF8 character in email address' local part. If `require_tld` is set to false, email addresses without a TLD in their domain will also be matched. If `ignore_max_length` is set to true, the validator will not check for the standard max length of an email. If `allow_ip_domain` is set to true, the validator will allow IP addresses in the host part. If `domain_specific_validation` is true, some additional validation will be enabled, e.g. disallowing certain syntactically valid email addresses that are rejected by Gmail. If `blacklisted_chars` receives a string, then the validator will reject emails that include any of the characters in the string, in the name part. If `host_blacklist` is set to an array of strings and the part of the email after the `@` symbol matches one of the strings defined in it, the validation fails. If `host_whitelist` is set to an array of strings and the part of the email after the `@` symbol matches none of the strings defined in it, the validation fails. **isEmpty(str [, options])** | check if the string has a length of zero.

`options` is an object which defaults to `{ ignore_whitespace: false }`. **isEthereumAddress(str)** | check if the string is an [Ethereum][Ethereum] address. Does not validate address checksums. **isFloat(str [, options])** | check if the string is a float.

`options` is an object which can contain the keys `min`, `max`, `gt`, and/or `lt` to validate the float is within boundaries (e.g. `{ min: 7.22, max: 9.55 }`) it also has `locale` as an option.

`min` and `max` are equivalent to 'greater or equal' and 'less or equal', respectively while `gt` and `lt` are their strict counterparts.

`locale` determines 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-CA', 'fr-FR', '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']`. Locale list is `validator.isFloatLocales`. **isFQDN(str [, options])** | check if the string is a fully qualified domain name (e.g. domain.com).

`options` is an object which defaults to `{ require_tld: true, allow_underscores: false, allow_trailing_dot: false, allow_numeric_tld: false, allow_wildcard: false, ignore_max_length: false }`. If `allow_wildcard` is set to true, the validator will allow domain starting with `*.` (e.g. `*.example.com` or `*.shop.example.com`). +**isFreightContainerID(str)** | alias for `isISO6346`, check if the string is a valid [ISO 6346](https://en.wikipedia.org/wiki/ISO_6346) shipping container identification. **isFullWidth(str)** | check if the string contains any full-width chars. **isHalfWidth(str)** | check if the string contains any half-width chars. **isHash(str, algorithm)** | check if the string is a hash of type algorithm.

Algorithm is one of `['crc32', 'crc32b', 'md4', 'md5', 'ripemd128', 'ripemd160', 'sha1', 'sha256', 'sha384', 'sha512', 'tiger128', 'tiger160', 'tiger192']`. **isHexadecimal(str)** | check if the string is a hexadecimal number. **isHexColor(str)** | check if the string is a hexadecimal color. **isHSL(str)** | check if the string is an HSL (hue, saturation, lightness, optional alpha) color based on [CSS Colors Level 4 specification][CSS Colors Level 4 Specification].

Comma-separated format supported. Space-separated format supported with the exception of a few edge cases (ex: `hsl(200grad+.1%62%/1)`). -**isIBAN(str)** | check if the string is an IBAN (International Bank Account Number). +**isIBAN(str, [, options])** | check if the string is an IBAN (International Bank Account Number).

`options` is an object which accepts two attributes: `whitelist`: where you can restrict IBAN codes you want to receive data from and `blacklist`: where you can remove some of the countries from the current list. For both you can use an array with the following values `['AD','AE','AL','AT','AZ','BA','BE','BG','BH','BR','BY','CH','CR','CY','CZ','DE','DK','DO','EE','EG','ES','FI','FO','FR','GB','GE','GI','GL','GR','GT','HR','HU','IE','IL','IQ','IR','IS','IT','JO','KW','KZ','LB','LC','LI','LT','LU','LV','MC','MD','ME','MK','MR','MT','MU','MZ','NL','NO','PK','PL','PS','PT','QA','RO','RS','SA','SC','SE','SI','SK','SM','SV','TL','TN','TR','UA','VA','VG','XK']`. **isIdentityCard(str [, locale])** | check if the string is a valid identity card code.

`locale` is one of `['LK', 'PL', 'ES', 'FI', 'IN', 'IT', 'IR', 'MZ', 'NO', 'TH', 'zh-TW', 'he-IL', 'ar-LY', 'ar-TN', 'zh-CN', 'zh-HK']` OR `'any'`. If 'any' is used, function will check if any of the locales match.

Defaults to 'any'. **isIMEI(str [, options]))** | check if the string is a valid [IMEI number][IMEI]. IMEI should be of format `###############` or `##-######-######-#`.

`options` is an object which can contain the keys `allow_hyphens`. Defaults to first format. If `allow_hyphens` is set to true, the validator will validate the second format. **isIn(str, values)** | check if the string is in an array of allowed values. @@ -129,6 +130,7 @@ Validator | Description **isIPRange(str [, version])** | check if the string is an IP Range (version 4 or 6). **isISBN(str [, options])** | check if the string is an [ISBN][ISBN].

`options` is an object that has no default.
**Options:**
`version`: ISBN version to compare to. Accepted values are '10' and '13'. If none provided, both will be tested. **isISIN(str)** | check if the string is an [ISIN][ISIN] (stock/security identifier). +**isISO6346(str)** | check if the string is a valid [ISO 6346](https://en.wikipedia.org/wiki/ISO_6346) shipping container identification. **isISO6391(str)** | check if the string is a valid [ISO 639-1][ISO 639-1] language code. **isISO8601(str [, options])** | check if the string is a valid [ISO 8601][ISO 8601] date.
`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. **isISO31661Alpha2(str)** | check if the string is a valid [ISO 3166-1 alpha-2][ISO 3166-1 alpha-2] officially assigned country code. @@ -143,17 +145,18 @@ Validator | Description **isLicensePlate(str, locale)** | check if the string matches the format of a country's license plate.

`locale` is one of `['cs-CZ', 'de-DE', 'de-LI', 'en-IN', '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. -**isLuhnValid(str)** | check if the string passes the [Luhn check][Luhn Check]. +**isLuhnNumber(str)** | check if the string passes the [Luhn algorithm check](https://en.wikipedia.org/wiki/Luhn_algorithm). **isMACAddress(str [, options])** | check if the string is a MAC address.

`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'. The options also allow a `eui` property to specify if it needs to be validated against EUI-48 or EUI-64. The accepted values of `eui` are: 48, 64. **isMagnetURI(str)** | check if the string is a [Magnet URI format][Magnet URI Format]. +**isMailtoURI(str, [, options])** | check if the string is a [Magnet URI format][Mailto URI Format].

`options` is an object of validating emails inside the URI (check `isEmail`s options for details). **isMD5(str)** | check if the string is a MD5 hash.

Please note that you can also use the `isHash(str, 'md5')` function. Keep in mind that MD5 has some collision weaknesses compared to other algorithms (e.g., SHA). **isMimeType(str)** | check if the string matches to a valid [MIME type][MIME Type] format. -**isMobilePhone(str [, locale [, options]])** | check if the string is a mobile phone number,

`locale` is either an array of locales (e.g. `['sk-SK', 'sr-RS']`) OR one of `['am-Am', 'ar-AE', 'ar-BH', 'ar-DZ', 'ar-EG', 'ar-EH', 'ar-IQ', 'ar-JO', 'ar-KW', 'ar-PS', 'ar-SA', 'ar-SY', 'ar-TN', 'ar-YE', 'az-AZ', 'az-LB', 'az-LY', 'be-BY', 'bg-BG', 'bn-BD', 'bs-BA', 'ca-AD', 'cs-CZ', 'da-DK', 'de-AT', 'de-CH', 'de-DE', 'de-LU', 'dv-MV', 'dz-BT', 'el-CY', 'el-GR', 'en-AG', 'en-AI', 'en-AU', 'en-BM', 'en-BS', 'en-BW', 'en-CA', 'en-GB', 'en-GG', 'en-GH', 'en-GY', 'en-HK', 'en-IE', 'en-IN', 'en-JM', 'en-KE', 'en-KI', 'en-KN', 'en-LS', 'en-MO', 'en-MT', 'en-MU', 'en-NG', 'en-NZ', 'en-PG', 'en-PH', 'en-PK', 'en-RW', 'en-SG', 'en-SL', 'en-SS', 'en-TZ', 'en-UG', 'en-US', 'en-ZA', 'en-ZM', 'en-ZW', 'es-AR', 'es-BO', 'es-CL', 'es-CO', 'es-CR', 'es-CU', 'es-DO', 'es-EC', 'es-ES', 'es-HN', 'es-MX', 'es-NI', 'es-PA', 'es-PE', 'es-PY', 'es-SV', 'es-UY', 'es-VE', 'et-EE', 'fa-AF', 'fa-IR', 'fi-FI', 'fj-FJ', 'fo-FO', 'fr-BE', 'fr-BF', 'fr-BJ', 'fr-CD', 'fr-FR', 'fr-GF', 'fr-GP', 'fr-MQ', 'fr-PF', 'fr-RE', 'ga-IE', 'he-IL', 'hu-HU', 'id-ID', 'ir-IR', 'it-IT', 'it-SM', 'ja-JP', 'ka-GE', 'kk-KZ', 'kl-GL', 'ko-KR', 'ky-KG', 'lt-LT', 'mg-MG', 'mn-MN', 'ms-MY', 'my-MM', 'mz-MZ', 'nb-NO', 'ne-NP', 'nl-AW', 'nl-BE', 'nl-NL', 'nn-NO', 'pl-PL', 'pt-AO', 'pt-BR', 'pt-PT', 'ro-Md', 'ro-RO', 'ru-RU', 'si-LK', 'sk-SK', 'sl-SI', 'sq-AL', 'sr-RS', 'sv-SE', 'tg-TJ', 'th-TH', 'tk-TM', 'tr-TR', 'uk-UA', 'uz-UZ', 'vi-VN', 'zh-CN', 'zh-HK', 'zh-MO', 'zh-TW']` OR defaults to `'any'`. If 'any' or a falsey value is used, function will check if any of the locales match).

`options` is an optional object that can be supplied with the following keys: `strictMode`, if this is set to `true`, the mobile phone number must be supplied with the country code and therefore must start with `+`. Locale list is `validator.isMobilePhoneLocales`. +**isMobilePhone(str [, locale [, options]])** | check if the string is a mobile phone number,

`locale` is either an array of locales (e.g. `['sk-SK', 'sr-RS']`) OR one of `['am-Am', 'ar-AE', 'ar-BH', 'ar-DZ', 'ar-EG', 'ar-EH', 'ar-IQ', 'ar-JO', 'ar-KW', 'ar-PS', 'ar-SA', 'ar-SD', 'ar-SY', 'ar-TN', 'ar-YE', 'az-AZ', 'az-LB', 'az-LY', 'be-BY', 'bg-BG', 'bn-BD', 'bs-BA', 'ca-AD', 'cs-CZ', 'da-DK', 'de-AT', 'de-CH', 'de-DE', 'de-LU', 'dv-MV', 'dz-BT', 'el-CY', 'el-GR', 'en-AG', 'en-AI', 'en-AU', 'en-BM', 'en-BS', 'en-BW', 'en-CA', 'en-GB', 'en-GG', 'en-GH', 'en-GY', 'en-HK', 'en-IE', 'en-IN', 'en-JM', 'en-KE', 'en-KI', 'en-KN', 'en-LS', 'en-MO', 'en-MT', 'en-MU', 'en-MW', 'en-NG', 'en-NZ', 'en-PG', 'en-PH', 'en-PK', 'en-RW', 'en-SG', 'en-SL', 'en-SS', 'en-TZ', 'en-UG', 'en-US', 'en-ZA', 'en-ZM', 'en-ZW', 'es-AR', 'es-BO', 'es-CL', 'es-CO', 'es-CR', 'es-CU', 'es-DO', 'es-EC', 'es-ES', 'es-HN', 'es-MX', 'es-NI', 'es-PA', 'es-PE', 'es-PY', 'es-SV', 'es-UY', 'es-VE', 'et-EE', 'fa-AF', 'fa-IR', 'fi-FI', 'fj-FJ', 'fo-FO', 'fr-BE', 'fr-BF', 'fr-BJ', 'fr-CD', 'fr-CF', 'fr-FR', 'fr-GF', 'fr-GP', 'fr-MQ', 'fr-PF', 'fr-RE', 'fr-WF', 'ga-IE', 'he-IL', 'hu-HU', 'id-ID', 'ir-IR', 'it-IT', 'it-SM', 'ja-JP', 'ka-GE', 'kk-KZ', 'kl-GL', 'ko-KR', 'ky-KG', 'lt-LT', 'mg-MG', 'mn-MN', 'ms-MY', 'my-MM', 'mz-MZ', 'nb-NO', 'ne-NP', 'nl-AW', 'nl-BE', 'nl-NL', 'nn-NO', 'pl-PL', 'pt-AO', 'pt-BR', 'pt-PT', 'ro-Md', 'ro-RO', 'ru-RU', 'si-LK', 'sk-SK', 'sl-SI', 'so-SO', 'sq-AL', 'sr-RS', 'sv-SE', 'tg-TJ', 'th-TH', 'tk-TM', 'tr-TR', 'uk-UA', 'uz-UZ', 'vi-VN', 'zh-CN', 'zh-HK', 'zh-MO', 'zh-TW']` OR defaults to `'any'`. If 'any' or a falsey value is used, function will check if any of the locales match).

`options` is an optional object that can be supplied with the following keys: `strictMode`, if this is set to `true`, the mobile phone number must be supplied with the country code and therefore must start with `+`. Locale list is `validator.isMobilePhoneLocales`. **isMongoId(str)** | check if the string is a valid hex-encoded representation of a [MongoDB ObjectId][mongoid]. **isMultibyte(str)** | check if the string contains one or more multibyte chars. **isNumeric(str [, options])** | check if the string contains only numbers.

`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 `.`).

`locale` determines 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.

`countryCode` is one of `['AM', 'AR', 'AT', 'AU', 'AZ', 'BE', 'BG', 'BY', 'BR', 'CA', 'CH', 'CN', 'CY', 'CZ', 'DE', 'DK', 'DZ', 'EE', 'ES', 'FI', 'FR', 'GB', 'GR', 'HR', 'HU', 'IE', 'IN', 'IR', 'ID', 'IS', 'IT', 'JM', 'JP', 'KR', 'KZ', 'LI', 'LT', 'LU', 'LV', 'LY', 'MT', 'MX', 'MY', 'MZ', 'NL', 'NZ', 'PH', 'PK', 'PL', 'PT', 'RO', 'RU', 'SE', 'SL', 'SK', 'TH', 'TR', 'UA', 'US']`. +**isPassportNumber(str, countryCode)** | check if the string is a valid passport number.

`countryCode` is one of `['AM', 'AR', 'AT', 'AU', 'AZ', 'BE', 'BG', 'BY', 'BR', 'CA', 'CH', 'CN', 'CY', 'CZ', 'DE', 'DK', 'DZ', 'EE', 'ES', 'FI', 'FR', 'GB', 'GR', 'HR', 'HU', 'IE', 'IN', 'IR', 'ID', 'IS', 'IT', 'JM', 'JP', 'KR', 'KZ', 'LI', 'LT', 'LU', 'LV', 'LY', 'MT', 'MX', 'MY', 'MZ', 'NL', 'NZ', 'PH', 'PK', 'PL', 'PT', 'RO', 'RU', 'SE', 'SL', 'SK', 'TH', 'TR', 'UA', 'US', 'ZA']`. **isPort(str)** | check if the string is a valid port number. **isPostalCode(str, locale)** | check if the string is a postal code.

`locale` is one of `['AD', 'AT', 'AU', 'AZ', 'BA', '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', 'KR', 'LI', 'LK', 'LT', 'LU', 'LV', 'MG', 'MT', 'MX', 'MY', 'NL', 'NO', 'NP', 'NZ', 'PL', 'PR', 'PT', 'RO', 'RU', 'SA', 'SE', 'SG', 'SI', 'SK', 'TH', 'TN', 'TW', 'UA', 'US', 'ZA', 'ZM']` OR `'any'`. If 'any' is used, function will check if any of the locales match. Locale list is `validator.isPostalCodeLocales`. **isRFC3339(str)** | check if the string is a valid [RFC 3339][RFC 3339] date. @@ -164,7 +167,7 @@ Validator | Description **isSlug(str)** | check if the string is of type slug. **isStrongPassword(str [, options])** | check if the string can be considered a strong password 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.
Default options:
`{ 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 }` **isTime(str [, options])** | check if the string is a valid time e.g. [`23:01:59`, new Date().toLocaleTimeString()].

`options` is an object which can contain the keys `hourFormat` or `mode`.

`hourFormat` is a key and defaults to `'hour24'`.

`mode` is a key and defaults to `'default'`.

`hourFomat` can contain the values `'hour12'` or `'hour24'`, `'hour24'` will validate hours in 24 format and `'hour12'` will validate hours in 12 format.

`mode` can contain the values `'default'` or `'withSeconds'`, `'default'` will validate `HH:MM` format, `'withSeconds'` will validate the `HH:MM:SS` format. -**isTaxID(str, locale)** | check if the string is a valid Tax Identification Number. Default locale is `en-US`.

More info about exact TIN support can be found in `src/lib/isTaxID.js`.

Supported locales: `[ 'bg-BG', 'cs-CZ', 'de-AT', 'de-DE', 'dk-DK', 'el-CY', 'el-GR', 'en-CA', 'en-GB', 'en-IE', 'en-US', 'es-ES', 'et-EE', 'fi-FI', 'fr-BE', 'fr-CA', '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' ]`. +**isTaxID(str, locale)** | check if the string is a valid Tax Identification Number. Default locale is `en-US`.

More info about exact TIN support can be found in `src/lib/isTaxID.js`.

Supported locales: `[ 'bg-BG', 'cs-CZ', 'de-AT', 'de-DE', 'dk-DK', 'el-CY', 'el-GR', 'en-CA', 'en-GB', 'en-IE', 'en-US', 'es-AR', 'es-ES', 'et-EE', 'fi-FI', 'fr-BE', 'fr-CA', '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 a URL.

`options` is an object which defaults to `{ protocols: ['http','https','ftp'], require_tld: true, require_protocol: false, require_host: true, require_port: false, require_valid_protocol: true, allow_underscores: false, host_whitelist: false, host_blacklist: false, allow_trailing_dot: false, allow_protocol_relative_urls: false, allow_fragments: true, allow_query_components: true, disallow_auth: false, validate_length: true }`.

`require_protocol` - if set to true isURL will return false if protocol is not present in the URL.
`require_valid_protocol` - isURL will check if the URL's protocol is present in the protocols option.
`protocols` - valid protocols can be modified with this option.
`require_host` - if set to false isURL will not check if host is present in the URL.
`require_port` - if set to true isURL will check if port is present in the URL.
`allow_protocol_relative_urls` - if set to true protocol relative URLs will be allowed.
`allow_fragments` - if set to false isURL will return false if fragments are present.
`allow_query_components` - if set to false isURL will return false if query components are present.
`validate_length` - if set to 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 1, 2, 3, 4 or 5). **isVariableWidth(str)** | check if the string contains a mixture of full and half-width chars. @@ -299,7 +302,8 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. [ISSN]: https://en.wikipedia.org/wiki/International_Standard_Serial_Number [Luhn Check]: https://en.wikipedia.org/wiki/Luhn_algorithm [Magnet URI Format]: https://en.wikipedia.org/wiki/Magnet_URI_scheme +[Mailto URI Format]: https://en.wikipedia.org/wiki/Mailto [MIME Type]: https://en.wikipedia.org/wiki/Media_type [mongoid]: http://docs.mongodb.org/manual/reference/object-id/ [RFC 3339]: https://tools.ietf.org/html/rfc3339 -[VAT Number]: https://en.wikipedia.org/wiki/VAT_identification_number \ No newline at end of file +[VAT Number]: https://en.wikipedia.org/wiki/VAT_identification_number diff --git a/package.json b/package.json index 13768746f..153e5c1a4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "validator", "description": "String validation and sanitization", - "version": "13.7.0", + "version": "13.11.0", "sideEffects": false, "homepage": "https://github.com/validatorjs/validator.js", "files": [ @@ -51,6 +51,7 @@ "rimraf": "^3.0.0", "rollup": "^0.47.0", "rollup-plugin-babel": "^4.0.1", + "timezone-mock": "^1.3.6", "uglify-js": "^3.0.19" }, "scripts": { diff --git a/src/index.js b/src/index.js index f45eead05..bef4cfff4 100644 --- a/src/index.js +++ b/src/index.js @@ -70,7 +70,7 @@ import isBefore from './lib/isBefore'; import isIn from './lib/isIn'; -import isLuhnValid from './lib/isLuhnValid'; +import isLuhnNumber from './lib/isLuhnNumber'; import isCreditCard from './lib/isCreditCard'; import isIdentityCard from './lib/isIdentityCard'; @@ -88,6 +88,7 @@ import isCurrency from './lib/isCurrency'; import isBtcAddress from './lib/isBtcAddress'; +import { isISO6346, isFreightContainerID } from './lib/isISO6346'; import isISO6391 from './lib/isISO6391'; import isISO8601 from './lib/isISO8601'; import isRFC3339 from './lib/isRFC3339'; @@ -100,6 +101,7 @@ import isBase58 from './lib/isBase58'; import isBase64 from './lib/isBase64'; import isDataURI from './lib/isDataURI'; import isMagnetURI from './lib/isMagnetURI'; +import isMailtoURI from './lib/isMailtoURI'; import isMimeType from './lib/isMimeType'; @@ -124,7 +126,7 @@ import isStrongPassword from './lib/isStrongPassword'; import isVAT from './lib/isVAT'; -const version = '13.7.0'; +const version = '13.11.0'; const validator = { version, @@ -185,7 +187,7 @@ const validator = { isAfter, isBefore, isIn, - isLuhnValid, + isLuhnNumber, isCreditCard, isIdentityCard, isEAN, @@ -199,6 +201,8 @@ const validator = { isEthereumAddress, isCurrency, isBtcAddress, + isISO6346, + isFreightContainerID, isISO6391, isISO8601, isRFC3339, @@ -210,6 +214,7 @@ const validator = { isBase64, isDataURI, isMagnetURI, + isMailtoURI, isMimeType, isLatLong, ltrim, diff --git a/src/lib/alpha.js b/src/lib/alpha.js index 0535e50d1..d540ed1cf 100644 --- a/src/lib/alpha.js +++ b/src/lib/alpha.js @@ -19,6 +19,7 @@ export const alpha = { 'pl-PL': /^[A-ZĄĆĘŚŁŃÓŻŹ]+$/i, 'pt-PT': /^[A-ZÃÁÀÂÄÇÉÊËÍÏÕÓÔÖÚÜ]+$/i, 'ru-RU': /^[А-ЯЁ]+$/i, + 'kk-KZ': /^[А-ЯЁ\u04D8\u04B0\u0406\u04A2\u0492\u04AE\u049A\u04E8\u04BA]+$/i, 'sl-SI': /^[A-ZČĆĐŠŽ]+$/i, 'sk-SK': /^[A-ZÁČĎÉÍŇÓŠŤÚÝŽĹŔĽÄÔ]+$/i, 'sr-RS@latin': /^[A-ZČĆŽŠĐ]+$/i, @@ -58,6 +59,7 @@ export const alphanumeric = { 'pl-PL': /^[0-9A-ZĄĆĘŚŁŃÓŻŹ]+$/i, 'pt-PT': /^[0-9A-ZÃÁÀÂÄÇÉÊËÍÏÕÓÔÖÚÜ]+$/i, 'ru-RU': /^[0-9А-ЯЁ]+$/i, + 'kk-KZ': /^[0-9А-ЯЁ\u04D8\u04B0\u0406\u04A2\u0492\u04AE\u049A\u04E8\u04BA]+$/i, 'sl-SI': /^[0-9A-ZČĆĐŠŽ]+$/i, 'sk-SK': /^[0-9A-ZÁČĎÉÍŇÓŠŤÚÝŽĹŔĽÄÔ]+$/i, 'sr-RS@latin': /^[0-9A-ZČĆŽŠĐ]+$/i, @@ -125,7 +127,7 @@ export const dotDecimal = ['ar-EG', 'ar-LB', 'ar-LY']; export const commaDecimal = [ 'bg-BG', 'cs-CZ', 'da-DK', 'de-DE', 'el-GR', 'en-ZM', 'es-ES', 'fr-CA', 'fr-FR', 'id-ID', 'it-IT', 'ku-IQ', 'hi-IN', 'hu-HU', 'nb-NO', 'nn-NO', 'nl-NL', 'pl-PL', 'pt-PT', - 'ru-RU', 'si-LK', 'sl-SI', 'sr-RS@latin', 'sr-RS', 'sv-SE', 'tr-TR', 'uk-UA', 'vi-VN', + 'ru-RU', 'kk-KZ', 'si-LK', 'sl-SI', 'sr-RS@latin', 'sr-RS', 'sv-SE', 'tr-TR', 'uk-UA', 'vi-VN', ]; for (let i = 0; i < dotDecimal.length; i++) { diff --git a/src/lib/isCreditCard.js b/src/lib/isCreditCard.js index 0a874c9cf..938679d39 100644 --- a/src/lib/isCreditCard.js +++ b/src/lib/isCreditCard.js @@ -1,5 +1,5 @@ import assertString from './util/assertString'; -import isLuhnValid from './isLuhnValid'; +import isLuhnValid from './isLuhnNumber'; const cards = { amex: /^3[47][0-9]{13}$/, @@ -10,9 +10,17 @@ const cards = { unionpay: /^(6[27][0-9]{14}|^(81[0-9]{14,17}))$/, visa: /^(?:4[0-9]{12})(?:[0-9]{3,6})?$/, }; -/* eslint-disable max-len */ -const allCards = /^(?:4[0-9]{12}(?:[0-9]{3,6})?|5[1-5][0-9]{14}|(222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}|6(?:011|5[0-9][0-9])[0-9]{12,15}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\d{3})\d{11}|6[27][0-9]{14}|^(81[0-9]{14,17}))$/; -/* eslint-enable max-len */ + +const allCards = (() => { + const tmpCardsArray = []; + for (const cardProvider in cards) { + // istanbul ignore else + if (cards.hasOwnProperty(cardProvider)) { + tmpCardsArray.push(cards[cardProvider]); + } + } + return tmpCardsArray; +})(); export default function isCreditCard(card, options = {}) { assertString(card); @@ -26,7 +34,7 @@ export default function isCreditCard(card, options = {}) { } else if (provider && !(provider.toLowerCase() in cards)) { /* specific provider not in the list */ throw new Error(`${provider} is not a valid credit card provider.`); - } else if (!(allCards.test(sanitized))) { + } else if (!allCards.some(cardProvider => cardProvider.test(sanitized))) { // no specific provider return false; } diff --git a/src/lib/isDate.js b/src/lib/isDate.js index 8f2d51f3d..4fde3686b 100644 --- a/src/lib/isDate.js +++ b/src/lib/isDate.js @@ -48,7 +48,37 @@ export default function isDate(input, options) { dateObj[formatWord.charAt(0)] = dateWord; } - return new Date(`${dateObj.m}/${dateObj.d}/${dateObj.y}`).getDate() === +dateObj.d; + let fullYear = dateObj.y; + + if (dateObj.y.length === 2) { + const parsedYear = parseInt(dateObj.y, 10); + + if (isNaN(parsedYear)) { + return false; + } + + const currentYearLastTwoDigits = new Date().getFullYear() % 100; + + if (parsedYear < currentYearLastTwoDigits) { + fullYear = `20${dateObj.y}`; + } else { + fullYear = `19${dateObj.y}`; + } + } + + let month = dateObj.m; + + if (dateObj.m.length === 1) { + month = `0${dateObj.m}`; + } + + let day = dateObj.d; + + if (dateObj.d.length === 1) { + day = `0${dateObj.d}`; + } + + return new Date(`${fullYear}-${month}-${day}T00:00:00.000Z`).getUTCDate() === +dateObj.d; } if (!options.strictMode) { diff --git a/src/lib/isEmail.js b/src/lib/isEmail.js index d1c35bd46..12938765e 100644 --- a/src/lib/isEmail.js +++ b/src/lib/isEmail.js @@ -1,12 +1,13 @@ import assertString from './util/assertString'; -import merge from './util/merge'; import isByteLength from './isByteLength'; import isFQDN from './isFQDN'; import isIP from './isIP'; +import merge from './util/merge'; const default_email_options = { allow_display_name: false, + allow_underscores: false, require_display_name: false, allow_utf8_local_part: true, require_tld: true, @@ -22,7 +23,7 @@ const splitNameAddress = /^([^\x00-\x1F\x7F-\x9F\cX]+) !(countryCode in ibanRegexThroughCountryCode)); + + if (countryCodeArrayFilteredWithObjectIbanCode.length > 0) { + return false; + } + + return true; +} + /** * Check whether string has correct universal IBAN format * The IBAN consists of up to 34 alphanumeric characters, as follows: @@ -95,14 +115,37 @@ const ibanRegexThroughCountryCode = { * NOTE: Permitted IBAN characters are: digits [0-9] and the 26 latin alphabetic [A-Z] * * @param {string} str - string under validation + * @param {object} options - object to pass the countries to be either whitelisted or blacklisted * @return {boolean} */ -function hasValidIbanFormat(str) { +function hasValidIbanFormat(str, options) { // Strip white spaces and hyphens const strippedStr = str.replace(/[\s\-]+/gi, '').toUpperCase(); const isoCountryCode = strippedStr.slice(0, 2).toUpperCase(); - return (isoCountryCode in ibanRegexThroughCountryCode) && + const isoCountryCodeInIbanRegexCodeObject = isoCountryCode in ibanRegexThroughCountryCode; + + if (options.whitelist) { + if (!hasOnlyValidCountryCodes(options.whitelist)) { + return false; + } + + const isoCountryCodeInWhiteList = options.whitelist.includes(isoCountryCode); + + if (!isoCountryCodeInWhiteList) { + return false; + } + } + + if (options.blacklist) { + const isoCountryCodeInBlackList = options.blacklist.includes(isoCountryCode); + + if (isoCountryCodeInBlackList) { + return false; + } + } + + return (isoCountryCodeInIbanRegexCodeObject) && ibanRegexThroughCountryCode[isoCountryCode].test(strippedStr); } @@ -130,10 +173,10 @@ function hasValidIbanChecksum(str) { return remainder === 1; } -export default function isIBAN(str) { +export default function isIBAN(str, options = {}) { assertString(str); - return hasValidIbanFormat(str) && hasValidIbanChecksum(str); + return hasValidIbanFormat(str, options) && hasValidIbanChecksum(str); } export const locales = Object.keys(ibanRegexThroughCountryCode); diff --git a/src/lib/isISO6346.js b/src/lib/isISO6346.js new file mode 100644 index 000000000..0cb657e7c --- /dev/null +++ b/src/lib/isISO6346.js @@ -0,0 +1,37 @@ +import assertString from './util/assertString'; + +// https://en.wikipedia.org/wiki/ISO_6346 +// according to ISO6346 standard, checksum digit is mandatory for freight container but recommended +// for other container types (J and Z) +const isISO6346Str = /^[A-Z]{3}(U[0-9]{7})|([J,Z][0-9]{6,7})$/; +const isDigit = /^[0-9]$/; + +export function isISO6346(str) { + assertString(str); + + str = str.toUpperCase(); + + if (!isISO6346Str.test(str)) return false; + + if (str.length === 11) { + let sum = 0; + for (let i = 0; i < str.length - 1; i++) { + if (!isDigit.test(str[i])) { + let convertedCode; + const letterCode = str.charCodeAt(i) - 55; + if (letterCode < 11) convertedCode = letterCode; + else if (letterCode >= 11 && letterCode <= 20) convertedCode = 12 + (letterCode % 11); + else if (letterCode >= 21 && letterCode <= 30) convertedCode = 23 + (letterCode % 21); + else convertedCode = 34 + (letterCode % 31); + sum += convertedCode * (2 ** i); + } else sum += str[i] * (2 ** i); + } + + const checkSumDigit = sum % 11; + return Number(str[str.length - 1]) === checkSumDigit; + } + + return true; +} + +export const isFreightContainerID = isISO6346; diff --git a/src/lib/isJWT.js b/src/lib/isJWT.js index 1a8896f98..1d0ade5ee 100644 --- a/src/lib/isJWT.js +++ b/src/lib/isJWT.js @@ -7,7 +7,7 @@ export default function isJWT(str) { const dotSplit = str.split('.'); const len = dotSplit.length; - if (len > 3 || len < 2) { + if (len !== 3) { return false; } diff --git a/src/lib/isLocale.js b/src/lib/isLocale.js index cacac8aec..ec84c8fce 100644 --- a/src/lib/isLocale.js +++ b/src/lib/isLocale.js @@ -1,11 +1,111 @@ import assertString from './util/assertString'; -const localeReg = /^[A-Za-z]{2,4}([_-]([A-Za-z]{4}|[\d]{3}))?([_-]([A-Za-z]{2}|[\d]{3}))?$/; +/* + = 3ALPHA ; selected ISO 639 codes + *2("-" 3ALPHA) ; permanently reserved + */ +const extlang = '([A-Za-z]{3}(-[A-Za-z]{3}){0,2})'; + +/* + = 2*3ALPHA ; shortest ISO 639 code + ["-" extlang] ; sometimes followed by + ; extended language subtags + / 4ALPHA ; or reserved for future use + / 5*8ALPHA ; or registered language subtag + */ +const language = `(([a-zA-Z]{2,3}(-${extlang})?)|([a-zA-Z]{5,8}))`; + +/* + = 4ALPHA ; ISO 15924 code + */ +const script = '([A-Za-z]{4})'; + +/* + = 2ALPHA ; ISO 3166-1 code + / 3DIGIT ; UN M.49 code + */ +const region = '([A-Za-z]{2}|\\d{3})'; + +/* + = 5*8alphanum ; registered variants + / (DIGIT 3alphanum) + */ +const variant = '([A-Za-z0-9]{5,8}|(\\d[A-Z-a-z0-9]{3}))'; + +/* + = DIGIT ; 0 - 9 + / %x41-57 ; A - W + / %x59-5A ; Y - Z + / %x61-77 ; a - w + / %x79-7A ; y - z + */ +const singleton = '(\\d|[A-W]|[Y-Z]|[a-w]|[y-z])'; + +/* + = singleton 1*("-" (2*8alphanum)) + ; Single alphanumerics + ; "x" reserved for private use + */ +const extension = `(${singleton}(-[A-Za-z0-9]{2,8})+)`; + +/* + = "x" 1*("-" (1*8alphanum)) + */ +const privateuse = '(x(-[A-Za-z0-9]{1,8})+)'; + +// irregular tags do not match the 'langtag' production and would not +// otherwise be considered 'well-formed'. These tags are all valid, but +// most are deprecated in favor of more modern subtags or subtag combination + +const irregular = '((en-GB-oed)|(i-ami)|(i-bnn)|(i-default)|(i-enochian)|' + + '(i-hak)|(i-klingon)|(i-lux)|(i-mingo)|(i-navajo)|(i-pwn)|(i-tao)|' + + '(i-tay)|(i-tsu)|(sgn-BE-FR)|(sgn-BE-NL)|(sgn-CH-DE))'; + +// regular tags match the 'langtag' production, but their subtags are not +// extended language or variant subtags: their meaning is defined by +// their registration and all of these are deprecated in favor of a more +// modern subtag or sequence of subtags + +const regular = '((art-lojban)|(cel-gaulish)|(no-bok)|(no-nyn)|(zh-guoyu)|' + + '(zh-hakka)|(zh-min)|(zh-min-nan)|(zh-xiang))'; + +/* + = irregular ; non-redundant tags registered + / regular ; during the RFC 3066 era + + */ +const grandfathered = `(${irregular}|${regular})`; + +/* + RFC 5646 defines delimitation of subtags via a hyphen: + + "Subtag" refers to a specific section of a tag, delimited by a + hyphen, such as the subtags 'zh', 'Hant', and 'CN' in the tag "zh- + Hant-CN". Examples of subtags in this document are enclosed in + single quotes ('Hant') + + However, we need to add "_" to maintain the existing behaviour. + */ +const delimiter = '(-|_)'; + +/* + = language + ["-" script] + ["-" region] + *("-" variant) + *("-" extension) + ["-" privateuse] + */ +const langtag = `${language}(${delimiter}${script})?(${delimiter}${region})?(${delimiter}${variant})*(${delimiter}${extension})*(${delimiter}${privateuse})?`; + +/* + Regex implementation based on BCP RFC 5646 + Tags for Identifying Languages + https://www.rfc-editor.org/rfc/rfc5646.html + */ +const languageTagRegex = new RegExp(`(^${privateuse}$)|(^${grandfathered}$)|(^${langtag}$)`); export default function isLocale(str) { assertString(str); - if (str === 'en_US_POSIX' || str === 'ca_ES_VALENCIA') { - return true; - } - return localeReg.test(str); + return languageTagRegex.test(str); } diff --git a/src/lib/isLuhnValid.js b/src/lib/isLuhnNumber.js similarity index 93% rename from src/lib/isLuhnValid.js rename to src/lib/isLuhnNumber.js index da205271f..95a066115 100644 --- a/src/lib/isLuhnValid.js +++ b/src/lib/isLuhnNumber.js @@ -1,6 +1,6 @@ import assertString from './util/assertString'; -export default function isLuhnValid(str) { +export default function isLuhnNumber(str) { assertString(str); const sanitized = str.replace(/[- ]+/g, ''); let sum = 0; diff --git a/src/lib/isMailtoURI.js b/src/lib/isMailtoURI.js new file mode 100644 index 000000000..0dd95b6a9 --- /dev/null +++ b/src/lib/isMailtoURI.js @@ -0,0 +1,67 @@ +import trim from './trim'; +import isEmail from './isEmail'; +import assertString from './util/assertString'; + +function parseMailtoQueryString(queryString) { + const allowedParams = new Set(['subject', 'body', 'cc', 'bcc']), + query = { cc: '', bcc: '' }; + let isParseFailed = false; + + const queryParams = queryString.split('&'); + + if (queryParams.length > 4) { + return false; + } + + for (const q of queryParams) { + const [key, value] = q.split('='); + + // checked for invalid and duplicated query params + if (key && !allowedParams.has(key)) { + isParseFailed = true; + break; + } + + if (value && (key === 'cc' || key === 'bcc')) { + query[key] = value; + } + + if (key) { + allowedParams.delete(key); + } + } + + return isParseFailed ? false : query; +} + +export default function isMailtoURI(url, options) { + assertString(url); + + if (url.indexOf('mailto:') !== 0) { + return false; + } + + const [to = '', queryString = ''] = url.replace('mailto:', '').split('?'); + + if (!to && !queryString) { + return true; + } + + const query = parseMailtoQueryString(queryString); + + if (!query) { + return false; + } + + return `${to},${query.cc},${query.bcc}` + .split(',') + .every((email) => { + email = trim(email, ' '); + + if (email) { + return isEmail(email, options); + } + + return true; + }); +} diff --git a/src/lib/isMobilePhone.js b/src/lib/isMobilePhone.js index 5c37d42bb..7c9b420e9 100644 --- a/src/lib/isMobilePhone.js +++ b/src/lib/isMobilePhone.js @@ -16,6 +16,7 @@ const phones = { 'ar-OM': /^((\+|00)968)?(9[1-9])\d{6}$/, 'ar-PS': /^(\+?970|0)5[6|9](\d{7})$/, 'ar-SA': /^(!?(\+?966)|0)?5\d{8}$/, + 'ar-SD': /^((\+?249)|0)?(9[012369]|1[012])\d{7}$/, 'ar-SY': /^(!?(\+?963)|0)?9\d{8}$/, 'ar-TN': /^(\+?216)?[2459]\d{7}$/, 'az-AZ': /^(\+994|0)(10|5[015]|7[07]|99)\d{7}$/, @@ -48,12 +49,14 @@ const phones = { 'en-IN': /^(\+?91|0)?[6789]\d{9}$/, 'en-JM': /^(\+?876)?\d{7}$/, 'en-KE': /^(\+?254|0)(7|1)\d{8}$/, + 'fr-CF': /^(\+?236| ?)(70|75|77|72|21|22)\d{6}$/, 'en-SS': /^(\+?211|0)(9[1257])\d{7}$/, 'en-KI': /^((\+686|686)?)?( )?((6|7)(2|3|8)[0-9]{6})$/, 'en-KN': /^(?:\+1|1)869(?:46\d|48[89]|55[6-8]|66\d|76[02-7])\d{4}$/, 'en-LS': /^(\+?266)(22|28|57|58|59|27|52)\d{6}$/, 'en-MT': /^(\+?356|0)?(99|79|77|21|27|22|25)[0-9]{6}$/, 'en-MU': /^(\+?230|0)?\d{8}$/, + 'en-MW': /^(\+?265|0)(((77|88|31|99|98|21)\d{7})|(((111)|1)\d{6})|(32000\d{4}))$/, 'en-NA': /^(\+?264|0)(6|8)\d{7}$/, 'en-NG': /^(\+?234|0)?[789]\d{9}$/, 'en-NZ': /^(\+?64|0)[28]\d{7,9}$/, @@ -75,7 +78,7 @@ const phones = { '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-CU': /^(\+53|0053)?5\d{7}/, + 'es-CU': /^(\+53|0053)?5\d{7}$/, 'es-DO': /^(\+?1)?8[024]9\d{7}$/, 'es-HN': /^(\+?504)?[9|8|3|2]\d{7}$/, 'es-EC': /^(\+?593|0)([2-7]|9[2-9])\d{7}$/, @@ -103,6 +106,7 @@ const phones = { 'fr-MQ': /^(\+?596|0|00596)[67]\d{8}$/, 'fr-PF': /^(\+?689)?8[789]\d{6}$/, 'fr-RE': /^(\+?262|0|00262)[67]\d{8}$/, + 'fr-WF': /^(\+681)?\d{6}$/, 'he-IL': /^(\+972|0)([23489]|5[012345689]|77)[1-9]\d{6}$/, 'hu-HU': /^(\+?36|06)(20|30|31|50|70)\d{7}$/, 'id-ID': /^(\+?62|0)8(1[123456789]|2[1238]|3[1238]|5[12356789]|7[78]|9[56789]|8[123456789])([\s?|\d]{5,11})$/, @@ -128,7 +132,7 @@ const phones = { 'nl-NL': /^(((\+|00)?31\(0\))|((\+|00)?31)|0)6{1}\d{8}$/, 'nl-AW': /^(\+)?297(56|59|64|73|74|99)\d{5}$/, 'nn-NO': /^(\+?47)?[49]\d{7}$/, - 'pl-PL': /^(\+?48)? ?[5-8]\d ?\d{3} ?\d{2} ?\d{2}$/, + 'pl-PL': /^(\+?48)? ?([5-8]\d|45) ?\d{3} ?\d{2} ?\d{2}$/, 'pt-BR': /^((\+?55\ ?[1-9]{2}\ ?)|(\+?55\ ?\([1-9]{2}\)\ ?)|(0[1-9]{2}\ ?)|(\([1-9]{2}\)\ ?)|([1-9]{2}\ ?))((\d{4}\-?\d{4})|(9[1-9]{1}\d{3}\-?\d{4}))$/, 'pt-PT': /^(\+?351)?9[1236]\d{7}$/, 'pt-AO': /^(\+244)\d{9}$/, @@ -138,6 +142,7 @@ const phones = { 'si-LK': /^(?:0|94|\+94)?(7(0|1|2|4|5|6|7|8)( |-)?)\d{7}$/, 'sl-SI': /^(\+386\s?|0)(\d{1}\s?\d{3}\s?\d{2}\s?\d{2}|\d{2}\s?\d{3}\s?\d{3})$/, 'sk-SK': /^(\+?421)? ?[1-9][0-9]{2} ?[0-9]{3} ?[0-9]{3}$/, + 'so-SO': /^(\+?252|0)((6[0-9])\d{7}|(7[1-9])\d{7})$/, 'sq-AL': /^(\+355|0)6[789]\d{6}$/, 'sr-RS': /^(\+3816|06)[- \d]{5,9}$/, 'sv-SE': /^(\+?46|0)[\s\-]?7[\s\-]?[02369]([\s\-]?\d){7}$/, diff --git a/src/lib/isPassportNumber.js b/src/lib/isPassportNumber.js index 11d01e8d1..4763d6566 100644 --- a/src/lib/isPassportNumber.js +++ b/src/lib/isPassportNumber.js @@ -66,6 +66,7 @@ const passportRegexByCountryCode = { TR: /^[A-Z]\d{8}$/, // TURKEY UA: /^[A-Z]{2}\d{6}$/, // UKRAINE US: /^\d{9}$/, // UNITED STATES + ZA: /^[TAMD]\d{8}$/, // SOUTH AFRICA }; /** diff --git a/src/lib/isTaxID.js b/src/lib/isTaxID.js index 933783f44..b82156ed1 100644 --- a/src/lib/isTaxID.js +++ b/src/lib/isTaxID.js @@ -376,6 +376,30 @@ function enUsCheck(tin) { return enUsGetPrefixes().indexOf(tin.slice(0, 2)) !== -1; } +/* + * es-AR validation function + * Clave Única de Identificación Tributaria (CUIT/CUIL) + * Sourced from: + * - https://servicioscf.afip.gob.ar/publico/abc/ABCpaso2.aspx?id_nivel1=3036&id_nivel2=3040&p=Conceptos%20b%C3%A1sicos + * - https://es.wikipedia.org/wiki/Clave_%C3%9Anica_de_Identificaci%C3%B3n_Tributaria + */ + +function esArCheck(tin) { + let accum = 0; + let digits = tin.split(''); + let digit = parseInt(digits.pop(), 10); + for (let i = 0; i < digits.length; i++) { + accum += digits[9 - i] * (2 + (i % 6)); + } + let verif = 11 - (accum % 11); + if (verif === 11) { + verif = 0; + } else if (verif === 10) { + verif = 9; + } + return digit === verif; +} + /* * es-ES validation function * (Documento Nacional de Identidad (DNI) @@ -1137,6 +1161,7 @@ const taxIdFormat = { 'en-GB': /^\d{10}$|^(?!GB|NK|TN|ZZ)(?![DFIQUV])[A-Z](?![DFIQUVO])[A-Z]\d{6}[ABCD ]$/i, 'en-IE': /^\d{7}[A-W][A-IW]{0,1}$/i, 'en-US': /^\d{2}[- ]{0,1}\d{7}$/, + 'es-AR': /(20|23|24|27|30|33|34)[0-9]{8}[0-9]/, 'es-ES': /^(\d{0,8}|[XYZKLM]\d{7})[A-HJ-NP-TV-Z]$/i, 'et-EE': /^[1-6]\d{6}(00[1-9]|0[1-9][0-9]|[1-6][0-9]{2}|70[0-9]|710)\d$/, 'fi-FI': /^\d{6}[-+A]\d{3}[0-9A-FHJ-NPR-Y]$/i, @@ -1175,6 +1200,7 @@ const taxIdCheck = { 'en-CA': isCanadianSIN, 'en-IE': enIeCheck, 'en-US': enUsCheck, + 'es-AR': esArCheck, 'es-ES': esEsCheck, 'et-EE': etEeCheck, 'fi-FI': fiFiCheck, diff --git a/src/lib/isVAT.js b/src/lib/isVAT.js index 95593569d..ece7d8560 100644 --- a/src/lib/isVAT.js +++ b/src/lib/isVAT.js @@ -1,6 +1,21 @@ import assertString from './util/assertString'; import * as algorithms from './util/algorithms'; +const CH = (str) => { + // @see {@link https://www.ech.ch/de/ech/ech-0097/5.2.0} + const hasValidCheckNumber = (digits) => { + const lastDigit = digits.pop(); // used as check number + const weights = [5, 4, 3, 2, 7, 6, 5, 4]; + const calculatedCheckNumber = (11 - (digits.reduce((acc, el, idx) => + acc + (el * weights[idx]), 0) % 11)) % 11; + + return lastDigit === calculatedCheckNumber; + }; + + // @see {@link https://www.estv.admin.ch/estv/de/home/mehrwertsteuer/uid/mwst-uid-nummer.html} + return /^(CHE[- ]?)?(\d{9}|(\d{3}\.\d{3}\.\d{3})|(\d{3} \d{3} \d{3})) ?(TVA|MWST|IVA)?$/.test(str) && hasValidCheckNumber((str.match(/\d/g).map(el => +el))); +}; + const PT = (str) => { const match = str.match(/^(PT)?(\d{9})$/); if (!match) { @@ -69,7 +84,7 @@ export const vatMatchers = { SM: str => /^(SM)?\d{5}$/.test(str), SA: str => /^(SA)?\d{15}$/.test(str), RS: str => /^(RS)?\d{9}$/.test(str), - CH: str => /^(CH)?(\d{6}|\d{9}|(\d{3}.\d{3})|(\d{3}.\d{3}.\d{3}))(TVA|MWST|IVA)$/.test(str), + CH, TR: str => /^(TR)?\d{10}$/.test(str), UA: str => /^(UA)?\d{12}$/.test(str), GB: str => /^GB((\d{3} \d{4} ([0-8][0-9]|9[0-6]))|(\d{9} \d{3})|(((GD[0-4])|(HA[5-9]))[0-9]{2}))$/.test(str), diff --git a/test/validators.test.js b/test/validators.test.js index 6c0fed5f0..b181b7df8 100644 --- a/test/validators.test.js +++ b/test/validators.test.js @@ -1,5 +1,6 @@ import assert from 'assert'; import fs from 'fs'; +import timezone_mock from 'timezone-mock'; import { format } from 'util'; import vm from 'vm'; import validator from '../src/index'; @@ -36,6 +37,7 @@ describe('Validators', () => { 'invalid.com', '@invalid.com', 'foo@bar.com.', + 'foo@_bar.com', 'somename@gmail.com', 'foo@bar.co.uk.', 'z@co.c', @@ -66,6 +68,9 @@ describe('Validators', () => { '"wrong()[]",:;<>@@gmail.com', 'username@domain.com�', 'username@domain.com©', + 'nbsp test@test.com', + 'nbsp_test@te st.com', + 'nbsp_test@test.co m', ], }); }); @@ -89,6 +94,16 @@ describe('Validators', () => { }); }); + it('should validate email addresses with underscores in the domain', () => { + test({ + validator: 'isEmail', + args: [{ allow_underscores: true }], + valid: [ + 'foobar@my_sarisari_store.typepad.com', + ], + invalid: [], + }); + }); it('should validate email addresses without UTF8 characters in local part', () => { test({ @@ -117,6 +132,7 @@ describe('Validators', () => { 'hans.m端ller@test.com', 'z@co.c', 'tüst@invalid.com', + 'nbsp test@test.com', ], }); }); @@ -1604,6 +1620,27 @@ describe('Validators', () => { }); }); + it('should validate kazakh alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['kk-KZ'], + valid: [ + 'Сәлем', + 'қанағаттандырылмағандықтарыңыздан', + 'Кешіріңіз', + 'Өкінішке', + 'Қайталаңызшы', + 'ағылшынша', + 'түсінбедім', + ], + invalid: [ + 'Кешіріңіз1', + ' Кет бар ', + 'مرحبا العا', + ], + }); + }); + it('should validate Vietnamese alpha strings', () => { test({ validator: 'isAlpha', @@ -2433,6 +2470,27 @@ describe('Validators', () => { }); }); + it('should validate kazakh alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['kk-KZ'], + valid: [ + 'Сәлем777', + '123Бәсе', + 'солай', + 'Жиенсу', + '90тоқсан', + 'жалғыз', + '570бердім', + ], + invalid: [ + ' кешіріңіз ', + 'abcағылшынша', + 'мүмкін!!', + ], + }); + }); + it('should validate kurdish alphanumeric strings', () => { test({ validator: 'isAlphanumeric', @@ -4674,10 +4732,11 @@ describe('Validators', () => { 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb3JlbSI6Imlwc3VtIn0.ymiJSsMJXR6tMSr8G9usjQ15_8hKPDv_CArLhxw28MI', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkb2xvciI6InNpdCIsImFtZXQiOlsibG9yZW0iLCJpcHN1bSJdfQ.rRpe04zbWbbJjwM43VnHzAboDzszJtGrNsUxaqQ-GQ8', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqb2huIjp7ImFnZSI6MjUsImhlaWdodCI6MTg1fSwiamFrZSI6eyJhZ2UiOjMwLCJoZWlnaHQiOjI3MH19.YRLPARDmhGMC3BBk_OhtwwK21PIkVCqQe8ncIRPKo-E', - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ', // No signature ], invalid: [ 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9', + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NSIsIm5hbWUiOiJKb2huIERvZSIsImlhdCI6MTUxNjIzOTAyMn0', + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NSIsIm5hbWUiOiJKb2huIERvZSIsImlhdCI6MTYxNjY1Mzg3Mn0.eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwiaWF0IjoxNjE2NjUzODcyLCJleHAiOjE2MTY2NTM4ODJ9.a1jLRQkO5TV5y5ERcaPAiM9Xm2gBdRjKrrCpHkGr_8M', '$Zs.ewu.su84', 'ks64$S/9.dy$§kz.3sd73b', ], @@ -4816,16 +4875,51 @@ describe('Validators', () => { 'uz_Latn_UZ', 'en', 'gsw', + 'en-US', 'es_ES', + 'es-419', 'sw_KE', 'am_ET', + 'zh-CHS', 'ca_ES_VALENCIA', 'en_US_POSIX', + 'hak-CN', + 'zh-Hant', + 'zh-Hans', + 'sr-Cyrl', + 'sr-Latn', + 'zh-cmn-Hans-CN', + 'cmn-Hans-CN', + 'zh-yue-HK', + 'yue-HK', + 'zh-Hans-CN', + 'sr-Latn-RS', + 'sl-rozaj', + 'sl-rozaj-biske', + 'sl-nedis', + 'de-CH-1901', + 'sl-IT-nedis', + 'hy-Latn-IT-arevela', + 'i-enochian', + 'en-scotland-fonipa', + 'sl-IT-rozaj-biske-1994', + 'de-CH-x-phonebk', + 'az-Arab-x-AZE-derbend', + 'x-whatever', + 'qaa-Qaaa-QM-x-southern', + 'de-Qaaa', + 'sr-Latn-QM', + 'sr-Qaaa-RS', + 'en-US-u-islamcal', + 'zh-CN-a-myext-x-private', + 'en-a-myext-b-another', ], invalid: [ 'lo_POP', '12', '12_DD', + 'de-419-DE', + 'a-DE', ], }); }); @@ -5164,8 +5258,51 @@ describe('Validators', () => { 'LB92000700000000123123456123', 'IR200170000000339545727003', 'MZ97123412341234123412341', + 'MA64011519000001205000534921', + ], + invalid: [ + 'XX22YYY1234567890123', + 'FR14 2004 1010 0505 0001 3', + 'FR7630006000011234567890189@', + 'FR7630006000011234567890189😅', + 'FR763000600001123456!!🤨7890189@', + ], + }); + test({ + validator: 'isIBAN', + args: [{ whitelist: ['DK', 'GB'] }], + valid: [ + 'DK5000400440116243', + 'GB29NWBK60161331926819', + ], + invalid: [ + 'BE71 0961 2345 6769', + 'FR76 3000 6000 0112 3456 7890 189', + 'DE91 1000 0000 0123 4567 89', + 'GR96 0810 0010 0000 0123 4567 890', + 'RO09 BCYP 0000 0012 3456 7890', + 'SA44 2000 0001 2345 6789 1234', + 'ES79 2100 0813 6101 2345 6789', + 'XX22YYY1234567890123', + 'FR14 2004 1010 0505 0001 3', + 'FR7630006000011234567890189@', + 'FR7630006000011234567890189😅', + 'FR763000600001123456!!🤨7890189@', ], + }); + test({ + validator: 'isIBAN', + args: [{ whitelist: ['XX', 'AA'] }], invalid: [ + 'DK5000400440116243', + 'GB29NWBK60161331926819', + 'BE71 0961 2345 6769', + 'FR76 3000 6000 0112 3456 7890 189', + 'DE91 1000 0000 0123 4567 89', + 'GR96 0810 0010 0000 0123 4567 890', + 'RO09 BCYP 0000 0012 3456 7890', + 'SA44 2000 0001 2345 6789 1234', + 'ES79 2100 0813 6101 2345 6789', 'XX22YYY1234567890123', 'FR14 2004 1010 0505 0001 3', 'FR7630006000011234567890189@', @@ -5173,6 +5310,41 @@ describe('Validators', () => { 'FR763000600001123456!!🤨7890189@', ], }); + test({ + validator: 'isIBAN', + args: [{ blacklist: ['IT'] }], + valid: [ + 'SC52BAHL01031234567890123456USD', + 'LC14BOSL123456789012345678901234', + 'MT31MALT01100000000000000000123', + 'SV43ACAT00000000000000123123', + 'EG800002000156789012345180002', + 'BE71 0961 2345 6769', + 'FR76 3000 6000 0112 3456 7890 189', + 'DE91 1000 0000 0123 4567 89', + 'GR96 0810 0010 0000 0123 4567 890', + 'RO09 BCYP 0000 0012 3456 7890', + 'SA44 2000 0001 2345 6789 1234', + 'ES79 2100 0813 6101 2345 6789', + 'CH56 0483 5012 3456 7800 9', + 'GB98 MIDL 0700 9312 3456 78', + 'IL170108000000012612345', + 'JO71CBJO0000000000001234567890', + 'TR320010009999901234567890', + 'BR1500000000000010932840814P2', + 'LB92000700000000123123456123', + 'IR200170000000339545727003', + 'MZ97123412341234123412341', + ], + invalid: [ + 'XX22YYY1234567890123', + 'FR14 2004 1010 0505 0001 3', + 'FR7630006000011234567890189@', + 'FR7630006000011234567890189😅', + 'FR763000600001123456!!🤨7890189@', + 'IT60X0542811101000000123456', + ], + }); }); it('should validate BIC codes', () => { @@ -5215,7 +5387,7 @@ describe('Validators', () => { it('should validate luhn numbers', () => { test({ - validator: 'isLuhnValid', + validator: 'isLuhnNumber', valid: [ '0', '5421', @@ -6777,6 +6949,24 @@ describe('Validators', () => { '0114152198', ], }, + { + locale: 'ar-SD', + valid: [ + '0128652312', + '+249919425113', + '249123212345', + '0993212345', + ], + invalid: [ + '12345', + '', + '+249972662622', + '+24946266262', + '+24933221097', + '0614152198', + '096554', + ], + }, { locale: 'ar-TN', valid: [ @@ -7404,6 +7594,31 @@ describe('Validators', () => { '+254800723845', ], }, + { + locale: 'fr-CF', + valid: [ + '+23670850000', + '+23675038756', + '+23677859002', + '+23672854202', + '+23621854052', + '+23622854072', + '72234650', + '70045902', + '77934567', + '21456794', + '22452389', + ], + invalid: [ + '+23689032', + '123456789', + '+236723845987', + '022452389', + '+236772345678', + '+236700456794', + + ], + }, { locale: 'en-KI', valid: [ @@ -7501,6 +7716,25 @@ describe('Validators', () => { '+255800723845', ], }, + { + locale: 'en-MW', + valid: [ + '+265994563785', + '+265111785436', + '+265318596857', + '0320008744', + '01256258', + '0882541896', + '+265984563214', + ], + invalid: [ + '58563', + '+2658256258', + '0896328741', + '0708574896', + '+26570857489635', + ], + }, { locale: 'es-PE', valid: [ @@ -7739,6 +7973,32 @@ describe('Validators', () => { '6898912345', ], }, + { + locale: 'fr-WF', + valid: [ + '+681408500', + '+681499387', + '+681728590', + '+681808542', + '+681828540', + '+681832014', + '408500', + '499387', + '728590', + '808542', + '828540', + '832014', + ], + invalid: [ + '+68189032', + '123456789', + '+681723845987', + '022452389', + '+681772345678', + '+681700456794', + + ], + }, { locale: 'ka-GE', valid: [ @@ -8271,6 +8531,7 @@ describe('Validators', () => { '', 'abc', '+535123457', + '56043029304', ], }, { @@ -8531,6 +8792,7 @@ describe('Validators', () => { '+48 56 6572724', '+48 67 621 5461', '48 67 621 5461', + '+48 45 621 5461', ], invalid: [ '+48 67 621 5461', @@ -8541,6 +8803,7 @@ describe('Validators', () => { '1800-88-8687', '+6019-5830837', '357562855', + '+48 44 621 5461', ], }, { @@ -9321,6 +9584,27 @@ describe('Validators', () => { 'NotANumber', ], }, + { + locale: 'so-SO', + valid: [ + '+252601234567', + '+252650101010', + '+252794567120', + '252650647388', + '252751234567', + '0601234567', + '0609876543', + ], + invalid: [ + '', + 'not a number', + '+2526012345678', + '25260123456', + '+252705555555', + '+0601234567', + '06945454545', + ], + }, { locale: 'sq-AL', valid: [ @@ -11946,6 +12230,58 @@ describe('Validators', () => { }); }); + + it('should validate ISO6346 shipping containerID', () => { + test({ + validator: 'isISO6346', + valid: [ + 'HLXU2008419', + 'TGHU7599330', + 'ECMU4657496', + 'MEDU6246078', + 'YMLU2809976', + 'MRKU0046221', + 'EMCU3811879', + 'OOLU8643084', + 'HJCU1922713', + 'QJRZ123456', + ], + invalid: [ + 'OOLU1922713', + 'HJCU1922413', + 'FCUI985619', + 'ECMJ4657496', + 'TBJA7176445', + 'AFFU5962593', + ], + }); + }); + it('should validate ISO6346 shipping containerID', () => { + test({ + validator: 'isFreightContainerID', + valid: [ + 'HLXU2008419', + 'TGHU7599330', + 'ECMU4657496', + 'MEDU6246078', + 'YMLU2809976', + 'MRKU0046221', + 'EMCU3811879', + 'OOLU8643084', + 'HJCU1922713', + 'QJRZ123456', + ], + invalid: [ + 'OOLU1922713', + 'HJCU1922413', + 'FCUI985619', + 'ECMJ4657496', + 'TBJA7176445', + 'AFFU5962593', + ], + }); + }); + // EU-UK valid numbers sourced from https://ec.europa.eu/taxation_customs/tin/specs/FS-TIN%20Algorithms-Public.docx or constructed by @tplessas. it('should validate taxID', () => { test({ @@ -12161,6 +12497,27 @@ describe('Validators', () => { '28-1234567', '96-1234567'], }); + test({ + validator: 'isTaxID', + args: ['es-AR'], + valid: [ + '20271633638', + '23274986069', + '27333234519', + '30678561165', + '33693450239', + '30534868460', + '23111111129', + '34557619099'], + invalid: [ + '20-27163363-8', + '20.27163363.8', + '33693450231', + '69345023', + '693450233123123', + '3369ew50231', + '34557619095'], + }); test({ validator: 'isTaxID', args: ['es-ES'], @@ -12643,6 +13000,7 @@ describe('Validators', () => { '15/7/2002', '15-7-2002', '15/07-02', + '30/04/--', ], }); test({ @@ -12657,6 +13015,7 @@ describe('Validators', () => { '15/7/02', '15-7-02', '5/7-02', + '3/4/aa', ], }); test({ @@ -12737,6 +13096,17 @@ describe('Validators', () => { '02.29.2020.20', ], }); + // emulating Pacific time zone offset & time + // which could potentially result in UTC conversion issues + timezone_mock.register('US/Pacific'); + test({ + validator: 'isDate', + valid: [ + new Date(2016, 2, 29), + '2017-08-04', + ], + }); + timezone_mock.unregister(); }); it('should validate time', () => { test({ @@ -13767,18 +14137,30 @@ describe('Validators', () => { validator: 'isVAT', args: ['CH'], valid: [ - 'CH123456TVA', - '123456TVA', - 'CH123456789MWST', - '123456789MWST', - 'CH123.456IVA', - '123.456IVA', - 'CH123.456.789TVA', - '123.456.789TVA', - ], - invalid: [ - 'CH 123456', - '12345', + // strictly valid + 'CHE-116.281.710 MWST', + 'CHE-116.281.710 IVA', + 'CHE-116.281.710 TVA', + // loosely valid presentation variants + 'CHE 116 281 710 IVA', // all separators are spaces + 'CHE-191.398.369MWST', // no space before suffix + 'CHE-116281710 MWST', // no number separators + 'CHE-116281710MWST', // no number separators and no space before suffix + 'CHE105854263MWST', // no separators + 'CHE-116.285.524', // no suffix (vat abbreviation) + 'CHE116281710', // no suffix and separators + '116.281.710 TVA', // no prefix (CHE, ISO-3166-1 Alpha-3) + '116281710MWST', // no prefix and separators + '100.218.485', // no prefix and suffix + '123456788', // no prefix, separators and suffix + ], + invalid: [ + 'CH-116.281.710 MWST', // invalid prefix (should be CHE) + 'CHE-116.281 MWST', // invalid number of digits (should be 9) + 'CHE-123.456.789 MWST', // invalid last digit (should match the calculated check-number 8) + 'CHE-123.356.780 MWST', // invalid check-number (there are no swiss UIDs with the calculated check number 10) + 'CH-116.281.710 VAT', // invalid suffix (should be MWST, IVA or TVA) + 'CHE-116/281/710 IVA', // invalid number separators (should be all dots or all spaces) ], }); test({ @@ -14082,4 +14464,55 @@ describe('Validators', () => { ], }); }); + it('should validate mailto URI', () => { + test({ + validator: 'isMailtoURI', + valid: [ + 'mailto:?subject=something&cc=valid@mail.com', + 'mailto:?subject=something&cc=valid@mail.com,another@mail.com,', + 'mailto:?subject=something&bcc=valid@mail.com', + 'mailto:?subject=something&bcc=valid@mail.com,another@mail.com', + 'mailto:?bcc=valid@mail.com,another@mail.com', + 'mailto:?cc=valid@mail.com,another@mail.com', + 'mailto:?cc=valid@mail.com', + 'mailto:?bcc=valid@mail.com', + 'mailto:?subject=something&body=something else', + 'mailto:?subject=something&body=something else&cc=hello@mail.com,another@mail.com', + 'mailto:?subject=something&body=something else&bcc=hello@mail.com,another@mail.com', + 'mailto:?subject=something&body=something else&cc=something@mail.com&bcc=hello@mail.com,another@mail.com', + 'mailto:hello@mail.com', + 'mailto:info@mail.com?', + 'mailto:hey@mail.com?subject=something', + 'mailto:info@mail.com?subject=something&cc=valid@mail.com', + 'mailto:info@mail.com?subject=something&cc=valid@mail.com,another@mail.com,', + 'mailto:info@mail.com?subject=something&bcc=valid@mail.com', + 'mailto:info@mail.com?subject=something&bcc=valid@mail.com,another@mail.com', + 'mailto:info@mail.com?bcc=valid@mail.com,another@mail.com', + 'mailto:info@mail.com?cc=valid@mail.com,another@mail.com', + 'mailto:info@mail.com?cc=valid@mail.com', + 'mailto:info@mail.com?bcc=valid@mail.com&', + 'mailto:info@mail.com?subject=something&body=something else', + 'mailto:info@mail.com?subject=something&body=something else&cc=hello@mail.com,another@mail.com', + 'mailto:info@mail.com?subject=something&body=something else&bcc=hello@mail.com,another@mail.com', + 'mailto:info@mail.com?subject=something&body=something else&cc=something@mail.com&bcc=hello@mail.com,another@mail.com', + 'mailto:', + ], + invalid: [ + '', + 'somthing', + 'valid@gmail.com', + 'mailto:?subject=okay&subject=444', + 'mailto:?subject=something&wrong=888', + 'mailto:somename@gmail.com', + 'mailto:hello@world.com?cc=somename@gmail.com', + 'mailto:hello@world.com?bcc=somename@gmail.com', + 'mailto:hello@world.com?bcc=somename@gmail.com&bcc', + 'mailto:valid@gmail.com?subject=anything&body=nothing&cc=&bcc=&key=', + 'mailto:hello@world.com?cc=somename', + 'mailto:somename', + 'mailto:info@mail.com?subject=something&body=something else&cc=something@mail.com&bcc=hello@mail.com,another@mail.com&', + 'mailto:?subject=something&body=something else&cc=something@mail.com&bcc=hello@mail.com,another@mail.com&', + ], + }); + }); }); diff --git a/test/validators/isFQDN.test.js b/test/validators/isFQDN.test.js new file mode 100644 index 000000000..134bab005 --- /dev/null +++ b/test/validators/isFQDN.test.js @@ -0,0 +1,26 @@ +import test from '../testFunctions'; + +describe('isFQDN', () => { + it('should validate domain names.', () => { + test({ + validator: 'isFQDN', + args: [], + valid: [ + 'google.com', + ], + invalid: [ + 'google.l33t', + ], + }); + test({ + validator: 'isFQDN', + args: [{ allow_numeric_tld: true }], + valid: [ + 'google.com', + 'google.l33t', + ], + invalid: [ + ], + }); + }); +});