diff --git a/web/ui/module/codemirror-promql/src/complete/hybrid.test.ts b/web/ui/module/codemirror-promql/src/complete/hybrid.test.ts index cc73161dce..e958a8113b 100644 --- a/web/ui/module/codemirror-promql/src/complete/hybrid.test.ts +++ b/web/ui/module/codemirror-promql/src/complete/hybrid.test.ts @@ -652,11 +652,22 @@ describe('durationWithUnitRegexp test', () => { { input: '2d', expected: true }, { input: '1w', expected: true }, { input: '1y', expected: true }, + { input: '1d2h', expected: true }, + { input: '2h30m', expected: true }, + { input: '1h2m3s', expected: true }, + { input: '250ms2s', expected: true }, + { input: '2h3m4s5ms', expected: true }, + { input: '5', expected: false }, + { input: '5m5', expected: false }, + { input: 'm', expected: false }, + { input: 'd', expected: false }, + { input: '', expected: false }, + { input: '1hms', expected: false }, + { input: '2x', expected: false }, ]; - testCases.forEach(({ input, expected }) => { expect(durationWithUnitRegexp.test(input)).toBe(expected); - }); + }); }); it('should not match durations without units or partial units', () => { diff --git a/web/ui/module/codemirror-promql/src/complete/hybrid.ts b/web/ui/module/codemirror-promql/src/complete/hybrid.ts index 8a2d575552..36fb59be5b 100644 --- a/web/ui/module/codemirror-promql/src/complete/hybrid.ts +++ b/web/ui/module/codemirror-promql/src/complete/hybrid.ts @@ -166,13 +166,25 @@ function arrayToCompletionResult(data: Completion[], from: number, to: number, i } as CompletionResult; } -// Matches complete duration with units (e.g., 5m, 30s, 1h, 500ms) -export const durationWithUnitRegexp = new RegExp(`^\\d+(?:${durationTerms.map((term) => escapeRegExp(term.label)).join('|')})$`); - -function escapeRegExp(value: string): string { - return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +// Polyfill RegExp.escape for compatibility with ES2024 and TypeScript. +// Ensures safe, standards-based regex escaping in all environments. +declare global { + interface RegExpConstructor { + escape?: (s: string) => string; + } } +if (!RegExp.escape) { + RegExp.escape = function(s: string): string { + return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + }; +} + +// Matches complete PromQL durations, including compound units (e.g., 5m, 1d2h, 1h30m, etc.) +export const durationWithUnitRegexp = new RegExp( + `^(\\d+(${durationTerms.map(term => RegExp.escape!(term.label)).join('|')}))+$` +); + // Determines if a duration already has a complete time unit to prevent autocomplete insertion (issue #15452) function hasCompleteDurationUnit(state: EditorState, node: SyntaxNode): boolean { if (node.from >= node.to) {