diff --git a/cli/src/format.rs b/cli/src/format.rs index e8f4ce688f..d14fa95ac0 100644 --- a/cli/src/format.rs +++ b/cli/src/format.rs @@ -181,8 +181,7 @@ fn fmt_all<'a>(arena: &'a Bump, buf: &mut Buf<'a>, ast: &'a Ast) { fmt_def(buf, arena.alloc(def.value), 0); } - buf.trim_end(); - buf.newline(); + buf.fmt_end_of_file(); } /// RemoveSpaces normalizes the ast to something that we _expect_ to be invariant under formatting. diff --git a/compiler/fmt/src/lib.rs b/compiler/fmt/src/lib.rs index 04ac68e720..9f54990955 100644 --- a/compiler/fmt/src/lib.rs +++ b/compiler/fmt/src/lib.rs @@ -91,9 +91,49 @@ impl<'a> Buf<'a> { } } - pub fn trim_end(&mut self) { - while self.text.ends_with(char::is_whitespace) { - self.text.truncate(self.text.len() - 1); + /// Ensures the text ends in a newline with no whitespace preceding it. + pub fn fmt_end_of_file(&mut self) { + fmt_text_eof(&mut self.text) + } +} + +/// Ensures the text ends in a newline with no whitespace preceding it. +fn fmt_text_eof(text: &mut bumpalo::collections::String<'_>) { + let mut chars_rev = text.chars().rev(); + let mut last_whitespace = None; + let mut last_whitespace_index = text.len(); + + // Keep going until we either run out of characters or encounter one + // that isn't whitespace. + loop { + match chars_rev.next() { + Some(ch) if ch.is_whitespace() => { + last_whitespace = Some(ch); + last_whitespace_index -= 1; + } + _ => { + break; + } + } + } + + match last_whitespace { + Some('\n') => { + // There may have been more whitespace after this newline; remove it! + text.truncate(last_whitespace_index + 1); + } + Some(_) => { + // There's some whitespace at the end of this file, but the first + // whitespace char after the last non-whitespace char isn't a newline. + // So replace that whitespace char (and everything after it) with a newline. + text.replace_range(last_whitespace_index.., "\n"); + } + None => { + debug_assert!(last_whitespace_index == text.len()); + debug_assert!(!text.ends_with(char::is_whitespace)); + + // This doesn't end in whitespace at all, so add a newline. + text.push('\n'); } } }