Repository: char0n/ramda-adjunct (js)

Issue:
trimStart/trimEnd
https://github.com/tc39/proposal-string-left-right-trim

Don't forget appropriate polyfills + aliases (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trimStart)

Pull request description:
feat(string): add trimStart #808
Closes #808

Patch:
diff --git a/package-lock.json b/package-lock.json
index 3977cf9a1d..4d4d060768 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5538,9 +5538,9 @@
       },
       "dependencies": {
         "is-buffer": {
-          "version": "2.0.3",
-          "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz",
-          "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==",
+          "version": "2.0.4",
+          "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz",
+          "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==",
           "dev": true
         }
       }
@@ -9344,9 +9344,9 @@
       }
     },
     "mocha": {
-      "version": "6.2.1",
-      "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.2.1.tgz",
-      "integrity": "sha512-VCcWkLHwk79NYQc8cxhkmI8IigTIhsCwZ6RTxQsqK6go4UvEhzJkYuHm8B2YtlSxcYq2fY+ucr4JBwoD6ci80A==",
+      "version": "6.2.2",
+      "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.2.2.tgz",
+      "integrity": "sha512-FgDS9Re79yU1xz5d+C4rv1G7QagNGHZ+iXF81hO8zY35YZZcLEsJVfFolfsqKFWunATEvNzMK0r/CwWd/szO9A==",
       "dev": true,
       "requires": {
         "ansi-colors": "3.2.3",
diff --git a/package.json b/package.json
index 0e591f8535..f5cb42c716 100644
--- a/package.json
+++ b/package.json
@@ -162,7 +162,7 @@
     "jsverify": "0.8.4",
     "license-cli": "1.1.6",
     "lint-staged": "9.4.2",
-    "mocha": "6.2.1",
+    "mocha": "6.2.2",
     "mocha-junit-reporter": "1.23.1",
     "mocha-multi-reporters": "1.1.7",
     "monet": "0.9.0",
diff --git a/src/index.js b/src/index.js
index 7a81dc141d..0dbe54041a 100644
--- a/src/index.js
+++ b/src/index.js
@@ -80,6 +80,7 @@ export { default as isNotRegExp } from './isNotRegExp';
 export { default as isSet } from './isSet';
 export { default as isSparseArray } from './isSparseArray';
 export { default as isSymbol } from './isSymbol';
+export { default as isSafeInteger } from './isSafeInteger';
 // Function
 export { default as stubUndefined } from './stubUndefined';
 export { default as stubNull } from './stubNull';
@@ -191,5 +192,7 @@ export { default as subtractNum } from './subtractNum';
 // String
 export { default as replaceAll } from './replaceAll';
 export { default as escapeRegExp } from './escapeRegExp';
+export { default as trimStart } from './trimStart';
+export { default as trimLeft } from './trimStart'; // alias of trimStart
 // Types
 export { default as Identity } from './fantasy-land/Identity';
diff --git a/src/internal/polyfills/Number.isSafeInteger.js b/src/internal/polyfills/Number.isSafeInteger.js
new file mode 100644
index 0000000000..82b2d39760
--- /dev/null
+++ b/src/internal/polyfills/Number.isSafeInteger.js
@@ -0,0 +1,11 @@
+import { both } from 'ramda';
+
+import isInteger from '../../isInteger';
+import MAX_SAFE_INTEGER from './Number.MAX_SAFE_INTEGER';
+
+const isSafeIntegerPolyfill = both(
+  isInteger,
+  value => Math.abs(value) <= MAX_SAFE_INTEGER
+);
+
+export default isSafeIntegerPolyfill;
diff --git a/src/internal/polyfills/String.trimStart.js b/src/internal/polyfills/String.trimStart.js
new file mode 100644
index 0000000000..0875eee2de
--- /dev/null
+++ b/src/internal/polyfills/String.trimStart.js
@@ -0,0 +1,5 @@
+import { replace } from 'ramda';
+
+const trimStart = replace(/^\s+/, '');
+
+export default trimStart;
diff --git a/src/isSafeInteger.js b/src/isSafeInteger.js
new file mode 100644
index 0000000000..4bd4fc4e03
--- /dev/null
+++ b/src/isSafeInteger.js
@@ -0,0 +1,41 @@
+import { bind } from 'ramda';
+import curry1 from 'ramda/src/internal/_curry1';
+
+import isFunction from './isFunction';
+import polyfill from './internal/polyfills/Number.isSafeInteger';
+
+export const isSafeIntegerPolyfill = curry1(polyfill);
+
+/**
+ * Checks whether the passed value is a safe `integer`.
+ *
+ * @func isSafeInteger
+ * @memberOf RA
+ * @since {@link https://char0n.github.io/ramda-adjunct/2.22.0|v2.22.0}
+ * @category Type
+ * @sig * -> Boolean
+ * @param {*} val The value to test
+ * @return {boolean}
+ * @example
+ *
+ * RA.isSafeInteger(3); //=> true
+ * RA.isSafeInteger(Math.pow(2, 53)) //=> false
+ * RA.isSafeInteger(Math.pow(2, 53) - 1); //=> true
+ * RA.isSafeInteger(NaN); //=> false
+ * RA.isSafeInteger(Infinity); //=> false
+ * RA.isSafeInteger('3') //=> false
+ * RA.isSafeInteger(3.1); //=> false
+ * RA.isSafeInteger(3.0); //=> true
+ * RA.isSafeInteger('string'); //=> false
+ * RA.isSafeInteger(null); //=> false
+ * RA.isSafeInteger(undefined); //=> false
+ * RA.isSafeInteger({}); //=> false
+ * RA.isSafeInteger(() => { }); //=> false
+ * RA.isSafeInteger(true); //=> false
+ */
+
+const isSafeInteger = isFunction(Number.isSafeInteger)
+  ? curry1(bind(Number.isSafeInteger, Number))
+  : isSafeIntegerPolyfill;
+
+export default isSafeInteger;
diff --git a/src/trimStart.js b/src/trimStart.js
new file mode 100644
index 0000000000..a249bc9c38
--- /dev/null
+++ b/src/trimStart.js
@@ -0,0 +1,29 @@
+import { invoker } from 'ramda';
+
+import polyfill from './internal/polyfills/String.trimStart';
+import isFunction from './isFunction';
+
+export const trimStartPolyfill = polyfill;
+
+export const trimStartInvoker = invoker(0, 'trimStart');
+
+/**
+ * Removes whitespace from the beginning of a string.
+ *
+ * @func trimStart
+ * @memberOf RA
+ * @since {@link https://char0n.github.io/ramda-adjunct/2.22.0|v2.22.0}
+ * @category String
+ * @sig String -> String
+ * @param {string} value String value to have the whitespace removed from the beginning
+ * @return {string} A new string representing the calling string stripped of whitespace from its beginning (left end).
+ * @example
+ *
+ * RA.trimStart('  abc'); //=> 'abc'
+ */
+
+const trimStart = isFunction(String.prototype.trimStart)
+  ? trimStartInvoker
+  : trimStartPolyfill;
+
+export default trimStart;
diff --git a/types/index.d.ts b/types/index.d.ts
index 7f2d017d62..56631a0cde 100644
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -330,6 +330,11 @@ declare namespace RamdaAdjunct {
          */
         isFloat(val: any): val is number;
 
+        /**
+         * Checks whether the passed value is a safe `integer`.
+         */
+        isSafeInteger(val: any): boolean;
+
         /**
          * Checks whether the passed value is complement of a `float`.
          */
@@ -1236,6 +1241,12 @@ declare namespace RamdaAdjunct {
          * Returns true if all items in the list are unique. `R.equals` is used to determine equality.
          */
         allUnique<T>(list: T[]): boolean;
+
+        /**
+         * Removes whitespace from the beginning of a string
+         */
+        trimStart(value: string): string;
+        trimLeft(value: string): string;
     }
 }
 


Test patch:
diff --git a/test/isSafeInteger.js b/test/isSafeInteger.js
new file mode 100644
index 0000000000..9e187a9dff
--- /dev/null
+++ b/test/isSafeInteger.js
@@ -0,0 +1,70 @@
+import { assert } from 'chai';
+import * as R from 'ramda';
+
+import * as RA from '../src';
+import { isSafeIntegerPolyfill } from '../src/isSafeInteger';
+import MAX_SAFE_INTEGER from '../src/internal/polyfills/Number.MAX_SAFE_INTEGER';
+
+describe('isSafeInteger', function() {
+  context('given a safe integer', function() {
+    specify('should return true', function() {
+      assert.isTrue(RA.isSafeInteger(3));
+      assert.isTrue(RA.isSafeInteger(MAX_SAFE_INTEGER));
+      assert.isTrue(RA.isSafeInteger(3.0));
+    });
+  });
+
+  context('given not safe integer', function() {
+    specify('should return false', function() {
+      assert.isFalse(RA.isSafeInteger(MAX_SAFE_INTEGER + 1));
+      assert.isFalse(RA.isSafeInteger(NaN));
+      assert.isFalse(RA.isSafeInteger(Infinity));
+      assert.isFalse(RA.isSafeInteger('3'));
+      assert.isFalse(RA.isSafeInteger(3.1));
+      assert.isFalse(RA.isSafeInteger('string'));
+      assert.isFalse(RA.isSafeInteger(null));
+      assert.isFalse(RA.isSafeInteger(undefined));
+      assert.isFalse(RA.isSafeInteger({}));
+      assert.isFalse(RA.isSafeInteger(() => {}));
+      assert.isFalse(RA.isSafeInteger(true));
+    });
+  });
+
+  specify('should support placeholder to specify "gaps"', function() {
+    const isSafeInteger = RA.isSafeInteger(R.__);
+
+    assert.isTrue(isSafeInteger(3));
+  });
+
+  context('isSafeIntegerPolyfill', function() {
+    context('given a safe integer', function() {
+      specify('should return true', function() {
+        assert.isTrue(isSafeIntegerPolyfill(3));
+        assert.isTrue(isSafeIntegerPolyfill(MAX_SAFE_INTEGER));
+        assert.isTrue(isSafeIntegerPolyfill(3.0));
+      });
+    });
+
+    context('given not safe integer', function() {
+      specify('should return false', function() {
+        assert.isFalse(isSafeIntegerPolyfill(MAX_SAFE_INTEGER + 1));
+        assert.isFalse(isSafeIntegerPolyfill(NaN));
+        assert.isFalse(isSafeIntegerPolyfill(Infinity));
+        assert.isFalse(isSafeIntegerPolyfill('3'));
+        assert.isFalse(isSafeIntegerPolyfill(3.1));
+        assert.isFalse(isSafeIntegerPolyfill('string'));
+        assert.isFalse(isSafeIntegerPolyfill(null));
+        assert.isFalse(isSafeIntegerPolyfill(undefined));
+        assert.isFalse(isSafeIntegerPolyfill({}));
+        assert.isFalse(isSafeIntegerPolyfill(() => {}));
+        assert.isFalse(isSafeIntegerPolyfill(true));
+      });
+    });
+
+    specify('should support placeholder to specify "gaps"', function() {
+      const isSafeInteger = isSafeIntegerPolyfill(R.__);
+
+      assert.isTrue(isSafeInteger(3));
+    });
+  });
+});
diff --git a/test/trimStart.js b/test/trimStart.js
new file mode 100644
index 0000000000..8a57b5232e
--- /dev/null
+++ b/test/trimStart.js
@@ -0,0 +1,119 @@
+import * as R from 'ramda';
+
+import * as RA from '../src';
+import { trimStartInvoker, trimStartPolyfill } from '../src/trimStart';
+import eq from './shared/eq';
+
+describe('trimStart', function() {
+  specify(
+    'should remove whitespace from the beginning of a string',
+    function() {
+      eq(RA.trimStart('  abc'), 'abc');
+    }
+  );
+
+  specify(
+    'should not remove whitespace from the rest of the string',
+    function() {
+      eq(RA.trimStart('a b c '), 'a b c ');
+    }
+  );
+
+  context('given an empty string', function() {
+    specify('should return an empty string', function() {
+      eq(RA.trimStart(''), '');
+    });
+  });
+
+  context('given an string with whitespaces only', function() {
+    specify('should return an empty string', function() {
+      eq(RA.trimStart('   '), '');
+    });
+  });
+
+  specify('should support placeholder to specify "gaps"', function() {
+    const trimStart = RA.trimStart(R.__);
+
+    eq(trimStart('  abc'), 'abc');
+  });
+
+  context('trimStartInvoker', function() {
+    beforeEach(function() {
+      if (RA.isNotFunction(String.prototype.trimStart)) {
+        this.skip();
+      }
+    });
+
+    specify(
+      'should remove whitespace from the beginning of a string',
+      function() {
+        eq(trimStartInvoker('  abc'), 'abc');
+      }
+    );
+
+    specify(
+      'should not remove whitespace from the rest of the string',
+      function() {
+        eq(trimStartInvoker('a b c '), 'a b c ');
+      }
+    );
+
+    context('given an empty string', function() {
+      specify('should return an empty string', function() {
+        eq(trimStartInvoker(''), '');
+      });
+    });
+
+    context('given an string with whitespaces only', function() {
+      specify('should return an empty string', function() {
+        eq(trimStartInvoker('   '), '');
+      });
+    });
+
+    specify('should support placeholder to specify "gaps"', function() {
+      const trimStart = trimStartInvoker(R.__);
+
+      eq(trimStart('  abc'), 'abc');
+    });
+  });
+
+  context('trimStartPolyfill', function() {
+    specify(
+      'should remove whitespace from the beginning of a string',
+      function() {
+        eq(trimStartPolyfill('  abc'), 'abc');
+      }
+    );
+
+    specify(
+      'should not remove whitespace from the rest of the string',
+      function() {
+        eq(trimStartPolyfill('a b c '), 'a b c ');
+      }
+    );
+
+    context('given an empty string', function() {
+      specify('should return an empty string', function() {
+        eq(trimStartPolyfill(''), '');
+      });
+    });
+
+    context('given an string with whitespaces only', function() {
+      specify('should return an empty string', function() {
+        eq(trimStartPolyfill('   '), '');
+      });
+    });
+
+    specify('should support placeholder to specify "gaps"', function() {
+      const trimStart = trimStartPolyfill(R.__);
+
+      eq(trimStart('  abc'), 'abc');
+    });
+  });
+});
+
+describe('trimLeft', function() {
+  it('tests an alias', function() {
+    eq(RA.trimLeft === RA.trimStart, true);
+  });
+});
diff --git a/types/test/isSafeInteger.ts b/types/test/isSafeInteger.ts
new file mode 100644
index 0000000000..0482e774d2
--- /dev/null
+++ b/types/test/isSafeInteger.ts
@@ -0,0 +1,16 @@
+import * as RA from 'ramda-adjunct';
+
+RA.isSafeInteger(3); // $ExpectType boolean
+RA.isSafeInteger(Math.pow(2, 53)); // $ExpectType boolean
+RA.isSafeInteger(Math.pow(2, 53) - 1); // $ExpectType boolean
+RA.isSafeInteger(NaN); // $ExpectType boolean
+RA.isSafeInteger(Infinity); // $ExpectType boolean
+RA.isSafeInteger('3'); // $ExpectType boolean
+RA.isSafeInteger(3.1); // $ExpectType boolean
+RA.isSafeInteger(3.0); // $ExpectType boolean
+RA.isSafeInteger('string'); // $ExpectType boolean
+RA.isSafeInteger(null); // $ExpectType boolean
+RA.isSafeInteger(undefined); // $ExpectType boolean
+RA.isSafeInteger({}); // $ExpectType boolean
+RA.isSafeInteger(() => { }); // $ExpectType boolean
+RA.isSafeInteger(true); // $ExpectType boolean
diff --git a/types/test/trimStart.ts b/types/test/trimStart.ts
new file mode 100644
index 0000000000..f3899cbb35
--- /dev/null
+++ b/types/test/trimStart.ts
@@ -0,0 +1,19 @@
+import * as RA from 'ramda-adjunct';
+
+RA.trimStart('abc'); // $ExpectType string
+RA.trimStart(''); // $ExpectType string
+
+RA.trimStart(1); // $ExpectError
+RA.trimStart({}); // $ExpectError
+RA.trimStart(null); // $ExpectError
+RA.trimStart(undefined); // $ExpectError
+RA.trimStart([]); // $ExpectError
+
+RA.trimLeft('abc'); // $ExpectType string
+RA.trimLeft(''); // $ExpectType string
+
+RA.trimLeft(1); // $ExpectError
+RA.trimLeft({}); // $ExpectError
+RA.trimLeft(null); // $ExpectError
+RA.trimLeft(undefined); // $ExpectError
+RA.trimLeft([]); // $ExpectError