diff --git a/crates/oxc_formatter/src/formatter/source_text.rs b/crates/oxc_formatter/src/formatter/source_text.rs index 93f44820ca26b..8cf8ffdb9ecf6 100644 --- a/crates/oxc_formatter/src/formatter/source_text.rs +++ b/crates/oxc_formatter/src/formatter/source_text.rs @@ -129,13 +129,24 @@ impl<'a> SourceText<'a> { self.text_for(&span).chars().count() } - /// Count consecutive line breaks after position + /// Count consecutive line breaks after position, returning `0` if only whitespace follows pub fn lines_after(&self, end: u32) -> usize { - self.slice_from(end) - .chars() - .filter(|&c| !is_white_space_single_line(c)) - .take_while(|&c| is_line_terminator(c)) - .count() + let mut count = 0; + for char in self.slice_from(end).chars() { + if is_white_space_single_line(char) { + continue; + } + + if is_line_terminator(char) { + count += 1; + continue; + } + + return count; + } + + // No non-whitespace characters found after position, so return `0` to avoid adding extra new lines + 0 } /// Count line breaks between syntax nodes, considering comments and parentheses @@ -146,7 +157,7 @@ impl<'a> SourceText<'a> { // Should skip the leading comments of the node. if let Some(comment) = comments.first() - && comment.span.end < start + && comment.span.end <= start { start = comment.span.start; } @@ -188,7 +199,7 @@ impl<'a> SourceText<'a> { count += 1; } - count + 0 } pub fn is_own_line_comment(&self, comment: &Comment) -> bool { diff --git a/crates/oxc_formatter/src/write/program.rs b/crates/oxc_formatter/src/write/program.rs index 447812c5dccfe..bb82f3435c4e5 100644 --- a/crates/oxc_formatter/src/write/program.rs +++ b/crates/oxc_formatter/src/write/program.rs @@ -7,7 +7,7 @@ use oxc_syntax::identifier::{ZWNBSP, is_line_terminator}; use crate::{ Buffer, Format, FormatResult, FormatTrailingCommas, TrailingSeparator, format_args, - formatter::{prelude::*, separated::FormatSeparatedIter, trivia::FormatTrailingComments}, + formatter::{prelude::*, trivia::FormatTrailingComments}, generated::ast_nodes::{AstNode, AstNodes}, utils::{ call_expression::is_test_call_expression, @@ -25,7 +25,7 @@ impl<'a> FormatWrite<'a> for AstNode<'a, Program<'a>> { fn write(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> { let format_trailing_comments = format_once(|f| { let comments = f.context().comments().comments_before(self.span.end); - FormatTrailingComments::Comments(comments).fmt(f) + write!(f, FormatTrailingComments::Comments(comments)) }); write!( @@ -112,19 +112,8 @@ impl<'a> Format<'a> for AstNode<'a, Vec<'a, Directive<'a>>> { // } //``` // so we should keep an extra empty line after JsDirectiveList - let source_text = f.context().source_text(); - let mut count = 0; - let mut source_text_chars = source_text.slice_from(last_directive.span.end).chars(); - for char in source_text_chars.by_ref() { - if is_line_terminator(char) { - count += 1; - } else if !char.is_whitespace() { - break; - } - } - // Need an extra empty line if it has the following line and still has non-characters after whitespace. - let need_extra_empty_line = source_text_chars.next().is_some() && count > 1; + let need_extra_empty_line = f.source_text().lines_after(last_directive.span.end) > 1; write!(f, if need_extra_empty_line { empty_line() } else { hard_line_break() }) } }