diff --git a/packages/bruno-app/src/utils/common/codemirror.js b/packages/bruno-app/src/utils/common/codemirror.js index 39b1cc1e4..168daf25d 100644 --- a/packages/bruno-app/src/utils/common/codemirror.js +++ b/packages/bruno-app/src/utils/common/codemirror.js @@ -1,5 +1,6 @@ import get from 'lodash/get'; import { mockDataFunctions } from '@usebruno/common'; +import { PROMPT_VARIABLE_TEXT_PATTERN } from '@usebruno/common/utils'; const CodeMirror = require('codemirror'); @@ -31,8 +32,8 @@ export const defineCodeMirrorBrunoVariablesMode = (_variables, mode, highlightPa if (ch === '}' && stream.peek() === '}') { stream.eat('}'); - // Prompt variable: starts with '?' - if (word.startsWith('?')) { + // Prompt variable: starts with '?', no leading/trailing spaces, no braces + if (PROMPT_VARIABLE_TEXT_PATTERN.test(word)) { return `variable-prompt`; } diff --git a/packages/bruno-common/src/utils/index.ts b/packages/bruno-common/src/utils/index.ts index 6042abb52..2c125dea3 100644 --- a/packages/bruno-common/src/utils/index.ts +++ b/packages/bruno-common/src/utils/index.ts @@ -13,6 +13,8 @@ export { } from './template-hasher'; export { + PROMPT_VARIABLE_TEXT_PATTERN, + PROMPT_VARIABLE_TEMPLATE_PATTERN, extractPromptVariables, extractPromptVariablesFromString } from './prompt-variables'; diff --git a/packages/bruno-common/src/utils/prompt-variables.spec.ts b/packages/bruno-common/src/utils/prompt-variables.spec.ts index 6bdf960da..cb821d4b7 100644 --- a/packages/bruno-common/src/utils/prompt-variables.spec.ts +++ b/packages/bruno-common/src/utils/prompt-variables.spec.ts @@ -45,10 +45,13 @@ describe('prompt variable utils', () => { expect(extractPromptVariables([{ text: 'Multiple {{?prompts}} in {{?one}} string', noPrompt: 'No prompt here' }, ['Another {{?test}} string', { prompt: '{{?nested}}', no: 'prompt' }]])).toEqual(['prompts', 'one', 'test', 'nested']); }); - it('should deduplicate prompt variables', () => { - // Strings - expect(extractPromptVariables(['{{?world}} prompt here', 'Hello {{?world}}'])).toEqual(['world']); - expect(extractPromptVariables(['Multiple {{?prompts}} in {{?one}} string', 'Another {{?one}} string'])).toEqual(['prompts', 'one']); + it('should not extract prompt variables from invalid template patterns', () => { + expect(extractPromptVariables('Prompt with valid {{?inner space}}')).toEqual(['inner space']); + expect(extractPromptVariables('Prompt with invalid {{? leading space}}')).toEqual([]); + expect(extractPromptVariables('Prompt with invalid {{?trailing space }}')).toEqual([]); + expect(extractPromptVariables('Prompt with invalid {{?{curly brace}}')).toEqual([]); + expect(extractPromptVariables('Prompt with invalid {{?}curly brace}}')).toEqual([]); + expect(extractPromptVariables('Prompt with invalid {{?{curly brace}}}')).toEqual([]); }); }); }); diff --git a/packages/bruno-common/src/utils/prompt-variables.ts b/packages/bruno-common/src/utils/prompt-variables.ts index a3b1a2f0d..7ef899bf7 100644 --- a/packages/bruno-common/src/utils/prompt-variables.ts +++ b/packages/bruno-common/src/utils/prompt-variables.ts @@ -1,14 +1,47 @@ +/** + * Inner regex pattern for prompt variable names (without braces or `?` prefix) + * + * Pattern: /[^{}\s](?:[^{}]*[^{}\s])?/ + * + * Breakdown: + * | Part | Meaning | + * | -------------- | ---------------------------------------------------------- | + * | `[^\s{}]` | First character: not whitespace, `{`, or `}` | + * | `(?:...)?` | Optional non-capturing group (allows single-char names) | + * | `[^{}]*` | Middle characters: any except `{` or `}` (spaces allowed) | + * | `[^\s{}]` | Last character: not whitespace, `{`, or `}` | + * + * This inner pattern is reused in: + * - PROMPT_VARIABLE_TEXT_PATTERN: Matches "?Name" format (with anchors) + * - PROMPT_VARIABLE_PATTERN: Matches "{{?Name}}" format (in templates) + * + * Valid examples: "Name", "Prompt Var", "x" + * Invalid examples: " Name", "Name ", "{Name}", "Na{me}" + */ +const PROMPT_VARIABLE_PATTERN = /[^{}\s](?:[^{}]*[^{}\s])?/; + +/** + * Valid examples: "?Name", "?Prompt Var", "?x" + * Invalid examples: "? Name", "?Name ", "?{{Name}}", "?{Name}" + */ +export const PROMPT_VARIABLE_TEXT_PATTERN = new RegExp(`^\\?(${PROMPT_VARIABLE_PATTERN.source})$`); + +/** + * Valid matches: "{{?Name}}", "{{?Prompt Var}}", "{{?x}}" + * Invalid: "{{? Name}}", "{{?Name }}", "{{?{Name}}}" + */ +export const PROMPT_VARIABLE_TEMPLATE_PATTERN = new RegExp(`{{\\?(${PROMPT_VARIABLE_PATTERN.source})}}`, 'g'); + /** * Extract prompt variables matching {{?}} from a string. * @param {string} str - The input string. * @returns {string[]} - An array of extracted prompt variables. */ export const extractPromptVariablesFromString = (str: string): string[] => { - const regex = /{{\?([^}]+)}}/g; const prompts = new Set(); let match; - while ((match = regex.exec(str)) !== null) { - prompts.add(match[1].trim()); + while ((match = PROMPT_VARIABLE_TEMPLATE_PATTERN.exec(str)) !== null) { + prompts.add(match[1]); } return Array.from(prompts); };