Skip to content

Commit e3e5e53

Browse files
authored
fix (q chat): ensure generated files end with newline character, and no trailing whitespaces (#769)
* feat(fs_write): ensure files end with newline character Fixes #768 This commit adds functionality to ensure all files created or modified by the fs_write tool end with a newline character, following Unix text file conventions. The change: - Adds newline handling to create, str_replace, and insert operations - Updates test cases to verify the new behavior - Maintains backward compatibility with existing functionality This improves compatibility with other tools and follows standard text file best practices. * fix: remove trailing whitespace in generated code This commit adds a helper function to clean file content by: 1. Removing trailing whitespace from each line 2. Ensuring files end with a newline The implementation refactors duplicate code across Create, StrReplace, and Insert operations into a single clean_file_content helper function. This ensures consistent handling of whitespace across all file operations. Added unit tests to verify the helper function works correctly with various inputs. Fixes issue with trailing whitespace in code generation.
1 parent d219c0c commit e3e5e53

File tree

2 files changed

+59
-9
lines changed

2 files changed

+59
-9
lines changed

crates/q_cli/src/cli/chat/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1146,6 +1146,6 @@ mod tests {
11461146
.await
11471147
.unwrap();
11481148

1149-
assert_eq!(ctx.fs().read_to_string("/file.txt").await.unwrap(), "Hello, world!");
1149+
assert_eq!(ctx.fs().read_to_string("/file.txt").await.unwrap(), "Hello, world!\n");
11501150
}
11511151
}

crates/q_cli/src/cli/chat/tools/fs_write.rs

Lines changed: 58 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,19 @@ pub enum FsWrite {
5050
}
5151

5252
impl FsWrite {
53+
/// Helper function to clean file content:
54+
/// 1. Ensures the file ends with a newline
55+
/// 2. Removes trailing whitespace from each line
56+
fn clean_file_content(content: String) -> String {
57+
let mut cleaned = content
58+
.lines()
59+
.map(|line| line.trim_end())
60+
.collect::<Vec<_>>()
61+
.join("\n");
62+
cleaned.push('\n');
63+
cleaned
64+
}
65+
5366
pub async fn invoke(&self, ctx: &Context, updates: &mut impl Write) -> Result<InvokeOutput> {
5467
let fs = ctx.fs();
5568
let cwd = ctx.env().current_dir()?;
@@ -70,7 +83,9 @@ impl FsWrite {
7083
style::ResetColor,
7184
style::Print("\n"),
7285
)?;
73-
fs.write(&path, file_text.as_bytes()).await?;
86+
87+
let cleaned_text = Self::clean_file_content(file_text);
88+
fs.write(&path, cleaned_text.as_bytes()).await?;
7489
Ok(Default::default())
7590
},
7691
FsWrite::StrReplace { path, old_str, new_str } => {
@@ -89,7 +104,8 @@ impl FsWrite {
89104
0 => Err(eyre!("no occurrences of \"{old_str}\" were found")),
90105
1 => {
91106
let file = file.replacen(old_str, new_str, 1);
92-
fs.write(path, file).await?;
107+
let cleaned_file = Self::clean_file_content(file);
108+
fs.write(path, cleaned_file).await?;
93109
Ok(Default::default())
94110
},
95111
x => Err(eyre!("{x} occurrences of old_str were found when only 1 is expected")),
@@ -120,7 +136,8 @@ impl FsWrite {
120136
i += line_len;
121137
}
122138
file.insert_str(i, new_str);
123-
fs.write(&path, &file).await?;
139+
let cleaned_file = Self::clean_file_content(file);
140+
fs.write(&path, &cleaned_file).await?;
124141
Ok(Default::default())
125142
},
126143
FsWrite::Append { path, new_str } => {
@@ -435,7 +452,10 @@ mod tests {
435452
.await
436453
.unwrap();
437454

438-
assert_eq!(ctx.fs().read_to_string("/my-file").await.unwrap(), file_text);
455+
assert_eq!(
456+
ctx.fs().read_to_string("/my-file").await.unwrap(),
457+
format!("{}\n", file_text)
458+
);
439459

440460
let file_text = "Goodbye, world!\nSee you later";
441461
let v = serde_json::json!({
@@ -449,7 +469,11 @@ mod tests {
449469
.await
450470
.unwrap();
451471

452-
assert_eq!(ctx.fs().read_to_string("/my-file").await.unwrap(), file_text);
472+
// File should end with a newline
473+
assert_eq!(
474+
ctx.fs().read_to_string("/my-file").await.unwrap(),
475+
format!("{}\n", file_text)
476+
);
453477

454478
let file_text = "This is a new string";
455479
let v = serde_json::json!({
@@ -463,7 +487,10 @@ mod tests {
463487
.await
464488
.unwrap();
465489

466-
assert_eq!(ctx.fs().read_to_string("/my-file").await.unwrap(), file_text);
490+
assert_eq!(
491+
ctx.fs().read_to_string("/my-file").await.unwrap(),
492+
format!("{}\n", file_text)
493+
);
467494
}
468495

469496
#[tokio::test]
@@ -613,7 +640,7 @@ mod tests {
613640
.await
614641
.unwrap();
615642
let actual = ctx.fs().read_to_string(test_file_path).await.unwrap();
616-
assert_eq!(actual, format!("{}{}", test_file_contents, new_str),);
643+
assert_eq!(actual, format!("{}{}\n", test_file_contents, new_str));
617644

618645
// Then, test prepending
619646
let v = serde_json::json!({
@@ -628,7 +655,7 @@ mod tests {
628655
.await
629656
.unwrap();
630657
let actual = ctx.fs().read_to_string(test_file_path).await.unwrap();
631-
assert_eq!(actual, format!("{}{}{}", new_str, test_file_contents, new_str),);
658+
assert_eq!(actual, format!("{}{}{}\n", new_str, test_file_contents, new_str));
632659
}
633660

634661
#[tokio::test]
@@ -683,4 +710,27 @@ mod tests {
683710
let s = "Hello, world!";
684711
assert_eq!(truncate_str(s, 0), "<...Truncated>");
685712
}
713+
714+
#[test]
715+
fn test_clean_file_content() {
716+
// Test removing trailing whitespace
717+
let content = "Hello world! \nThis is a test \nWith trailing spaces ";
718+
let expected = "Hello world!\nThis is a test\nWith trailing spaces\n";
719+
assert_eq!(FsWrite::clean_file_content(content.to_string()), expected);
720+
721+
// Test ensuring ending newline
722+
let content = "Hello world!\nNo ending newline";
723+
let expected = "Hello world!\nNo ending newline\n";
724+
assert_eq!(FsWrite::clean_file_content(content.to_string()), expected);
725+
726+
// Test with content already having ending newline
727+
let content = "Hello world!\nWith ending newline\n";
728+
let expected = "Hello world!\nWith ending newline\n";
729+
assert_eq!(FsWrite::clean_file_content(content.to_string()), expected);
730+
731+
// Test with empty string
732+
let content = "";
733+
let expected = "\n";
734+
assert_eq!(FsWrite::clean_file_content(content.to_string()), expected);
735+
}
686736
}

0 commit comments

Comments
 (0)