diff --git a/packages/grafana-data/src/text/string.test.ts b/packages/grafana-data/src/text/string.test.ts index b9cd3eb0866..c0ab2b3a854 100644 --- a/packages/grafana-data/src/text/string.test.ts +++ b/packages/grafana-data/src/text/string.test.ts @@ -57,12 +57,32 @@ describe('stringToMs', () => { }); }); +describe('[un]escapeStringForRegex', () => { + it.each([ + '[]', + '\\', + '[(abc])', + 'onetwothree', + '', + 'string\\with\\backslash', + 'everyspecialchar([{])}.,/?&*-^&<>#', + ])('should be symmetric', (input) => { + const output = unEscapeStringFromRegex(escapeStringForRegex(input)); + expect(output).toEqual(input); + }); +}); + describe('escapeStringForRegex', () => { - describe('when using a string with special chars', () => { - it('then all special chars should be escaped', () => { - const result = escapeStringForRegex('([{}])|*+-.?<>#&^$'); - expect(result).toBe('\\(\\[\\{\\}\\]\\)\\|\\*\\+\\-\\.\\?\\<\\>\\#\\&\\^\\$'); - }); + it.each([ + '[[[', + '[]\\', + '[(abc])', + 'onetwothree', + '', + 'string\\with\\backslash', + 'everyspecialchar([{])}.,/?&*-^&<>#', + ])('should always produce output that compiles', (value) => { + expect(() => new RegExp(escapeStringForRegex(value))).not.toThrowError(); }); describe('when using a string without special chars', () => { diff --git a/packages/grafana-data/src/text/string.ts b/packages/grafana-data/src/text/string.ts index 79cf3ae05cd..12f628c7cb0 100644 --- a/packages/grafana-data/src/text/string.ts +++ b/packages/grafana-data/src/text/string.ts @@ -1,12 +1,16 @@ import { camelCase } from 'lodash'; -const specialChars = ['(', '[', '{', '}', ']', ')', '|', '*', '+', '-', '.', '?', '<', '>', '#', '&', '^', '$']; + +const specialChars = ['(', '[', '{', '}', ']', ')', '\\', '|', '*', '+', '-', '.', '?', '<', '>', '#', '&', '^', '$']; +const specialMatcher = '([\\' + specialChars.join('\\') + '])'; +const specialCharEscape = new RegExp(specialMatcher, 'g'); +const specialCharUnescape = new RegExp('(\\\\)' + specialMatcher, 'g'); export const escapeStringForRegex = (value: string) => { if (!value) { return value; } - return specialChars.reduce((escaped, currentChar) => escaped.replace(currentChar, '\\' + currentChar), value); + return value.replace(specialCharEscape, '\\$1'); }; export const unEscapeStringFromRegex = (value: string) => { @@ -14,7 +18,7 @@ export const unEscapeStringFromRegex = (value: string) => { return value; } - return specialChars.reduce((escaped, currentChar) => escaped.replace('\\' + currentChar, currentChar), value); + return value.replace(specialCharUnescape, '$2'); }; export function stringStartsAsRegEx(str: string): boolean {