From 6bf9d07e5f462a80355f82a0d8d20ac3ff8208bd Mon Sep 17 00:00:00 2001 From: Dominic Baur Date: Tue, 26 Aug 2025 09:10:35 +0200 Subject: [PATCH 01/10] Add string utility functions: ltrim, rtrim, trim (neolution-ch#29) --- src/lib/string.spec.ts | 62 +++++++++++++++++++++++++++++++++++++- src/lib/string.ts | 68 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+), 1 deletion(-) diff --git a/src/lib/string.spec.ts b/src/lib/string.spec.ts index 94fc71e..ae26d18 100644 --- a/src/lib/string.spec.ts +++ b/src/lib/string.spec.ts @@ -1,4 +1,4 @@ -import { isNullOrEmpty, isNullOrWhitespace, capitalize, uncapitalize, truncate } from "./string"; +import { isNullOrEmpty, isNullOrWhitespace, capitalize, uncapitalize, truncate, ltrim, rtrim, trim } from "./string"; describe("string tests", () => { test.each([ @@ -120,4 +120,64 @@ describe("string tests", () => { ])("truncate without suffix parameter", (value, maxLength, expected) => { expect(truncate(value, maxLength)).toBe(expected); }); + + test.each([ + [null as unknown as string, " ", null], + [undefined as unknown as string, " ", undefined], + [" hello world", " ", "hello world"], + [" hello world", " ", "hello world"], + [" hello world", " ", "hello world"], + ])("left trim", (haystack, needle, expected) => { + expect(ltrim(haystack, needle)).toBe(expected); + }); + + test.each([ + [null as unknown as string, " ", null], + [undefined as unknown as string, " ", undefined], + [" hello world", "", " hello world"], + [" hello world", "", " hello world"], + [" hello world", "", " hello world"], + ])("left trim without needle", (haystack, needle, expected) => { + expect(ltrim(haystack, needle)).toBe(expected); + }); + + test.each([ + [null as unknown as string, " ", null], + [undefined as unknown as string, " ", undefined], + ["hello world ", " ", "hello world"], + ["hello world ", " ", "hello world"], + ["hello world ", " ", "hello world"], + ])("right trim", (haystack, needle, expected) => { + expect(rtrim(haystack, needle)).toBe(expected); + }); + + test.each([ + [null as unknown as string, " ", null], + [undefined as unknown as string, " ", undefined], + ["hello world ", "", "hello world "], + ["hello world ", "", "hello world "], + ["hello world ", "", "hello world "], + ])("right trim without needle", (haystack, needle, expected) => { + expect(rtrim(haystack, needle)).toBe(expected); + }); + + test.each([ + [null as unknown as string, " ", null], + [undefined as unknown as string, " ", undefined], + [" hello world ", " ", "hello world"], + [" hello world ", " ", "hello world"], + [" hello world ", " ", "hello world"], + ])("trim", (haystack, needle, expected) => { + expect(trim(haystack, needle)).toBe(expected); + }); + + test.each([ + [null as unknown as string, " ", null], + [undefined as unknown as string, " ", undefined], + [" hello world ", "", " hello world "], + [" hello world ", "", " hello world "], + [" hello world ", "", " hello world "], + ])("trim without needle", (haystack, needle, expected) => { + expect(trim(haystack, needle)).toBe(expected); + }); }); diff --git a/src/lib/string.ts b/src/lib/string.ts index c0666b3..6ec2bac 100644 --- a/src/lib/string.ts +++ b/src/lib/string.ts @@ -64,3 +64,71 @@ export function truncate(value: string | undefined, maxLength: number, suffix = return `${value.slice(0, maxLength)}${suffix}`; } + +/** + * Removes all occurrences of needle from the start of haystack + * @param haystack string to trim + * @param needle the thing to trim + * @returns the string trimmed from the left side + */ +export function ltrim(haystack: string, needle: string): string { + if (!haystack || !needle) return haystack; + + const needleLength = needle.length; + if (needleLength === 0 || haystack.length === 0) { + return haystack; + } + + let offset = 0; + + while (haystack.indexOf(needle, offset) === offset) { + offset = offset + needleLength; + } + return haystack.slice(offset); +} + +/** + * Removes all occurrences of needle from the end of haystack + * @param haystack string to trim + * @param needle the thing to trim + * @returns the string trimmed from the right side + */ +export function rtrim(haystack: string, needle: string): string { + if (!haystack || !needle) { + return haystack; + } + + const needleLength = needle.length, + haystackLen = haystack.length; + + if (needleLength === 0 || haystackLen === 0) { + return haystack; + } + + let offset = haystackLen, + idx = -1; + + while (true) { + idx = haystack.lastIndexOf(needle, offset - 1); + if (idx === -1 || idx + needleLength !== offset) { + break; + } + if (idx === 0) { + return ""; + } + offset = idx; + } + + return haystack.slice(0, offset); +} + +/** + * Removes all occurrences of needle from the start and the end of haystack + * @param haystack string to trim + * @param needle the thing to trim + * @returns the string trimmed from the right and left side + */ +export function trim(haystack: string, needle: string): string { + const trimmed = ltrim(haystack, needle); + return rtrim(trimmed, needle); +} From b1b10f06bcbbe9ee491a03ad09a099fd632bc64a Mon Sep 17 00:00:00 2001 From: Dominic Baur Date: Tue, 26 Aug 2025 10:34:07 +0200 Subject: [PATCH 02/10] Add Changelog entry --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c15e1c6..d18ab76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.1.0] - 2025-08-26 + +### Added + +- `ltrim`, `ltrim` and `ltrim` string type utility functions + ## [2.0.0] - 2025-07-29 ### Added From bfbe6565c5dbfe7367c25412a18f38b852f716e7 Mon Sep 17 00:00:00 2001 From: Dominic Baur Date: Tue, 26 Aug 2025 14:23:22 +0200 Subject: [PATCH 03/10] Fix changelog --- CHANGELOG.md | 2 -- src/lib/string.spec.ts | 52 ++++++++---------------------------------- src/lib/string.ts | 12 +++------- 3 files changed, 13 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d18ab76..8ef0821 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [2.1.0] - 2025-08-26 - ### Added - `ltrim`, `ltrim` and `ltrim` string type utility functions diff --git a/src/lib/string.spec.ts b/src/lib/string.spec.ts index ae26d18..87e3026 100644 --- a/src/lib/string.spec.ts +++ b/src/lib/string.spec.ts @@ -122,62 +122,30 @@ describe("string tests", () => { }); test.each([ - [null as unknown as string, " ", null], - [undefined as unknown as string, " ", undefined], + ["", " ", ""], + ["", "", ""], + ["hello world", "", "hello world"], [" hello world", " ", "hello world"], - [" hello world", " ", "hello world"], - [" hello world", " ", "hello world"], ])("left trim", (haystack, needle, expected) => { expect(ltrim(haystack, needle)).toBe(expected); }); test.each([ - [null as unknown as string, " ", null], - [undefined as unknown as string, " ", undefined], - [" hello world", "", " hello world"], - [" hello world", "", " hello world"], - [" hello world", "", " hello world"], - ])("left trim without needle", (haystack, needle, expected) => { - expect(ltrim(haystack, needle)).toBe(expected); - }); - - test.each([ - [null as unknown as string, " ", null], - [undefined as unknown as string, " ", undefined], + ["", " ", ""], + ["", "", ""], + ["hello world", "hello world", ""], + ["hello world", "", "hello world"], ["hello world ", " ", "hello world"], - ["hello world ", " ", "hello world"], - ["hello world ", " ", "hello world"], ])("right trim", (haystack, needle, expected) => { expect(rtrim(haystack, needle)).toBe(expected); }); test.each([ - [null as unknown as string, " ", null], - [undefined as unknown as string, " ", undefined], - ["hello world ", "", "hello world "], - ["hello world ", "", "hello world "], - ["hello world ", "", "hello world "], - ])("right trim without needle", (haystack, needle, expected) => { - expect(rtrim(haystack, needle)).toBe(expected); - }); - - test.each([ - [null as unknown as string, " ", null], - [undefined as unknown as string, " ", undefined], + ["", " ", ""], + ["", "", ""], + ["hello world", "", "hello world"], [" hello world ", " ", "hello world"], - [" hello world ", " ", "hello world"], - [" hello world ", " ", "hello world"], ])("trim", (haystack, needle, expected) => { expect(trim(haystack, needle)).toBe(expected); }); - - test.each([ - [null as unknown as string, " ", null], - [undefined as unknown as string, " ", undefined], - [" hello world ", "", " hello world "], - [" hello world ", "", " hello world "], - [" hello world ", "", " hello world "], - ])("trim without needle", (haystack, needle, expected) => { - expect(trim(haystack, needle)).toBe(expected); - }); }); diff --git a/src/lib/string.ts b/src/lib/string.ts index 6ec2bac..2215cf9 100644 --- a/src/lib/string.ts +++ b/src/lib/string.ts @@ -72,8 +72,6 @@ export function truncate(value: string | undefined, maxLength: number, suffix = * @returns the string trimmed from the left side */ export function ltrim(haystack: string, needle: string): string { - if (!haystack || !needle) return haystack; - const needleLength = needle.length; if (needleLength === 0 || haystack.length === 0) { return haystack; @@ -94,18 +92,14 @@ export function ltrim(haystack: string, needle: string): string { * @returns the string trimmed from the right side */ export function rtrim(haystack: string, needle: string): string { - if (!haystack || !needle) { - return haystack; - } - const needleLength = needle.length, - haystackLen = haystack.length; + haystackLength = haystack.length; - if (needleLength === 0 || haystackLen === 0) { + if (needleLength === 0 || haystackLength === 0) { return haystack; } - let offset = haystackLen, + let offset = haystackLength, idx = -1; while (true) { From 2bf21995b058ca4e5a16578f31bb24194293854c Mon Sep 17 00:00:00 2001 From: "Sandro C." Date: Tue, 26 Aug 2025 14:27:01 +0200 Subject: [PATCH 04/10] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ef0821..c466d79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- `ltrim`, `ltrim` and `ltrim` string type utility functions +- `ltrim`, `rtrim` and `trim` string type utility functions ## [2.0.0] - 2025-07-29 From 874b0bef972085ffd3b7af7157e95cd5080383dd Mon Sep 17 00:00:00 2001 From: Dominic Baur Date: Tue, 26 Aug 2025 16:19:49 +0200 Subject: [PATCH 05/10] Add check for parameters with a value of null or undefined --- src/lib/string.spec.ts | 17 +++++++++-------- src/lib/string.ts | 13 +++++++------ 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/lib/string.spec.ts b/src/lib/string.spec.ts index 87e3026..820ca55 100644 --- a/src/lib/string.spec.ts +++ b/src/lib/string.spec.ts @@ -122,27 +122,28 @@ describe("string tests", () => { }); test.each([ - ["", " ", ""], - ["", "", ""], - ["hello world", "", "hello world"], + [null as unknown as string, " ", null as unknown as string], + [undefined as unknown as string, " ", undefined as unknown as string], + ["hello world", "hello world", ""], + ["hello world", " ", "hello world"], [" hello world", " ", "hello world"], ])("left trim", (haystack, needle, expected) => { expect(ltrim(haystack, needle)).toBe(expected); }); test.each([ - ["", " ", ""], - ["", "", ""], + [null as unknown as string, " ", null as unknown as string], + [undefined as unknown as string, " ", undefined as unknown as string], ["hello world", "hello world", ""], - ["hello world", "", "hello world"], ["hello world ", " ", "hello world"], + ["hello world", " ", "hello world"], ])("right trim", (haystack, needle, expected) => { expect(rtrim(haystack, needle)).toBe(expected); }); test.each([ - ["", " ", ""], - ["", "", ""], + [null as unknown as string, " ", null as unknown as string], + [undefined as unknown as string, " ", undefined as unknown as string], ["hello world", "", "hello world"], [" hello world ", " ", "hello world"], ])("trim", (haystack, needle, expected) => { diff --git a/src/lib/string.ts b/src/lib/string.ts index 2215cf9..8481f20 100644 --- a/src/lib/string.ts +++ b/src/lib/string.ts @@ -72,11 +72,12 @@ export function truncate(value: string | undefined, maxLength: number, suffix = * @returns the string trimmed from the left side */ export function ltrim(haystack: string, needle: string): string { - const needleLength = needle.length; - if (needleLength === 0 || haystack.length === 0) { + if (isNullOrEmpty(haystack) || isNullOrEmpty(needle)) { return haystack; } + const needleLength = needle.length; + let offset = 0; while (haystack.indexOf(needle, offset) === offset) { @@ -92,13 +93,13 @@ export function ltrim(haystack: string, needle: string): string { * @returns the string trimmed from the right side */ export function rtrim(haystack: string, needle: string): string { - const needleLength = needle.length, - haystackLength = haystack.length; - - if (needleLength === 0 || haystackLength === 0) { + if (isNullOrEmpty(haystack) || isNullOrEmpty(needle)) { return haystack; } + const needleLength = needle.length, + haystackLength = haystack.length; + let offset = haystackLength, idx = -1; From d9b461dcdf302d22a138af713a50b8b8f612542c Mon Sep 17 00:00:00 2001 From: Dominic Baur Date: Thu, 4 Sep 2025 10:45:48 +0200 Subject: [PATCH 06/10] Formatted --- src/lib/string.spec.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib/string.spec.ts b/src/lib/string.spec.ts index fbb475d..9bf7fbd 100644 --- a/src/lib/string.spec.ts +++ b/src/lib/string.spec.ts @@ -4,6 +4,9 @@ import { capitalize, uncapitalize, truncate, + ltrim, + rtrim, + trim, isValidSwissIbanNumber, isValidSwissSocialSecurityNumber, } from "./string"; @@ -157,7 +160,8 @@ describe("string tests", () => { ])("trim", (haystack, needle, expected) => { expect(trim(haystack, needle)).toBe(expected); }); - + + test.each([ [null as unknown as string, false], [undefined as unknown as string, false], ["CH9300762011623852957", true], From 5cd4df8d0cfce325ab3f811fda7dcc473550bc62 Mon Sep 17 00:00:00 2001 From: Dominic Baur Date: Mon, 8 Sep 2025 12:39:44 +0200 Subject: [PATCH 07/10] Formatted --- src/lib/string.spec.ts | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/lib/string.spec.ts b/src/lib/string.spec.ts index efc4501..820ca55 100644 --- a/src/lib/string.spec.ts +++ b/src/lib/string.spec.ts @@ -1,13 +1,4 @@ -import { - isNullOrEmpty, - isNullOrWhitespace, - capitalize, - uncapitalize, - truncate, - ltrim, - rtrim, - trim, -} from "./string"; +import { isNullOrEmpty, isNullOrWhitespace, capitalize, uncapitalize, truncate, ltrim, rtrim, trim } from "./string"; describe("string tests", () => { test.each([ From 7da65c5c4dd87c09a19df4ff9a81b39f25dc9423 Mon Sep 17 00:00:00 2001 From: Dominic Baur Date: Mon, 8 Sep 2025 16:37:04 +0200 Subject: [PATCH 08/10] Changed the name of the function ltrim to trimStart, the function rtrim to trimEnd and improved the code --- CHANGELOG.md | 2 +- src/lib/string.spec.ts | 9 ++++++--- src/lib/string.ts | 19 +++++++------------ 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index acdc949..960009b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- `ltrim`, `rtrim` and `trim` string type utility functions +- `trimStart`, `trimEnd` and `trim` string type utility functions ### Changed diff --git a/src/lib/string.spec.ts b/src/lib/string.spec.ts index 820ca55..95b9caa 100644 --- a/src/lib/string.spec.ts +++ b/src/lib/string.spec.ts @@ -1,4 +1,4 @@ -import { isNullOrEmpty, isNullOrWhitespace, capitalize, uncapitalize, truncate, ltrim, rtrim, trim } from "./string"; +import { isNullOrEmpty, isNullOrWhitespace, capitalize, uncapitalize, truncate, trimStart, trimEnd, trim } from "./string"; describe("string tests", () => { test.each([ @@ -128,7 +128,7 @@ describe("string tests", () => { ["hello world", " ", "hello world"], [" hello world", " ", "hello world"], ])("left trim", (haystack, needle, expected) => { - expect(ltrim(haystack, needle)).toBe(expected); + expect(trimStart(haystack, needle)).toBe(expected); }); test.each([ @@ -138,7 +138,7 @@ describe("string tests", () => { ["hello world ", " ", "hello world"], ["hello world", " ", "hello world"], ])("right trim", (haystack, needle, expected) => { - expect(rtrim(haystack, needle)).toBe(expected); + expect(trimEnd(haystack, needle)).toBe(expected); }); test.each([ @@ -146,6 +146,9 @@ describe("string tests", () => { [undefined as unknown as string, " ", undefined as unknown as string], ["hello world", "", "hello world"], [" hello world ", " ", "hello world"], + ["hello world ", " ", "hello world"], + [" hello world", " ", "hello world"], + ["hello worldhello world", "hello world", ""], ])("trim", (haystack, needle, expected) => { expect(trim(haystack, needle)).toBe(expected); }); diff --git a/src/lib/string.ts b/src/lib/string.ts index 8481f20..b8efeb9 100644 --- a/src/lib/string.ts +++ b/src/lib/string.ts @@ -71,17 +71,15 @@ export function truncate(value: string | undefined, maxLength: number, suffix = * @param needle the thing to trim * @returns the string trimmed from the left side */ -export function ltrim(haystack: string, needle: string): string { +export function trimStart(haystack: string, needle: string): string { if (isNullOrEmpty(haystack) || isNullOrEmpty(needle)) { return haystack; } - const needleLength = needle.length; - let offset = 0; while (haystack.indexOf(needle, offset) === offset) { - offset = offset + needleLength; + offset = offset + needle.length; } return haystack.slice(offset); } @@ -92,20 +90,17 @@ export function ltrim(haystack: string, needle: string): string { * @param needle the thing to trim * @returns the string trimmed from the right side */ -export function rtrim(haystack: string, needle: string): string { +export function trimEnd(haystack: string, needle: string): string { if (isNullOrEmpty(haystack) || isNullOrEmpty(needle)) { return haystack; } - const needleLength = needle.length, - haystackLength = haystack.length; - - let offset = haystackLength, + let offset = haystack.length, idx = -1; while (true) { idx = haystack.lastIndexOf(needle, offset - 1); - if (idx === -1 || idx + needleLength !== offset) { + if (idx === -1 || idx + needle.length !== offset) { break; } if (idx === 0) { @@ -124,6 +119,6 @@ export function rtrim(haystack: string, needle: string): string { * @returns the string trimmed from the right and left side */ export function trim(haystack: string, needle: string): string { - const trimmed = ltrim(haystack, needle); - return rtrim(trimmed, needle); + const trimmed = trimStart(haystack, needle); + return trimEnd(trimmed, needle); } From 08c192e8af0b73490c6e2a8d54c05b6c9044f670 Mon Sep 17 00:00:00 2001 From: Dominic Baur Date: Tue, 9 Sep 2025 10:08:37 +0200 Subject: [PATCH 09/10] Added more testscases --- src/lib/string.spec.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/string.spec.ts b/src/lib/string.spec.ts index 95b9caa..be8559e 100644 --- a/src/lib/string.spec.ts +++ b/src/lib/string.spec.ts @@ -127,6 +127,8 @@ describe("string tests", () => { ["hello world", "hello world", ""], ["hello world", " ", "hello world"], [" hello world", " ", "hello world"], + ["hello world hello world", "hello world", " hello world"], + ["hello worldhello world", "hello world", ""], ])("left trim", (haystack, needle, expected) => { expect(trimStart(haystack, needle)).toBe(expected); }); @@ -137,6 +139,8 @@ describe("string tests", () => { ["hello world", "hello world", ""], ["hello world ", " ", "hello world"], ["hello world", " ", "hello world"], + ["hello world hello world", "hello world", "hello world "], + ["hello worldhello world", "hello world", ""], ])("right trim", (haystack, needle, expected) => { expect(trimEnd(haystack, needle)).toBe(expected); }); From 6ae4533ae19ce8017b36bad8d2782a4311ccb3fd Mon Sep 17 00:00:00 2001 From: Dominic Baur Date: Tue, 9 Sep 2025 13:06:04 +0200 Subject: [PATCH 10/10] Formatted --- src/lib/string.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/string.spec.ts b/src/lib/string.spec.ts index 80dbc74..c76adcc 100644 --- a/src/lib/string.spec.ts +++ b/src/lib/string.spec.ts @@ -156,7 +156,7 @@ describe("string tests", () => { ])("trim", (haystack, needle, expected) => { expect(trim(haystack, needle)).toBe(expected); }); - + test.each([ ["", false, false, []], [null as unknown as string, false, false, []],