From 88788f775d05b148b2d67fc307fcad29872bfd10 Mon Sep 17 00:00:00 2001 From: Ryu <114303361+ryuapp@users.noreply.github.com> Date: Sun, 15 Mar 2026 19:47:44 +0900 Subject: [PATCH] fix(fmt): ensure formatter always emits a trailing newline --- README.md | 2 +- examples/hello-world.zy | 2 +- src/commands/check.rs | 12 +++++++++--- src/fmt.rs | 3 +++ src/tests/fmt.rs | 21 +++++++++++++++++++++ 5 files changed, 35 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 10d09d7..5f92c7e 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ A statically typed programming language. ```zy const std = import("std") -std.debug.print("Hello World!") +std.debug.print("Hello, 世界") ``` ## License diff --git a/examples/hello-world.zy b/examples/hello-world.zy index c0d42eb..80cfc59 100644 --- a/examples/hello-world.zy +++ b/examples/hello-world.zy @@ -1,2 +1,2 @@ const std = import("std") -std.debug.print("Hello World!") +std.debug.print("Hello, 世界") diff --git a/src/commands/check.rs b/src/commands/check.rs index 9417594..465d350 100644 --- a/src/commands/check.rs +++ b/src/commands/check.rs @@ -165,8 +165,14 @@ fn fmt_diff(source: &str, formatted: &str) -> String { } } } - let trimmed_len = out.trim_end().len(); - out.truncate(trimmed_len); + if out.is_empty() && source != formatted { + // Only difference is trailing newline — show as an added empty line + let ln = source.lines().count() + 1; + out.push_str(&crate::colors::green(&format!("{} | +", ln))); + } else { + let trimmed_len = out.trim_end().len(); + out.truncate(trimmed_len); + } out } @@ -196,7 +202,7 @@ fn check_and_report( let mut changed = false; if parse_errors.is_empty() { let formatted = crate::fmt::format_program(&ast, &blanks); - if formatted.trim() != source.trim() { + if formatted != source { if fix { std::fs::write(path, &formatted) .unwrap_or_else(|e| panic!("Failed to write '{}': {}", path, e)); diff --git a/src/fmt.rs b/src/fmt.rs index 73c9833..7bdb398 100644 --- a/src/fmt.rs +++ b/src/fmt.rs @@ -362,5 +362,8 @@ fn precedence(op: &BinOp) -> u8 { pub fn format_program(program: &crate::parser::Program, blanks: &[bool]) -> String { let mut f = Formatter::new(); f.format_program(program, blanks); + if !f.buf.ends_with('\n') { + f.buf.push('\n'); + } f.buf } diff --git a/src/tests/fmt.rs b/src/tests/fmt.rs index 4bf7d11..337bcad 100644 --- a/src/tests/fmt.rs +++ b/src/tests/fmt.rs @@ -296,6 +296,27 @@ fn test_fmt_modulo() { idempotent(&out); } +#[test] +fn test_fmt_trailing_newline() { + let out = fmt("const x = 1"); + assert!( + out.ends_with('\n'), + "expected trailing newline, got: {:?}", + out + ); +} + +#[test] +fn test_fmt_trailing_newline_not_doubled() { + let out = fmt("const x = 1\n"); + assert!(out.ends_with('\n'), "expected trailing newline"); + assert!( + !out.ends_with("\n\n"), + "trailing newline doubled: {:?}", + out + ); +} + #[test] fn test_fmt_fixtures_idempotent() { let fixtures = std::fs::read_dir("src/tests/fixtures")