mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-26 13:04:49 -07:00
Fix dollar sign replacement bug in file editing (#7871)
Co-authored-by: Jacob Richman <jacob314@gmail.com>
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { safeLiteralReplace } from './textUtils.js';
|
||||
|
||||
describe('safeLiteralReplace', () => {
|
||||
it('returns original string when oldString empty or not found', () => {
|
||||
expect(safeLiteralReplace('abc', '', 'X')).toBe('abc');
|
||||
expect(safeLiteralReplace('abc', 'z', 'X')).toBe('abc');
|
||||
});
|
||||
|
||||
it('fast path when newString has no $', () => {
|
||||
expect(safeLiteralReplace('abc', 'b', 'X')).toBe('aXc');
|
||||
});
|
||||
|
||||
it('treats $ literally', () => {
|
||||
expect(safeLiteralReplace('foo', 'foo', "bar$'baz")).toBe("bar$'baz");
|
||||
});
|
||||
|
||||
it("does not interpret replacement patterns like $&, $', $` and $1", () => {
|
||||
expect(safeLiteralReplace('hello', 'hello', '$&-replacement')).toBe(
|
||||
'$&-replacement',
|
||||
);
|
||||
expect(safeLiteralReplace('mid', 'mid', 'new$`content')).toBe(
|
||||
'new$`content',
|
||||
);
|
||||
expect(safeLiteralReplace('test', 'test', '$1$2value')).toBe('$1$2value');
|
||||
});
|
||||
|
||||
it('preserves end-of-line $ in regex-like text', () => {
|
||||
const current = "| select('match', '^[sv]d[a-z]$')";
|
||||
const oldStr = "'^[sv]d[a-z]$'";
|
||||
const newStr = "'^[sv]d[a-z]$' # updated";
|
||||
const expected = "| select('match', '^[sv]d[a-z]$' # updated)";
|
||||
expect(safeLiteralReplace(current, oldStr, newStr)).toBe(expected);
|
||||
});
|
||||
|
||||
it('handles multiple $ characters', () => {
|
||||
expect(safeLiteralReplace('x', 'x', '$$$')).toBe('$$$');
|
||||
});
|
||||
|
||||
it('preserves pre-escaped $$ literally', () => {
|
||||
expect(safeLiteralReplace('x', 'x', '$$value')).toBe('$$value');
|
||||
});
|
||||
|
||||
it('handles complex malicious patterns from PR #7871', () => {
|
||||
const original = 'The price is PRICE.';
|
||||
const result = safeLiteralReplace(
|
||||
original,
|
||||
'PRICE',
|
||||
"$& Wow, that's a lot! $'",
|
||||
);
|
||||
expect(result).toBe("The price is $& Wow, that's a lot! $'.");
|
||||
});
|
||||
|
||||
it('handles multiple replacements correctly', () => {
|
||||
const text = 'Replace FOO and FOO again';
|
||||
const result = safeLiteralReplace(text, 'FOO', '$100');
|
||||
expect(result).toBe('Replace $100 and $100 again');
|
||||
});
|
||||
|
||||
it('preserves $ at different positions', () => {
|
||||
expect(safeLiteralReplace('test', 'test', '$')).toBe('$');
|
||||
expect(safeLiteralReplace('test', 'test', 'prefix$')).toBe('prefix$');
|
||||
expect(safeLiteralReplace('test', 'test', '$suffix')).toBe('$suffix');
|
||||
});
|
||||
|
||||
it('handles edge case with $$$$', () => {
|
||||
expect(safeLiteralReplace('x', 'x', '$$$$')).toBe('$$$$');
|
||||
});
|
||||
|
||||
it('handles newString with only dollar signs', () => {
|
||||
expect(safeLiteralReplace('abc', 'b', '$$')).toBe('a$$c');
|
||||
});
|
||||
});
|
||||
@@ -4,6 +4,27 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Safely replaces text with literal strings, avoiding ECMAScript GetSubstitution issues.
|
||||
* Escapes $ characters to prevent template interpretation.
|
||||
*/
|
||||
export function safeLiteralReplace(
|
||||
str: string,
|
||||
oldString: string,
|
||||
newString: string,
|
||||
): string {
|
||||
if (oldString === '' || !str.includes(oldString)) {
|
||||
return str;
|
||||
}
|
||||
|
||||
if (!newString.includes('$')) {
|
||||
return str.replaceAll(oldString, newString);
|
||||
}
|
||||
|
||||
const escapedNewString = newString.replaceAll('$', '$$$$');
|
||||
return str.replaceAll(oldString, escapedNewString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a Buffer is likely binary by testing for the presence of a NULL byte.
|
||||
* The presence of a NULL byte is a strong indicator that the data is not plain text.
|
||||
|
||||
Reference in New Issue
Block a user