fix: disallow prompts with leading or trailing spaces (#6201)

This commit is contained in:
Bijin A B
2025-11-25 16:53:05 +05:30
committed by GitHub
parent 6aaccabc04
commit c2d000e805
4 changed files with 48 additions and 9 deletions

View File

@@ -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`;
}

View File

@@ -13,6 +13,8 @@ export {
} from './template-hasher';
export {
PROMPT_VARIABLE_TEXT_PATTERN,
PROMPT_VARIABLE_TEMPLATE_PATTERN,
extractPromptVariables,
extractPromptVariablesFromString
} from './prompt-variables';

View File

@@ -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([]);
});
});
});

View File

@@ -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 {{?<Prompt Text>}} 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<string>();
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);
};