Repository navigation
Closed
Description
es-toolkit version
1.52.0
Entry point
es-toolkit/compat
Reproduction
import { truncate } from 'es-toolkit/compat';
// 1. Quantifiers and bracket characters cause fatal syntax errors:
truncate('hello world? how are you doing today?', { length: 20, separator: '?' });
// SyntaxError: Invalid regular expression: /(?<result>.*(?:(?!?).))(?:?)/u: Nothing to repeat
truncate('section [1] details and notes', { length: 20, separator: '[' });
// SyntaxError: Invalid regular expression: /(?<result>.*(?:(?![).))(?:[)/u: Unterminated character class
truncate('part (A) overview notes', { length: 20, separator: '(' });
// SyntaxError: Invalid regular expression: /(?<result>.*(?:(?!().))(?:()/u: Unterminated group
truncate('a + b + c + d + e', { length: 10, separator: '+' });
// SyntaxError: Invalid regular expression: /(?<result>.*(?:(?!+).))(?:+)/u: Nothing to repeat
// 2. Wildcard metacharacters match unintended characters:
truncate('hello world foo bar', { length: 15, separator: '.' });
// Returns 'hello world ...' because '.' in RegExp matches any character rather than a literal period.Current vs. expected behavior
Lodash (Expected)
Lodash accepts any string separator and treats it as a literal substring:
import _ from 'lodash';
_.truncate('hello world? how are you doing today?', { length: 20, separator: '?' });
// => "hello world?..."
_.truncate('section [1] details and notes', { length: 20, separator: '[' });
// => "section ..."
_.truncate('a + b + c + d + e', { length: 10, separator: '+' });
// => "a + b + c..."
_.truncate('hello world foo bar', { length: 15, separator: '.' });
// => "hello world ..." (doesn't cut at arbitrary letters because '.' is treated as a literal character)es-toolkit/compat (Current)
In src/compat/string/truncate.ts (lines 84-86):
const search = separator instanceof RegExp ? separator.source : separator;
const flags = 'u' + (separator instanceof RegExp ? separator.flags.replace('u', '') : '');
const withoutSeparator = new RegExp(`(?<result>.*(?:(?!${search}).))(?:${search})`, flags).exec(base);When separator is a string, it is directly interpolated into new RegExp(...) without escaping regex metacharacters (?, +, *, [, (, \, {, |, .). This causes:
- Fatal
SyntaxErrorcrashes on quantifiers and bracket characters. - Wildcard matching when using
.as a separator.
Root cause & Proposed fix
The string separator should be escaped using escapeRegExp(separator) before being interpolated into the RegExp. es-toolkit already provides escapeRegExp in src/string/escapeRegExp.ts.
I have isolated the fix and verified it locally with regression tests, and would be happy to open a PR for this.
Validations
- I searched the existing issues and discussions.
- I read the Contributing Guidelines.
- I wrote this description myself.
Metadata
Metadata
Assignees
Labels
No labels
Activity
fix(compat/truncate): escape string separator in RegExp (#2129) (#2130)