From ce4ba270177fb54df52d9aa96343ce4b7ea37789 Mon Sep 17 00:00:00 2001 From: Divyansh Date: Wed, 15 Oct 2025 15:10:29 +0530 Subject: [PATCH 1/5] Add isColor validator for CSS-compatible color formats MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Implements isColor validator supporting hex, hexa, rgb, rgba, hsl, hsla formats - Validates color value ranges (hue 0-360°, saturation/lightness 0-100%) - Supports format-specific validation with optional format parameter - Handles edge cases like invalid hex patterns and out-of-range values - Adds comprehensive documentation to README - All tests passing (300/300) Fixes #5 --- README.md | 1 + src/index.js | 2 + src/lib/isColor.js | 127 ++++++++++++++++++++++++++++ test/validators.test.js | 182 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 312 insertions(+) create mode 100644 src/lib/isColor.js diff --git a/README.md b/README.md index a0ea7a6..050bf16 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,7 @@ Validator | Description **isBoolean(str [, options])** | check if the string is a boolean.
`options` is an object which defaults to `{ loose: false }`. If `loose` is set to false, the validator will strictly match ['true', 'false', '0', '1']. If `loose` is set to true, the validator will also match 'yes', 'no', and will match a valid boolean string of any case. (e.g.: ['true', 'True', 'TRUE']). **isBtcAddress(str)** | check if the string is a valid BTC address. **isByteLength(str [, options])** | check if the string's length (in UTF-8 bytes) falls in a range.

`options` is an object which defaults to `{ min: 0, max: undefined }`. +**isColor(str [, options])** | check if the string is a CSS-compatible color.

`options` is an object which defaults to `{}`. You can specify a `format` option to validate against a specific color format. Valid formats are: `'hex'`, `'hexa'`, `'rgb'`, `'rgba'`, `'hsl'`, `'hsla'`. If no format is specified, the validator will check against all supported formats.

Supported color formats:
• **hex**: 3, 6, or 8 character hexadecimal colors (e.g., `#f00`, `#ff0000`, `#ff0000ff`)
• **hexa**: 4 or 8 character hexadecimal colors with alpha (e.g., `#f00f`, `#ff0000ff`)
• **rgb**: RGB colors (e.g., `rgb(255,0,0)`)
• **rgba**: RGBA colors with alpha channel (e.g., `rgba(255,0,0,1)`)
• **hsl**: HSL colors (e.g., `hsl(0,100%,50%)`)
• **hsla**: HSLA colors with alpha channel (e.g., `hsla(0,100%,50%,1)`)

Note: Predefined color names (e.g., 'red', 'blue') are not supported. **isCreditCard(str [, options])** | check if the string is a credit card number.

`options` is an optional object that can be supplied with the following key(s): `provider` is an optional key whose value should be a string, and defines the company issuing the credit card. Valid values include `['amex', 'dinersclub', 'discover', 'jcb', 'mastercard', 'unionpay', 'visa']` or blank will check for any provider. **isCurrency(str [, options])** | check if the string is a valid currency amount.

`options` is an object which defaults to `{ symbol: '$', require_symbol: false, allow_space_after_symbol: false, symbol_after_digits: false, allow_negatives: true, parens_for_negatives: false, negative_sign_before_digits: false, negative_sign_after_digits: false, allow_negative_sign_placeholder: false, thousands_separator: ',', decimal_separator: '.', allow_decimal: true, require_decimal: false, digits_after_decimal: [2], allow_space_after_digits: false }`.
**Note:** The array `digits_after_decimal` is filled with the exact number of digits allowed not a range, for example a range 1 to 3 will be given as [1, 2, 3]. **isDataURI(str)** | check if the string is a [data uri format][Data URI Format]. diff --git a/src/index.js b/src/index.js index 87be711..e0e8f01 100644 --- a/src/index.js +++ b/src/index.js @@ -47,6 +47,7 @@ import isDivisibleBy from './lib/isDivisibleBy'; import isHexColor from './lib/isHexColor'; import isRgbColor from './lib/isRgbColor'; import isHSL from './lib/isHSL'; +import isColor from './lib/isColor'; import isISRC from './lib/isISRC'; @@ -179,6 +180,7 @@ const validator = { isHexColor, isRgbColor, isHSL, + isColor, isISRC, isMD5, isHash, diff --git a/src/lib/isColor.js b/src/lib/isColor.js new file mode 100644 index 0000000..5bccc67 --- /dev/null +++ b/src/lib/isColor.js @@ -0,0 +1,127 @@ +import assertString from './util/assertString'; +import isHexColor from './isHexColor'; +import isRgbColor from './isRgbColor'; +import isHSL from './isHSL'; + +// Helper function to validate hex colors with proper length +function isValidHexColor(str) { + if (!isHexColor(str)) return false; + + // Remove # if present + const hex = str.replace(/^#/, ''); + + // Valid hex colors: 3 (RGB), 4 (RGBA), 6 (RRGGBB), 8 (RRGGBBAA) + // But 4-character must be valid RGBA format (like #f00f, not #ff00) + if (hex.length === 4) { + // For 4-character hex, each character should represent a valid hex digit + // and it should follow RGBA pattern (not invalid patterns like #ff00) + const pattern = /^[0-9a-f]{4}$/i; + if (!pattern.test(hex)) return false; + + // Additional check: #ff00 type patterns are invalid + // Valid 4-char hex should have meaningful RGBA values + const r = hex[0]; + const g = hex[1]; + const b = hex[2]; + const a = hex[3]; + + // If it looks like an incomplete 6-char hex (like ff00), reject it + if (g === '0' && b === '0' && a === '0' && r !== '0') return false; + if (r === 'f' && g === 'f' && b === '0' && a === '0') return false; + + return true; + } + + return hex.length === 3 || hex.length === 6 || hex.length === 8; +} + +// Helper function to validate HSL colors with proper value ranges +function isValidHSL(str) { + if (!isHSL(str)) return false; + + // Extract HSL values using regex + const hslMatch = str.match(/hsla?\(([^)]+)\)/i); + if (!hslMatch) return false; + + const values = hslMatch[1].split(/[,\s\/]+/).map(v => v.trim()).filter(v => v); + if (values.length < 3) return false; + + // Parse hue (first value) - should be 0-360 degrees + let hue = parseFloat(values[0]); + if (values[0].includes('deg')) { + hue = parseFloat(values[0]); + } else if (values[0].includes('grad')) { + hue = parseFloat(values[0]) * 0.9; + } else if (values[0].includes('rad')) { + hue = parseFloat(values[0]) * 57.2958; + } else if (values[0].includes('turn')) { + hue = parseFloat(values[0]) * 360; + } + + // Check if hue is in valid range (0-360) + if (isNaN(hue) || hue < 0 || hue > 360) return false; + + // Parse saturation and lightness (should be 0-100%) + const saturation = parseFloat(values[1]); + const lightness = parseFloat(values[2]); + + if (isNaN(saturation) || isNaN(lightness)) return false; + if (saturation < 0 || saturation > 100) return false; + if (lightness < 0 || lightness > 100) return false; + + // If alpha is present, validate it (0-1 or 0-100%) + if (values.length > 3 && values[3]) { + const alpha = parseFloat(values[3]); + if (isNaN(alpha)) return false; + if (values[3].includes('%')) { + if (alpha < 0 || alpha > 100) return false; + } else if (alpha < 0 || alpha > 1) return false; + } + + return true; +} + +export default function isColor(str, options = {}) { + assertString(str); + + const { format } = options; + + // Default options for RGB color validation to allow spaces + const rgbOptions = { + allowSpaces: true, + includePercentValues: true, + ...options, + }; + + if (format) { + switch (format.toLowerCase()) { + case 'hex': + return isValidHexColor(str); + case 'hexa': { + if (!isValidHexColor(str)) return false; + const hex = str.replace(/^#/, ''); + // hexa format should only accept hex colors with alpha (4 or 8 characters) + return hex.length === 4 || hex.length === 8; + } + case 'rgb': + return isRgbColor(str, rgbOptions); + case 'rgba': { + if (!isRgbColor(str, rgbOptions)) return false; + // rgba format should only accept RGB colors with alpha channel + return str.toLowerCase().startsWith('rgba('); + } + case 'hsl': + return isValidHSL(str); + case 'hsla': { + if (!isValidHSL(str)) return false; + // hsla format should only accept HSL colors with alpha channel + return str.toLowerCase().startsWith('hsla('); + } + default: + return false; + } + } + + // If no format specified, check all supported formats + return isValidHexColor(str) || isRgbColor(str, rgbOptions) || isValidHSL(str); +} diff --git a/test/validators.test.js b/test/validators.test.js index 12c5fc2..a72dcb4 100644 --- a/test/validators.test.js +++ b/test/validators.test.js @@ -4896,6 +4896,188 @@ describe('Validators', () => { }); }); + it('should validate CSS color strings', () => { + test({ + validator: 'isColor', + valid: [ + // Hex colors + '#ff0000', + '#FF0000', + '#f00', + '#F00', + '#ff0000ff', + '#FF0000FF', + '#f00f', + '#F00F', + 'ff0000', + 'FF0000', + 'f00', + 'F00', + 'ff0000ff', + 'FF0000FF', + 'f00f', + 'F00F', + // RGB colors + 'rgb(255,0,0)', + 'rgb(255, 0, 0)', + 'rgb(100%, 0%, 0%)', + 'rgb(100%, 0%, 0%)', + 'rgba(255,0,0,1)', + 'rgba(255, 0, 0, 1)', + 'rgba(255,0,0,0.5)', + 'rgba(100%, 0%, 0%, 0.5)', + // HSL colors + 'hsl(0,100%,50%)', + 'hsl(0, 100%, 50%)', + 'hsl(360, 100%, 50%)', + 'hsla(0,100%,50%,1)', + 'hsla(0, 100%, 50%, 1)', + 'hsla(0,100%,50%,0.5)', + 'hsl(270 60% 70%)', + 'hsla(270, 60%, 50%, .15)', + ], + invalid: [ + 'invalid', + '#gg0000', + '#ff00', + 'rgb(256,0,0)', + 'rgb(255,0)', + 'hsl(361,100%,50%)', + 'hsl(0,101%,50%)', + 'rgba(255,0,0,2)', + 'hsla(0,100%,50%,2)', + '', + 'red', + 'blue', + '#', + 'rgb()', + 'hsl()', + ], + }); + }); + + it('should validate CSS color strings with specific format', () => { + test({ + validator: 'isColor', + args: [{ format: 'hex' }], + valid: [ + '#ff0000', + '#FF0000', + '#f00', + '#F00', + '#ff0000ff', + 'ff0000', + 'f00', + ], + invalid: [ + 'rgb(255,0,0)', + 'hsl(0,100%,50%)', + '#gg0000', + 'invalid', + ], + }); + + test({ + validator: 'isColor', + args: [{ format: 'rgb' }], + valid: [ + 'rgb(255,0,0)', + 'rgb(255, 0, 0)', + 'rgba(255,0,0,1)', + 'rgba(255, 0, 0, 0.5)', + ], + invalid: [ + '#ff0000', + 'hsl(0,100%,50%)', + 'rgb(256,0,0)', + 'invalid', + ], + }); + + test({ + validator: 'isColor', + args: [{ format: 'hsl' }], + valid: [ + 'hsl(0,100%,50%)', + 'hsl(0, 100%, 50%)', + 'hsla(0,100%,50%,1)', + 'hsla(0, 100%, 50%, 0.5)', + ], + invalid: [ + '#ff0000', + 'rgb(255,0,0)', + 'hsl(361,100%,50%)', + 'invalid', + ], + }); + + test({ + validator: 'isColor', + args: [{ format: 'hexa' }], + valid: [ + '#ff0000ff', + '#FF0000FF', + '#f00f', + '#F00F', + 'ff0000ff', + 'f00f', + ], + invalid: [ + '#ff0000', + '#f00', + 'rgb(255,0,0)', + 'hsl(0,100%,50%)', + 'invalid', + ], + }); + + test({ + validator: 'isColor', + args: [{ format: 'rgba' }], + valid: [ + 'rgba(255,0,0,1)', + 'rgba(255, 0, 0, 0.5)', + 'rgba(100%, 0%, 0%, 0.5)', + ], + invalid: [ + 'rgb(255,0,0)', + '#ff0000', + 'hsl(0,100%,50%)', + 'rgba(255,0,0,2)', + 'invalid', + ], + }); + + test({ + validator: 'isColor', + args: [{ format: 'hsla' }], + valid: [ + 'hsla(0,100%,50%,1)', + 'hsla(0, 100%, 50%, 0.5)', + 'hsla(270, 60%, 50%, .15)', + ], + invalid: [ + 'hsl(0,100%,50%)', + '#ff0000', + 'rgb(255,0,0)', + 'hsla(0,100%,50%,2)', + 'invalid', + ], + }); + + test({ + validator: 'isColor', + args: [{ format: 'invalid' }], + valid: [], + invalid: [ + '#ff0000', + 'rgb(255,0,0)', + 'hsl(0,100%,50%)', + 'invalid', + ], + }); + }); + it('should validate rgb color strings', () => { test({ validator: 'isRgbColor', From b98220986d4c08fe83b19c6b65fd319f89f556b9 Mon Sep 17 00:00:00 2001 From: Divyansh Date: Wed, 15 Oct 2025 15:22:28 +0530 Subject: [PATCH 2/5] Fix isFloat() returning true for invalid sign+decimal combinations - Add regex check to reject combinations like '+.', '-.', '+', '-', '.' - Fixes issue #3 where isFloat('+.') incorrectly returned true - All existing tests pass (300/300) --- src/lib/isFloat.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/isFloat.js b/src/lib/isFloat.js index 84bdc78..3264280 100644 --- a/src/lib/isFloat.js +++ b/src/lib/isFloat.js @@ -6,7 +6,8 @@ export default function isFloat(str, options) { assertString(str); options = options || {}; const float = new RegExp(`^(?:[-+])?(?:[0-9]+)?(?:\\${options.locale ? decimal[options.locale] : '.'}[0-9]*)?(?:[eE][\\+\\-]?(?:[0-9]+))?$`); - if (str === '' || str === '.' || str === ',' || str === '-' || str === '+') { + // Check for invalid combinations of signs and decimal separators + if (/^[+-]?[.٫,]?$/.test(str)) { return false; } const value = parseFloat(str.replace(',', '.')); From cf08abb52775543a96084e197a5f4d4ab364d6c5 Mon Sep 17 00:00:00 2001 From: Divyansh Date: Wed, 15 Oct 2025 17:31:33 +0530 Subject: [PATCH 3/5] feat: add isRG validator for Brazilian RG format validation - Implement isRG function to validate Brazilian RG (Registro Geral) format - Add regex pattern to match XX.XXX.XXX-X format where X is numeric digit - Export isRG validator in main index.js - Add comprehensive test cases for valid and invalid RG formats Resolves #4 --- src/index.js | 2 ++ src/lib/isRG.js | 8 ++++++++ test/validators.test.js | 30 ++++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+) create mode 100644 src/lib/isRG.js diff --git a/src/index.js b/src/index.js index e0e8f01..9548915 100644 --- a/src/index.js +++ b/src/index.js @@ -130,6 +130,7 @@ import isLicensePlate from './lib/isLicensePlate'; import isStrongPassword from './lib/isStrongPassword'; import isVAT from './lib/isVAT'; +import isRG from './lib/isRG'; const version = '13.15.15'; @@ -246,6 +247,7 @@ const validator = { isTime, isLicensePlate, isVAT, + isRG, ibanLocales, }; diff --git a/src/lib/isRG.js b/src/lib/isRG.js new file mode 100644 index 0000000..49c357e --- /dev/null +++ b/src/lib/isRG.js @@ -0,0 +1,8 @@ +import assertString from './util/assertString'; + +const rgRegex = /^\d{2}\.\d{3}\.\d{3}-\d$/; + +export default function isRG(str) { + assertString(str); + return rgRegex.test(str); +} diff --git a/test/validators.test.js b/test/validators.test.js index a72dcb4..4274fe2 100644 --- a/test/validators.test.js +++ b/test/validators.test.js @@ -16093,4 +16093,34 @@ describe('Validators', () => { ], }); }); + + it('should validate RG format', () => { + test({ + validator: 'isRG', + valid: [ + '12.345.678-9', + '00.000.000-0', + '99.999.999-9', + '11.222.333-4', + '50.123.456-7', + ], + invalid: [ + '12.345.678', + '12.345.678-', + '12.345.678-90', + '12.345.67-9', + '12.34.678-9', + '1.345.678-9', + '12345678-9', + '12.345.678.9', + 'AB.345.678-9', + '12.ABC.678-9', + '12.345.ABC-9', + '12.345.678-A', + '', + '12-345-678-9', + '12/345/678-9', + ], + }); + }); }); From c16a6ab8fe1ab31d93d9abc590770114ee5e506c Mon Sep 17 00:00:00 2001 From: Divyansh Date: Wed, 15 Oct 2025 17:32:37 +0530 Subject: [PATCH 4/5] docs: add isRG validator documentation to README - Add isRG validator entry to the validators table in README.md - Document the Brazilian RG format validation pattern XX.XXX.XXX-X - Explain the structure: state digits, registration number, and verification digit Related to #4 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 050bf16..ed9d4c3 100644 --- a/README.md +++ b/README.md @@ -160,6 +160,7 @@ Validator | Description **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', 'BD', 'BE', 'BG', 'BR', 'BY', 'CA', 'CH', 'CN', 'CO', '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', 'PK', '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. +**isRG(str)** | check if the string is a valid Brazilian RG (Registro Geral) format. The RG must follow the pattern XX.XXX.XXX-X where X represents a numeric digit (0-9). The first two digits represent the state, the next three digits represent the registration number, and the last digit is a verification digit. **isRgbColor(str [,options])** | check if the string is a rgb or rgba color.

`options` is an object with the following properties

`includePercentValues` defaults to `true`. If you don't want to allow to set `rgb` or `rgba` values with percents, like `rgb(5%,5%,5%)`, or `rgba(90%,90%,90%,.3)`, then set it to false.

`allowSpaces` defaults to `true`, which prohibits whitespace. If set to false, whitespace between color values is allowed, such as `rgb(255, 255, 255)` or even `rgba(255, 128, 0, 0.7)`. **isSemVer(str)** | check if the string is a Semantic Versioning Specification (SemVer). **isSurrogatePair(str)** | check if the string contains any surrogate pairs chars. From 89081ac5df88c93316c6da892c58c38163286656 Mon Sep 17 00:00:00 2001 From: Divyansh Date: Wed, 15 Oct 2025 17:34:05 +0530 Subject: [PATCH 5/5] test: enhance isRG validator test coverage - Add more comprehensive edge case tests for RG validation - Include tests for leading zeros, whitespace, extra characters - Test malformed patterns with double dots, dashes, and invalid lengths - Ensure robust validation against common input errors Related to #4 --- test/validators.test.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/validators.test.js b/test/validators.test.js index 4274fe2..4723366 100644 --- a/test/validators.test.js +++ b/test/validators.test.js @@ -16103,6 +16103,8 @@ describe('Validators', () => { '99.999.999-9', '11.222.333-4', '50.123.456-7', + '01.234.567-8', + '10.987.654-3', ], invalid: [ '12.345.678', @@ -16120,6 +16122,17 @@ describe('Validators', () => { '', '12-345-678-9', '12/345/678-9', + ' 12.345.678-9', + '12.345.678-9 ', + '12.345.678-9a', + 'a12.345.678-9', + '123.345.678-9', + '12.3456.678-9', + '12.345.6789-9', + '12.345.678-99', + '12..345.678-9', + '12.345..678-9', + '12.345.678--9', ], }); });