feat: preserve EOL in files (#16087)

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: Adib234 <30782825+Adib234@users.noreply.github.com>
Co-authored-by: Jack Wotherspoon <jackwoth@google.com>
This commit is contained in:
Thomas Shephard
2026-01-30 00:57:06 +00:00
committed by GitHub
parent d43d772e65
commit 695785c69d
7 changed files with 353 additions and 18 deletions

View File

@@ -639,6 +639,33 @@ describe('editCorrector', () => {
expect(result.params).toEqual(originalParams);
});
});
describe('Scenario Group 7: Trimming with Newline Preservation', () => {
it('Test 7.1: should preserve trailing newlines in new_string when trimming is applied', async () => {
const currentContent = ' find me'; // Matches old_string initially
const originalParams = {
file_path: '/test/file.txt',
old_string: ' find me', // Matches, but has whitespace to trim
new_string: ' replaced\n\n', // Needs trimming but preserve newlines
};
const result = await ensureCorrectEdit(
'/test/file.txt',
currentContent,
originalParams,
mockGeminiClientInstance,
mockBaseLlmClientInstance,
abortSignal,
false,
);
// old_string should be trimmed to 'find me' because 'find me' also exists uniquely in ' find me'
expect(result.params.old_string).toBe('find me');
// new_string should be trimmed of spaces but keep ALL newlines
expect(result.params.new_string).toBe('replaced\n\n');
expect(result.occurrences).toBe(1);
});
});
});
describe('ensureCorrectFileContent', () => {

View File

@@ -689,13 +689,20 @@ Return ONLY the corrected string in the specified JSON format with the key 'corr
}
}
function trimPreservingTrailingNewline(str: string): string {
const trimmedEnd = str.trimEnd();
const trailingWhitespace = str.slice(trimmedEnd.length);
const trailingNewlines = trailingWhitespace.replace(/[^\r\n]/g, '');
return str.trim() + trailingNewlines;
}
function trimPairIfPossible(
target: string,
trimIfTargetTrims: string,
currentContent: string,
expectedReplacements: number,
) {
const trimmedTargetString = target.trim();
const trimmedTargetString = trimPreservingTrailingNewline(target);
if (target.length !== trimmedTargetString.length) {
const trimmedTargetOccurrences = countOccurrences(
currentContent,
@@ -703,7 +710,8 @@ function trimPairIfPossible(
);
if (trimmedTargetOccurrences === expectedReplacements) {
const trimmedReactiveString = trimIfTargetTrims.trim();
const trimmedReactiveString =
trimPreservingTrailingNewline(trimIfTargetTrims);
return {
targetString: trimmedTargetString,
pair: trimmedReactiveString,

View File

@@ -54,6 +54,17 @@ export function isBinary(
return false;
}
/**
* Detects the line ending style of a string.
* @param content The string content to analyze.
* @returns '\r\n' for Windows-style, '\n' for Unix-style.
*/
export function detectLineEnding(content: string): '\r\n' | '\n' {
// If a Carriage Return is found, assume Windows-style endings.
// This is a simple but effective heuristic.
return content.includes('\r\n') ? '\r\n' : '\n';
}
/**
* Truncates a string to a maximum length, appending a suffix if truncated.
* @param str The string to truncate.