mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-14 05:42:54 -07:00
fix(core): preserve backslashes before newlines in unescaping logic
Fixes #18469 by updating the unescapeStringForGeminiBug regex to exclude literal newlines. This ensures C-macros with backslash-newline continuations are not corrupted during file processing.
This commit is contained in:
@@ -47,10 +47,11 @@ describe('editCorrector', () => {
|
||||
'\nCorrect\t`',
|
||||
);
|
||||
});
|
||||
it('should handle backslash followed by actual newline character', () => {
|
||||
expect(unescapeStringForGeminiBug('\\\n')).toBe('\n');
|
||||
it('should NOT strip backslash before actual newline character', () => {
|
||||
// FIX FOR ISSUE #18469: This test now expects the backslash to be PRESERVED.
|
||||
expect(unescapeStringForGeminiBug('\\\n')).toBe('\\\n');
|
||||
expect(unescapeStringForGeminiBug('First line\\\nSecond line')).toBe(
|
||||
'First line\nSecond line',
|
||||
'First line\\\nSecond line',
|
||||
);
|
||||
});
|
||||
it('should handle multiple backslashes before an escapable character (aggressive unescaping)', () => {
|
||||
@@ -75,7 +76,7 @@ describe('editCorrector', () => {
|
||||
it('should handle complex cases with mixed slashes and characters', () => {
|
||||
expect(
|
||||
unescapeStringForGeminiBug('\\\\\\\nLine1\\\nLine2\\tTab\\\\`Tick\\"'),
|
||||
).toBe('\nLine1\nLine2\tTab`Tick"');
|
||||
).toBe('\\\nLine1\\\nLine2\tTab`Tick"');
|
||||
});
|
||||
it('should handle escaped backslashes', () => {
|
||||
expect(unescapeStringForGeminiBug('\\\\')).toBe('\\');
|
||||
|
||||
@@ -139,44 +139,37 @@ Return ONLY the corrected string in the specified JSON format with the key 'corr
|
||||
export function unescapeStringForGeminiBug(inputString: string): string {
|
||||
// Regex explanation:
|
||||
// \\ : Matches exactly one literal backslash character.
|
||||
// (n|t|r|'|"|`|\\|\n) : This is a capturing group. It matches one of the following:
|
||||
// (n|t|r|'|"|`|\\) : This is a capturing group. It matches one of the following:
|
||||
// n, t, r, ', ", ` : These match the literal characters 'n', 't', 'r', single quote, double quote, or backtick.
|
||||
// This handles cases like "\\n", "\\`", etc.
|
||||
// \\ : This matches a literal backslash. This handles cases like "\\\\" (escaped backslash).
|
||||
// \n : This matches an actual newline character. This handles cases where the input
|
||||
// string might have something like "\\\n" (a literal backslash followed by a newline).
|
||||
// g : Global flag, to replace all occurrences.
|
||||
|
||||
return inputString.replace(
|
||||
/\\+(n|t|r|'|"|`|\\|\n)/g,
|
||||
(match, capturedChar) => {
|
||||
// 'match' is the entire erroneous sequence, e.g., if the input (in memory) was "\\\\`", match is "\\\\`".
|
||||
// 'capturedChar' is the character that determines the true meaning, e.g., '`'.
|
||||
return inputString.replace(/\\+(n|t|r|'|"|`|\\)/g, (match, capturedChar) => {
|
||||
// 'match' is the entire erroneous sequence, e.g., if the input (in memory) was "\\\\`", match is "\\\\`".
|
||||
// 'capturedChar' is the character that determines the true meaning, e.g., '`'.
|
||||
|
||||
switch (capturedChar) {
|
||||
case 'n':
|
||||
return '\n'; // Correctly escaped: \n (newline character)
|
||||
case 't':
|
||||
return '\t'; // Correctly escaped: \t (tab character)
|
||||
case 'r':
|
||||
return '\r'; // Correctly escaped: \r (carriage return character)
|
||||
case "'":
|
||||
return "'"; // Correctly escaped: ' (apostrophe character)
|
||||
case '"':
|
||||
return '"'; // Correctly escaped: " (quotation mark character)
|
||||
case '`':
|
||||
return '`'; // Correctly escaped: ` (backtick character)
|
||||
case '\\': // This handles when 'capturedChar' is a literal backslash
|
||||
return '\\'; // Replace escaped backslash (e.g., "\\\\") with single backslash
|
||||
case '\n': // This handles when 'capturedChar' is an actual newline
|
||||
return '\n'; // Replace the whole erroneous sequence (e.g., "\\\n" in memory) with a clean newline
|
||||
default:
|
||||
// This fallback should ideally not be reached if the regex captures correctly.
|
||||
// It would return the original matched sequence if an unexpected character was captured.
|
||||
return match;
|
||||
}
|
||||
},
|
||||
);
|
||||
switch (capturedChar) {
|
||||
case 'n':
|
||||
return '\n'; // Correctly escaped: \n (newline character)
|
||||
case 't':
|
||||
return '\t'; // Correctly escaped: \t (tab character)
|
||||
case 'r':
|
||||
return '\r'; // Correctly escaped: \r (carriage return character)
|
||||
case "'":
|
||||
return "'"; // Correctly escaped: ' (apostrophe character)
|
||||
case '"':
|
||||
return '"'; // Correctly escaped: " (quotation mark character)
|
||||
case '`':
|
||||
return '`'; // Correctly escaped: ` (backtick character)
|
||||
case '\\': // This handles when 'capturedChar' is a literal backslash
|
||||
return '\\'; // Replace escaped backslash (e.g., "\\\\") with single backslash
|
||||
default:
|
||||
// This fallback should ideally not be reached if the regex captures correctly.
|
||||
// It would return the original matched sequence if an unexpected character was captured.
|
||||
return match;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function resetEditCorrectorCaches_TEST_ONLY() {
|
||||
|
||||
@@ -809,6 +809,18 @@ describe('fileUtils', () => {
|
||||
expect(result.error).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should preserve backslashes in C-style macros', async () => {
|
||||
const content =
|
||||
'#define MY_MACRO \\\n do { \\\n something(); \\\n } while (0)';
|
||||
actualNodeFs.writeFileSync(testTextFilePath, content);
|
||||
const result = await processSingleFileContent(
|
||||
testTextFilePath,
|
||||
tempRootDir,
|
||||
new StandardFileSystemService(),
|
||||
);
|
||||
expect(result.llmContent).toBe(content);
|
||||
});
|
||||
|
||||
it('should handle file not found', async () => {
|
||||
const result = await processSingleFileContent(
|
||||
nonexistentFilePath,
|
||||
|
||||
Reference in New Issue
Block a user