diff --git a/crates/oxc_formatter/src/ast_nodes/generated/format.rs b/crates/oxc_formatter/src/ast_nodes/generated/format.rs index 613896f7107d8..36b2f9cb4b6f1 100644 --- a/crates/oxc_formatter/src/ast_nodes/generated/format.rs +++ b/crates/oxc_formatter/src/ast_nodes/generated/format.rs @@ -25,6 +25,17 @@ impl<'a> Format<'a> for AstNode<'a, Program<'a>> { impl<'a> Format<'a> for AstNode<'a, Expression<'a>> { #[inline] fn fmt(&self, f: &mut Formatter<'_, 'a>) { + if f.comments().has_trailing_suppression_comment(self.span().end) { + format_leading_comments(self.span()).fmt(f); + FormatSuppressedNode(self.span()).fmt(f); + format_trailing_comments( + self.parent.span(), + self.inner.span(), + self.following_span_start, + ) + .fmt(f); + return; + } let allocator = self.allocator; let parent = self.parent; match self.inner { diff --git a/crates/oxc_formatter/src/formatter/comments.rs b/crates/oxc_formatter/src/formatter/comments.rs index 946ec7e67d9b1..2aafaf386f1d9 100644 --- a/crates/oxc_formatter/src/formatter/comments.rs +++ b/crates/oxc_formatter/src/formatter/comments.rs @@ -140,6 +140,35 @@ pub struct Comments<'a> { } impl<'a> Comments<'a> { + #[inline] + const fn is_end_of_line_comment_gap_byte(byte: u8) -> bool { + matches!(byte, b'\t' | b' ' | b'=' | b':') + } + + #[inline] + const fn is_trailing_suppression_gap_byte(byte: u8) -> bool { + Self::is_end_of_line_comment_gap_byte(byte) || matches!(byte, b',' | b';') + } + + fn end_of_line_comments_after_with( + &self, + mut pos: u32, + is_allowed_gap_byte: impl Fn(u8) -> bool + Copy, + ) -> &'a [Comment] { + let comments = self.comments_after(pos); + for (index, comment) in comments.iter().enumerate() { + if self.source_text.all_bytes_match(pos, comment.span.start, is_allowed_gap_byte) { + if comment.is_line() || comment.followed_by_newline() { + return &comments[..=index]; + } + pos = comment.span.end; + } else { + break; + } + } + &[] + } + pub fn new(source_text: SourceText<'a>, comments: &'a [Comment]) -> Self { Comments { source_text, @@ -194,21 +223,8 @@ impl<'a> Comments<'a> { } /// Returns end-of-line comments that are after the given position (excluding printed ones). - pub fn end_of_line_comments_after(&self, mut pos: u32) -> &'a [Comment] { - let comments = self.comments_after(pos); - for (index, comment) in comments.iter().enumerate() { - if self.source_text.all_bytes_match(pos, comment.span.start, |b| { - matches!(b, b'\t' | b' ' | b'=' | b':') - }) { - if comment.is_line() || comment.followed_by_newline() { - return &comments[..=index]; - } - pos = comment.span.end; - } else { - break; - } - } - &[] + pub fn end_of_line_comments_after(&self, pos: u32) -> &'a [Comment] { + self.end_of_line_comments_after_with(pos, Self::is_end_of_line_comment_gap_byte) } /// Returns comments that start after the given position (excluding printed ones). @@ -394,8 +410,9 @@ impl<'a> Comments<'a> { /// This supports patterns like: /// `statement(); // prettier-ignore` /// `statement(); /* prettier-ignore */` + /// `value, // prettier-ignore` pub fn has_trailing_suppression_comment(&self, pos: u32) -> bool { - self.end_of_line_comments_after(pos) + self.end_of_line_comments_after_with(pos, Self::is_trailing_suppression_gap_byte) .iter() .any(|comment| self.is_suppression_comment(comment)) } diff --git a/crates/oxc_formatter/tests/fixtures/js/ignore/oxfmt.js b/crates/oxc_formatter/tests/fixtures/js/ignore/oxfmt.js index fabaaa3a1a3f9..c1770211eab0b 100644 --- a/crates/oxc_formatter/tests/fixtures/js/ignore/oxfmt.js +++ b/crates/oxc_formatter/tests/fixtures/js/ignore/oxfmt.js @@ -65,6 +65,10 @@ export const item={ a:1,b:2}; // prettier-ignore const config={ retries:10,timeout:5000}; // prettier-ignore let data=[ 1,2,3 ]; // prettier-ignore +const items = [ + {a:aa(),b:bb(),c:cc(),d:dd(),e:ee(),f:ff(),g:gg()}, // prettier-ignore +]; + function demo() { return {a:1,b:2}; // prettier-ignore } diff --git a/crates/oxc_formatter/tests/fixtures/js/ignore/oxfmt.js.snap b/crates/oxc_formatter/tests/fixtures/js/ignore/oxfmt.js.snap index 09f61bed03319..4a39943326ba6 100644 --- a/crates/oxc_formatter/tests/fixtures/js/ignore/oxfmt.js.snap +++ b/crates/oxc_formatter/tests/fixtures/js/ignore/oxfmt.js.snap @@ -1,5 +1,6 @@ --- source: crates/oxc_formatter/tests/fixtures/mod.rs +assertion_line: 229 --- ==================== Input ==================== // This file copies from https://github.com/prettier/prettier/blob/main/tests/format/js/ignore/ignore.js, @@ -69,6 +70,10 @@ export const item={ a:1,b:2}; // prettier-ignore const config={ retries:10,timeout:5000}; // prettier-ignore let data=[ 1,2,3 ]; // prettier-ignore +const items = [ + {a:aa(),b:bb(),c:cc(),d:dd(),e:ee(),f:ff(),g:gg()}, // prettier-ignore +]; + function demo() { return {a:1,b:2}; // prettier-ignore } @@ -169,6 +174,10 @@ export const item={ a:1,b:2}; // prettier-ignore const config={ retries:10,timeout:5000}; // prettier-ignore let data=[ 1,2,3 ]; // prettier-ignore +const items = [ + {a:aa(),b:bb(),c:cc(),d:dd(),e:ee(),f:ff(),g:gg()}, // prettier-ignore +]; + function demo() { return {a:1,b:2}; // prettier-ignore } @@ -268,6 +277,10 @@ export const item={ a:1,b:2}; // prettier-ignore const config={ retries:10,timeout:5000}; // prettier-ignore let data=[ 1,2,3 ]; // prettier-ignore +const items = [ + {a:aa(),b:bb(),c:cc(),d:dd(),e:ee(),f:ff(),g:gg()}, // prettier-ignore +]; + function demo() { return {a:1,b:2}; // prettier-ignore } diff --git a/tasks/ast_tools/src/generators/formatter/format.rs b/tasks/ast_tools/src/generators/formatter/format.rs index 9743323ad1499..475d12b5579e2 100644 --- a/tasks/ast_tools/src/generators/formatter/format.rs +++ b/tasks/ast_tools/src/generators/formatter/format.rs @@ -318,22 +318,34 @@ fn generate_enum_implementation(enum_def: &EnumDef, schema: &Schema) -> TokenStr }; let node_type = get_node_type(&enum_ty); - let inline_trailing_suppression = if enum_def.name() == "Statement" { - // Expression statements need specialized ASI-safe suppression handling in - // `AstNode::write`. - quote! { - if !matches!(self.inner, Statement::ExpressionStatement(_)) - && f.comments().has_trailing_suppression_comment(self.span().end) - { - format_leading_comments(self.span()).fmt(f); - FormatSuppressedNode(self.span()).fmt(f); - format_trailing_comments(self.parent.span(), self.inner.span(), self.following_span_start) - .fmt(f); - return; + let inline_trailing_suppression = match enum_def.name() { + "Statement" => { + // Expression statements need specialized ASI-safe suppression handling in + // `AstNode::write`. + quote! { + if !matches!(self.inner, Statement::ExpressionStatement(_)) + && f.comments().has_trailing_suppression_comment(self.span().end) + { + format_leading_comments(self.span()).fmt(f); + FormatSuppressedNode(self.span()).fmt(f); + format_trailing_comments(self.parent.span(), self.inner.span(), self.following_span_start) + .fmt(f); + return; + } } } - } else { - quote! {} + "Expression" => { + quote! { + if f.comments().has_trailing_suppression_comment(self.span().end) { + format_leading_comments(self.span()).fmt(f); + FormatSuppressedNode(self.span()).fmt(f); + format_trailing_comments(self.parent.span(), self.inner.span(), self.following_span_start) + .fmt(f); + return; + } + } + } + _ => quote! {}, }; quote! {