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 trimEnd #808
Closes #808

Patch:
diff --git a/src/index.js b/src/index.js
index a4a4f5b15d..238ced776b 100644
--- a/src/index.js
+++ b/src/index.js
@@ -196,6 +196,8 @@ 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
+export { default as trimEnd } from './trimEnd';
+export { default as trimRight } from './trimEnd'; // alias of trimEnd
 export { default as padCharsEnd } from './padCharsEnd';
 // Types
 export { default as Identity } from './fantasy-land/Identity';
diff --git a/src/internal/polyfills/String.trimEnd.js b/src/internal/polyfills/String.trimEnd.js
new file mode 100644
index 0000000000..4c5e20b90d
--- /dev/null
+++ b/src/internal/polyfills/String.trimEnd.js
@@ -0,0 +1,5 @@
+import { replace } from 'ramda';
+
+const trimStart = replace(/\s+$/, '');
+
+export default trimStart;
diff --git a/src/trimEnd.js b/src/trimEnd.js
new file mode 100644
index 0000000000..7d3ebb4d3f
--- /dev/null
+++ b/src/trimEnd.js
@@ -0,0 +1,30 @@
+import { invoker } from 'ramda';
+
+import polyfill from './internal/polyfills/String.trimEnd';
+import isFunction from './isFunction';
+
+export const trimEndPolyfill = polyfill;
+
+export const trimEndInvoker = invoker(0, 'trimEnd');
+
+/**
+ * Removes whitespace from the end of a string.
+ *
+ * @func trimEnd
+ * @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 end
+ * @return {string} A new string representing the calling string stripped of whitespace from its end (right end).
+ * @see {@link RA.trimEnd|trimEnd}
+ * @example
+ *
+ * RA.trimEnd('abc   '); //=> 'abc'
+ */
+
+const trimEnd = isFunction(String.prototype.trimEnd)
+  ? trimEndInvoker
+  : trimEndPolyfill;
+
+export default trimEnd;
diff --git a/types/index.d.ts b/types/index.d.ts
index 1d6c7429e3..dcc3de9743 100644
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -1254,7 +1254,13 @@ declare namespace RamdaAdjunct {
         trimStart(value: string): string;
         trimLeft(value: string): string;
 
-        /*
+        /**
+         * Removes whitespace from the end of a string.
+         */
+        trimEnd(value: string): string;
+        trimRight(value: string): string;
+
+        /**
          * The function pads the current string with a given string
          * (repeated, if needed) so that the resulting string reaches a given length.
          * The padding is applied from the end of the current string.


Test patch:
diff --git a/test/trimEnd.js b/test/trimEnd.js
new file mode 100644
index 0000000000..17d8466294
--- /dev/null
+++ b/test/trimEnd.js
@@ -0,0 +1,110 @@
+import * as R from 'ramda';
+
+import * as RA from '../src';
+import { trimEndInvoker, trimEndPolyfill } from '../src/trimEnd';
+import eq from './shared/eq';
+
+describe('trimEnd', function() {
+  specify('should remove whitespace from the end of a string', function() {
+    eq(RA.trimEnd('abc   '), 'abc');
+  });
+
+  specify(
+    'should not remove whitespace from the rest of the string',
+    function() {
+      eq(RA.trimEnd(' a b c'), ' a b c');
+    }
+  );
+
+  context('given an empty string', function() {
+    specify('should return an empty string', function() {
+      eq(RA.trimEnd(''), '');
+    });
+  });
+
+  context('given an string with whitespaces only', function() {
+    specify('should return an empty string', function() {
+      eq(RA.trimEnd('   '), '');
+    });
+  });
+
+  specify('should support placeholder to specify "gaps"', function() {
+    const trimEnd = RA.trimEnd(R.__);
+
+    eq(trimEnd('abc   '), 'abc');
+  });
+
+  context('trimEndInvoker', function() {
+    beforeEach(function() {
+      if (RA.isNotFunction(String.prototype.trimEnd)) {
+        this.skip();
+      }
+    });
+
+    specify('should remove whitespace from the end of a string', function() {
+      eq(trimEndInvoker('abc   '), 'abc');
+    });
+
+    specify(
+      'should not remove whitespace from the rest of the string',
+      function() {
+        eq(trimEndInvoker(' a b c'), ' a b c');
+      }
+    );
+
+    context('given an empty string', function() {
+      specify('should return an empty string', function() {
+        eq(trimEndInvoker(''), '');
+      });
+    });
+
+    context('given an string with whitespaces only', function() {
+      specify('should return an empty string', function() {
+        eq(trimEndInvoker('   '), '');
+      });
+    });
+
+    specify('should support placeholder to specify "gaps"', function() {
+      const trimEnd = trimEndInvoker(R.__);
+
+      eq(trimEnd('abc   '), 'abc');
+    });
+  });
+
+  context('trimEndPolyfill', function() {
+    specify('should remove whitespace from the end of a string', function() {
+      eq(trimEndPolyfill('abc   '), 'abc');
+    });
+
+    specify(
+      'should not remove whitespace from the rest of the string',
+      function() {
+        eq(trimEndPolyfill(' a b c'), ' a b c');
+      }
+    );
+
+    context('given an empty string', function() {
+      specify('should return an empty string', function() {
+        eq(trimEndPolyfill(''), '');
+      });
+    });
+
+    context('given an string with whitespaces only', function() {
+      specify('should return an empty string', function() {
+        eq(trimEndPolyfill('   '), '');
+      });
+    });
+
+    specify('should support placeholder to specify "gaps"', function() {
+      const trimEnd = trimEndPolyfill(R.__);
+
+      eq(trimEnd('abc   '), 'abc');
+    });
+  });
+});
+
+describe('trimRight', function() {
+  it('should be alias of trimEnd', function() {
+    eq(RA.trimRight === RA.trimEnd, true);
+  });
+});
diff --git a/types/test/trimEnd.ts b/types/test/trimEnd.ts
new file mode 100644
index 0000000000..ac20f41ad0
--- /dev/null
+++ b/types/test/trimEnd.ts
@@ -0,0 +1,20 @@
+import * as RA from 'ramda-adjunct';
+
+RA.trimEnd('abc'); // $ExpectType string
+RA.trimEnd(''); // $ExpectType string
+
+RA.trimEnd(1); // $ExpectError
+RA.trimEnd({}); // $ExpectError
+RA.trimEnd(null); // $ExpectError
+RA.trimEnd(undefined); // $ExpectError
+RA.trimEnd([]); // $ExpectError
+
+// alias
+RA.trimRight('abc'); // $ExpectType string
+RA.trimRight(''); // $ExpectType string
+
+RA.trimRight(1); // $ExpectError
+RA.trimRight({}); // $ExpectError
+RA.trimRight(null); // $ExpectError
+RA.trimRight(undefined); // $ExpectError
+RA.trimRight([]); // $ExpectError