export type ReplaceCallback = ( match: RegExpExecArray | RegExpMatchArray, pushIndex: number, ) => R; export type ConvertPartCallback = (text: string, pushIndex: number) => R; export const findAndReplace = ( text: string, regex: RegExp, replace: ReplaceCallback, convertPart: ConvertPartCallback, ): Array => { const result: Array = []; let lastEnd = 0; let match: RegExpExecArray | RegExpMatchArray | null = regex.exec(text); while (match !== null && typeof match.index === 'number') { result.push(convertPart(text.slice(lastEnd, match.index), result.length)); result.push(replace(match, result.length)); lastEnd = match.index + match[0].length; // A non-global regex always returns the same first match from exec() (its // lastIndex never advances), so re-running it would loop forever. Treat a // non-global regex as a single match and stop after processing it. match = regex.global ? regex.exec(text) : null; } result.push(convertPart(text.slice(lastEnd), result.length)); return result; };