Repository: validatorjs/validator.js
Pull request #1159 (2019-10-15 15:21:45)

Added feature isHSL
As suggested in https://github.com/validatorjs/validator.js/issues/1156, I made an isHSL validator. It accepts padded zeroes and white spaces. I basically tried playing around with https://www.w3schools.com/colors/tryit.asp?filename=trycolors_hsl and checked what worked and what didn't work.

Summary of changes:
* `src/lib/isHSL.js`: Source of validator
* `src/index.js`: Modified to recognize newly created isHSL
* `test/validators.js`: Added test cases for isHSL
* `README.md`: Added short description of isHSL

ezkemboi: @kurisu-gh, I will review this PR. 
Also, try run tests and push the changes. 
guischafer: Should values greater than 360 and lesser than 0 be invalid as the first parameter (hue)? Since it's a circle, you just have to account turns in order to get the right value. If you try this out in chrome devtools you'll see that the browser handles turns in the circle.
kurisu-gh: > Should values greater than 360 and lesser than 0 be invalid as the first parameter (hue)? Since it's a circle, you just have to account turns in order to get the right value. If you try this out in chrome devtools you'll see that the browser handles turns in the circle.

You're right. I confirmed this myself in https://www.w3schools.com/colors/tryit.asp?filename=trycolors_hsl

I just pushed some modifications to this PR that allows values larger than 360 and less than 0. +/- signs are also validated. I added some more test cases considering that modulo 360 is possible.
profnandaa: @ezkemboi -- please have a look at this again.
ezkemboi: Alright, checking @profnandaa 
profnandaa: @kurisu-gh -- ping!
kurisu-gh: Sorry for the delay! Was bogged down with work.

I've pushed some updates and synced with upstream. This PR now handles the test cases that @ezkemboi brought up. But upon further reviewing of the [MDM documentation](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value), validating HSL is a bit more involved than I thought. To summarize:

- HSL actually supports the alpha parameter (optionally) previously mentioned in https://github.com/validatorjs/validator.js/pull/1159#discussion_r376074851 and I see there is now a feature request https://github.com/validatorjs/validator.js/issues/1245. As of CSS Colors Level 4, hsla() is merely an alias to hsl(). If you use hsla() without the alpha parameter, it would still work fine due to hsl() alias. In other words, isHSL() needs to validate the optional 4th parameter. In this PR, I added the 4th parameter check. I think we no longer need an isHSLA() validator because hsla() is really hsl(). 
- There is a comma-separated and a space-separated way to express hsl(). Comma-separated is straight forward, but space-separated adds the 4th parameter (alpha) with a `/` delimiter. Space-separated can actually have no spaces at all like: `hsl(200grad+.1%62%/1)` . It gets complicated when taking into account that the optional +/- is like a space separator. My PR doesn't fully capture all the cases.
- Percentage values and alpha values [0,1] (or percentage) can be actually be set to values beyond their normal bounds. They will just saturate to 0% (0.0) or 100% (1.0) in practice. This PR handles this.
- My regex patterns are a bit brute force right now. It would be nicer to break them down into reusable pieces. Many parts of my patterns are repeated. For example, checking for a CSS <number> which has an optional +/-, an integer followed by an optional scientific notation portion; or it can be a (+/- optional) float with optional leading 0's before the decimal, followed by an optional scientific notion portion. I think there are definitely smarter, more modular ways to express the patterns here.
- More test cases should be considered.

So in short, the PR handles the additional checks requested by @ezkemboi as well as the optional alpha parameter. More work is needed to handle space-separated validation. I can continue development of this feature but it will be slow. If anyone wants to take over development of this, feel free. Also open to suggestions on simplifying my regex patterns.
profnandaa: @ezkemboi -- ping ^
kurisu-gh: Thank you for reviewing @ezkemboi and @profnandaa. Let me just update the readme in a little bit to note that an optional alpha parameter is also supported.
kurisu-gh: @ezkemboi @profnandaa, updated the `README.md` appropriately and synced up the PR as well. Thank you for the efforts!

Patch:
diff --git a/README.md b/README.md
index c8d960d..c5d18a6 100644
--- a/README.md
+++ b/README.md
@@ -110,6 +110,7 @@ Validator                               | Description
 **isHash(str, algorithm)**              | check if the string is a hash of type algorithm.<br/><br/>Algorithm is one of `['md4', 'md5', 'sha1', 'sha256', 'sha384', 'sha512', 'ripemd128', 'ripemd160', 'tiger128', 'tiger160', 'tiger192', 'crc32', 'crc32b']`
 **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](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value).<br/><br/>Comma-separated format supported. Space-separated format supported with the exception of a few edge cases (ex: `hsl(200grad+.1%62%/1)`).
 **isRgbColor(str, [, includePercentValues])**                     | check if the string is a rgb or rgba color.<br/><br/>`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.
 **isIdentityCard(str [, locale])**      | check if the string is a valid identity card code.<br/><br/>`locale` is one of `['ES', 'zh-TW', 'he-IL']` OR `'any'`. If 'any' is used, function will check if any of the locals match.<br/><br/>Defaults to 'any'.
 **isIn(str, values)**                   | check if the string is in a array of allowed values.
@@ -187,8 +188,8 @@ In general, we follow the "fork-and-pull" Git workflow.
 2. Clone the project to your own machine
 3. Work on your fork
     1. Make your changes and additions
-        - Most of your changes should be focused on `src/` and `test/` folders and/or `README.md`. 
-        - Files such as `validator.js`, `validator.min.js` and files in `lib/` folder are autogenerated when running tests (`npm test`) and need not to be changed **manually**. 
+        - Most of your changes should be focused on `src/` and `test/` folders and/or `README.md`.
+        - Files such as `validator.js`, `validator.min.js` and files in `lib/` folder are autogenerated when running tests (`npm test`) and need not to be changed **manually**.
     2. Change or add tests if needed
     3. Run tests and make sure they pass
     4. Add changes to README.md if needed
diff --git a/src/index.js b/src/index.js
index 68645f7..42549f1 100644
--- a/src/index.js
+++ b/src/index.js
@@ -41,6 +41,7 @@ import isDivisibleBy from './lib/isDivisibleBy';
 
 import isHexColor from './lib/isHexColor';
 import isRgbColor from './lib/isRgbColor';
+import isHSL from './lib/isHSL';
 
 import isISRC from './lib/isISRC';
 
@@ -155,6 +156,7 @@ const validator = {
   isDivisibleBy,
   isHexColor,
   isRgbColor,
+  isHSL,
   isISRC,
   isMD5,
   isHash,
diff --git a/src/lib/isHSL.js b/src/lib/isHSL.js
new file mode 100644
index 0000000..b4f4463
--- /dev/null
+++ b/src/lib/isHSL.js
@@ -0,0 +1,10 @@
+import assertString from './util/assertString';
+
+
+const hslcomma = /^(hsl)a?\(\s*((\+|\-)?([0-9]+(\.[0-9]+)?(e(\+|\-)?[0-9]+)?|\.[0-9]+(e(\+|\-)?[0-9]+)?))(deg|grad|rad|turn|\s*)(\s*,\s*(\+|\-)?([0-9]+(\.[0-9]+)?(e(\+|\-)?[0-9]+)?|\.[0-9]+(e(\+|\-)?[0-9]+)?)%){2}\s*(,\s*((\+|\-)?([0-9]+(\.[0-9]+)?(e(\+|\-)?[0-9]+)?|\.[0-9]+(e(\+|\-)?[0-9]+)?)%?)\s*)?\)$/i;
+const hslspace = /^(hsl)a?\(\s*((\+|\-)?([0-9]+(\.[0-9]+)?(e(\+|\-)?[0-9]+)?|\.[0-9]+(e(\+|\-)?[0-9]+)?))(deg|grad|rad|turn|\s)(\s*(\+|\-)?([0-9]+(\.[0-9]+)?(e(\+|\-)?[0-9]+)?|\.[0-9]+(e(\+|\-)?[0-9]+)?)%){2}\s*(\/\s*((\+|\-)?([0-9]+(\.[0-9]+)?(e(\+|\-)?[0-9]+)?|\.[0-9]+(e(\+|\-)?[0-9]+)?)%?)\s*)?\)$/i;
+
+export default function isHSL(str) {
+  assertString(str);
+  return hslcomma.test(str) || hslspace.test(str);
+}


Test patch:
diff --git a/test/validators.js b/test/validators.js
index b5e4134..d87a188 100644
--- a/test/validators.js
+++ b/test/validators.js
@@ -3126,6 +3126,54 @@ describe('Validators', () => {
     });
   });
 
+  it('should validate HSL color strings', () => {
+    test({
+      validator: 'isHSL',
+      valid: [
+        'hsl(360,0000000000100%,000000100%)',
+        'hsl(000010, 00000000001%, 00000040%)',
+        'HSL(00000,0000000000100%,000000100%)',
+        'hsL(0, 0%, 0%)',
+        'hSl(  360  , 100%  , 100%   )',
+        'Hsl(  00150  , 000099%  , 01%   )',
+        'hsl(01080, 03%, 4%)',
+        'hsl(-540, 03%, 4%)',
+        'hsla(+540, 03%, 4%)',
+        'hsla(+540, 03%, 4%, 500)',
+        'hsl(+540deg, 03%, 4%, 500)',
+        'hsl(+540gRaD, 03%, 4%, 500)',
+        'hsl(+540.01e-98rad, 03%, 4%, 500)',
+        'hsl(-540.5turn, 03%, 4%, 500)',
+        'hsl(+540, 03%, 4%, 500e-01)',
+        'hsl(+540, 03%, 4%, 500e+80)',
+        'hsl(4.71239rad, 60%, 70%)',
+        'hsl(270deg, 60%, 70%)',
+        'hsl(200, +.1%, 62%, 1)',
+        'hsl(270 60% 70%)',
+        'hsl(200, +.1e-9%, 62e10%, 1)',
+        'hsl(.75turn, 60%, 70%)',
+        // 'hsl(200grad+.1%62%/1)', //supposed to pass, but need to handle delimiters
+        'hsl(200grad +.1% 62% / 1)',
+        'hsl(270, 60%, 50%, .15)',
+        'hsl(270, 60%, 50%, 15%)',
+        'hsl(270 60% 50% / .15)',
+        'hsl(270 60% 50% / 15%)',
+      ],
+      invalid: [
+        'hsl (360,0000000000100%,000000100%)',
+        'hsl(0260, 100 %, 100%)',
+        'hsl(0160, 100%, 100%, 100 %)',
+        'hsl(-0160, 100%, 100a)',
+        'hsl(-0160, 100%, 100)',
+        'hsl(-0160 100%, 100%, )',
+        'hsl(270 deg, 60%, 70%)',
+        'hsl( deg, 60%, 70%)',
+        'hsl(, 60%, 70%)',
+        'hsl(3000deg, 70%)',
+      ],
+    });
+  });
+
   it('should validate rgb color strings', () => {
     test({
       validator: 'isRgbColor',