diff --git a/packages/core/src/utils/editCorrector.test.ts b/packages/core/src/utils/editCorrector.test.ts index f9620d74b5..d2e35e82b3 100644 --- a/packages/core/src/utils/editCorrector.test.ts +++ b/packages/core/src/utils/editCorrector.test.ts @@ -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('\\'); diff --git a/packages/core/src/utils/editCorrector.ts b/packages/core/src/utils/editCorrector.ts index 2c58bad98f..6753c37b9b 100644 --- a/packages/core/src/utils/editCorrector.ts +++ b/packages/core/src/utils/editCorrector.ts @@ -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() { diff --git a/packages/core/src/utils/fileUtils.test.ts b/packages/core/src/utils/fileUtils.test.ts index 5e7c6d3df2..1d7b5f9cc7 100644 --- a/packages/core/src/utils/fileUtils.test.ts +++ b/packages/core/src/utils/fileUtils.test.ts @@ -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,