Skip to content

Commit 5900fa9

Browse files
perf(trimStart): optimize invisible character removal with single regex replace
1 parent c9ea247 commit 5900fa9

2 files changed

Lines changed: 20 additions & 9 deletions

File tree

packages/helpers/src/strings.js

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
11
const invisibleCharRegex = '[\\s\\p{C}\u034f\u17b4\u17b5\u2800\u115f\u1160\u3164\uffa0]';
2-
const trimStartRegex = new RegExp('^' + invisibleCharRegex, 'u');
2+
const trimStartRegex = new RegExp('^' + invisibleCharRegex + '+', 'u');
33
const trimEndRegex = new RegExp(invisibleCharRegex + '$', 'u');
44

55
export function trimStart(str) {
6-
while (trimStartRegex.test(str)) {
7-
str = str.replace(trimStartRegex, '');
8-
}
9-
10-
return str;
6+
return str.replace(trimStartRegex, '');
117
}
128

139
export function trimEnd(str) {

packages/helpers/src/strings.test.js

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,31 @@ import { trimEnd, trimStart, truncate } from './strings.js';
33
describe('helpers', () => {
44
describe('trimStart', () => {
55
it('should remove invisible characters from the start of a string', () => {
6-
const input = '\u034f\u034fHello';
7-
const result = trimStart(input);
8-
expect(result).toBe('Hello');
6+
expect(trimStart(' A')).toBe('A');
7+
expect(trimStart('\nB')).toBe('B');
8+
expect(trimStart('\tC')).toBe('C');
9+
expect(trimStart('\rD')).toBe('D');
10+
expect(trimStart('\u034fE')).toBe('E');
11+
expect(trimStart('\u17b4F')).toBe('F');
12+
expect(trimStart('\u17b5G')).toBe('G');
13+
expect(trimStart('\u2800F')).toBe('F');
14+
expect(trimStart('\u115fH')).toBe('H');
15+
expect(trimStart('\u1160I')).toBe('I');
16+
expect(trimStart('\u3164J')).toBe('J');
17+
expect(trimStart('\uffa0K')).toBe('K');
918
});
1019

1120
it('should return the same string if no invisible characters are at the start', () => {
1221
const input = 'Hello';
1322
const result = trimStart(input);
1423
expect(result).toBe('Hello');
1524
});
25+
26+
it('should not hang (ReDoS) on long string of invisible chars', () => {
27+
const long = '\u034f'.repeat(1_000_000) + 'Hello';
28+
const result = trimStart(long);
29+
expect(result).toBe('Hello');
30+
}, 200);
1631
});
1732

1833
describe('trimEnd', () => {

0 commit comments

Comments
 (0)