Skip to content
Merged
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- `ltrim`, `rtrim` and `trim` string type utility functions

## [2.0.0] - 2025-07-29

### Added
Expand Down
30 changes: 29 additions & 1 deletion src/lib/string.spec.ts
Original file line number Diff line number Diff line change
@@ -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([
Expand Down Expand Up @@ -120,4 +120,32 @@ describe("string tests", () => {
])("truncate without suffix parameter", (value, maxLength, expected) => {
expect(truncate(value, maxLength)).toBe(expected);
});

test.each([
["", " ", ""],
Comment thread
manni497 marked this conversation as resolved.
Outdated
["", "", ""],
["hello world", "", "hello world"],
[" hello world", " ", "hello world"],
])("left trim", (haystack, needle, expected) => {
expect(ltrim(haystack, needle)).toBe(expected);
});

test.each([
["", " ", ""],
["", "", ""],
["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([
["", " ", ""],
["", "", ""],
["hello world", "", "hello world"],
Comment thread
manni497 marked this conversation as resolved.
[" hello world ", " ", "hello world"],
])("trim", (haystack, needle, expected) => {
expect(trim(haystack, needle)).toBe(expected);
});
});
62 changes: 62 additions & 0 deletions src/lib/string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,65 @@ 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 {
Comment thread
manni497 marked this conversation as resolved.
Outdated
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 {
Comment thread
manni497 marked this conversation as resolved.
Outdated
const needleLength = needle.length,
Comment thread
manni497 marked this conversation as resolved.
Outdated
haystackLength = haystack.length;

if (needleLength === 0 || haystackLength === 0) {
return haystack;
}

let offset = haystackLength,
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);
}
Loading