From 265aa3e8f36f4faa68fbd99cba0ec9ff0731e8e1 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Tue, 6 Sep 2022 18:45:16 +0200 Subject: [PATCH 01/45] refactor(formatter): Refactor comments --- crates/rome_formatter/src/builders.rs | 162 +--- crates/rome_formatter/src/comments.rs | 883 ++++++++++++++++-- crates/rome_formatter/src/format_element.rs | 4 - crates/rome_formatter/src/lib.rs | 131 +-- crates/rome_formatter/src/prelude.rs | 4 +- crates/rome_formatter/src/printer/mod.rs | 20 +- crates/rome_formatter/src/token.rs | 866 ++++------------- crates/rome_js_formatter/src/builders.rs | 349 ++----- crates/rome_js_formatter/src/comments.rs | 350 ++++++- crates/rome_js_formatter/src/context.rs | 6 +- .../assignments/array_assignment_pattern.rs | 1 + .../src/js/auxiliary/function_body.rs | 28 +- .../src/js/bindings/array_binding_pattern.rs | 1 + .../src/js/bindings/parameters.rs | 1 + .../src/js/classes/extends_clause.rs | 16 +- ...tatic_initialization_block_class_member.rs | 1 + .../src/js/declarations/catch_declaration.rs | 1 + .../js/declarations/function_declaration.rs | 15 +- .../src/js/expressions/array_expression.rs | 22 +- .../expressions/arrow_function_expression.rs | 19 +- .../src/js/expressions/call_arguments.rs | 131 ++- .../src/js/expressions/new_expression.rs | 8 +- .../src/js/expressions/template_element.rs | 11 +- .../src/js/expressions/unary_expression.rs | 10 +- .../src/js/lists/array_element_list.rs | 54 +- .../src/js/lists/call_argument_list.rs | 4 +- .../lists/export_named_from_specifier_list.rs | 4 +- .../js/lists/export_named_specifier_list.rs | 4 +- .../js/lists/import_assertion_entry_list.rs | 4 +- .../js/lists/named_import_specifier_list.rs | 4 +- ...object_assignment_pattern_property_list.rs | 6 +- .../object_binding_pattern_property_list.rs | 6 +- .../src/js/lists/object_member_list.rs | 7 +- .../src/js/lists/parameter_list.rs | 10 +- .../src/js/lists/variable_declarator_list.rs | 2 +- .../src/js/module/export_named_clause.rs | 1 + .../src/js/module/export_named_from_clause.rs | 1 + .../src/js/module/import_assertion.rs | 1 + .../src/js/module/named_import_specifiers.rs | 1 + .../src/js/statements/block_statement.rs | 72 +- .../src/js/statements/break_statement.rs | 8 +- .../src/js/statements/continue_statement.rs | 8 +- .../src/js/statements/do_while_statement.rs | 1 + .../src/js/statements/for_statement.rs | 17 +- .../src/js/statements/if_statement.rs | 19 +- .../src/js/statements/labeled_statement.rs | 7 +- .../src/js/statements/return_statement.rs | 151 ++- .../src/js/statements/switch_statement.rs | 1 + .../src/js/statements/throw_statement.rs | 24 +- .../src/js/statements/while_statement.rs | 1 + .../src/js/unknown/unknown.rs | 4 + .../src/js/unknown/unknown_assignment.rs | 4 + .../src/js/unknown/unknown_binding.rs | 4 + .../src/js/unknown/unknown_expression.rs | 4 + .../unknown/unknown_import_assertion_entry.rs | 4 + .../src/js/unknown/unknown_member.rs | 4 + .../unknown/unknown_named_import_specifier.rs | 4 + .../src/js/unknown/unknown_parameter.rs | 4 + .../src/js/unknown/unknown_statement.rs | 4 + crates/rome_js_formatter/src/lib.rs | 64 +- crates/rome_js_formatter/src/prelude.rs | 4 +- crates/rome_js_formatter/src/separated.rs | 14 +- .../assignments/type_assertion_assignment.rs | 1 + .../src/ts/auxiliary/module_block.rs | 1 + .../src/ts/bindings/type_parameters.rs | 1 + .../src/ts/declarations/enum_declaration.rs | 1 + .../ts/declarations/interface_declaration.rs | 33 +- .../src/ts/expressions/type_arguments.rs | 1 + .../expressions/type_assertion_expression.rs | 1 + .../src/ts/lists/enum_member_list.rs | 4 +- .../src/ts/lists/tuple_type_element_list.rs | 4 +- .../src/ts/lists/type_argument_list.rs | 4 +- .../src/ts/lists/type_list.rs | 4 +- .../src/ts/lists/type_member_list.rs | 9 +- .../src/ts/lists/type_parameter_list.rs | 4 +- .../src/ts/types/mapped_type.rs | 1 + .../src/ts/types/tuple_type.rs | 1 + .../src/ts/types/union_type.rs | 10 +- crates/rome_js_formatter/src/utils/array.rs | 6 +- .../src/utils/assignment_like.rs | 37 +- .../src/utils/binary_like_expression.rs | 42 +- .../src/utils/conditional.rs | 38 +- .../src/utils/format_class.rs | 1 + crates/rome_js_formatter/src/utils/mod.rs | 49 +- .../src/utils/object_like.rs | 7 +- .../src/utils/object_pattern_like.rs | 1 + .../js/module/class/class_comments.js.snap | 1 + .../expression/logical_expression.js.snap | 7 +- .../tests/specs/jsx/element.jsx.snap | 3 +- ...mbers-negative-comment-after-minus.js.snap | 121 --- .../numbers-with-tricky-comments.js.snap | 10 +- .../js/assignment-comments/number.js.snap | 125 --- .../prettier/js/babel-plugins/bigint.js.snap | 293 ------ .../js/babel-plugins/optional-chaining.js | 1 - .../babel-plugins/optional-chaining.js.snap | 159 ---- .../private-fields-in-in.js.snap | 141 --- .../specs/prettier/js/classes/method.js.snap | 52 -- .../closure-compiler-type-cast.js.snap | 154 --- .../comment-placement.js.snap | 70 -- .../issue-4124.js.snap | 71 -- .../issue-8045.js.snap | 38 +- .../object-with-comment.js.snap | 58 -- .../binary-expressions-block-comments.js.snap | 183 ---- .../js/comments/binary-expressions.js.snap | 274 ------ .../break-continue-statements.js.snap | 55 -- .../prettier/js/comments/call_comment.js.snap | 73 -- .../prettier/js/comments/dangling.js.snap | 61 -- .../prettier/js/comments/dangling_for.js.snap | 39 - .../comments/preserve-new-line-last.js.snap | 80 -- .../js/comments/return-statement.js.snap | 15 +- .../js/comments/single-star-jsdoc.js.snap | 95 -- .../specs/prettier/js/comments/switch.js.snap | 16 +- .../js/comments/template-literal.js.snap | 64 -- .../js/comments/trailing-jsdocs.js.snap | 101 -- .../specs/prettier/js/comments/while.js.snap | 88 -- .../prettier/js/conditional/comments.js.snap | 150 +-- .../for/continue-and-break-comment-2.js.snap | 480 ---------- .../specs/prettier/js/if/if_comments.js.snap | 191 ---- .../js/line-suffix-boundary/boundary.js.snap | 107 --- .../specs/prettier/js/return/comment.js.snap | 14 +- .../prettier/js/template/comment.js.snap | 60 -- .../prettier/js/ternaries/nested.js.snap | 226 ----- .../tests/specs/prettier/js/try/catch.js.snap | 84 -- .../typescript/arrow/comments.ts.snap | 11 +- .../typescript/class/generics.ts.snap | 52 -- .../comments/after_jsx_generic.ts.snap | 93 -- .../typescript/comments/methods.ts.snap | 131 --- .../conditional-types/comments.ts.snap | 218 ----- .../typescript/export/comment.ts.snap | 32 - .../keyword-types/conditional-types.ts.snap | 56 -- ...keyword-types-with-parens-comments.ts.snap | 168 ---- 131 files changed, 2073 insertions(+), 6285 deletions(-) delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/arrays/numbers-negative-comment-after-minus.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/assignment-comments/number.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/babel-plugins/bigint.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/babel-plugins/optional-chaining.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/babel-plugins/private-fields-in-in.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/classes/method.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/closure-compiler-type-cast.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/comment-placement.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/issue-4124.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/object-with-comment.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/comments/binary-expressions-block-comments.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/comments/binary-expressions.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/comments/break-continue-statements.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/comments/call_comment.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/comments/dangling.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/comments/dangling_for.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/comments/preserve-new-line-last.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/comments/single-star-jsdoc.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/comments/template-literal.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/comments/trailing-jsdocs.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/comments/while.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/for/continue-and-break-comment-2.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/if/if_comments.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/line-suffix-boundary/boundary.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/template/comment.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/ternaries/nested.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/try/catch.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/typescript/class/generics.ts.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/typescript/comments/after_jsx_generic.ts.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/typescript/comments/methods.ts.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/typescript/conditional-types/comments.ts.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/typescript/export/comment.ts.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/typescript/keyword-types/conditional-types.ts.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/typescript/keyword-types/keyword-types-with-parens-comments.ts.snap diff --git a/crates/rome_formatter/src/builders.rs b/crates/rome_formatter/src/builders.rs index 7fef20d8a04..c0e084058cc 100644 --- a/crates/rome_formatter/src/builders.rs +++ b/crates/rome_formatter/src/builders.rs @@ -1,15 +1,11 @@ use crate::prelude::*; -use crate::{ - format_element, write, Argument, Arguments, BufferSnapshot, FormatState, GroupId, TextRange, - TextSize, -}; +use crate::{format_element, write, Argument, Arguments, GroupId, TextRange, TextSize}; use crate::{Buffer, VecBuffer}; use rome_rowan::{Language, SyntaxNode, SyntaxToken, SyntaxTokenText, TextLen}; use std::borrow::Cow; use std::cell::Cell; use std::marker::PhantomData; use std::num::NonZeroU8; -use std::ops::Deref; /// A line break that only gets printed if the enclosing `Group` doesn't fit on a single line. /// It's omitted if the enclosing `Group` fits on a single line. @@ -1307,7 +1303,7 @@ impl Format for Group<'_, Context> { return f.write_fmt(Arguments::from(&self.content)); } - let mut buffer = GroupBuffer::new(f); + let mut buffer = VecBuffer::new(f.state_mut()); buffer.write_fmt(Arguments::from(&self.content))?; @@ -1322,9 +1318,7 @@ impl Format for Group<'_, Context> { let group = format_element::Group::new(content).with_id(self.group_id); - f.write_element(FormatElement::Group(group))?; - - Ok(()) + f.write_element(FormatElement::Group(group)) } } @@ -1338,154 +1332,6 @@ impl std::fmt::Debug for Group<'_, Context> { } } -/// Custom buffer implementation for `GroupElements` that moves the leading comments out of the group -/// to prevent that a leading line comment expands the token's enclosing group. -/// -/// # Examples -/// -/// ```javascript -/// /* a comment */ -/// [1] -/// ``` -/// -/// The `/* a comment */` belongs to the `[` group token that is part of a group wrapping the whole -/// `[1]` expression. It's important that the comment `/* a comment */` gets moved out of the group element -/// to avoid that the `[1]` group expands because of the line break inserted by the comment. -struct GroupBuffer<'inner, Context> { - inner: &'inner mut dyn Buffer, - - /// The group inner content - content: Vec, -} - -impl<'inner, Context> GroupBuffer<'inner, Context> { - fn new(inner: &'inner mut dyn Buffer) -> Self { - Self { - inner, - content: Vec::new(), - } - } - - fn into_vec(self) -> Vec { - self.content - } - - fn write_interned(&mut self, interned: Interned) -> FormatResult<()> { - debug_assert!(self.content.is_empty()); - - match interned.deref() { - FormatElement::Comment(_) => { - self.inner.write_element(FormatElement::Interned(interned)) - } - FormatElement::List(list) => { - let mut content_start = 0; - - for element in list.iter() { - match element { - element @ FormatElement::Comment(_) => { - content_start += 1; - // Cloning comments should be alright as they are rarely nested - // and the case where all elements of an interned data structure are comments - // are rare - self.inner.write_element(element.clone())?; - } - FormatElement::Interned(interned) => { - self.write_interned(interned.clone())?; - content_start += 1; - - if !self.content.is_empty() { - // Interned struct contained non-comment - break; - } - } - _ => { - // Found the first non-comment / nested interned element - break; - } - } - } - - // No leading comments, this group has no comments - if content_start == 0 { - self.content.push(FormatElement::Interned(interned)); - return Ok(()); - } - - let content = &list[content_start..]; - - // It is necessary to mutate the interned elements, write cloned elements - self.write_elements(content.iter().cloned()) - } - FormatElement::Interned(interned) => self.write_interned(interned.clone()), - _ => { - self.content.push(FormatElement::Interned(interned)); - Ok(()) - } - } - } -} - -impl Buffer for GroupBuffer<'_, Context> { - type Context = Context; - - fn write_element(&mut self, element: FormatElement) -> FormatResult<()> { - if self.content.is_empty() { - match element { - FormatElement::List(list) => { - self.write_elements(list.into_vec())?; - } - FormatElement::Interned(interned) => match Interned::try_unwrap(interned) { - Ok(owned) => self.write_element(owned)?, - Err(interned) => self.write_interned(interned)?, - }, - comment @ FormatElement::Comment { .. } => { - self.inner.write_element(comment)?; - } - element => self.content.push(element), - } - } else { - match element { - FormatElement::List(list) => { - self.content.extend(list.into_vec()); - } - element => self.content.push(element), - } - } - - Ok(()) - } - - fn elements(&self) -> &[FormatElement] { - &self.content - } - - fn state(&self) -> &FormatState { - self.inner.state() - } - - fn state_mut(&mut self) -> &mut FormatState { - self.inner.state_mut() - } - - fn snapshot(&self) -> BufferSnapshot { - BufferSnapshot::Any(Box::new(GroupElementsBufferSnapshot { - inner: self.inner.snapshot(), - content_len: self.content.len(), - })) - } - - fn restore_snapshot(&mut self, snapshot: BufferSnapshot) { - let snapshot = snapshot.unwrap_any::(); - self.inner.restore_snapshot(snapshot.inner); - self.content.truncate(snapshot.content_len); - } -} - -struct GroupElementsBufferSnapshot { - inner: BufferSnapshot, - content_len: usize, -} - /// IR element that forces the parent group to print in expanded mode. /// /// Has no effect if used outside of a group or element that introduce implicit groups (fill element). @@ -2217,7 +2063,7 @@ pub fn get_lines_before(next_node: &SyntaxNode) -> usize { // will handle newlines between the comment and the node !piece.is_comments() }) - .filter(|piece| piece.is_newline()) + .filter(|piece| piece.is_newline() || piece.is_skipped()) .count() } else { 0 diff --git a/crates/rome_formatter/src/comments.rs b/crates/rome_formatter/src/comments.rs index d6c34cad742..a9eed0dbbeb 100644 --- a/crates/rome_formatter/src/comments.rs +++ b/crates/rome_formatter/src/comments.rs @@ -1,12 +1,25 @@ +use crate::{FormatLanguage, TextSize}; +use rome_rowan::syntax::SyntaxTriviaPieceSkipped; use rome_rowan::{ - Direction, Language, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxTriviaPieceComments, - WalkEvent, + Direction, Language, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, + SyntaxTriviaPieceComments, WalkEvent, }; #[cfg(debug_assertions)] use std::cell::RefCell; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use std::rc::Rc; +/// Tests if the node has any leading comment that will be placed on its own line. +pub fn has_leading_own_line_comment( + node: &SyntaxNode, + comments: &Comments, +) -> bool { + comments + .leading_comments(node) + .iter() + .any(|comment| comment.lines_after() > 0) +} + #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub enum CommentKind { /// An inline comment that can appear between any two tokens and doesn't contain any line breaks. @@ -45,13 +58,49 @@ pub enum CommentKind { Line, } +impl CommentKind { + pub const fn is_line(&self) -> bool { + matches!(self, CommentKind::Line) + } + + pub const fn is_block(&self) -> bool { + matches!(self, CommentKind::Block) + } + + pub const fn is_inline_block(&self) -> bool { + matches!(self, CommentKind::InlineBlock) + } + + /// Returns `true` for comments that can appear inline between any two tokens. + /// + /// ## Examples + /// + /// ```rust + /// use rome_formatter::CommentKind; + /// + /// // Block and InlineBlock comments can appear inline + /// assert!(CommentKind::Block.is_inline()); + /// assert!(CommentKind::InlineBlock.is_inline()); + /// + /// // But not line comments + /// assert!(!CommentKind::Line.is_inline()) + /// ``` + pub const fn is_inline(&self) -> bool { + matches!(self, CommentKind::InlineBlock | CommentKind::Block) + } +} + #[derive(Debug, Clone)] pub struct SourceComment { /// The number of lines appearing before this comment lines_before: u32, + lines_after: u32, + /// The comment piece piece: SyntaxTriviaPieceComments, + + kind: CommentKind, } impl SourceComment { @@ -60,6 +109,10 @@ impl SourceComment { Self { lines_before: 0, piece, + + // FIXME + kind: CommentKind::InlineBlock, + lines_after: 0, } } @@ -68,6 +121,9 @@ impl SourceComment { Self { lines_before, piece, + // FIXME + kind: CommentKind::InlineBlock, + lines_after: 0, } } @@ -80,58 +136,153 @@ impl SourceComment { pub fn lines_before(&self) -> u32 { self.lines_before } + + pub fn lines_after(&self) -> u32 { + self.lines_after + } + + /// The kind of the comment + pub fn kind(&self) -> CommentKind { + self.kind + } } -impl CommentKind { - pub const fn is_line(&self) -> bool { - matches!(self, CommentKind::Line) +#[derive(Debug, Clone)] +pub struct SkippedTokenTrivia { + lines_before: u32, + piece: SyntaxTriviaPieceSkipped, +} + +#[derive(Debug, Clone)] +pub enum DanglingTrivia { + Comment(SourceComment), + SkippedToken(SkippedTokenTrivia), +} + +impl DanglingTrivia { + pub const fn is_comment(&self) -> bool { + matches!(self, DanglingTrivia::Comment(_)) } +} - pub const fn is_block(&self) -> bool { - matches!(self, CommentKind::Block) +#[derive(Debug, Clone)] +pub struct DecoratedComment { + preceding: Option>, + following: Option>, + following_token: SyntaxToken, + lines_before: u32, + lines_after: u32, + trailing_token_comment: bool, + comment: SyntaxTriviaPieceComments, + kind: CommentKind, +} + +impl DecoratedComment { + /// The node that fully encloses the comment (the comment's start and end position are fully in the + /// node's bounds). + pub fn enclosing_node(&self) -> SyntaxNode { + // SAFETY: Guaranteed by the fact that comments are extracted from a root node. + self.comment + .as_piece() + .token() + .parent() + .expect("Expected token to have a parent node.") } - pub const fn is_inline_block(&self) -> bool { - matches!(self, CommentKind::InlineBlock) + pub fn enclosing_token(&self) -> SyntaxToken { + self.comment.as_piece().token() } - /// Returns `true` for comments that can appear inline between any two tokens. - /// - /// ## Examples - /// - /// ```rust - /// use rome_formatter::CommentKind; - /// - /// // Block and InlineBlock comments can appear inline - /// assert!(CommentKind::Block.is_inline()); - /// assert!(CommentKind::InlineBlock.is_inline()); - /// - /// // But not line comments - /// assert!(!CommentKind::Line.is_inline()) - /// ``` - pub const fn is_inline(&self) -> bool { - matches!(self, CommentKind::InlineBlock | CommentKind::Block) + /// The node directly preceding the comment or [None] if the comment is preceded by a token or is the first + /// token in the program. + pub fn preceding_node(&self) -> Option<&SyntaxNode> { + self.preceding.as_ref() + } + + fn take_preceding_node(&mut self) -> Option> { + self.preceding.take() + } + + /// The node directly following the comment or [None] if the comment is followed by a token or is the last token in the program. + pub fn following_node(&self) -> Option<&SyntaxNode> { + self.following.as_ref() } + + fn take_following_node(&mut self) -> Option> { + self.following.take() + } + + /// The number of lines between this comment and the **previous** token, comment or skipped trivia. + pub fn lines_before(&self) -> u32 { + self.lines_before + } + + pub fn lines_after(&self) -> u32 { + self.lines_after + } + + /// `true` if the comment is part of the tokens [trailing trivia](SyntaxToken::trailing_trivia) + pub fn is_trailing_token_trivia(&self) -> bool { + self.trailing_token_comment + } + + /// Returns the [kind](CommentKind) of the comment. + pub fn kind(&self) -> CommentKind { + self.kind + } + + pub fn following_token(&self) -> &SyntaxToken { + &self.following_token + } +} + +impl From> for SourceComment { + fn from(decorated: DecoratedComment) -> Self { + Self { + lines_before: decorated.lines_before, + lines_after: decorated.lines_after, + piece: decorated.comment, + kind: decorated.kind, + } + } +} + +#[derive(Debug)] +pub enum CommentPosition { + /// Overrides the positioning of the comment to be a leading node comment. + Leading { + node: SyntaxNode, + comment: DecoratedComment, + }, + /// Overrides the positioning of the comment to be a trailing node comment. + Trailing { + node: SyntaxNode, + comment: DecoratedComment, + }, + + Dangling { + token: SyntaxToken, + comment: DecoratedComment, + }, + + /// Uses the default positioning rules for the comment. + /// TODO document rules + Default(DecoratedComment), } /// Defines how to format comments for a specific [Language]. -pub trait CommentStyle { +pub trait CommentStyle { + type Language: Language; + /// Returns `true` if a comment with the given `text` is a `rome-ignore format:` suppression comment. - fn is_suppression(&self, text: &str) -> bool; + fn is_suppression(text: &str) -> bool; /// Returns the (kind)[CommentKind] of the comment - fn get_comment_kind(&self, comment: &SyntaxTriviaPieceComments) -> CommentKind; - - /// Returns `true` if a token with the passed `kind` marks the start of a group. Common group tokens are: - /// * left parentheses: `(`, `[`, `{` - fn is_group_start_token(&self, kind: L::Kind) -> bool; + fn get_comment_kind(comment: &SyntaxTriviaPieceComments) -> CommentKind; - /// Returns `true` if a token with the passed `kind` marks the end of a group. Common group end tokens are: - /// * right parentheses: `)`, `]`, `}` - /// * end of statement token: `;` - /// * element separator: `,` or `.`. - /// * end of file token: `EOF` - fn is_group_end_token(&self, kind: L::Kind) -> bool; + fn position_comment( + comment: DecoratedComment, + ) -> CommentPosition; } /// Type that stores the comments of a tree and gives access to: @@ -141,7 +292,7 @@ pub trait CommentStyle { /// * the dangling comments of a token /// /// Cloning `comments` is cheap as it only involves bumping a reference counter. -#[derive(Debug, Default, Clone)] +#[derive(Debug, Clone)] pub struct Comments { /// The use of a [Rc] is necessary to achieve that [Comments] has a lifetime that is independent from the [crate::Formatter]. /// Having independent lifetimes is necessary to support the use case where a (formattable object)[crate::Format] @@ -163,65 +314,123 @@ pub struct Comments { impl Comments { /// Extracts all the suppressions from `root` and its child nodes. - pub fn from_node(root: &SyntaxNode, language: &FormatLanguage) -> Self + pub fn from_node(root: &SyntaxNode) -> Self where FormatLanguage: crate::FormatLanguage, { - let mut suppressed_nodes = HashSet::new(); - let mut current_node = None; + let mut builder = CommentsBuilderVisitor::::new(); for event in root.preorder_with_tokens(Direction::Next) { match event { WalkEvent::Enter(SyntaxElement::Node(node)) => { - // Lists cannot have a suppression comment attached, it must - // belong to either the entire parent node or one of the children - if node.kind().is_root() || node.kind().is_list() { - continue; - } - - if current_node.is_none() { - current_node = Some(node); - } + builder.visit_node(WalkEvent::Enter(node)) } + WalkEvent::Leave(SyntaxElement::Node(node)) => { - if current_node == Some(node) { - current_node = None; - } - } - WalkEvent::Enter(SyntaxElement::Token(token)) => { - if let Some(current_node) = current_node.take() { - for comment in token - .leading_trivia() - .pieces() - .filter_map(|piece| piece.as_comments()) - { - if language.comment_style().is_suppression(comment.text()) { - suppressed_nodes.insert(current_node); - break; - } - } - } + builder.visit_node(WalkEvent::Leave(node)) } + + WalkEvent::Enter(SyntaxElement::Token(token)) => builder.visit_token(token), WalkEvent::Leave(SyntaxElement::Token(_)) => { - // Token already handled as part of the enter event. + // Handled as part of enter } } } - let data = CommentsData { - suppressed_nodes, - #[cfg(debug_assertions)] - checked_suppressions: RefCell::default(), - }; - Self { - data: Rc::new(data), + data: Rc::new(dbg!(builder.finish())), } } + /// Returns `true` if the given [node] has + /// * any leading comments + /// * any trailing comments + /// * if any direct child token has any dangling trivia (either a skipped token trivia or a comment) + pub fn has_trivia(&self, node: &SyntaxNode) -> bool { + self.has_leading_comments(node) + || self.has_trailing_comments(node) + || self.has_node_dangling_trivia(node) + } + + /// Returns `true` if the given `node` has any leading or trailing comments. + #[inline] + pub fn has_comments(&self, node: &SyntaxNode) -> bool { + self.has_leading_comments(node) || self.has_trailing_comments(node) + } + + /// Returns `true` if the given [node] has any leading comments. + /// By default, a comment is a node's leading comment if: + /// * the previous sibling is a token + /// * there's a line break before the commend ending before this comment and the comment. + #[inline] + pub fn has_leading_comments(&self, node: &SyntaxNode) -> bool { + !self.leading_comments(node).is_empty() + } + + /// Returns `true` if the given [node] has any trailing comments. + /// By default, a comment is a node's trailing comment if: + /// * the next sibling is a token + /// * there's **no** line break between the node and this comment. + #[inline] + pub fn has_trailing_comments(&self, node: &SyntaxNode) -> bool { + !self.trailing_comments(node).is_empty() + } + + /// Returns `true` if any direct child token of [node] has any dangling trivia. + pub fn has_node_dangling_trivia(&self, node: &SyntaxNode) -> bool { + node.tokens().any(|token| self.has_dangling_trivia(&token)) + } + + /// Returns the [node]'s leading comments. + #[inline] + pub fn leading_comments(&self, node: &SyntaxNode) -> &[SourceComment] { + self.data.leading_comments.get(node) + } + + /// Returns the [node]'s trailing comments. + #[inline] + pub fn trailing_comments(&self, node: &SyntaxNode) -> &[SourceComment] { + self.data.trailing_comments.get(node) + } + + pub fn node_comments(&self, node: &SyntaxNode) -> impl Iterator> { + self.leading_comments(node) + .iter() + .chain(self.trailing_comments(node).iter()) + } + + /// Skipped token trivia or comment trivia that + #[inline] + pub fn dangling_trivia(&self, token: &SyntaxToken) -> &[DanglingTrivia] { + self.data.dangling_trivia.get(token) + } + + pub fn dangling_comments( + &self, + token: &SyntaxToken, + ) -> impl Iterator> { + self.dangling_trivia(token) + .iter() + .filter_map(|trivia| match trivia { + DanglingTrivia::Comment(comment) => Some(comment), + DanglingTrivia::SkippedToken(_) => None, + }) + } + + #[inline] + pub fn has_dangling_trivia(&self, token: &SyntaxToken) -> bool { + !self.dangling_trivia(token).is_empty() + } + + pub fn has_dangling_comments(&self, token: &SyntaxToken) -> bool { + let dangling_trivia = self.dangling_trivia(token); + + !dangling_trivia.is_empty() && dangling_trivia.iter().all(|trivia| trivia.is_comment()) + } + /// Returns `true` if the passed `node` has a leading suppression comment. /// - /// Suppression comments only apply if they are at the start of a node and they suppress the most + /// Suppression comments only apply if they at the start of a node and they suppress the most /// outer node. /// /// # Examples @@ -235,7 +444,11 @@ impl Comments { /// call expression is nested inside of the expression statement. pub fn is_suppressed(&self, node: &SyntaxNode) -> bool { self.mark_suppression_checked(node); - self.data.suppressed_nodes.contains(node) + let is_suppression = self.data.is_suppression; + + self.leading_comments(node) + .iter() + .any(|comment| is_suppression(comment.piece().text())) } /// Marks that it isn't necessary for the given node to check if it has been suppressed or not. @@ -285,10 +498,17 @@ Node: } } -#[derive(Debug, Default)] struct CommentsData { - /// Stores the nodes that have at least one leading suppression comment. - suppressed_nodes: HashSet>, + is_suppression: fn(&str) -> bool, + + /// Stores all leading node comments by node + leading_comments: AppendOnlyMultiMap, SourceComment>, + + /// Stores the trailing node comments by node + trailing_comments: AppendOnlyMultiMap, SourceComment>, + + /// Stores the dangling trivia by token + dangling_trivia: AppendOnlyMultiMap, DanglingTrivia>, /// Stores all nodes for which [Comments::is_suppressed] has been called. /// This index of nodes that have been checked if they have a suppression comments is used to @@ -301,3 +521,502 @@ struct CommentsData { #[cfg(debug_assertions)] checked_suppressions: RefCell>>, } + +impl std::fmt::Debug for CommentsData { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut debug_comments: Vec> = Vec::new(); + + for node in self.leading_comments.keys() { + debug_comments.extend( + self.leading_comments + .get(node) + .iter() + .map(|comment| DebugComment::Leading { node, comment }), + ); + } + + for node in self.trailing_comments.keys() { + debug_comments.extend( + self.trailing_comments + .get(node) + .iter() + .map(|comment| DebugComment::Trailing { node, comment }), + ); + } + + for token in self.dangling_trivia.keys() { + debug_comments.extend( + self.dangling_trivia + .get(token) + .iter() + .map(|trivia| DebugComment::Dangling { token, trivia }), + ); + } + + debug_comments.sort_unstable_by_key(|comment| comment.start()); + + f.debug_list().entries(debug_comments).finish() + } +} + +/// Helper for printing a comment of [Comments] +enum DebugComment<'a, L: Language> { + Leading { + comment: &'a SourceComment, + node: &'a SyntaxNode, + }, + Trailing { + comment: &'a SourceComment, + node: &'a SyntaxNode, + }, + Dangling { + trivia: &'a DanglingTrivia, + token: &'a SyntaxToken, + }, +} + +impl DebugComment<'_, L> { + fn start(&self) -> TextSize { + match self { + DebugComment::Leading { comment, .. } | DebugComment::Trailing { comment, .. } => { + comment.piece.text_range().start() + } + DebugComment::Dangling { trivia, .. } => match trivia { + DanglingTrivia::Comment(comment) => comment.piece.text_range().start(), + DanglingTrivia::SkippedToken(skipped) => skipped.piece.text_range().start(), + }, + } + } +} + +impl std::fmt::Debug for DebugComment<'_, L> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + DebugComment::Leading { node, comment } => f + .debug_struct("Leading") + .field("node", node) + .field("comment", comment) + .finish(), + DebugComment::Trailing { node, comment } => f + .debug_struct("Trailing") + .field("node", node) + .field("comment", comment) + .finish(), + DebugComment::Dangling { trivia, token } => f + .debug_struct("Dangling") + .field("token", token) + .field("trivia", trivia) + .finish(), + } + } +} + +#[derive(Debug, Default)] +struct CommentsBuilderVisitor { + node_comments: NodeCommentsBuilder, + dangling_trivia: DanglingTriviaBuilder, + preceding_node: Option>, + following_node: Option>, + last_token: Option>, +} + +impl CommentsBuilderVisitor +where + Language: FormatLanguage, +{ + fn new() -> Self { + Self { + node_comments: NodeCommentsBuilder::default(), + dangling_trivia: DanglingTriviaBuilder::default(), + preceding_node: None, + following_node: None, + last_token: None, + } + } + + fn visit_node(&mut self, event: WalkEvent>) { + match event { + WalkEvent::Enter(node) => { + // Lists cannot have comments attached. They either belong to the entire parent or to + // the first child. + if node.kind().is_list() { + return; + } + + // Associate comments with the most outer node + if self.following_node.is_none() { + self.following_node = Some(node); + } + } + + WalkEvent::Leave(node) => { + if self.following_node.as_ref() == Some(&node) { + self.following_node = None; + } + if !node.kind().is_list() { + self.preceding_node = Some(node); + } + } + } + } + + fn visit_token(&mut self, token: SyntaxToken) { + // Store the last processed comment so that we can set `line_break_after` + let mut last_comment = None; + + if let Some(last_token) = self.last_token.take() { + for piece in last_token + .trailing_trivia() + .pieces() + .filter_map(|piece| piece.as_comments()) + { + if let Some(last_comment) = last_comment.take() { + self.handle_comment(last_comment, &token); + } + + last_comment = Some(DecoratedComment { + preceding: self.preceding_node.clone(), + following: self.following_node.clone(), + following_token: token.clone(), + lines_before: 0, + lines_after: 0, + trailing_token_comment: true, + kind: Language::CommentStyle::get_comment_kind(&piece), + comment: piece, + }); + } + } + + let mut lines_before = 0; + let mut has_skipped = false; + + for leading in token.leading_trivia().pieces() { + if leading.is_newline() { + lines_before += 1; + } else if let Some(skipped) = leading.as_skipped() { + if let Some(mut last_comment) = last_comment.take() { + last_comment.lines_after = lines_before; + self.handle_comment(last_comment, &token); + } + + self.dangling_trivia.insert_trivia( + token.clone(), + DanglingTrivia::SkippedToken(SkippedTokenTrivia { + lines_before, + piece: skipped, + }), + ); + + lines_before = 0; + has_skipped = true; + } else if let Some(comment) = leading.as_comments() { + if let Some(mut last_comment) = last_comment.take() { + last_comment.lines_after = lines_before; + self.handle_comment(last_comment, &token); + } + + let kind = Language::CommentStyle::get_comment_kind(&comment); + if has_skipped { + self.dangling_trivia.insert_trivia( + token.clone(), + DanglingTrivia::Comment(SourceComment { + lines_before, + // FIXME + lines_after: 0, + piece: comment, + kind, + }), + ); + } else { + last_comment = Some(DecoratedComment { + preceding: self.preceding_node.clone(), + following: self.following_node.clone(), + following_token: token.clone(), + lines_before, + lines_after: 0, + trailing_token_comment: false, + kind, + comment, + }); + } + lines_before = 0; + } + } + + if let Some(mut last_comment) = last_comment.take() { + last_comment.lines_after = lines_before; + self.handle_comment(last_comment, &token); + } + + // Any comment following now is preceded by 'token' and not a node. + self.preceding_node = None; + self.following_node = None; + self.last_token = Some(token); + } + + fn handle_comment( + &mut self, + comment: DecoratedComment, + token: &SyntaxToken, + ) { + match Language::CommentStyle::position_comment(comment) { + CommentPosition::Leading { node, comment } => { + self.node_comments + .insert_leading_comment(node, comment.into()); + } + CommentPosition::Trailing { node, comment } => { + self.node_comments + .insert_trailing_comment(node, comment.into()); + } + CommentPosition::Dangling { token, comment } => self + .dangling_trivia + .insert_trivia(token, DanglingTrivia::Comment(comment.into())), + CommentPosition::Default(mut comment) => { + if comment.is_trailing_token_trivia() { + let enclosing = comment.enclosing_node(); + + // The enclosing can only ever be a list if the comment is a leading or trailing comment of a + // separator token in a separated list. + // Example: + // ```js + // [ + // a, // test + // b + // ] + // ``` + // The default algorithm would make `// test` a leading comment of the node `b` but + // it should be a trailing comment of `a` because that's most likely what the user intended. + if enclosing.kind().is_list() && comment.lines_after() > 0 { + if let Some(SyntaxElement::Node(node)) = + comment.comment.as_piece().token().prev_sibling_or_token() + { + self.node_comments + .insert_trailing_comment(node, comment.into()); + return; + } + } + + match (comment.take_preceding_node(), comment.take_following_node()) { + (Some(preceding), Some(following)) => { + if Language::CommentStyle::is_suppression(comment.comment.text()) { + self.node_comments + .insert_leading_comment(following, comment.into()); + } else { + self.node_comments + .insert_trailing_comment(preceding, comment.into()); + } + } + (Some(preceding), None) => { + self.node_comments + .insert_trailing_comment(preceding, comment.into()); + } + (None, Some(following)) => { + self.node_comments + .insert_leading_comment(following, comment.into()); + } + (None, None) => { + self.dangling_trivia.insert_trivia( + token.clone(), + DanglingTrivia::Comment(comment.into()), + ); + } + } + } else { + match (comment.take_following_node(), comment.take_preceding_node()) { + (Some(following), _) => { + self.node_comments + .insert_leading_comment(following, comment.into()); + } + (None, Some(preceding)) => { + self.node_comments + .insert_trailing_comment(preceding, comment.into()); + } + (None, None) => { + self.dangling_trivia.insert_trivia( + token.clone(), + DanglingTrivia::Comment(comment.into()), + ); + } + } + } + } + } + } + + fn finish(self) -> CommentsData { + let (leading_comments, trailing_comments) = self.node_comments.finish(); + let dangling_trivia = self.dangling_trivia.finish(); + + CommentsData { + is_suppression: Language::CommentStyle::is_suppression, + leading_comments, + trailing_comments, + dangling_trivia, + + #[cfg(debug_assertions)] + checked_suppressions: RefCell::default(), + } + } +} + +// TODO necessary? +#[derive(Debug)] +struct NodeCommentsBuilder { + leading_comments: AppendOnlyMultiMap, SourceComment>, + trailing_comments: AppendOnlyMultiMap, SourceComment>, +} + +impl NodeCommentsBuilder { + fn insert_leading_comment(&mut self, node: SyntaxNode, comment: SourceComment) { + self.leading_comments.append(node, comment); + } + + fn insert_trailing_comment(&mut self, node: SyntaxNode, comment: SourceComment) { + self.trailing_comments.append(node, comment); + } + + fn finish( + self, + ) -> ( + AppendOnlyMultiMap, SourceComment>, + AppendOnlyMultiMap, SourceComment>, + ) { + (self.leading_comments, self.trailing_comments) + } +} + +impl Default for NodeCommentsBuilder { + fn default() -> Self { + Self { + leading_comments: AppendOnlyMultiMap::new(), + trailing_comments: AppendOnlyMultiMap::new(), + } + } +} + +#[derive(Debug)] +struct DanglingTriviaBuilder { + trivia: AppendOnlyMultiMap, DanglingTrivia>, +} + +impl DanglingTriviaBuilder { + fn insert_trivia(&mut self, token: SyntaxToken, trivia: DanglingTrivia) { + self.trivia.append(token, trivia) + } + + fn finish(self) -> AppendOnlyMultiMap, DanglingTrivia> { + self.trivia + } +} + +impl Default for DanglingTriviaBuilder { + fn default() -> Self { + Self { + trivia: AppendOnlyMultiMap::new(), + } + } +} + +/// Multimap implementation that uses a shared vector to store the values for each key. +/// +/// The map uses a single vector to store the values of all keys together with a map +/// that stores the the value range for each key. The upside of using a single vector for all +/// values is that it avoids allocating a new vector for every element. The downside is that the values +/// for a key must all be appended in order. +#[derive(Clone)] +struct AppendOnlyMultiMap { + index: HashMap, + values: Vec, +} + +impl AppendOnlyMultiMap { + pub fn new() -> Self { + Self { + index: HashMap::new(), + values: Vec::new(), + } + } + + /// Appends the `value` to the `key`'s values. + /// + /// # Panics + /// If `key` is already present in the map but other keys have been inserted since it was initially inserted. + pub fn append(&mut self, key: K, value: V) { + if let Some(range) = self.index.get_mut(&key) { + assert_eq!(self.values.len(), range.end()); + + self.values.push(value); + range.increment_end(); + } else { + let range = ValueRange::single(self.values.len()); + self.values.push(value); + self.index.insert(key, range); + } + } + + /// Returns an iterator over all the keys + pub fn keys(&self) -> impl Iterator { + self.index.keys() + } + + /// Returns a slice of the values associated with `key`. + pub fn get(&self, key: &K) -> &[V] { + if let Some(range) = self.index.get(key) { + &self.values[range.start()..range.end()] + } else { + &[] + } + } +} + +impl Default for AppendOnlyMultiMap { + fn default() -> Self { + Self { + values: Vec::new(), + index: HashMap::new(), + } + } +} + +impl std::fmt::Debug for AppendOnlyMultiMap +where + K: std::fmt::Debug, + V: std::fmt::Debug, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut builder = f.debug_map(); + + for (key, range) in &self.index { + builder.entry(&key, &&self.values[range.start()..range.end()]); + } + + builder.finish() + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +struct ValueRange { + start: u32, + end: u32, +} + +impl ValueRange { + fn single(position: usize) -> Self { + Self { + start: position as u32, + end: (position + 1) as u32, + } + } + + fn start(&self) -> usize { + self.start as usize + } + + fn end(&self) -> usize { + self.end as usize + } + + fn increment_end(&mut self) { + self.end += 1; + } +} diff --git a/crates/rome_formatter/src/format_element.rs b/crates/rome_formatter/src/format_element.rs index c2a1b566aff..f0b4e1902f1 100644 --- a/crates/rome_formatter/src/format_element.rs +++ b/crates/rome_formatter/src/format_element.rs @@ -405,10 +405,6 @@ impl Interned { pub(crate) fn new(element: FormatElement) -> Self { Self(Rc::new(element)) } - - pub(crate) fn try_unwrap(this: Interned) -> Result { - Rc::try_unwrap(this.0).map_err(Interned) - } } impl PartialEq for Interned { diff --git a/crates/rome_formatter/src/lib.rs b/crates/rome_formatter/src/lib.rs index 7ec3d306858..f6ada102607 100644 --- a/crates/rome_formatter/src/lib.rs +++ b/crates/rome_formatter/src/lib.rs @@ -41,7 +41,6 @@ pub mod token; use crate::formatter::Formatter; use crate::group_id::UniqueGroupIdBuilder; use crate::prelude::syntax_token_cow_slice; -use std::any::TypeId; #[cfg(debug_assertions)] use crate::printed_tokens::PrintedTokens; @@ -53,13 +52,16 @@ pub use builders::{ if_group_fits_on_line, indent, labelled, line_suffix, soft_block_indent, soft_line_break, soft_line_break_or_space, soft_line_indent_or_space, space, text, BestFitting, }; -pub use comments::{CommentKind, CommentStyle, Comments, SourceComment}; +pub use comments::{ + has_leading_own_line_comment, CommentKind, CommentPosition, CommentStyle, Comments, + DanglingTrivia, DecoratedComment, SourceComment, +}; pub use format_element::{normalize_newlines, FormatElement, Text, Verbatim, LINE_TERMINATORS}; pub use group_id::GroupId; use indexmap::IndexSet; use rome_rowan::{ - Language, RawSyntaxKind, SyntaxElement, SyntaxError, SyntaxKind, SyntaxNode, SyntaxResult, - SyntaxToken, SyntaxTriviaPieceComments, TextRange, TextSize, TokenAtOffset, + Language, SyntaxElement, SyntaxError, SyntaxNode, SyntaxResult, SyntaxToken, SyntaxTriviaPiece, + TextRange, TextSize, TokenAtOffset, }; pub use source_map::{TransformSourceMap, TransformSourceMapBuilder}; use std::error::Error; @@ -232,14 +234,10 @@ pub trait FormatOptions { /// The context customizes the comments formatting and stores the comments of the CST. pub trait CstFormatContext: FormatContext { type Language: Language; - type Style: CommentStyle; - - /// Rule for formatting leading comments. - type LeadingCommentRule: FormatRule, Context = Self> + Default; + type Style: CommentStyle; - /// Customizes how comments are formatted - #[deprecated(note = "Prefer FormatLanguage::comment_style")] - fn comment_style(&self) -> Self::Style; + /// Rule for formatting comments. + type CommentRule: FormatRule, Context = Self> + Default; /// Returns a reference to the program's comments. fn comments(&self) -> &Comments; @@ -832,14 +830,11 @@ pub trait FormatLanguage { type Context: CstFormatContext; /// The type specifying how to format comments. - type CommentStyle: CommentStyle; + type CommentStyle: CommentStyle; /// The rule type that can format a [SyntaxNode] of this language type FormatRule: FormatRule, Context = Self::Context> + Default; - /// Customizes how comments are formatted - fn comment_style(&self) -> Self::CommentStyle; - /// Performs an optional pre-processing of the tree. This can be useful to remove nodes /// that otherwise complicate formatting. /// @@ -860,6 +855,8 @@ pub trait FormatLanguage { true } + // TODO move is_suppression and + /// Returns the formatting options fn options(&self) -> &::Options; @@ -884,7 +881,7 @@ pub fn format_node( None => (root.clone(), None), }; - let comments = Comments::from_node(&root, &language); + let comments = Comments::from_node::(&root); let format_node = FormatRefWithRule::new(&root, L::FormatRule::default()); let context = language.create_context(comments, source_map); @@ -1250,7 +1247,7 @@ pub fn format_sub_tree( )) } -impl Format for SyntaxTriviaPieceComments { +impl Format for SyntaxTriviaPiece { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { let range = self.text_range(); @@ -1258,7 +1255,7 @@ impl Format for SyntaxTriviaPieceComments { f, [syntax_token_cow_slice( normalize_newlines(self.text().trim(), LINE_TERMINATORS), - &self.as_piece().token(), + &self.token(), range.start() )] ) @@ -1275,12 +1272,6 @@ pub struct FormatState { group_id_builder: UniqueGroupIdBuilder, - /// `true` if the last formatted output is an inline comment that may need a space between the next token or comment. - last_content_inline_comment: bool, - - /// The kind of the last formatted token - last_token_kind: Option, - /// Tracks comments that have been formatted manually and shouldn't be emitted again /// when formatting the token the comments belong to. /// @@ -1288,7 +1279,7 @@ pub struct FormatState { /// Storing the position is sufficient because comments are guaranteed to not be empty /// (all start with a specific comment sequence) and thus, no two comments can have the same /// absolute position. - manually_formatted_comments: IndexSet, + manually_formatted_token_trivia: IndexSet, // This is using a RefCell as it only exists in debug mode, // the Formatter is still completely immutable in release builds @@ -1303,11 +1294,6 @@ where fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { f.debug_struct("FormatState") .field("context", &self.context) - .field( - "has_trailing_inline_comment", - &self.last_content_inline_comment, - ) - .field("last_token_kind", &self.last_token_kind) .finish() } } @@ -1318,9 +1304,7 @@ impl FormatState { Self { context, group_id_builder: Default::default(), - last_content_inline_comment: false, - last_token_kind: None, - manually_formatted_comments: IndexSet::default(), + manually_formatted_token_trivia: IndexSet::default(), #[cfg(debug_assertions)] printed_tokens: Default::default(), } @@ -1330,36 +1314,7 @@ impl FormatState { self.context } - /// Returns `true` if the last written content is an inline comment with no trailing whitespace. - /// - /// The formatting of the next content may need to insert a whitespace to separate the - /// inline comment from the next content. - pub fn is_last_content_inline_comment(&self) -> bool { - self.last_content_inline_comment - } - - /// Sets whether the last written content is an inline comment that has no trailing whitespace. - pub fn set_last_content_inline_comment(&mut self, has_comment: bool) { - self.last_content_inline_comment = has_comment; - } - - /// Returns the kind of the last formatted token. - pub fn last_token_kind(&self) -> Option { - self.last_token_kind - } - - /// Sets the kind of the last formatted token and sets `last_content_inline_comment` to `false`. - pub fn set_last_token_kind(&mut self, kind: Kind) { - self.set_last_token_kind_raw(Some(LastTokenKind { - kind_type: TypeId::of::(), - kind: kind.to_raw(), - })); - } - - pub fn set_last_token_kind_raw(&mut self, kind: Option) { - self.last_token_kind = kind; - } - + /// FIXME Update documentation /// Mark the passed comment as formatted. This is necessary if a comment from a token is formatted /// to avoid that the comment gets emitted again when formatting that token. /// @@ -1380,22 +1335,16 @@ impl FormatState { /// /// This can be accomplished by manually formatting the leading/trailing trivia of the string literal expression /// before/after the close parentheses and then mark the comments as handled. - pub fn mark_comment_as_formatted( - &mut self, - comment: &SyntaxTriviaPieceComments, - ) { - self.manually_formatted_comments - .insert(comment.text_range().start()); + pub fn mark_token_trivia_formatted(&mut self, token: &SyntaxToken) { + self.manually_formatted_token_trivia + .insert(token.text_range().start()); } /// Returns `true` if this comment has already been formatted manually /// and shouldn't be formatted again when formatting the token to which the comment belongs. - pub fn is_comment_formatted( - &self, - comment: &SyntaxTriviaPieceComments, - ) -> bool { - self.manually_formatted_comments - .contains(&comment.text_range().start()) + pub fn is_token_trivia_formatted(&self, token: &SyntaxToken) -> bool { + self.manually_formatted_token_trivia + .contains(&token.text_range().start()) } /// Returns the context specifying how to format the current CST @@ -1445,9 +1394,7 @@ where { pub fn snapshot(&self) -> FormatStateSnapshot { FormatStateSnapshot { - last_content_inline_comment: self.last_content_inline_comment, - last_token_kind: self.last_token_kind, - manual_handled_comments_len: self.manually_formatted_comments.len(), + manually_handled_trivia_len: self.manually_formatted_token_trivia.len(), #[cfg(debug_assertions)] printed_tokens: self.printed_tokens.clone(), } @@ -1455,16 +1402,12 @@ where pub fn restore_snapshot(&mut self, snapshot: FormatStateSnapshot) { let FormatStateSnapshot { - last_content_inline_comment, - last_token_kind, - manual_handled_comments_len, + manually_handled_trivia_len: manual_handled_comments_len, #[cfg(debug_assertions)] printed_tokens, } = snapshot; - self.last_content_inline_comment = last_content_inline_comment; - self.last_token_kind = last_token_kind; - self.manually_formatted_comments + self.manually_formatted_token_trivia .truncate(manual_handled_comments_len); cfg_if::cfg_if! { if #[cfg(debug_assertions)] { @@ -1474,26 +1417,8 @@ where } } -#[derive(Eq, PartialEq, Debug, Copy, Clone)] -pub struct LastTokenKind { - kind_type: TypeId, - kind: RawSyntaxKind, -} - -impl LastTokenKind { - pub fn as_language(&self) -> Option { - if self.kind_type == TypeId::of::() { - Some(L::Kind::from_raw(self.kind)) - } else { - None - } - } -} - pub struct FormatStateSnapshot { - last_content_inline_comment: bool, - last_token_kind: Option, - manual_handled_comments_len: usize, + manually_handled_trivia_len: usize, #[cfg(debug_assertions)] printed_tokens: PrintedTokens, } diff --git a/crates/rome_formatter/src/prelude.rs b/crates/rome_formatter/src/prelude.rs index 7e796d60179..969bf9ead3e 100644 --- a/crates/rome_formatter/src/prelude.rs +++ b/crates/rome_formatter/src/prelude.rs @@ -4,8 +4,8 @@ pub use crate::format_extensions::{FormatOptional as _, MemoizeFormat, Memoized} pub use crate::formatter::Formatter; pub use crate::printer::PrinterOptions; pub use crate::token::{ - format_leading_trivia, format_only_if_breaks, format_removed, format_replaced, - format_trailing_trivia, format_trimmed_token, + format_dangling_trivia, format_leading_comments, format_only_if_breaks, format_removed, + format_replaced, format_trailing_comments, format_trimmed_token, }; pub use crate::{ diff --git a/crates/rome_formatter/src/printer/mod.rs b/crates/rome_formatter/src/printer/mod.rs index 826a3ffe593..3135ce8e378 100644 --- a/crates/rome_formatter/src/printer/mod.rs +++ b/crates/rome_formatter/src/printer/mod.rs @@ -355,28 +355,10 @@ impl<'a> Printer<'a> { return; } - // If the indentation level has changed since these line suffixes were queued, - // insert a line break before to push the comments into the new indent block - // SAFETY: Indexing into line_suffixes is guarded by the above call to is_empty - let has_line_break = self.state.line_suffixes[0].args.indent.level() < args.indent.level(); - // Print this line break element again once all the line suffixes have been flushed let call_self = PrintElementCall::new(line_break, args); - let line_break = if has_line_break { - // Duplicate this line break element before the line - // suffixes if a line break is required - Some(call_self.clone()) - } else { - None - }; - - queue.extend( - line_break - .into_iter() - .chain(self.state.line_suffixes.drain(..)) - .chain(once(call_self)), - ); + queue.extend(self.state.line_suffixes.drain(..).chain(once(call_self))); } /// Tries to fit as much content as possible on a single line. diff --git a/crates/rome_formatter/src/token.rs b/crates/rome_formatter/src/token.rs index 680c89c9503..37ae40b0d1e 100644 --- a/crates/rome_formatter/src/token.rs +++ b/crates/rome_formatter/src/token.rs @@ -1,278 +1,251 @@ -use crate::comments::CommentStyle; use crate::prelude::*; use crate::{ - format_args, write, Argument, Arguments, CommentKind, CstFormatContext, FormatRefWithRule, - GroupId, LastTokenKind, SourceComment, + write, Argument, Arguments, CommentKind, CstFormatContext, DanglingTrivia, FormatRefWithRule, + GroupId, SourceComment, }; -use rome_rowan::{Language, SyntaxToken, SyntaxTriviaPiece}; +use rome_rowan::{Language, SyntaxNode, SyntaxToken}; ///! Provides builders for working with tokens and the tokens trivia -/// Formats a token without its leading or trailing trivia -/// -/// ## Warning -/// It's your responsibility to format leading or trailing comments and skipped trivia. -pub const fn format_trimmed_token(token: &SyntaxToken) -> FormatTrimmedToken { - FormatTrimmedToken { token } +/// Formats the leading comments of `node` +pub const fn format_leading_comments( + node: &SyntaxNode, +) -> FormatLeadingComments { + FormatLeadingComments::Node(node) } -#[derive(Debug, Eq, PartialEq, Copy, Clone)] -pub struct FormatTrimmedToken<'a, L: Language> { - token: &'a SyntaxToken, +/// Formats the leading comments of a node. +#[derive(Debug, Copy, Clone)] +pub enum FormatLeadingComments<'a, L: Language> { + Node(&'a SyntaxNode), + Comments(&'a [SourceComment]), } -impl Format for FormatTrimmedToken<'_, L> +impl Format for FormatLeadingComments<'_, Context::Language> where - C: CstFormatContext, + Context: CstFormatContext, { - fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { - write_space_between_comment_and_token(self.token.kind(), f)?; - - f.state_mut().set_last_token_kind(self.token.kind()); + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + let comments = f.context().comments().clone(); + + let leading_comments = match self { + FormatLeadingComments::Node(node) => comments.leading_comments(node), + FormatLeadingComments::Comments(comments) => comments, + }; + + for comment in leading_comments { + let format_comment = FormatRefWithRule::new(comment, Context::CommentRule::default()); + write!(f, [format_comment])?; + + match comment.kind() { + CommentKind::Block | CommentKind::InlineBlock => { + match comment.lines_after() { + 0 => write!(f, [space()])?, + 1 => { + if comment.lines_before() == 0 { + write!(f, [soft_line_break_or_space()])?; + } else { + write!(f, [hard_line_break()])?; + } + } + _ => write!(f, [empty_line()])?, + }; + } + CommentKind::Line => match comment.lines_after() { + 0 | 1 => write!(f, [hard_line_break()])?, + _ => write!(f, [empty_line()])?, + }, + } + } - let trimmed_range = self.token.text_trimmed_range(); - syntax_token_text_slice(self.token, trimmed_range).fmt(f) + Ok(()) } } -/// Formats a token that has been inserted by the formatter and isn't present in the source text. -/// Takes care of correctly handling spacing to the previous token's trailing trivia. -pub struct FormatInserted -where - L: Language, -{ - kind: L::Kind, - text: &'static str, +/// Formats the trailing comments of `node`. +pub const fn format_trailing_comments( + node: &SyntaxNode, +) -> FormatTrailingComments { + FormatTrailingComments::Node(node) } -impl FormatInserted -where - L: Language, -{ - pub fn new(kind: L::Kind, text: &'static str) -> Self { - Self { kind, text } - } +/// Formats the trailing comments of `node` +#[derive(Debug, Clone, Copy)] +pub enum FormatTrailingComments<'a, L: Language> { + Node(&'a SyntaxNode), + Comments(&'a [SourceComment]), } -impl Format for FormatInserted +impl Format for FormatTrailingComments<'_, Context::Language> where - L: Language + 'static, - C: CstFormatContext, + Context: CstFormatContext, { - fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { - write_space_between_comment_and_token(self.kind, f)?; + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + let comments = f.context().comments().clone(); + let trailing_comments = match self { + FormatTrailingComments::Node(node) => comments.trailing_comments(node), + FormatTrailingComments::Comments(comments) => comments, + }; - f.state_mut().set_last_token_kind(self.kind); - text(self.text).fmt(f) - } -} + let mut total_lines_before = 0; -fn write_space_between_comment_and_token( - token_kind: ::Kind, - f: &mut Formatter, -) -> FormatResult<()> -where - Context: CstFormatContext, -{ - let is_last_content_inline_content = f.state().is_last_content_inline_comment(); - - // Insert a space if the previous token has any trailing comments and this is not a group - // end token - #[allow(deprecated)] - if is_last_content_inline_content && !f.context().comment_style().is_group_end_token(token_kind) - { - space().fmt(f)?; - } + for comment in trailing_comments { + total_lines_before += comment.lines_before(); - f.state_mut().set_last_content_inline_comment(false); + let format_comment = FormatRefWithRule::new(comment, Context::CommentRule::default()); - Ok(()) -} + // This allows comments at the end of nested structures: + // { + // x: 1, + // y: 2 + // // A comment + // } + // Those kinds of comments are almost always leading comments, but + // here it doesn't go "outside" the block and turns it into a + // trailing comment for `2`. We can simulate the above by checking + // if this a comment on its own line; normal trailing comments are + // always at the end of another expression. + if total_lines_before > 0 { + write!( + f, + [ + line_suffix(&format_with(|f| { + match comment.lines_before() { + 0 | 1 => write!(f, [hard_line_break()])?, + _ => write!(f, [empty_line()])?, + }; + + write!(f, [format_comment]) + })), + expand_parent() + ] + )?; + } else { + let content = format_with(|f| write!(f, [space(), format_comment])); + if comment.kind().is_line() { + write!(f, [line_suffix(&content), expand_parent()])?; + } else { + write!(f, [content])?; + } + } + } -/// Inserts a open parentheses before the specified token and ensures -/// that any leading trivia of the token is formatted **before** the inserted parentheses. -/// -/// # Example -/// -/// ```javascript -/// /* leading */ "string"; -/// ``` -/// -/// Becomes -/// -/// ```javascript -/// /* leading */ ("string"; -/// ``` -/// -/// when inserting the "(" before the "string" token. -#[derive(Clone, Debug)] -pub struct FormatInsertedOpenParen<'a, L> -where - L: Language, -{ - /// The token before which the open paren must be inserted - before_token: Option<&'a SyntaxToken>, + Ok(()) + } +} - /// The token text of the open paren - text: &'static str, +pub const fn format_dangling_trivia( + token: &SyntaxToken, +) -> FormatDanglingTrivia { + FormatDanglingTrivia { + token, + indent: false, + ignore_formatted_check: false, + } +} - /// The kind of the open paren - kind: L::Kind, +/// Formats the dangling trivia of `token`. +pub struct FormatDanglingTrivia<'a, L: Language> { + token: &'a SyntaxToken, + indent: bool, + ignore_formatted_check: bool, } -impl<'a, L> FormatInsertedOpenParen<'a, L> -where - L: Language, -{ - pub fn new( - before_token: Option<&'a SyntaxToken>, - kind: L::Kind, - text: &'static str, - ) -> Self { - Self { - before_token, - kind, - text, - } +impl FormatDanglingTrivia<'_, L> { + pub fn indented(mut self) -> Self { + self.indent = true; + self + } + + pub fn ignore_formatted_check(mut self) -> Self { + self.ignore_formatted_check = true; + self } } -impl Format for FormatInsertedOpenParen<'_, L> +impl Format for FormatDanglingTrivia<'_, Context::Language> where - L: Language + 'static, - Context: CstFormatContext, + Context: CstFormatContext, { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { - let mut comments = Vec::new(); - - if let Some(before_token) = &self.before_token { - // Format the leading trivia of the next token as the leading trivia of the open paren. - let leading_pieces = before_token.leading_trivia().pieces(); - - let mut lines_before = 0; - - for piece in leading_pieces { - if let Some(comment) = piece.as_comments() { - comments.push(SourceComment::leading(comment, lines_before)); - lines_before = 0; - } else if piece.is_newline() { - lines_before += 1; - } else if piece.is_skipped() { - // Keep the skipped trivia inside of the parens. Handled by the - // formatting of the `before_token`. - break; + if !self.ignore_formatted_check && f.state().is_token_trivia_formatted(self.token) { + return Ok(()); + } + + let comments = f.context().comments().clone(); + let dangling_trivia = comments.dangling_trivia(self.token); + let mut leading_comments_end = 0; + let mut last_line_comment = false; + + let format_leading_comments = format_once(|f| { + if self.indent && matches!(dangling_trivia.first(), Some(DanglingTrivia::Comment(_))) { + write!(f, [hard_line_break()])?; + } + + // Write all comments up to the first skipped token trivia or the token + let mut join = f.join_with(hard_line_break()); + + for trivia in dangling_trivia { + match trivia { + DanglingTrivia::Comment(comment) => { + let format_comment = + FormatRefWithRule::new(comment, Context::CommentRule::default()); + join.entry(&format_comment); + + last_line_comment = comment.kind().is_line(); + leading_comments_end += 1; + } + _ => { + break; + } } } - write!( - f, - [FormatLeadingComments { - comments: &comments, - trim_mode: TriviaPrintMode::Full, - lines_before_token: lines_before, - }] - )?; + join.finish() + }); + + if self.indent { + write!(f, [block_indent(&format_leading_comments)])?; + } else { + write!(f, [format_leading_comments])?; + + if last_line_comment { + write!(f, [hard_line_break()])?; + } } - write!( - f, - [FormatInserted { - kind: self.kind, - text: self.text, - }] - )?; - - // Prevent that the comments get formatted again when formatting the - // `before_token` - for comment in comments { - f.state_mut().mark_comment_as_formatted(comment.piece()); + if leading_comments_end != dangling_trivia.len() { + panic!("Skipped token trivia not yet supported"); } + f.state_mut().mark_token_trivia_formatted(self.token); + Ok(()) } } -/// Inserts a closing parentheses before another token and moves that tokens -/// trailing trivia after the closing parentheses. -/// -/// # Example -/// -/// ```javascript -/// "string" /* trailing */; -/// ``` -/// -/// Becomes +/// Formats a token without its leading or trailing trivia /// -/// ```javascript -/// "string") /* trailing */ -/// ``` -#[derive(Clone, Debug)] -pub struct FormatInsertedCloseParen -where - L: Language, -{ - /// The token after which the close paren must be inserted - comments: Vec>, - - /// The token text of the close paren - text: &'static str, - - /// The kind of the close paren - kind: L::Kind, +/// ## Warning +/// It's your responsibility to format leading or trailing comments and skipped trivia. +pub const fn format_trimmed_token(token: &SyntaxToken) -> FormatTrimmedToken { + FormatTrimmedToken { token } } -impl FormatInsertedCloseParen -where - L: Language, -{ - pub fn after_token( - after_token: Option<&SyntaxToken>, - kind: L::Kind, - text: &'static str, - f: &mut Formatter, - ) -> Self { - let mut comments = Vec::new(); - - if let Some(after_token) = after_token { - // "Steal" the trailing comments and mark them as handled. - // Must be done eagerly before formatting because the `after_token` - // gets formatted **before** formatting the inserted paren. - for piece in after_token.trailing_trivia().pieces() { - if let Some(comment) = piece.as_comments() { - f.state_mut().mark_comment_as_formatted(&comment); - comments.push(SourceComment::trailing(comment)); - } - } - } - - Self { - comments, - kind, - text, - } - } +#[derive(Debug, Eq, PartialEq, Copy, Clone)] +pub struct FormatTrimmedToken<'a, L: Language> { + token: &'a SyntaxToken, } -impl Format for FormatInsertedCloseParen +impl Format for FormatTrimmedToken<'_, L> where - L: Language + 'static, - Context: CstFormatContext, + C: CstFormatContext, { - fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { - write!( - f, - [ - FormatInserted { - kind: self.kind, - text: self.text, - }, - FormatTrailingTrivia::new(self.comments.iter().cloned(), self.kind,) - .skip_formatted_check() - ] - ) + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + let trimmed_range = self.token.text_trimmed_range(); + syntax_token_text_slice(self.token, trimmed_range).fmt(f) } } - /// Formats the leading and trailing trivia of a removed token. /// /// Formats all leading and trailing comments up to the first line break or skipped token trivia as a trailing @@ -301,58 +274,10 @@ where fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { f.state_mut().track_token(self.token); - let last = f.state().last_token_kind(); - - write_removed_token_trivia(self.token, last, f) + write!(f, [format_dangling_trivia(self.token)]) } } -/// Writes the trivia of a removed token -fn write_removed_token_trivia( - token: &SyntaxToken, - last_token: Option, - f: &mut Formatter, -) -> FormatResult<()> -where - L: Language + 'static, - C: CstFormatContext, -{ - let mut pieces = token - .leading_trivia() - .pieces() - .chain(token.trailing_trivia().pieces()) - .peekable(); - - let last_token = last_token.and_then(|token| token.as_language::()); - - // If this isn't the first token than format all comments that are before the first skipped token - // trivia or line break as the trailing trivia of the previous token (which these comments will - // become if the document gets formatted a second time). - if let Some(last_token) = last_token.as_ref() { - let mut trailing_comments = vec![]; - - while let Some(piece) = pieces.peek() { - if let Some(comment) = piece.as_comments() { - if !f.state().is_comment_formatted(&comment) { - trailing_comments.push(SourceComment::trailing(comment)); - } - } else if piece.is_newline() || piece.is_skipped() { - break; - } - - pieces.next(); - } - - FormatTrailingTrivia::new(trailing_comments.into_iter(), *last_token).fmt(f)?; - } - - write_leading_trivia(pieces, token, TriviaPrintMode::Full, f, || { - token.prev_token() - })?; - - Ok(()) -} - /// Print out a `token` from the original source with a different `content`. /// /// This will print the trivia that belong to `token` to `content`; @@ -389,14 +314,9 @@ where fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { f.state_mut().track_token(self.token); - format_leading_trivia(self.token).fmt(f)?; + write!(f, [format_dangling_trivia(self.token)])?; - write_space_between_comment_and_token(self.token.kind(), f)?; - - f.state_mut().set_last_token_kind(self.token.kind()); - - f.write_fmt(Arguments::from(&self.content))?; - format_trailing_trivia(self.token).fmt(f) + f.write_fmt(Arguments::from(&self.content)) } } @@ -443,408 +363,14 @@ where C: CstFormatContext, { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { - // Store the last token and last trailing comment before formatting the content which will override the state. - // Is it safe to set `last_trailing_comment` only in the format removed because format removed may set it to true - // but it's false for the "break" case. Ignorable, because it's after a new line break in that case? - let last_token = f.state().last_token_kind(); - let is_last_content_inline_comment = f.state().is_last_content_inline_comment(); - write!( f, [ if_group_breaks(&Arguments::from(&self.content)).with_group_id(self.group_id), // Print the trivia otherwise - if_group_fits_on_line(&format_with(|f| { - // Restore state to how it was before formatting the "breaks" variant - f.state_mut().set_last_token_kind_raw(last_token); - f.state_mut() - .set_last_content_inline_comment(is_last_content_inline_comment); - - write_removed_token_trivia(self.token, last_token, f) - })) - .with_group_id(self.group_id), + if_group_fits_on_line(&format_dangling_trivia(self.token)) + .with_group_id(self.group_id), ] ) } } - -/// Determines if the whitespace separating comment trivia -/// from their associated tokens should be printed or trimmed -#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)] -pub enum TriviaPrintMode { - #[default] - Full, - Trim, -} - -/// Formats the leading trivia (comments, skipped token trivia) of a token -pub fn format_leading_trivia(token: &SyntaxToken) -> FormatLeadingTrivia { - FormatLeadingTrivia { - trim_mode: TriviaPrintMode::Full, - token, - } -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct FormatLeadingTrivia<'a, L> -where - L: Language, -{ - trim_mode: TriviaPrintMode, - token: &'a SyntaxToken, -} - -impl<'a, L> FormatLeadingTrivia<'a, L> -where - L: Language, -{ - pub fn with_trim_mode(mut self, mode: TriviaPrintMode) -> Self { - self.trim_mode = mode; - self - } -} - -impl Format for FormatLeadingTrivia<'_, L> -where - L: Language, - C: CstFormatContext, -{ - fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { - write_leading_trivia( - self.token.leading_trivia().pieces(), - self.token, - self.trim_mode, - f, - || self.token.prev_token(), - ) - } -} - -fn write_leading_trivia( - pieces: I, - token: &SyntaxToken, - trim_mode: TriviaPrintMode, - f: &mut Formatter, - previous_token: F, -) -> FormatResult<()> -where - I: IntoIterator>, - L: Language, - C: CstFormatContext, - F: FnOnce() -> Option>, -{ - let mut lines_before = 0; - let mut comments = Vec::new(); - let mut pieces = pieces.into_iter(); - - while let Some(piece) = pieces.next() { - if let Some(comment) = piece.as_comments() { - if !f.state().is_comment_formatted(&comment) { - comments.push(SourceComment::leading(comment, lines_before)); - } - - lines_before = 0; - } else if piece.is_newline() { - lines_before += 1; - } else if piece.is_skipped() { - // Special handling for tokens that have skipped trivia: - // - // ``` - // class { - // // comment - // @test(/* inner */) // trailing - // /* token leading */ - // method() {} - // } - // ``` - // If `@test(/*inner)` are skipped trivia that are part of the `method` tokens leading trivia, then the - // following code splits the trivia into for parts: - // 1. The first skipped trivia's leading comments: Comments that come before the first skipped trivia `@`: The `// comment` - // 2. Skipped trivia: All trivia pieces between the first and last skipped trivia: `@test(/* inner *)`. Gets formatted as verbatim - // 3. Trailing comments of the last skipped token: All comments that are on the same line as the last skipped trivia. The `// trailing` comment - // 4. The token's leading trivia: All comments that are not on the same line as the last skipped token trivia: `/* token leading */` - - // Format the 1. part, the skipped trivia's leading comments - FormatLeadingComments { - comments: &comments, - trim_mode: TriviaPrintMode::Full, - lines_before_token: lines_before, - } - .fmt(f)?; - - let has_preceding_whitespace = previous_token() - .and_then(|token| token.trailing_trivia().pieces().last()) - .map_or(false, |piece| piece.is_newline() || piece.is_whitespace()); - - // Maintain a leading whitespace in front of the skipped token trivia - // if the previous token has a trailing whitespace (and there's no comment between the two tokens). - if comments.is_empty() && has_preceding_whitespace { - write!(f, [space()])?; - } - - comments.clear(); - lines_before = 0; - - // Count the whitespace between the last skipped token trivia and the token - let mut spaces = 0; - // The range that covers from the first to the last skipped token trivia - let mut skipped_trivia_range = piece.text_range(); - - for piece in pieces { - if piece.is_whitespace() { - spaces += 1; - continue; - } - - spaces = 0; - - // If this is another skipped trivia, then extend the skipped range and - // clear all accumulated comments because they are formatted as verbatim as part of the - // skipped token trivia - if piece.is_skipped() { - skipped_trivia_range = skipped_trivia_range.cover(piece.text_range()); - comments.clear(); - lines_before = 0; - } else if let Some(comment) = piece.as_comments() { - comments.push(SourceComment::leading(comment, lines_before)); - lines_before = 0; - } else if piece.is_newline() { - lines_before += 1; - } - } - - // Format the skipped token trivia range - syntax_token_text_slice(token, skipped_trivia_range).fmt(f)?; - - // Find the start position of the next token's leading comments. - // The start is the first comment that is preceded by a line break. - let first_token_leading_comment = comments - .iter() - .position(|comment| comment.lines_before() > 0) - .unwrap_or(comments.len()); - - // Everything before the start position are trailing comments of the last skipped token - let token_leading_comments = comments.split_off(first_token_leading_comment); - let skipped_trailing_comments = comments; - - // Format the trailing comments of the last skipped token trivia - FormatTrailingTrivia::skipped(skipped_trailing_comments.into_iter()).fmt(f)?; - - // Ensure that there's some whitespace between the last skipped token trivia and the - // next token except if there was no whitespace present in the source. - if lines_before > 0 { - write!(f, [hard_line_break()])?; - } else if spaces > 0 { - write!(f, [space()])?; - }; - - // Write leading comments of the next token - FormatLeadingComments { - comments: &token_leading_comments, - lines_before_token: lines_before, - trim_mode, - } - .fmt(f)?; - - return Ok(()); - } - } - - FormatLeadingComments { - comments: &comments, - trim_mode, - lines_before_token: lines_before, - } - .fmt(f) -} - -struct FormatLeadingComments<'a, L> -where - L: Language, -{ - comments: &'a [SourceComment], - trim_mode: TriviaPrintMode, - lines_before_token: u32, -} - -impl Format for FormatLeadingComments<'_, L> -where - L: Language, - C: CstFormatContext, -{ - fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { - let mut first = true; - let mut last_inline_comment = f.state().is_last_content_inline_comment(); - - for (index, comment) in self.comments.iter().enumerate() { - if f.state().is_comment_formatted(comment.piece()) { - continue; - } - - let lines_after = self - .comments - .get(index + 1) - .map(|comment| comment.lines_before()) - .unwrap_or_else(|| match self.trim_mode { - TriviaPrintMode::Full => self.lines_before_token, - TriviaPrintMode::Trim => 0, - }); - - #[allow(deprecated)] - let comment_kind = f - .context() - .comment_style() - .get_comment_kind(comment.piece()); - - last_inline_comment = comment_kind.is_inline() && lines_after == 0; - - let format_content = format_with(|f| { - if comment.lines_before() > 0 && first { - write!(f, [hard_line_break()])?; - } else if !first { - write!(f, [space()])?; - }; - - let format_comment = - FormatRefWithRule::new(comment, C::LeadingCommentRule::default()); - - write!(f, [format_comment])?; - - match comment_kind { - CommentKind::Line => match lines_after { - 0 | 1 => write!(f, [hard_line_break()])?, - _ => write!(f, [empty_line()])?, - }, - CommentKind::InlineBlock | CommentKind::Block => { - match lines_after { - 0 => { - // space between last comment and token handled at the end. - // space between comments is inserted before each comment - } - 1 => write!(f, [hard_line_break()])?, - _ => write!(f, [empty_line()])?, - } - } - } - - Ok(()) - }); - - write!(f, [crate::comment(&format_content)])?; - first = false; - } - - f.state_mut() - .set_last_content_inline_comment(last_inline_comment); - - Ok(()) - } -} - -/// Formats the trailing trivia (comments) of a token -pub fn format_trailing_trivia( - token: &SyntaxToken, -) -> FormatTrailingTrivia> + Clone, L> { - let comments = token - .trailing_trivia() - .pieces() - .filter_map(|piece| piece.as_comments().map(SourceComment::trailing)); - - FormatTrailingTrivia::new(comments, token.kind()) -} - -#[derive(Debug, Copy, Clone)] -pub struct FormatTrailingTrivia -where - I: Iterator> + Clone, -{ - /// The comments to format - comments: I, - - /// The kind of the token of which the comments are the trailing trivia. - /// `Some(kind)` for a regular token. `None` for a skipped token trivia OR - token_kind: Option<::Kind>, - - skip_formatted_check: bool, -} - -impl FormatTrailingTrivia -where - I: Iterator> + Clone, -{ - pub fn new(comments: I, token_kind: L::Kind) -> Self { - Self { - comments, - token_kind: Some(token_kind), - skip_formatted_check: false, - } - } - - pub fn skipped(comments: I) -> Self { - Self { - comments, - token_kind: None, - skip_formatted_check: false, - } - } - - pub fn skip_formatted_check(mut self) -> Self { - self.skip_formatted_check = true; - self - } -} - -impl Format for FormatTrailingTrivia -where - I: Iterator> + Clone, - C: CstFormatContext, -{ - fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { - let comments = self.comments.clone(); - let mut last_inline_comment = f.state().is_last_content_inline_comment(); - - for (index, comment) in comments.enumerate() { - if !self.skip_formatted_check && f.state().is_comment_formatted(comment.piece()) { - continue; - } - - #[allow(deprecated)] - let kind = f - .context() - .comment_style() - .get_comment_kind(comment.piece()); - last_inline_comment = kind.is_inline(); - let is_single_line = kind.is_line(); - - let content = format_with(|f: &mut Formatter| { - if !is_single_line { - match self.token_kind { - // Don't write a space if this is a group start token and it isn't the first trailing comment - #[allow(deprecated)] - Some(token) - if f.context().comment_style().is_group_start_token(token) - && index == 0 => {} - // Write a space for all other cases - _ => space().fmt(f)?, - } - comment.piece().fmt(f) - } else { - write![ - f, - [ - line_suffix(&format_args![space(), comment.piece()]), - expand_parent() - ] - ] - } - }); - - crate::comment(&content).fmt(f)?; - } - - f.state_mut() - .set_last_content_inline_comment(last_inline_comment); - - Ok(()) - } -} diff --git a/crates/rome_js_formatter/src/builders.rs b/crates/rome_js_formatter/src/builders.rs index a25bd449673..5498f619356 100644 --- a/crates/rome_js_formatter/src/builders.rs +++ b/crates/rome_js_formatter/src/builders.rs @@ -1,12 +1,11 @@ use crate::prelude::*; use crate::AsFormat; -use rome_formatter::token::{FormatInserted, FormatInsertedCloseParen, FormatInsertedOpenParen}; +use rome_formatter::token::{FormatLeadingComments, FormatTrailingComments}; use rome_formatter::{ - format_args, write, Argument, Arguments, CstFormatContext, FormatContext, GroupId, - PreambleBuffer, VecBuffer, + write, Argument, Arguments, CstFormatContext, FormatContext, GroupId, VecBuffer, }; -use rome_js_syntax::{JsLanguage, JsSyntaxKind, JsSyntaxNode, JsSyntaxToken}; -use rome_rowan::{AstNode, Direction, Language, SyntaxElement, SyntaxTriviaPiece, TextRange}; +use rome_js_syntax::{JsLanguage, JsSyntaxNode, JsSyntaxToken}; +use rome_rowan::{AstNode, Direction, SyntaxElement, TextRange}; /// Formats a node using its [`AsFormat`] implementation but falls back to printing the node as /// it is in the source document if the formatting returns an [`FormatError`]. @@ -44,119 +43,6 @@ where } } -pub fn format_inserted(kind: JsSyntaxKind) -> FormatInserted { - FormatInserted::new( - kind, - kind.to_string().expect("Expected a punctuation token"), - ) -} - -pub fn format_inserted_open_paren( - before_token: Option<&JsSyntaxToken>, - kind: JsSyntaxKind, -) -> FormatInsertedOpenParen { - FormatInsertedOpenParen::new( - before_token, - kind, - kind.to_string() - .expect("Expected a punctuation token as the open paren token."), - ) -} - -pub fn format_inserted_close_paren( - after_token: Option<&JsSyntaxToken>, - kind: JsSyntaxKind, - f: &mut JsFormatter, -) -> FormatInsertedCloseParen { - FormatInsertedCloseParen::after_token( - after_token, - kind, - kind.to_string() - .expect("Expected a punctuation token as the close paren token."), - f, - ) -} - -/// Adds parentheses around some content -/// Ensures that the leading trivia of the `first_content_token` is moved -/// before the opening parentheses and the trailing trivia of the `last_content_token` -/// is moved after the closing parentheses. -/// -/// # Examples -/// Adding parentheses around the string literal -/// -/// ```javascript -/// /* leading */ "test" /* trailing */; -/// ``` -/// -/// becomes -/// -/// ```javascript -/// /* leading */ ("test") /* trailing */; -/// ``` -pub fn format_parenthesize<'a, Content>( - first_content_token: Option<&'a JsSyntaxToken>, - content: &'a Content, - last_content_token: Option<&'a JsSyntaxToken>, -) -> FormatParenthesize<'a> -where - Content: Format, -{ - FormatParenthesize { - first_content_token, - content: Argument::new(content), - last_content_token, - grouped: false, - } -} - -/// Adds parentheses around an expression -#[derive(Clone)] -pub struct FormatParenthesize<'a> { - grouped: bool, - first_content_token: Option<&'a JsSyntaxToken>, - content: Argument<'a, JsFormatContext>, - last_content_token: Option<&'a JsSyntaxToken>, -} - -impl FormatParenthesize<'_> { - /// Groups the open parenthesis, the content, and the closing parenthesis inside of a group - /// and indents the content with a soft block indent. - pub fn grouped_with_soft_block_indent(mut self) -> Self { - self.grouped = true; - self - } -} - -impl Format for FormatParenthesize<'_> { - fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { - let format_open_paren = - format_inserted_open_paren(self.first_content_token, JsSyntaxKind::L_PAREN); - let format_close_paren = - format_inserted_close_paren(self.last_content_token, JsSyntaxKind::R_PAREN, f); - - if self.grouped { - write!( - f, - [group(&format_args![ - format_open_paren, - soft_block_indent(&Arguments::from(&self.content)), - format_close_paren - ])] - ) - } else { - write!( - f, - [ - format_open_paren, - Arguments::from(&self.content), - format_close_paren - ] - ) - } - } -} - /// "Formats" a node according to its original formatting in the source text. Being able to format /// a node "as is" is useful if a node contains syntax errors. Formatting a node with syntax errors /// has the risk that Rome misinterprets the structure of the code and formatting it could @@ -192,22 +78,6 @@ impl Format for FormatVerbatimNode<'_> { } } - fn skip_whitespace(piece: &SyntaxTriviaPiece) -> bool { - piece.is_newline() || piece.is_whitespace() - } - - fn write_trivia_token( - f: &mut JsFormatter, - piece: SyntaxTriviaPiece, - ) -> FormatResult<()> { - syntax_token_cow_slice( - normalize_newlines(piece.text(), LINE_TERMINATORS), - &piece.token(), - piece.text_range().start(), - ) - .fmt(f) - } - let trimmed_source_range = f.context().source_map().map_or_else( || self.node.text_trimmed_range(), |source_map| source_map.trimmed_source_range(self.node), @@ -224,58 +94,72 @@ impl Format for FormatVerbatimNode<'_> { .map_or_else(|| range, |source_map| source_map.source_range(range)) } - for leading_trivia in self + let comments = f.context().comments().clone(); + let leading_comments = comments.leading_comments(self.node); + + let outside_trimmed_range = leading_comments.partition_point(|comment| { + dbg!(trimmed_source_range) + .contains_range(dbg!(source_range(f, comment.piece().text_range()))) + }); + + dbg!(outside_trimmed_range); + + write!( + f, + [FormatLeadingComments::Comments( + &leading_comments[..outside_trimmed_range] + )] + )?; + + // Find the source position of the first + let start_source = self .node .first_leading_trivia() .into_iter() .flat_map(|trivia| trivia.pieces()) - .skip_while(skip_whitespace) - { - let trivia_source_range = source_range(f, leading_trivia.text_range()); - - if trivia_source_range.start() >= trimmed_source_range.start() { - break; - } - - write_trivia_token(f, leading_trivia)?; - } + .find_map(|piece| { + let source_range = source_range(f, piece.text_range()); + + if (piece.is_skipped() || piece.is_comments()) + && !trimmed_source_range.contains(source_range.start()) + { + Some(source_range.start()) + } else { + None + } + }) + .unwrap_or(trimmed_source_range.start()); let original_source = f.context().source_map().map_or_else( || self.node.text_trimmed().to_string(), - |source_map| source_map.text()[trimmed_source_range].to_string(), + |source_map| { + source_map.text()[trimmed_source_range.cover_offset(start_source)] + .to_string() + }, ); + dbg!(&original_source); + dynamic_text( &normalize_newlines(&original_source, LINE_TERMINATORS), self.node.text_trimmed_range().start(), ) .fmt(f)?; - let mut trailing_trivia = self - .node - .last_trailing_trivia() - .into_iter() - .flat_map(|trivia| trivia.pieces()); - - let mut trailing_back = trailing_trivia.by_ref().rev().peekable(); - - while let Some(trailing) = trailing_back.peek() { - let is_whitespace = skip_whitespace(trailing); - - let trailing_source_range = source_range(f, trailing.text_range()); - let is_in_trimmed_range = - trailing_source_range.start() < trimmed_source_range.end(); + let comments = f.context().comments().clone(); + let trailing_comments = comments.trailing_comments(self.node); - if is_whitespace || is_in_trimmed_range { - trailing_back.next(); - } else { - break; - } - } + let outside_trimmed_range_start = trailing_comments.partition_point(|comment| { + !trimmed_source_range + .contains_range(source_range(f, comment.piece().text_range())) + }); - for trailing_trivia in trailing_trivia { - write_trivia_token(f, trailing_trivia)?; - } + write!( + f, + [FormatTrailingComments::Comments( + &trailing_comments[outside_trimmed_range_start..] + )] + )?; Ok(()) })] @@ -333,7 +217,7 @@ impl Format for FormatSuppressedNode<'_> { hard_line_break(), FormatVerbatimNode { node: self.node, - kind: VerbatimKind::Suppressed + kind: VerbatimKind::Suppressed, } ] ) @@ -359,6 +243,7 @@ pub fn format_delimited<'a, 'content>( } } +// FIXME delete `BlockIndent`, maybe delete soft_block_indent` and spaces too? #[derive(Copy, Clone)] pub struct FormatDelimited<'a, 'content> { open_token: &'a JsSyntaxToken, @@ -414,45 +299,21 @@ impl Format for FormatDelimited<'_, '_> { grouped, } = self; - let open_delimiter = format_open_delimiter(open_token); - let close_delimiter = format_close_delimiter(close_token); - - open_delimiter.format_leading_trivia().fmt(f)?; - - let open_token_trailing_trivia = open_delimiter.format_trailing_trivia(); - - let close_token_leading_trivia = close_delimiter.format_leading_trivia(); - let delimited = format_with(|f| { - open_delimiter.format_token().fmt(f)?; + open_token.format().fmt(f)?; let format_content = format_with(|f| f.write_fmt(Arguments::from(content))); match mode { - DelimitedMode::BlockIndent => block_indent(&format_args![ - open_token_trailing_trivia, - format_content, close_token_leading_trivia - ]) - .fmt(f)?, - DelimitedMode::SoftBlockIndent(_) => soft_block_indent(&format_args![ - open_token_trailing_trivia, - format_content, close_token_leading_trivia - ]) - .fmt(f)?, + DelimitedMode::BlockIndent => block_indent(&format_content).fmt(f)?, + DelimitedMode::SoftBlockIndent(_) => soft_block_indent(&format_content).fmt(f)?, DelimitedMode::SoftBlockSpaces(_) => { let mut is_empty = true; let format_content = format_once(|f| { let mut recording = f.start_recording(); - write!( - recording, - [ - open_token_trailing_trivia, - format_content, - close_token_leading_trivia - ] - )?; + write!(recording, [format_content])?; is_empty = recording.stop().is_empty(); @@ -467,7 +328,7 @@ impl Format for FormatDelimited<'_, '_> { } }; - close_delimiter.format_token().fmt(f) + close_token.format().fmt(f) }); match mode { @@ -484,7 +345,7 @@ impl Format for FormatDelimited<'_, '_> { DelimitedMode::BlockIndent => write!(f, [delimited])?, }; - write!(f, [format_trailing_trivia(close_token)]) + Ok(()) } } @@ -494,91 +355,3 @@ enum DelimitedMode { SoftBlockIndent(Option), SoftBlockSpaces(Option), } - -/// Use this function to create an open delimiter, where you can extract the formatting of -/// trivias and token, separately. -/// -/// This function assumes that you will use the token to replicate [format_delimited], which means -/// that it will add possible line breaks -pub(crate) fn format_open_delimiter(open_token: &JsSyntaxToken) -> OpenDelimiter { - OpenDelimiter::new(open_token) -} - -/// Use this function to create an close delimiter, where you can extract the formatting of -/// trivias and token, separately. -/// -/// This function assumes that you will use the token to replicate [format_delimited], which means -/// that it will add possible line breaks -pub(crate) fn format_close_delimiter(close_token: &JsSyntaxToken) -> CloseDelimiter { - CloseDelimiter::new(close_token) -} - -pub(crate) struct OpenDelimiter<'t> { - open_token: &'t JsSyntaxToken, -} - -impl<'t> OpenDelimiter<'t> { - pub(crate) fn new(open_token: &'t JsSyntaxToken) -> Self { - Self { open_token } - } - - /// It extracts the formatted leading trivia of the token, without writing it in the buffer - pub(crate) fn format_leading_trivia(&self) -> impl Format + 't { - format_leading_trivia(self.open_token) - } - - /// It extracts the formatted trailing trivia of the token, without writing it in the buffer - pub(crate) fn format_trailing_trivia(&self) -> impl Format + 't { - format_with(|f| { - let mut recording = f.start_recording(); - write!(recording, [format_trailing_trivia(self.open_token)])?; - let recorded = recording.stop(); - - if !recorded.is_empty() { - soft_line_break().fmt(f)?; - } - - Ok(()) - }) - } - - /// It extracts the formatted token, without writing it in the buffer - pub(crate) fn format_token(&self) -> impl Format + 't { - format_with(|f| { - f.state_mut().track_token(self.open_token); - write!(f, [format_trimmed_token(self.open_token)]) - }) - } -} - -pub(crate) struct CloseDelimiter<'t> { - close_token: &'t JsSyntaxToken, -} - -impl<'t> CloseDelimiter<'t> { - pub(crate) fn new(close_token: &'t JsSyntaxToken) -> Self { - Self { close_token } - } - - /// It extracts the formatted leading trivia of the token, without writing it in the buffer - pub(crate) fn format_trailing_trivia(&self) -> impl Format + 't { - format_trailing_trivia(self.close_token) - } - - /// It extracts the formatted trailing trivia of the token, without writing it in the buffer - pub(crate) fn format_leading_trivia(&self) -> impl Format + 't { - format_with(|f| { - let mut buffer = PreambleBuffer::new(f, soft_line_break()); - - write!(buffer, [format_leading_trivia(self.close_token)]) - }) - } - - /// It extracts the formatted token, without writing it in the buffer - pub(crate) fn format_token(&self) -> impl Format + 't { - format_with(|f| { - f.state_mut().track_token(self.close_token); - write!(f, [format_trimmed_token(self.close_token)]) - }) - } -} diff --git a/crates/rome_js_formatter/src/comments.rs b/crates/rome_js_formatter/src/comments.rs index 711350197d5..c0cb0c2dfa4 100644 --- a/crates/rome_js_formatter/src/comments.rs +++ b/crates/rome_js_formatter/src/comments.rs @@ -1,9 +1,17 @@ use crate::prelude::*; -use rome_formatter::write; +use crate::utils::JsAnyBinaryLikeExpression; +use rome_formatter::{write, CommentPosition, DecoratedComment}; use rome_formatter::{CommentKind, CommentStyle, SourceComment}; use rome_js_syntax::suppression::{parse_suppression_comment, SuppressionCategory}; -use rome_js_syntax::{JsLanguage, JsSyntaxKind}; -use rome_rowan::{SyntaxTriviaPieceComments, TextLen}; +use rome_js_syntax::{ + JsAnyStatement, JsArrayAssignmentPattern, JsArrayBindingPattern, JsArrayExpression, + JsArrayHole, JsBlockStatement, JsBreakStatement, JsCallArgumentList, JsCallArguments, + JsContinueStatement, JsDefaultClause, JsFunctionBody, JsLanguage, JsSyntaxKind, JsSyntaxNode, + JsSyntaxToken, +}; +use rome_rowan::{ + declare_node_union, match_ast, SyntaxKind, SyntaxResult, SyntaxTriviaPieceComments, TextLen, +}; #[derive(Default)] pub struct FormatJsLeadingComment; @@ -47,7 +55,7 @@ impl FormatRule> for FormatJsLeadingComment { )] ) } else { - write!(f, [comment.piece()]) + write!(f, [comment.piece().as_piece()]) } } } @@ -116,14 +124,16 @@ pub fn is_doc_comment(comment: &SyntaxTriviaPieceComments) -> bool { #[derive(Eq, PartialEq, Copy, Clone, Debug, Default)] pub struct JsCommentStyle; -impl CommentStyle for JsCommentStyle { - fn is_suppression(&self, text: &str) -> bool { +impl CommentStyle for JsCommentStyle { + type Language = JsLanguage; + + fn is_suppression(text: &str) -> bool { parse_suppression_comment(text) .flat_map(|suppression| suppression.categories) .any(|(category, _)| category == SuppressionCategory::Format) } - fn get_comment_kind(&self, comment: &SyntaxTriviaPieceComments) -> CommentKind { + fn get_comment_kind(comment: &SyntaxTriviaPieceComments) -> CommentKind { if comment.text().starts_with("/*") { if comment.has_newline() { CommentKind::Block @@ -135,26 +145,314 @@ impl CommentStyle for JsCommentStyle { } } - fn is_group_start_token(&self, kind: JsSyntaxKind) -> bool { - matches!( - kind, - JsSyntaxKind::L_PAREN - | JsSyntaxKind::L_BRACK - | JsSyntaxKind::L_CURLY - | JsSyntaxKind::DOLLAR_CURLY - ) + fn position_comment( + comment: DecoratedComment, + ) -> CommentPosition { + let enclosing_node = comment.enclosing_node(); + + if let Some(following_node) = comment.following_node() { + match following_node.kind() { + // Move leading comments in front of the `{` inside of the block + // ``` + // if (test) /* comment */ { + // console.log('test'); + // } + // ``` + // + // becomes + // ``` + // if (test) { + // /* comment */ console.log('test'); + // } + // ``` + JsSyntaxKind::JS_BLOCK_STATEMENT + if !JsDefaultClause::can_cast(enclosing_node.kind()) => + { + let block = JsBlockStatement::unwrap_cast(following_node.clone()); + + if let (Ok(_), Ok(r_curly_token)) = + (block.l_curly_token(), block.r_curly_token()) + { + return match block.statements().first() { + Some(JsAnyStatement::JsEmptyStatement(_)) => { + CommentPosition::Dangling { + token: r_curly_token, + comment, + } + } + Some(first_statement) => CommentPosition::Leading { + node: first_statement.into_syntax(), + comment, + }, + _ => CommentPosition::Dangling { + token: r_curly_token, + comment, + }, + }; + } + } + + // Move comments in front of the `{` inside of the function body + JsSyntaxKind::JS_FUNCTION_BODY + if (!comment.is_trailing_token_trivia() || comment.kind().is_line()) => + { + let function_body = JsFunctionBody::unwrap_cast(following_node.clone()); + + if let (Ok(_), Ok(r_curly_token)) = + (function_body.l_curly_token(), function_body.r_curly_token()) + { + let first_directive = function_body + .directives() + .first() + .map(|node| node.into_syntax()); + let first_statement = function_body + .statements() + .first() + .map(|node| node.into_syntax()); + return if let Some(first_node) = first_directive.or(first_statement) { + CommentPosition::Leading { + node: first_node, + comment, + } + } else { + CommentPosition::Dangling { + token: r_curly_token, + comment, + } + }; + } + } + _ => { + // fall through + } + } + }; + + match enclosing_node.kind() { + // TODO move to general formatter handling? + // WHat does that mean for invalid syntax... + kind if kind.is_unknown() => { + return CommentPosition::Dangling { + token: comment.enclosing_token(), + comment, + } + } + // Handles comments attached to operators of binary like expressions. + // + // Associates trailing comments with the left expression if they're directly followed by a line break. + // ```javascript + // a = b || /** Comment */ + // c; + // + // a = b /** Comment */ || + // c; + // ``` + // + // Associates leading operator comments with the right side + // ```javascript + // 0 + // // Comment + // + x + // ``` + kind if JsAnyBinaryLikeExpression::can_cast(kind) => { + let binary_like = JsAnyBinaryLikeExpression::unwrap_cast(enclosing_node.clone()); + + if comment.is_trailing_token_trivia() && comment.lines_after() > 0 { + if let Ok(left) = binary_like.left() { + return CommentPosition::Trailing { + node: left.into_syntax(), + comment, + }; + } + } else { + if let Ok(right) = binary_like.right() { + return CommentPosition::Leading { + node: right.into_syntax(), + comment, + }; + } + } + } + + // Makes comments of `break` and `continue` statements trailing comments EXCEPT if there's a label + // + // ```javascript + // break /* comment */ + // break /* comment */; + // ``` + JsSyntaxKind::JS_BREAK_STATEMENT | JsSyntaxKind::JS_CONTINUE_STATEMENT => { + let (argument, semicolon) = match_ast! { + match &enclosing_node { + JsBreakStatement(break_statement) => (break_statement.label_token(), break_statement.semicolon_token()), + JsContinueStatement(continue_statement) => (continue_statement.label_token(), continue_statement.semicolon_token()), + _ => unreachable!() + } + }; + + if argument.is_none() + && (semicolon.is_none() + || Some(comment.following_token()) == semicolon.as_ref()) + { + return CommentPosition::Trailing { + node: enclosing_node, + comment, + }; + } + } + + JsSyntaxKind::JS_FOR_IN_STATEMENT | JsSyntaxKind::JS_FOR_OF_STATEMENT => { + return CommentPosition::Leading { + node: enclosing_node, + comment, + } + } + + _ => { + // fall through + } + } + + if let Some(preceding_node) = comment.preceding_node() { + // Handles array hole comments. Array holes have no token so all comments + // become trailing comments by default. Override it that all comments are elading comments. + if JsArrayHole::can_cast(preceding_node.kind()) { + return CommentPosition::Leading { + node: preceding_node.clone(), + comment, + }; + } + } + + match dbg!(comment.following_token().kind()) { + JsSyntaxKind::R_BRACK => { + // Handles comments before the `]` token of an array + // + // ```javascript + // let example = [ + // "FOO", + // "BAR", + // // Comment + // ]; + // ``` + // Makes the comment before the `]` a trailing comment of the last element. + if !comment.is_trailing_token_trivia() + && JsAnyArrayLike::can_cast(enclosing_node.kind()) + { + let array = JsAnyArrayLike::unwrap_cast(enclosing_node.clone()); + + if array.r_brack_token().as_ref() == Ok(comment.following_token()) { + if let Some(Ok(last_element)) = array.last_element() { + if last_element.kind() == JsSyntaxKind::JS_ARRAY_HOLE { + return CommentPosition::Leading { + node: last_element, + comment, + }; + } else { + return CommentPosition::Trailing { + node: last_element, + comment, + }; + } + } + } + } + // Handles trailing comments after the last array element before the `]` token. + // else if matches!( + // enclosing_node.kind(), + // JsSyntaxKind::JS_ARRAY_ELEMENT_LIST + // | JsSyntaxKind::JS_ARRAY_BINDING_PATTERN_ELEMENT_LIST + // | JsSyntaxKind::JS_ARRAY_ASSIGNMENT_PATTERN_ELEMENT_LIST + // ) { + // if let Some(last_element) = enclosing_node.last_child() { + // if last_element.kind() == JsSyntaxKind::JS_ARRAY_HOLE { + // return CommentPosition::Leading { + // node: last_element, + // comment, + // }; + // } + // } + // } + } + + JsSyntaxKind::R_PAREN => { + // Make line comments inside of an empty call arguments trailing comments of the call arguments + // so that they get moved out of the parentheses. + // ```javascript + // expect( // remains a dangling comment + // // test + // ) + // ``` + // becomes + // ```javascript + // expect(); // remains a dangling comment + // // test + // ``` + if let Some(arguments) = JsCallArguments::cast_ref(&enclosing_node) { + if arguments.r_paren_token().as_ref() == Ok(comment.following_token()) { + if arguments.args().is_empty() && comment.kind().is_line() { + return CommentPosition::Trailing { + node: arguments.into_syntax(), + comment, + }; + } + } + } + // Makes the last comment in a non-empty call arguments list a trailing comment of the + // last argument + // ```javascript + // f(a, /* test */) // make test a trailing comment of a + // ``` + else if let Some(arguments_list) = JsCallArgumentList::cast_ref(&enclosing_node) { + if let Some(Ok(last_argument)) = arguments_list.last() { + return CommentPosition::Trailing { + node: last_argument.into_syntax(), + comment, + }; + } + } + } + _ => { + // fall through + } + } + + CommentPosition::Default(comment) + } +} + +declare_node_union! { + JsAnyArrayLike = JsArrayExpression + | JsArrayAssignmentPattern + | JsArrayBindingPattern +} + +impl JsAnyArrayLike { + fn r_brack_token(&self) -> SyntaxResult { + match self { + JsAnyArrayLike::JsArrayExpression(expression) => expression.r_brack_token(), + JsAnyArrayLike::JsArrayAssignmentPattern(assignment) => assignment.r_brack_token(), + JsAnyArrayLike::JsArrayBindingPattern(binding) => binding.r_brack_token(), + } } - fn is_group_end_token(&self, kind: JsSyntaxKind) -> bool { - matches!( - kind, - JsSyntaxKind::R_BRACK - | JsSyntaxKind::R_CURLY - | JsSyntaxKind::R_PAREN - | JsSyntaxKind::COMMA - | JsSyntaxKind::SEMICOLON - | JsSyntaxKind::DOT - | JsSyntaxKind::EOF - ) + fn last_element(&self) -> Option> { + match self { + JsAnyArrayLike::JsArrayExpression(array) => match array.elements().iter().last() { + Some(Ok(element)) => Some(Ok(element.into_syntax())), + Some(Err(error)) => Some(Err(error)), + None => None, + }, + JsAnyArrayLike::JsArrayAssignmentPattern(array) => match array.elements().iter().last() + { + Some(Ok(element)) => Some(Ok(element.into_syntax())), + Some(Err(error)) => Some(Err(error)), + None => None, + }, + JsAnyArrayLike::JsArrayBindingPattern(array) => match array.elements().iter().last() { + Some(Ok(element)) => Some(Ok(element.into_syntax())), + Some(Err(error)) => Some(Err(error)), + None => None, + }, + } } } diff --git a/crates/rome_js_formatter/src/context.rs b/crates/rome_js_formatter/src/context.rs index b627f8938b8..1bdac97a591 100644 --- a/crates/rome_js_formatter/src/context.rs +++ b/crates/rome_js_formatter/src/context.rs @@ -65,11 +65,7 @@ impl FormatContext for JsFormatContext { impl CstFormatContext for JsFormatContext { type Language = JsLanguage; type Style = JsCommentStyle; - type LeadingCommentRule = FormatJsLeadingComment; - - fn comment_style(&self) -> Self::Style { - JsCommentStyle - } + type CommentRule = FormatJsLeadingComment; fn comments(&self) -> &Comments { &self.comments diff --git a/crates/rome_js_formatter/src/js/assignments/array_assignment_pattern.rs b/crates/rome_js_formatter/src/js/assignments/array_assignment_pattern.rs index a27dcda70ab..8551dd25b72 100644 --- a/crates/rome_js_formatter/src/js/assignments/array_assignment_pattern.rs +++ b/crates/rome_js_formatter/src/js/assignments/array_assignment_pattern.rs @@ -1,3 +1,4 @@ +use crate::builders::format_delimited; use crate::parentheses::NeedsParentheses; use crate::prelude::*; use rome_formatter::write; diff --git a/crates/rome_js_formatter/src/js/auxiliary/function_body.rs b/crates/rome_js_formatter/src/js/auxiliary/function_body.rs index a4196e33908..ff3684ce196 100644 --- a/crates/rome_js_formatter/src/js/auxiliary/function_body.rs +++ b/crates/rome_js_formatter/src/js/auxiliary/function_body.rs @@ -16,14 +16,26 @@ impl FormatNodeRule for FormatJsFunctionBody { r_curly_token, } = node.as_fields(); - write!( - f, - [format_delimited( - &l_curly_token?, - &format_args![directives.format(), statements.format()], - &r_curly_token?, + let r_curly_token = r_curly_token?; + + if statements.is_empty() { + write!( + f, + [ + l_curly_token.format(), + format_dangling_trivia(&r_curly_token).indented(), + r_curly_token.format() + ] + ) + } else { + write!( + f, + [ + l_curly_token.format(), + block_indent(&format_args![directives.format(), statements.format()]), + r_curly_token.format(), + ] ) - .block_indent()] - ) + } } } diff --git a/crates/rome_js_formatter/src/js/bindings/array_binding_pattern.rs b/crates/rome_js_formatter/src/js/bindings/array_binding_pattern.rs index 0bed8341c80..303c8d012ba 100644 --- a/crates/rome_js_formatter/src/js/bindings/array_binding_pattern.rs +++ b/crates/rome_js_formatter/src/js/bindings/array_binding_pattern.rs @@ -1,5 +1,6 @@ use crate::prelude::*; +use crate::builders::format_delimited; use rome_formatter::write; use rome_js_syntax::JsArrayBindingPattern; use rome_js_syntax::JsArrayBindingPatternFields; diff --git a/crates/rome_js_formatter/src/js/bindings/parameters.rs b/crates/rome_js_formatter/src/js/bindings/parameters.rs index 295335d98ab..ea01247a909 100644 --- a/crates/rome_js_formatter/src/js/bindings/parameters.rs +++ b/crates/rome_js_formatter/src/js/bindings/parameters.rs @@ -6,6 +6,7 @@ use crate::js::lists::parameter_list::{ AnyParameter, FormatJsAnyParameterList, JsAnyParameterList, }; +use crate::builders::format_delimited; use rome_js_syntax::{ JsAnyConstructorParameter, JsAnyFormalParameter, JsCallExpression, JsConstructorParameters, JsParameters, JsSyntaxKind, JsSyntaxToken, TsType, diff --git a/crates/rome_js_formatter/src/js/classes/extends_clause.rs b/crates/rome_js_formatter/src/js/classes/extends_clause.rs index 64b00a294a9..f5be4a36388 100644 --- a/crates/rome_js_formatter/src/js/classes/extends_clause.rs +++ b/crates/rome_js_formatter/src/js/classes/extends_clause.rs @@ -1,9 +1,9 @@ use crate::prelude::*; use rome_formatter::{format_args, write}; +use rome_js_syntax::JsExtendsClause; use rome_js_syntax::JsExtendsClauseFields; use rome_js_syntax::JsSyntaxKind::JS_ASSIGNMENT_EXPRESSION; -use rome_js_syntax::{JsExtendsClause, JsSyntaxKind}; #[derive(Debug, Clone, Default)] pub struct FormatJsExtendsClause; @@ -34,24 +34,16 @@ impl FormatNodeRule for FormatJsExtendsClause { .map_or(false, |p| p.kind() == JS_ASSIGNMENT_EXPRESSION) { if super_class.syntax().has_leading_comments() || has_trailing_comments { - write!( - f, - [format_parenthesize( - super_class.syntax().first_token().as_ref(), - &content, - super_class.syntax().last_token().as_ref() - )] - ) + write!(f, [text("("), &content, text(")")]) } else { let content = content.memoized(); write!( f, [ if_group_breaks(&format_args![ - // Format_inserted is fine here because it is known that super has no comments - format_inserted(JsSyntaxKind::L_PAREN), + text("("), &soft_block_indent(&content), - format_inserted(JsSyntaxKind::R_PAREN), + text(")"), ]), if_group_fits_on_line(&content) ] diff --git a/crates/rome_js_formatter/src/js/classes/static_initialization_block_class_member.rs b/crates/rome_js_formatter/src/js/classes/static_initialization_block_class_member.rs index 560a42ee71c..3fd1f421ee9 100644 --- a/crates/rome_js_formatter/src/js/classes/static_initialization_block_class_member.rs +++ b/crates/rome_js_formatter/src/js/classes/static_initialization_block_class_member.rs @@ -1,5 +1,6 @@ use crate::prelude::*; +use crate::builders::format_delimited; use rome_formatter::write; use rome_js_syntax::JsStaticInitializationBlockClassMember; use rome_js_syntax::JsStaticInitializationBlockClassMemberFields; diff --git a/crates/rome_js_formatter/src/js/declarations/catch_declaration.rs b/crates/rome_js_formatter/src/js/declarations/catch_declaration.rs index 47af6b3a294..3247ee304ab 100644 --- a/crates/rome_js_formatter/src/js/declarations/catch_declaration.rs +++ b/crates/rome_js_formatter/src/js/declarations/catch_declaration.rs @@ -1,5 +1,6 @@ use crate::prelude::*; +use crate::builders::format_delimited; use rome_formatter::{format_args, write}; use rome_js_syntax::JsCatchDeclaration; use rome_js_syntax::JsCatchDeclarationFields; diff --git a/crates/rome_js_formatter/src/js/declarations/function_declaration.rs b/crates/rome_js_formatter/src/js/declarations/function_declaration.rs index 57a23de71c2..279afab1531 100644 --- a/crates/rome_js_formatter/src/js/declarations/function_declaration.rs +++ b/crates/rome_js_formatter/src/js/declarations/function_declaration.rs @@ -207,21 +207,10 @@ pub(crate) fn should_group_function_parameters( let result = if parameter_count != 1 { false } else { - // TODO https://github.com/rome/tools/issues/2768 - // THIS is a hack that is necessary to avoid that the formatter doesn't insert a space - // between `)` and the `:` of the return type annotation IF there's a an inline comment - // after the last comment that has been written. This can be deleted once the comments refactor lands. - let is_last_content_inline_comment = f.state().is_last_content_inline_comment(); - f.state_mut().set_last_content_inline_comment(false); - - let group = matches!( + matches!( return_type, TsAnyReturnType::TsType(TsType::TsObjectType(_) | TsType::TsMappedType(_)) - ) || formatted_return_type.inspect(f)?.will_break(); - - f.state_mut() - .set_last_content_inline_comment(is_last_content_inline_comment); - group + ) || formatted_return_type.inspect(f)?.will_break() }; Ok(result) diff --git a/crates/rome_js_formatter/src/js/expressions/array_expression.rs b/crates/rome_js_formatter/src/js/expressions/array_expression.rs index 1e6dfd0aac3..4cd4d9979a6 100644 --- a/crates/rome_js_formatter/src/js/expressions/array_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/array_expression.rs @@ -1,5 +1,6 @@ use crate::prelude::*; +use crate::builders::format_delimited; use crate::parentheses::NeedsParentheses; use rome_formatter::write; use rome_js_syntax::{ @@ -19,11 +20,22 @@ impl FormatNodeRule for FormatJsArrayExpression { r_brack_token, } = node.as_fields(); - if should_break(&elements)? { + let r_brack_token = r_brack_token?; + + if elements.is_empty() { + write!( + f, + [ + l_brack_token.format(), + format_dangling_trivia(&r_brack_token).indented(), + r_brack_token.format(), + ] + ) + } else if should_break(&elements)? { write!( f, [ - format_delimited(&l_brack_token?, &elements.format(), &r_brack_token?) + format_delimited(&l_brack_token?, &elements.format(), &r_brack_token) .block_indent() ] ) @@ -34,10 +46,8 @@ impl FormatNodeRule for FormatJsArrayExpression { write!( f, - [ - format_delimited(&l_brack_token?, &elements, &r_brack_token?) - .soft_block_indent_with_group_id(Some(group_id)) - ] + [format_delimited(&l_brack_token?, &elements, &r_brack_token) + .soft_block_indent_with_group_id(Some(group_id))] ) } } diff --git a/crates/rome_js_formatter/src/js/expressions/arrow_function_expression.rs b/crates/rome_js_formatter/src/js/expressions/arrow_function_expression.rs index d705c509534..92d2ec6af88 100644 --- a/crates/rome_js_formatter/src/js/expressions/arrow_function_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/arrow_function_expression.rs @@ -107,6 +107,10 @@ impl FormatNodeRule for FormatJsArrowFunctionExpressi _ => false, }, }; + let body_has_leading_line_comment = + dbg!(f.context().comments().leading_comments(body.syntax())) + .iter() + .any(|comment| comment.lines_after() > 0); // Add parentheses to avoid confusion between `a => b ? c : d` and `a <= b ? c : d` // but only if the body isn't an object/function or class expression because parentheses are always required in that @@ -128,7 +132,8 @@ impl FormatNodeRule for FormatJsArrowFunctionExpressi _ => false, }; - if body_has_soft_line_break && !should_add_parens { + if body_has_soft_line_break && !should_add_parens && !body_has_leading_line_comment + { write![f, [format_signature, space(), body.format()]] } else { write!( @@ -173,14 +178,14 @@ fn format_signature(arrow: &JsArrowFunctionExpression) -> impl Format write!( f, - [format_parenthesize( - binding.syntax().first_token().as_ref(), + [ + text("("), &soft_block_indent(&format_args![ binding.format(), if_group_breaks(&text(",")) ]), - binding.syntax().last_token().as_ref(), - )] + text(")") + ] )?, JsAnyArrowFunctionParameters::JsParameters(params) => { write!(f, [params.format()])?; @@ -365,9 +370,9 @@ impl Format for ArrowChain { write!( f, [group(&format_args![ - format_inserted(JsSyntaxKind::L_PAREN,), + text("("), soft_block_indent(&tail_body.format()), - format_inserted(JsSyntaxKind::R_PAREN) + text(")") ])] ) } else { diff --git a/crates/rome_js_formatter/src/js/expressions/call_arguments.rs b/crates/rome_js_formatter/src/js/expressions/call_arguments.rs index 8958e7f90bd..6bd6cd9b417 100644 --- a/crates/rome_js_formatter/src/js/expressions/call_arguments.rs +++ b/crates/rome_js_formatter/src/js/expressions/call_arguments.rs @@ -1,11 +1,10 @@ -use crate::builders::{format_close_delimiter, format_open_delimiter}; use crate::prelude::*; use crate::utils::{is_call_like_expression, write_arguments_multi_line}; -use rome_formatter::{format_args, write}; +use rome_formatter::{format_args, write, Comments, CstFormatContext}; use rome_js_syntax::{ JsAnyCallArgument, JsAnyExpression, JsAnyFunctionBody, JsAnyLiteralExpression, JsAnyName, JsAnyStatement, JsArrayExpression, JsArrowFunctionExpression, JsCallArgumentList, - JsCallArguments, JsCallArgumentsFields, JsCallExpression, JsSyntaxKind, TsReferenceType, + JsCallArguments, JsCallArgumentsFields, JsCallExpression, JsLanguage, TsReferenceType, }; use rome_rowan::{AstSeparatedList, SyntaxResult, SyntaxTokenText}; @@ -28,7 +27,7 @@ impl FormatNodeRule for FormatJsCallArguments { f, [ l_paren_token.format(), - args.format(), + format_dangling_trivia(&r_paren_token), r_paren_token.format() ] ); @@ -58,14 +57,16 @@ impl FormatNodeRule for FormatJsCallArguments { false }; - let is_react_hook_with_deps_array = - is_react_hook_with_deps_array(&first_argument, &second_argument)? - && !node.syntax().first_or_last_token_have_comments(); + let is_react_hook_with_deps_array = is_react_hook_with_deps_array( + &first_argument, + &second_argument, + f.context().comments(), + )?; if is_framework_test_call || is_react_hook_with_deps_array { write!(f, [l_paren_token.format(),])?; let separated = args - .format_separated(JsSyntaxKind::COMMA) + .format_separated(",") .with_trailing_separator(TrailingSeparator::Omit); f.join_with(space()).entries(separated).finish()?; @@ -73,23 +74,16 @@ impl FormatNodeRule for FormatJsCallArguments { } }; - // we create open a close delimiters - let open_delimiter = format_open_delimiter(&l_paren_token); - let close_delimiter = format_close_delimiter(&r_paren_token); - // we now extracts the formatted version of trivias and tokens of the delimiters // tokens on the left - let l_leading_trivia = open_delimiter.format_leading_trivia(); - let l_paren = open_delimiter.format_token(); - let l_trailing_trivia = open_delimiter.format_trailing_trivia(); + let l_paren = l_paren_token.format(); // tokens on the right - let r_leading_trivia = close_delimiter.format_leading_trivia(); - let r_paren = close_delimiter.format_token(); - let r_trailing_trivia = close_delimiter.format_trailing_trivia(); + let r_paren = r_paren_token.format(); - let should_group_first_argument = should_group_first_argument(&args)?; - let should_group_last_argument = should_group_last_argument(&args)?; + let comments = f.context().comments(); + let should_group_first_argument = should_group_first_argument(&args, comments)?; + let should_group_last_argument = should_group_last_argument(&args, comments)?; // if the first or last groups needs grouping, then we prepare some special formatting if should_group_first_argument || should_group_last_argument { @@ -98,7 +92,7 @@ impl FormatNodeRule for FormatJsCallArguments { // we can't attempt to print the same node twice without incur in "printed token twice" errors. // We also disallow the trailing separator, we are interested in doing it manually. let mut separated: Vec<_> = args - .format_separated(JsSyntaxKind::COMMA) + .format_separated(",") .with_trailing_separator(TrailingSeparator::Omit) .map(|e| e.memoized()) .collect(); @@ -120,14 +114,10 @@ impl FormatNodeRule for FormatJsCallArguments { // We now cache them the delimiters tokens. This is needed because `[rome_formatter::best_fitting]` will try to // print each version first // tokens on the left - let l_leading_trivia = l_leading_trivia.memoized(); let l_paren = l_paren.memoized(); - let l_trailing_trivia = l_trailing_trivia.memoized(); // tokens on the right - let r_leading_trivia = r_leading_trivia.memoized(); let r_paren = r_paren.memoized(); - let r_trailing_trivia = r_trailing_trivia.memoized(); // This is the version of where all the arguments are broken out let all_arguments_expanded = format_with(|f| { @@ -136,25 +126,21 @@ impl FormatNodeRule for FormatJsCallArguments { write!( f, [ - &l_leading_trivia, &l_paren, &group(&format_with(|f| { write!( f, [ &soft_block_indent(&format_args![ - &l_trailing_trivia, format_with(|f| { write_arguments_multi_line(separated.iter(), f) }), - &r_leading_trivia, soft_line_break() ]), &r_paren ] ) })), - &r_trailing_trivia ] ) }); @@ -168,7 +154,7 @@ impl FormatNodeRule for FormatJsCallArguments { // which means that if one is `false`, then the other is `true`. // This means that in this branch we format the case where `should_group_first_argument`, // in the else branch we format the case where `should_group_last_argument` is `true`. - write!(f, [l_leading_trivia, l_paren, l_trailing_trivia,])?; + write!(f, [l_paren])?; if should_group_first_argument { // special formatting of the first element let mut iter = separated.iter(); @@ -193,22 +179,18 @@ impl FormatNodeRule for FormatJsCallArguments { })) .finish()?; } - write!(f, [r_leading_trivia, r_paren, r_trailing_trivia]) + write!(f, [r_paren]) }); write!( f, [best_fitting![ format_args![ - l_leading_trivia, l_paren, - l_trailing_trivia, group(&format_with(|f| { write_arguments_multi_line(separated.iter(), f) })), - r_leading_trivia, r_paren, - r_trailing_trivia ], edge_arguments_do_not_break, all_arguments_expanded @@ -217,30 +199,27 @@ impl FormatNodeRule for FormatJsCallArguments { } else { write!( f, - [ - l_leading_trivia, - group(&format_args![ - l_paren, - l_trailing_trivia, - soft_block_indent(&format_with(|f| { - let separated = args - .format_separated(JsSyntaxKind::COMMA) - .with_trailing_separator(TrailingSeparator::Omit) - .nodes_grouped(); - write_arguments_multi_line(separated, f) - })), - r_leading_trivia, - r_paren, - ]), - r_trailing_trivia - ] + [group(&format_args![ + l_paren, + soft_block_indent(&format_with(|f| { + let separated = args + .format_separated(",") + .with_trailing_separator(TrailingSeparator::Omit) + .nodes_grouped(); + write_arguments_multi_line(separated, f) + })), + r_paren, + ]),] ) } } } /// Checks if the the first argument requires grouping -fn should_group_first_argument(list: &JsCallArgumentList) -> SyntaxResult { +fn should_group_first_argument( + list: &JsCallArgumentList, + comments: &Comments, +) -> SyntaxResult { if list.len() != 2 { return Ok(false); } @@ -249,8 +228,6 @@ fn should_group_first_argument(list: &JsCallArgumentList) -> SyntaxResult let first = iter.next().unwrap()?; let second = iter.next().unwrap()?; - let has_comments = first.syntax().has_comments_direct(); - let is_function_like = match first.as_js_any_expression() { Some(JsAnyExpression::JsFunctionExpression(_)) => true, Some(JsAnyExpression::JsArrowFunctionExpression(arrow)) => { @@ -269,17 +246,23 @@ fn should_group_first_argument(list: &JsCallArgumentList) -> SyntaxResult ); ( second_arg_is_function_like, - could_group_expression_argument(second_expression, false)?, + could_group_expression_argument(second_expression, false, comments)?, ) } None => (false, false), }; - Ok(!has_comments && is_function_like && !second_arg_is_function_like && !can_group) + Ok(!comments.has_comments(first.syntax()) + && is_function_like + && !second_arg_is_function_like + && !can_group) } /// Checks if the last group requires grouping -fn should_group_last_argument(list: &JsCallArgumentList) -> SyntaxResult { +fn should_group_last_argument( + list: &JsCallArgumentList, + comments: &Comments, +) -> SyntaxResult { let list_len = list.len(); let mut iter = list.iter(); let last = iter.next_back(); @@ -305,12 +288,15 @@ fn should_group_last_argument(list: &JsCallArgumentList) -> SyntaxResult { let can_group = match &last { JsAnyCallArgument::JsAnyExpression(expression) => { - could_group_expression_argument(expression, false)? + could_group_expression_argument(expression, false, comments)? } _ => false, }; - Ok(!last.syntax().has_comments_direct() && can_group && check_with_penultimate) + Ok(!comments.has_leading_comments(last.syntax()) + && !comments.has_trailing_comments(last.syntax()) + && can_group + && check_with_penultimate) } else { Ok(false) } @@ -320,27 +306,24 @@ fn should_group_last_argument(list: &JsCallArgumentList) -> SyntaxResult { fn could_group_expression_argument( argument: &JsAnyExpression, is_arrow_recursion: bool, + comments: &Comments, ) -> SyntaxResult { let result = match argument { JsAnyExpression::JsObjectExpression(object_expression) => { object_expression.members().len() > 0 - || object_expression - .syntax() - .first_or_last_token_have_comments() + || comments.has_comments(object_expression.syntax()) } JsAnyExpression::JsArrayExpression(array_expression) => { array_expression.elements().len() > 0 - || array_expression - .syntax() - .first_or_last_token_have_comments() + || comments.has_comments(array_expression.syntax()) } JsAnyExpression::TsTypeAssertionExpression(assertion_expression) => { - could_group_expression_argument(&assertion_expression.expression()?, false)? + could_group_expression_argument(&assertion_expression.expression()?, false, comments)? } JsAnyExpression::TsAsExpression(as_expression) => { - could_group_expression_argument(&as_expression.expression()?, false)? + could_group_expression_argument(&as_expression.expression()?, false, comments)? } JsAnyExpression::JsArrowFunctionExpression(arrow_function) => { let body = arrow_function.body()?; @@ -395,7 +378,9 @@ fn could_group_expression_argument( .body() .ok() .and_then(|body| body.as_js_any_expression().cloned()) - .and_then(|body| could_group_expression_argument(&body, true).ok()) + .and_then(|body| { + could_group_expression_argument(&body, true, comments).ok() + }) .unwrap_or(false) } else { false @@ -430,7 +415,14 @@ fn could_group_expression_argument( fn is_react_hook_with_deps_array( first_argument: &JsAnyCallArgument, second_argument: &JsAnyCallArgument, + comments: &Comments, ) -> SyntaxResult { + if comments.has_comments(first_argument.syntax()) + || comments.has_comments(second_argument.syntax()) + { + return Ok(false); + } + let first_expression = match first_argument { JsAnyCallArgument::JsAnyExpression(expression) => Some(expression), _ => None, @@ -450,7 +442,6 @@ fn is_react_hook_with_deps_array( }; let second_node_matches = matches!(second_argument, JsAnyCallArgument::JsAnyExpression(_)); - // let no_comments = !node.syntax().first_or_last_token_have_comments(); if first_node_matches && second_node_matches { Ok(true) } else { diff --git a/crates/rome_js_formatter/src/js/expressions/new_expression.rs b/crates/rome_js_formatter/src/js/expressions/new_expression.rs index cc95edbf40e..5016238f2d2 100644 --- a/crates/rome_js_formatter/src/js/expressions/new_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/new_expression.rs @@ -32,13 +32,7 @@ impl FormatNodeRule for FormatJsNewExpression { write!(f, [arguments.format()]) } None => { - write!( - f, - [ - format_inserted(JsSyntaxKind::L_PAREN), - format_inserted(JsSyntaxKind::R_PAREN) - ] - ) + write!(f, [text("("), text(")")]) } } } diff --git a/crates/rome_js_formatter/src/js/expressions/template_element.rs b/crates/rome_js_formatter/src/js/expressions/template_element.rs index ac176e79ce6..6b413391bd7 100644 --- a/crates/rome_js_formatter/src/js/expressions/template_element.rs +++ b/crates/rome_js_formatter/src/js/expressions/template_element.rs @@ -1,6 +1,8 @@ use crate::prelude::*; use rome_formatter::printer::{PrintWidth, Printer}; -use rome_formatter::{format_args, write, FormatOptions, FormatRuleWithOptions, VecBuffer}; +use rome_formatter::{ + format_args, write, CstFormatContext, FormatOptions, FormatRuleWithOptions, VecBuffer, +}; use crate::context::TabWidth; use crate::js::lists::template_element_list::{TemplateElementIndention, TemplateElementLayout}; @@ -63,8 +65,6 @@ impl FormatTemplateElement { impl Format for FormatTemplateElement { fn fmt(&self, f: &mut JsFormatter) -> FormatResult<()> { - let has_comments = self.element.syntax().has_comments_direct(); - let format_expression = format_with(|f| match &self.element { AnyTemplateElement::JsTemplateElement(template) => { write!(f, [template.expression().format()]) @@ -110,7 +110,10 @@ impl Format for FormatTemplateElement { // It's preferred to break after/before `${` and `}` rather than breaking in the // middle of some expressions. - let indent = has_comments + let indent = f + .context() + .comments() + .has_comments(&self.element.inner_syntax()?) || matches!( expression, Some( diff --git a/crates/rome_js_formatter/src/js/expressions/unary_expression.rs b/crates/rome_js_formatter/src/js/expressions/unary_expression.rs index 5e54eebca4c..eae44ac6aa1 100644 --- a/crates/rome_js_formatter/src/js/expressions/unary_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/unary_expression.rs @@ -1,10 +1,10 @@ use crate::prelude::*; -use rome_formatter::{format_args, write}; +use rome_formatter::{format_args, write, CstFormatContext}; use crate::parentheses::{unary_like_expression_needs_parentheses, NeedsParentheses}; use rome_js_syntax::JsSyntaxNode; -use rome_js_syntax::{JsSyntaxKind, JsUnaryExpression}; +use rome_js_syntax::JsUnaryExpression; use rome_js_syntax::{JsUnaryExpressionFields, JsUnaryOperator}; use rome_rowan::match_ast; @@ -33,13 +33,13 @@ impl FormatNodeRule for FormatJsUnaryExpression { write!(f, [space()])?; } - if argument.syntax().has_leading_comments() { + if f.context().comments().has_comments(argument.syntax()) { write!( f, [group(&format_args![ - format_inserted(JsSyntaxKind::L_PAREN), + text("("), soft_block_indent(&argument.format()), - format_inserted(JsSyntaxKind::R_PAREN) + text(")") ])] ) } else { diff --git a/crates/rome_js_formatter/src/js/lists/array_element_list.rs b/crates/rome_js_formatter/src/js/lists/array_element_list.rs index 18250a5d941..9a3ab4f6fbc 100644 --- a/crates/rome_js_formatter/src/js/lists/array_element_list.rs +++ b/crates/rome_js_formatter/src/js/lists/array_element_list.rs @@ -1,10 +1,9 @@ use crate::prelude::*; -use rome_formatter::{write, FormatRuleWithOptions, GroupId}; +use rome_formatter::{write, Comments, CstFormatContext, FormatRuleWithOptions, GroupId}; use crate::utils::array::write_array_node; -use crate::utils::{has_token_trailing_line_comment, has_trailing_line_comment}; -use rome_js_syntax::{JsArrayElementList, JsSyntaxKind}; +use rome_js_syntax::{JsArrayElementList, JsLanguage}; use rome_rowan::{AstNode, AstSeparatedList}; #[derive(Debug, Clone, Default)] @@ -25,7 +24,7 @@ impl FormatRule for FormatJsArrayElementList { type Context = JsFormatContext; fn fmt(&self, node: &JsArrayElementList, f: &mut JsFormatter) -> FormatResult<()> { - let layout = if can_print_fill(node) { + let layout = if can_print_fill(node, f.context().comments()) { ArrayLayout::Fill } else { ArrayLayout::OnePerLine @@ -36,10 +35,10 @@ impl FormatRule for FormatJsArrayElementList { let mut filler = f.fill(); // Using format_separated is valid in this case as can_print_fill does not allow holes - for (element, formatted) in node.iter().zip( - node.format_separated(JsSyntaxKind::COMMA) - .with_group_id(self.group_id), - ) { + for (element, formatted) in node + .iter() + .zip(node.format_separated(",").with_group_id(self.group_id)) + { filler.entry( &format_once(|f| { if get_lines_before(element?.syntax()) > 1 { @@ -90,7 +89,7 @@ enum ArrayLayout { /// The underlying logic only allows lists of literal expressions /// with 10 or less characters, potentially wrapped in a "short" /// unary expression (+, -, ~ or !) -fn can_print_fill(list: &JsArrayElementList) -> bool { +fn can_print_fill(list: &JsArrayElementList, comments: &Comments) -> bool { use rome_js_syntax::JsAnyArrayElement::*; use rome_js_syntax::JsAnyExpression::*; use rome_js_syntax::JsUnaryOperator::*; @@ -100,16 +99,6 @@ fn can_print_fill(list: &JsArrayElementList) -> bool { } list.elements().all(|item| { - let separator_has_trailing = item.trailing_separator().map_or(true, |separator| { - separator.map_or(false, |separator| { - has_token_trailing_line_comment(separator) - }) - }); - - if separator_has_trailing { - return false; - } - let syntax = match item.into_node() { Ok(JsAnyExpression(JsAnyLiteralExpression( rome_js_syntax::JsAnyLiteralExpression::JsNumberLiteralExpression(literal), @@ -123,15 +112,8 @@ fn can_print_fill(list: &JsArrayElementList) -> bool { Ok(JsAnyLiteralExpression( rome_js_syntax::JsAnyLiteralExpression::JsNumberLiteralExpression(literal), )) => { - let has_operator_comments = expr - .operator_token() - .map_or(false, |operator| operator.has_trailing_comments()); - - if signed - && !literal.syntax().has_leading_comments() - && !has_operator_comments - { - literal.into_syntax() + if signed && !comments.has_comments(literal.syntax()) { + expr.into_syntax() } else { return false; } @@ -147,6 +129,20 @@ fn can_print_fill(list: &JsArrayElementList) -> bool { } }; - !has_trailing_line_comment(&syntax) + // Does not have a line comment ending on the same line + // ```javascript + // [ a // not this + // b]; + // + // [ + // // This is fine + // thats + // ] + // ``` + !comments + .trailing_comments(&syntax) + .iter() + .filter(|comment| comment.kind().is_line()) + .any(|comment| comment.lines_before() == 0) }) } diff --git a/crates/rome_js_formatter/src/js/lists/call_argument_list.rs b/crates/rome_js_formatter/src/js/lists/call_argument_list.rs index f36afa497a2..cb939ee7d32 100644 --- a/crates/rome_js_formatter/src/js/lists/call_argument_list.rs +++ b/crates/rome_js_formatter/src/js/lists/call_argument_list.rs @@ -1,7 +1,7 @@ use crate::prelude::*; use crate::utils::write_arguments_multi_line; use rome_formatter::write; -use rome_js_syntax::{JsCallArgumentList, JsSyntaxKind}; +use rome_js_syntax::JsCallArgumentList; #[derive(Debug, Clone, Default)] pub struct FormatJsCallArgumentList; @@ -18,7 +18,7 @@ impl FormatRule for FormatJsCallArgumentList { f, [&group(&soft_block_indent(&format_with(|f| { let separated = node - .format_separated(JsSyntaxKind::COMMA) + .format_separated(",") .with_trailing_separator(TrailingSeparator::Omit); write_arguments_multi_line(separated, f) })))] diff --git a/crates/rome_js_formatter/src/js/lists/export_named_from_specifier_list.rs b/crates/rome_js_formatter/src/js/lists/export_named_from_specifier_list.rs index 07f2d09095a..a185765e85a 100644 --- a/crates/rome_js_formatter/src/js/lists/export_named_from_specifier_list.rs +++ b/crates/rome_js_formatter/src/js/lists/export_named_from_specifier_list.rs @@ -1,5 +1,5 @@ use crate::prelude::*; -use rome_js_syntax::{JsExportNamedFromSpecifierList, JsSyntaxKind}; +use rome_js_syntax::JsExportNamedFromSpecifierList; #[derive(Debug, Clone, Default)] pub struct FormatJsExportNamedFromSpecifierList; @@ -9,7 +9,7 @@ impl FormatRule for FormatJsExportNamedFromSpeci fn fmt(&self, node: &JsExportNamedFromSpecifierList, f: &mut JsFormatter) -> FormatResult<()> { f.join_with(&soft_line_break_or_space()) - .entries(node.format_separated(JsSyntaxKind::COMMA)) + .entries(node.format_separated(",")) .finish() } } diff --git a/crates/rome_js_formatter/src/js/lists/export_named_specifier_list.rs b/crates/rome_js_formatter/src/js/lists/export_named_specifier_list.rs index 0f1bda76b46..11df691d86f 100644 --- a/crates/rome_js_formatter/src/js/lists/export_named_specifier_list.rs +++ b/crates/rome_js_formatter/src/js/lists/export_named_specifier_list.rs @@ -1,5 +1,5 @@ use crate::prelude::*; -use rome_js_syntax::{JsExportNamedSpecifierList, JsSyntaxKind}; +use rome_js_syntax::JsExportNamedSpecifierList; #[derive(Debug, Clone, Default)] pub struct FormatJsExportNamedSpecifierList; @@ -9,7 +9,7 @@ impl FormatRule for FormatJsExportNamedSpecifierList fn fmt(&self, node: &JsExportNamedSpecifierList, f: &mut JsFormatter) -> FormatResult<()> { f.join_with(&soft_line_break_or_space()) - .entries(node.format_separated(JsSyntaxKind::COMMA)) + .entries(node.format_separated(",")) .finish() } } diff --git a/crates/rome_js_formatter/src/js/lists/import_assertion_entry_list.rs b/crates/rome_js_formatter/src/js/lists/import_assertion_entry_list.rs index dcfeb474825..1d09d2ccbac 100644 --- a/crates/rome_js_formatter/src/js/lists/import_assertion_entry_list.rs +++ b/crates/rome_js_formatter/src/js/lists/import_assertion_entry_list.rs @@ -1,5 +1,5 @@ use crate::prelude::*; -use rome_js_syntax::{JsImportAssertionEntryList, JsSyntaxKind}; +use rome_js_syntax::JsImportAssertionEntryList; #[derive(Debug, Clone, Default)] pub struct FormatJsImportAssertionEntryList; @@ -9,7 +9,7 @@ impl FormatRule for FormatJsImportAssertionEntryList fn fmt(&self, node: &JsImportAssertionEntryList, f: &mut JsFormatter) -> FormatResult<()> { f.join_with(&soft_line_break_or_space()) - .entries(node.format_separated(JsSyntaxKind::COMMA)) + .entries(node.format_separated(",")) .finish() } } diff --git a/crates/rome_js_formatter/src/js/lists/named_import_specifier_list.rs b/crates/rome_js_formatter/src/js/lists/named_import_specifier_list.rs index 1eb56aac5f7..f9dd9876147 100644 --- a/crates/rome_js_formatter/src/js/lists/named_import_specifier_list.rs +++ b/crates/rome_js_formatter/src/js/lists/named_import_specifier_list.rs @@ -1,5 +1,5 @@ use crate::prelude::*; -use rome_js_syntax::{JsNamedImportSpecifierList, JsSyntaxKind}; +use rome_js_syntax::JsNamedImportSpecifierList; #[derive(Debug, Clone, Default)] pub struct FormatJsNamedImportSpecifierList; @@ -9,7 +9,7 @@ impl FormatRule for FormatJsNamedImportSpecifierList fn fmt(&self, node: &JsNamedImportSpecifierList, f: &mut JsFormatter) -> FormatResult<()> { f.join_with(&soft_line_break_or_space()) - .entries(node.format_separated(JsSyntaxKind::COMMA)) + .entries(node.format_separated(",")) .finish() } } diff --git a/crates/rome_js_formatter/src/js/lists/object_assignment_pattern_property_list.rs b/crates/rome_js_formatter/src/js/lists/object_assignment_pattern_property_list.rs index 0fbfe3fae6d..334430a4324 100644 --- a/crates/rome_js_formatter/src/js/lists/object_assignment_pattern_property_list.rs +++ b/crates/rome_js_formatter/src/js/lists/object_assignment_pattern_property_list.rs @@ -1,7 +1,5 @@ use crate::prelude::*; -use rome_js_syntax::{ - JsAnyObjectAssignmentPatternMember, JsObjectAssignmentPatternPropertyList, JsSyntaxKind, -}; +use rome_js_syntax::{JsAnyObjectAssignmentPatternMember, JsObjectAssignmentPatternPropertyList}; #[derive(Debug, Clone, Default)] pub struct FormatJsObjectAssignmentPatternPropertyList; @@ -32,7 +30,7 @@ impl FormatRule }; let entries = node - .format_separated(JsSyntaxKind::COMMA) + .format_separated(",") .with_trailing_separator(trailing_separator) .zip(node.iter()); diff --git a/crates/rome_js_formatter/src/js/lists/object_binding_pattern_property_list.rs b/crates/rome_js_formatter/src/js/lists/object_binding_pattern_property_list.rs index e6a9034bab8..9d795715d19 100644 --- a/crates/rome_js_formatter/src/js/lists/object_binding_pattern_property_list.rs +++ b/crates/rome_js_formatter/src/js/lists/object_binding_pattern_property_list.rs @@ -1,7 +1,5 @@ use crate::prelude::*; -use rome_js_syntax::{ - JsAnyObjectBindingPatternMember, JsObjectBindingPatternPropertyList, JsSyntaxKind, -}; +use rome_js_syntax::{JsAnyObjectBindingPatternMember, JsObjectBindingPatternPropertyList}; #[derive(Debug, Clone, Default)] pub struct FormatJsObjectBindingPatternPropertyList; @@ -30,7 +28,7 @@ impl FormatRule for FormatJsObjectBindingPat }; let entries = node - .format_separated(JsSyntaxKind::COMMA) + .format_separated(",") .with_trailing_separator(trailing_separator) .zip(node.iter()); diff --git a/crates/rome_js_formatter/src/js/lists/object_member_list.rs b/crates/rome_js_formatter/src/js/lists/object_member_list.rs index 683c9c85a5c..057f60a1ef5 100644 --- a/crates/rome_js_formatter/src/js/lists/object_member_list.rs +++ b/crates/rome_js_formatter/src/js/lists/object_member_list.rs @@ -1,5 +1,5 @@ use crate::prelude::*; -use rome_js_syntax::{JsObjectMemberList, JsSyntaxKind}; +use rome_js_syntax::JsObjectMemberList; use rome_rowan::{AstNode, AstSeparatedList}; #[derive(Debug, Clone, Default)] @@ -11,10 +11,7 @@ impl FormatRule for FormatJsObjectMemberList { fn fmt(&self, node: &JsObjectMemberList, f: &mut JsFormatter) -> FormatResult<()> { let mut join = f.join_nodes_with_soft_line(); - for (element, formatted) in node - .elements() - .zip(node.format_separated(JsSyntaxKind::COMMA)) - { + for (element, formatted) in node.elements().zip(node.format_separated(",")) { join.entry(element.node()?.syntax(), &formatted); } diff --git a/crates/rome_js_formatter/src/js/lists/parameter_list.rs b/crates/rome_js_formatter/src/js/lists/parameter_list.rs index e48d6897dc5..330846276e3 100644 --- a/crates/rome_js_formatter/src/js/lists/parameter_list.rs +++ b/crates/rome_js_formatter/src/js/lists/parameter_list.rs @@ -3,7 +3,7 @@ use crate::prelude::*; use rome_js_syntax::{ JsAnyConstructorParameter, JsAnyParameter, JsConstructorParameterList, JsLanguage, - JsParameterList, JsSyntaxKind, + JsParameterList, }; use rome_rowan::{declare_node_union, AstSeparatedListNodesIterator, SyntaxResult}; @@ -64,7 +64,7 @@ impl Format for FormatJsAnyParameterList<'_> { match self.list { JsAnyParameterList::JsParameterList(list) => { let entries = list - .format_separated(JsSyntaxKind::COMMA) + .format_separated(",") .with_trailing_separator(trailing_separator) .zip(list.iter()); @@ -74,7 +74,7 @@ impl Format for FormatJsAnyParameterList<'_> { } JsAnyParameterList::JsConstructorParameterList(list) => { let entries = list - .format_separated(JsSyntaxKind::COMMA) + .format_separated(",") .with_trailing_separator(trailing_separator) .zip(list.iter()); @@ -91,11 +91,11 @@ impl Format for FormatJsAnyParameterList<'_> { match self.list { JsAnyParameterList::JsParameterList(list) => join.entries( - list.format_separated(JsSyntaxKind::COMMA) + list.format_separated(",") .with_trailing_separator(TrailingSeparator::Omit), ), JsAnyParameterList::JsConstructorParameterList(list) => join.entries( - list.format_separated(JsSyntaxKind::COMMA) + list.format_separated(",") .with_trailing_separator(TrailingSeparator::Omit), ), }; diff --git a/crates/rome_js_formatter/src/js/lists/variable_declarator_list.rs b/crates/rome_js_formatter/src/js/lists/variable_declarator_list.rs index bc8894ac7b7..025233ceec5 100644 --- a/crates/rome_js_formatter/src/js/lists/variable_declarator_list.rs +++ b/crates/rome_js_formatter/src/js/lists/variable_declarator_list.rs @@ -35,7 +35,7 @@ impl FormatRule for FormatJsVariableDeclaratorList { }); let mut declarators = node.iter().zip( - node.format_separated(JsSyntaxKind::COMMA) + node.format_separated(",") .with_trailing_separator(TrailingSeparator::Disallowed), ); diff --git a/crates/rome_js_formatter/src/js/module/export_named_clause.rs b/crates/rome_js_formatter/src/js/module/export_named_clause.rs index ea03e76178c..b626bb05608 100644 --- a/crates/rome_js_formatter/src/js/module/export_named_clause.rs +++ b/crates/rome_js_formatter/src/js/module/export_named_clause.rs @@ -3,6 +3,7 @@ use rome_formatter::write; use crate::utils::FormatWithSemicolon; +use crate::builders::format_delimited; use rome_js_syntax::JsExportNamedClause; use rome_js_syntax::JsExportNamedClauseFields; diff --git a/crates/rome_js_formatter/src/js/module/export_named_from_clause.rs b/crates/rome_js_formatter/src/js/module/export_named_from_clause.rs index b8e0948cb2a..1ae1a713f33 100644 --- a/crates/rome_js_formatter/src/js/module/export_named_from_clause.rs +++ b/crates/rome_js_formatter/src/js/module/export_named_from_clause.rs @@ -2,6 +2,7 @@ use crate::prelude::*; use crate::utils::{node_has_leading_newline, FormatWithSemicolon}; use rome_formatter::write; +use crate::builders::format_delimited; use rome_js_syntax::JsExportNamedFromClause; use rome_js_syntax::JsExportNamedFromClauseFields; use rome_rowan::AstNode; diff --git a/crates/rome_js_formatter/src/js/module/import_assertion.rs b/crates/rome_js_formatter/src/js/module/import_assertion.rs index a087824a2eb..af81feb3ae4 100644 --- a/crates/rome_js_formatter/src/js/module/import_assertion.rs +++ b/crates/rome_js_formatter/src/js/module/import_assertion.rs @@ -1,5 +1,6 @@ use crate::prelude::*; +use crate::builders::format_delimited; use rome_formatter::write; use rome_js_syntax::JsImportAssertion; use rome_js_syntax::JsImportAssertionFields; diff --git a/crates/rome_js_formatter/src/js/module/named_import_specifiers.rs b/crates/rome_js_formatter/src/js/module/named_import_specifiers.rs index 8f5473ba5d5..f26f55f4bce 100644 --- a/crates/rome_js_formatter/src/js/module/named_import_specifiers.rs +++ b/crates/rome_js_formatter/src/js/module/named_import_specifiers.rs @@ -1,5 +1,6 @@ use crate::prelude::*; +use crate::builders::format_delimited; use rome_formatter::write; use rome_js_syntax::JsNamedImportSpecifiers; use rome_js_syntax::JsNamedImportSpecifiersFields; diff --git a/crates/rome_js_formatter/src/js/statements/block_statement.rs b/crates/rome_js_formatter/src/js/statements/block_statement.rs index 3b1ad2389d2..8867ab4663e 100644 --- a/crates/rome_js_formatter/src/js/statements/block_statement.rs +++ b/crates/rome_js_formatter/src/js/statements/block_statement.rs @@ -18,7 +18,14 @@ impl FormatNodeRule for FormatJsBlockStatement { r_curly_token, } = node.as_fields(); - if is_non_collapsable_empty_block(node, f.context().comments()) { + write!(f, [l_curly_token.format()])?; + + let r_curly_token = r_curly_token?; + + let comments = f.context().comments(); + if is_empty_block(node, comments) { + let has_dangling_trivia = comments.has_dangling_trivia(&r_curly_token); + for stmt in statements .iter() .filter_map(|stmt| JsEmptyStatement::cast(stmt.into_syntax())) @@ -26,44 +33,20 @@ impl FormatNodeRule for FormatJsBlockStatement { f.state_mut().track_token(&stmt.semicolon_token()?) } - write!( - f, - [ - l_curly_token.format(), - hard_line_break(), - r_curly_token.format() - ] - ) + if has_dangling_trivia { + write!(f, [format_dangling_trivia(&r_curly_token).indented()])?; + } else if is_non_collapsible(node) { + write!(f, [hard_line_break()])?; + } } else { - write!( - f, - [ - format_delimited(&l_curly_token?, &statements.format(), &r_curly_token?) - .block_indent() - ] - ) + write!(f, [block_indent(&statements.format())])?; } + + write!(f, [r_curly_token.format()]) } } -// Formatting of curly braces for an: -// * empty block: same line `{}`, -// * empty block that is the 'cons' or 'alt' of an if statement: two lines `{\n}` -// * non empty block: put each stmt on its own line: `{\nstmt1;\nstmt2;\n}` -// * non empty block with comments (trailing comments on {, or leading comments on }) -fn is_non_collapsable_empty_block( - block: &JsBlockStatement, - suppressions: &Comments, -) -> bool { - if block - .l_curly_token() - .map_or_else(|_| false, |token| token.has_trailing_comments()) - || block - .r_curly_token() - .map_or_else(|_| false, |token| token.has_leading_comments()) - { - return false; - } +fn is_empty_block(block: &JsBlockStatement, comments: &Comments) -> bool { // add extra branch to avoid formatting the same code twice and generating different code, // here is an example: // ```js @@ -89,15 +72,20 @@ fn is_non_collapsable_empty_block( // } finally { // } // ``` - if !block.statements().is_empty() - && block.statements().iter().any(|s| { - !matches!(s, JsAnyStatement::JsEmptyStatement(_)) - || s.syntax().has_comments_direct() - || suppressions.is_suppressed(s.syntax()) + block.statements().is_empty() + && block.statements().iter().all(|s| { + matches!(s, JsAnyStatement::JsEmptyStatement(_)) + && !comments.has_comments(s.syntax()) + && !comments.is_suppressed(s.syntax()) }) - { - return false; - } +} + +// Formatting of curly braces for an: +// * empty block: same line `{}`, +// * empty block that is the 'cons' or 'alt' of an if statement: two lines `{\n}` +// * non empty block: put each stmt on its own line: `{\nstmt1;\nstmt2;\n}` +// * non empty block with comments (trailing comments on {, or leading comments on }) +fn is_non_collapsible(block: &JsBlockStatement) -> bool { // reference https://github.com/prettier/prettier/blob/b188c905cfaeb238a122b4a95c230da83f2f3226/src/language-js/print/block.js#L19 let parent = block.syntax().parent(); match parent.clone().map(|p| p.kind()) { diff --git a/crates/rome_js_formatter/src/js/statements/break_statement.rs b/crates/rome_js_formatter/src/js/statements/break_statement.rs index 81d50009f5f..8183c2ceaad 100644 --- a/crates/rome_js_formatter/src/js/statements/break_statement.rs +++ b/crates/rome_js_formatter/src/js/statements/break_statement.rs @@ -1,5 +1,5 @@ use crate::prelude::*; -use rome_formatter::write; +use rome_formatter::{write, CstFormatContext}; use crate::utils::FormatWithSemicolon; @@ -20,10 +20,14 @@ impl FormatNodeRule for FormatJsBreakStatement { write!( f, [FormatWithSemicolon::new( - &format_with(|f| { + &format_with(|f: &mut JsFormatter| { write!(f, [break_token.format()])?; if let Some(label) = &label_token { + if f.context().comments().has_dangling_trivia(&label) { + write!(f, [space(), format_dangling_trivia(label)])?; + } + write!(f, [space(), label.format()])?; } diff --git a/crates/rome_js_formatter/src/js/statements/continue_statement.rs b/crates/rome_js_formatter/src/js/statements/continue_statement.rs index 28f8c01a193..933d4e45e31 100644 --- a/crates/rome_js_formatter/src/js/statements/continue_statement.rs +++ b/crates/rome_js_formatter/src/js/statements/continue_statement.rs @@ -1,5 +1,5 @@ use crate::prelude::*; -use rome_formatter::write; +use rome_formatter::{write, CstFormatContext}; use crate::utils::FormatWithSemicolon; @@ -20,10 +20,14 @@ impl FormatNodeRule for FormatJsContinueStatement { write!( f, [FormatWithSemicolon::new( - &format_with(|f| { + &format_with(|f: &mut JsFormatter| { write!(f, [continue_token.format()])?; if let Some(label) = &label_token { + if f.context().comments().has_dangling_trivia(&label) { + write!(f, [space(), format_dangling_trivia(label)])?; + } + write!(f, [space(), label.format()])?; } diff --git a/crates/rome_js_formatter/src/js/statements/do_while_statement.rs b/crates/rome_js_formatter/src/js/statements/do_while_statement.rs index 738c5006423..dc89449da02 100644 --- a/crates/rome_js_formatter/src/js/statements/do_while_statement.rs +++ b/crates/rome_js_formatter/src/js/statements/do_while_statement.rs @@ -1,5 +1,6 @@ use crate::prelude::*; +use crate::builders::format_delimited; use crate::utils::{FormatStatementBody, FormatWithSemicolon}; use rome_formatter::{format_args, write}; use rome_js_syntax::JsDoWhileStatementFields; diff --git a/crates/rome_js_formatter/src/js/statements/for_statement.rs b/crates/rome_js_formatter/src/js/statements/for_statement.rs index 72451c761b7..0df9baf78e6 100644 --- a/crates/rome_js_formatter/src/js/statements/for_statement.rs +++ b/crates/rome_js_formatter/src/js/statements/for_statement.rs @@ -1,5 +1,5 @@ use crate::prelude::*; -use rome_formatter::{format_args, write}; +use rome_formatter::{format_args, write, CstFormatContext}; use crate::utils::FormatStatementBody; use rome_js_syntax::JsForStatement; @@ -23,9 +23,24 @@ impl FormatNodeRule for FormatJsForStatement { } = node.as_fields(); let body = body?; + let l_paren_token = l_paren_token?; let format_body = FormatStatementBody::new(&body); + // Move dangling trivia between the `for /* this */ (` to the top of the `for` and + // add a line break after. + let comments = f.context().comments(); + let dangling_trivia = comments.dangling_trivia(&l_paren_token); + if !dangling_trivia.is_empty() && dangling_trivia.iter().all(|trivia| trivia.is_comment()) { + write!( + f, + [ + format_dangling_trivia(&l_paren_token), + soft_line_break_or_space() + ] + )?; + } + if initializer.is_none() && test.is_none() && update.is_none() { return write!( f, diff --git a/crates/rome_js_formatter/src/js/statements/if_statement.rs b/crates/rome_js_formatter/src/js/statements/if_statement.rs index 9f553d9b3e9..a9b9de0d927 100644 --- a/crates/rome_js_formatter/src/js/statements/if_statement.rs +++ b/crates/rome_js_formatter/src/js/statements/if_statement.rs @@ -1,6 +1,7 @@ use crate::prelude::*; -use rome_formatter::{format_args, write}; +use rome_formatter::{format_args, write, CstFormatContext}; +use crate::builders::format_delimited; use crate::utils::FormatStatementBody; use rome_js_syntax::JsIfStatement; use rome_js_syntax::JsIfStatementFields; @@ -37,7 +38,21 @@ impl FormatNodeRule for FormatJsIfStatement { )?; if let Some(else_clause) = else_clause { - let else_on_same_line = matches!(consequent, JsBlockStatement(_)); + // const commentOnOwnLine = + // hasComment( + // node.consequent, + // CommentCheckFlags.Trailing | CommentCheckFlags.Line + // ) || needsHardlineAfterDanglingComment(node); + + let trailing_line_comment = f + .context() + .comments() + .trailing_comments(else_clause.syntax()) + .iter() + .any(|comment| comment.kind().is_line()); + + let else_on_same_line = + matches!(consequent, JsBlockStatement(_)) && !trailing_line_comment; if else_on_same_line { write!(f, [space()])?; diff --git a/crates/rome_js_formatter/src/js/statements/labeled_statement.rs b/crates/rome_js_formatter/src/js/statements/labeled_statement.rs index 1d3c94fed4c..8ef898b4a2f 100644 --- a/crates/rome_js_formatter/src/js/statements/labeled_statement.rs +++ b/crates/rome_js_formatter/src/js/statements/labeled_statement.rs @@ -1,8 +1,8 @@ use crate::prelude::*; use rome_formatter::write; +use rome_js_syntax::JsLabeledStatementFields; use rome_js_syntax::{JsAnyStatement, JsLabeledStatement}; -use rome_js_syntax::{JsLabeledStatementFields, JsSyntaxKind}; #[derive(Debug, Clone, Default)] pub struct FormatJsLabeledStatement; @@ -20,10 +20,7 @@ impl FormatNodeRule for FormatJsLabeledStatement { match body? { JsAnyStatement::JsEmptyStatement(empty) => { // If the body is an empty statement, force semicolon insertion - write!( - f, - [empty.format(), format_inserted(JsSyntaxKind::SEMICOLON)] - ) + write!(f, [empty.format(), text(";")]) } body => { write!(f, [space(), body.format()]) diff --git a/crates/rome_js_formatter/src/js/statements/return_statement.rs b/crates/rome_js_formatter/src/js/statements/return_statement.rs index 1493da0fb9e..6ce3f0fd665 100644 --- a/crates/rome_js_formatter/src/js/statements/return_statement.rs +++ b/crates/rome_js_formatter/src/js/statements/return_statement.rs @@ -1,64 +1,122 @@ use crate::prelude::*; -use crate::utils::{FormatWithSemicolon, JsAnyBinaryLikeExpression}; +use crate::utils::{FormatWithSemicolon, JsAnyBinaryLikeExpression, JsAnyBinaryLikeLeftExpression}; -use rome_formatter::{format_args, write}; +use rome_formatter::{ + format_args, has_leading_own_line_comment, write, Comments, CstFormatContext, +}; +use crate::parentheses::get_expression_left_side; use rome_js_syntax::{ - JsAnyExpression, JsReturnStatement, JsReturnStatementFields, JsSequenceExpression, JsSyntaxKind, + JsAnyExpression, JsLanguage, JsReturnStatement, JsSequenceExpression, JsSyntaxToken, + JsThrowStatement, }; +use rome_rowan::{declare_node_union, SyntaxResult}; #[derive(Debug, Clone, Default)] pub struct FormatJsReturnStatement; impl FormatNodeRule for FormatJsReturnStatement { fn fmt_fields(&self, node: &JsReturnStatement, f: &mut JsFormatter) -> FormatResult<()> { - let JsReturnStatementFields { - return_token, - argument, - semicolon_token, - } = node.as_fields(); + JsAnyStatementWithArgument::from(node.clone()).fmt(f) + } +} + +declare_node_union! { + pub(super) JsAnyStatementWithArgument = JsThrowStatement | JsReturnStatement +} + +impl Format for JsAnyStatementWithArgument { + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + write!(f, [self.operation_token().format()])?; + + let argument = self.argument()?; + + if let Some(semicolon) = self.semicolon_token() { + if let Some(argument) = argument { + write!(f, [space(), FormatReturnOrThrowArgument(&argument)])?; + } - let format_inner = format_with(|f| { - write!(f, [return_token.format()])?; + let comments = f.context().comments(); + let has_dangling_comments = comments.has_dangling_comments(&semicolon); - if let Some(argument) = &argument { - write!(f, [space(), FormatReturnOrThrowArgument(argument)])?; + let is_last_comment_line = has_dangling_comments + && comments + .dangling_comments(&semicolon) + .chain(comments.trailing_comments(self.syntax())) + .last() + .map_or(false, |comment| comment.kind().is_line()); + + // We'll format it after the semicolon + f.state_mut().mark_token_trivia_formatted(&semicolon); + + if is_last_comment_line { + write!(f, [semicolon.format()])?; + } + + if has_dangling_comments { + write!( + f, + [ + space(), + format_dangling_trivia(&semicolon).ignore_formatted_check() + ] + )?; + } + + if !is_last_comment_line { + write!(f, [semicolon.format()])?; } Ok(()) - }); - - write!( - f, - [FormatWithSemicolon::new( - &format_inner, - semicolon_token.as_ref() - )] - ) + } else { + write!( + f, + [FormatWithSemicolon::new( + &format_with(|f| { + if let Some(argument) = &argument { + write!(f, [space(), FormatReturnOrThrowArgument(&argument)])?; + } + + Ok(()) + }), + None + )] + ) + } } } -pub(super) struct FormatReturnOrThrowArgument<'a>(&'a JsAnyExpression); +impl JsAnyStatementWithArgument { + fn operation_token(&self) -> SyntaxResult { + match self { + JsAnyStatementWithArgument::JsThrowStatement(throw) => throw.throw_token(), + JsAnyStatementWithArgument::JsReturnStatement(ret) => ret.return_token(), + } + } -impl<'a> FormatReturnOrThrowArgument<'a> { - pub fn new(argument: &'a JsAnyExpression) -> Self { - Self(argument) + fn argument(&self) -> SyntaxResult> { + match self { + JsAnyStatementWithArgument::JsThrowStatement(throw) => throw.argument().map(Some), + JsAnyStatementWithArgument::JsReturnStatement(ret) => Ok(ret.argument()), + } + } + + fn semicolon_token(&self) -> Option { + match self { + JsAnyStatementWithArgument::JsThrowStatement(throw) => throw.semicolon_token(), + JsAnyStatementWithArgument::JsReturnStatement(ret) => ret.semicolon_token(), + } } } +pub(super) struct FormatReturnOrThrowArgument<'a>(&'a JsAnyExpression); + impl Format for FormatReturnOrThrowArgument<'_> { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { let argument = self.0; - if has_argument_leading_comments(argument) { - write!( - f, - [ - format_inserted(JsSyntaxKind::L_PAREN), - &block_indent(&argument.format()), - format_inserted(JsSyntaxKind::R_PAREN) - ] - ) + if has_argument_leading_comments(argument, f.context().comments()) { + write!(f, [text("("), &block_indent(&argument.format()), text(")")]) } else if is_binary_or_sequence_argument(argument) { write!( f, @@ -80,13 +138,28 @@ impl Format for FormatReturnOrThrowArgument<'_> { /// /// Traversing the left nodes is necessary in case the first node is parenthesized because /// parentheses will be removed (and be re-added by the return statement, but only if the argument breaks) -fn has_argument_leading_comments(argument: &JsAnyExpression) -> bool { - if matches!(argument, JsAnyExpression::JsxTagExpression(_)) { - // JSX formatting takes care of adding parens - return false; +fn has_argument_leading_comments( + argument: &JsAnyExpression, + comments: &Comments, +) -> bool { + let mut current: Option = Some(argument.clone().into()); + + while let Some(expression) = current { + if has_leading_own_line_comment(expression.syntax(), comments) { + return true; + } + + match expression { + JsAnyBinaryLikeLeftExpression::JsAnyExpression(expression) => { + current = get_expression_left_side(&expression); + } + JsAnyBinaryLikeLeftExpression::JsPrivateName(_) => { + break; + } + } } - argument.syntax().has_leading_comments() + false } fn is_binary_or_sequence_argument(argument: &JsAnyExpression) -> bool { diff --git a/crates/rome_js_formatter/src/js/statements/switch_statement.rs b/crates/rome_js_formatter/src/js/statements/switch_statement.rs index 9ddb27381aa..9295382d608 100644 --- a/crates/rome_js_formatter/src/js/statements/switch_statement.rs +++ b/crates/rome_js_formatter/src/js/statements/switch_statement.rs @@ -1,5 +1,6 @@ use crate::prelude::*; +use crate::builders::format_delimited; use rome_formatter::write; use rome_js_syntax::{JsSwitchStatement, JsSwitchStatementFields}; use rome_rowan::AstNodeList; diff --git a/crates/rome_js_formatter/src/js/statements/throw_statement.rs b/crates/rome_js_formatter/src/js/statements/throw_statement.rs index 23253d0588b..c390f7c3f57 100644 --- a/crates/rome_js_formatter/src/js/statements/throw_statement.rs +++ b/crates/rome_js_formatter/src/js/statements/throw_statement.rs @@ -1,33 +1,13 @@ use crate::prelude::*; -use rome_formatter::{format_args, write}; -use crate::utils::FormatWithSemicolon; - -use crate::js::statements::return_statement::FormatReturnOrThrowArgument; +use crate::js::statements::return_statement::JsAnyStatementWithArgument; use rome_js_syntax::JsThrowStatement; -use rome_js_syntax::JsThrowStatementFields; #[derive(Debug, Clone, Default)] pub struct FormatJsThrowStatement; impl FormatNodeRule for FormatJsThrowStatement { fn fmt_fields(&self, node: &JsThrowStatement, f: &mut JsFormatter) -> FormatResult<()> { - let JsThrowStatementFields { - throw_token, - argument, - semicolon_token, - } = node.as_fields(); - - write!( - f, - [FormatWithSemicolon::new( - &format_args![ - throw_token.format(), - space(), - FormatReturnOrThrowArgument::new(&argument?) - ], - semicolon_token.as_ref() - )] - ) + JsAnyStatementWithArgument::from(node.clone()).fmt(f) } } diff --git a/crates/rome_js_formatter/src/js/statements/while_statement.rs b/crates/rome_js_formatter/src/js/statements/while_statement.rs index 89ca7ab8a33..f21ce269fd7 100644 --- a/crates/rome_js_formatter/src/js/statements/while_statement.rs +++ b/crates/rome_js_formatter/src/js/statements/while_statement.rs @@ -1,5 +1,6 @@ use crate::prelude::*; +use crate::builders::format_delimited; use crate::utils::FormatStatementBody; use rome_formatter::{format_args, write}; use rome_js_syntax::JsWhileStatement; diff --git a/crates/rome_js_formatter/src/js/unknown/unknown.rs b/crates/rome_js_formatter/src/js/unknown/unknown.rs index 70fd4e21471..c6418e9531c 100644 --- a/crates/rome_js_formatter/src/js/unknown/unknown.rs +++ b/crates/rome_js_formatter/src/js/unknown/unknown.rs @@ -10,4 +10,8 @@ impl FormatNodeRule for FormatJsUnknown { fn fmt_fields(&self, node: &JsUnknown, formatter: &mut JsFormatter) -> FormatResult<()> { format_unknown_node(node.syntax()).fmt(formatter) } + + fn prints_comments(&self, _item: &JsUnknown) -> bool { + true + } } diff --git a/crates/rome_js_formatter/src/js/unknown/unknown_assignment.rs b/crates/rome_js_formatter/src/js/unknown/unknown_assignment.rs index 4cf01d7f3c6..a2415b21ec6 100644 --- a/crates/rome_js_formatter/src/js/unknown/unknown_assignment.rs +++ b/crates/rome_js_formatter/src/js/unknown/unknown_assignment.rs @@ -19,6 +19,10 @@ impl FormatNodeRule for FormatJsUnknownAssignment { fn needs_parentheses(&self, item: &JsUnknownAssignment) -> bool { item.needs_parentheses() } + + fn prints_comments(&self, _item: &JsUnknownAssignment) -> bool { + true + } } impl NeedsParentheses for JsUnknownAssignment { diff --git a/crates/rome_js_formatter/src/js/unknown/unknown_binding.rs b/crates/rome_js_formatter/src/js/unknown/unknown_binding.rs index 31cb8a2db66..4457aa5504a 100644 --- a/crates/rome_js_formatter/src/js/unknown/unknown_binding.rs +++ b/crates/rome_js_formatter/src/js/unknown/unknown_binding.rs @@ -10,4 +10,8 @@ impl FormatNodeRule for FormatJsUnknownBinding { fn fmt_fields(&self, node: &JsUnknownBinding, formatter: &mut JsFormatter) -> FormatResult<()> { format_unknown_node(node.syntax()).fmt(formatter) } + + fn prints_comments(&self, _item: &JsUnknownBinding) -> bool { + true + } } diff --git a/crates/rome_js_formatter/src/js/unknown/unknown_expression.rs b/crates/rome_js_formatter/src/js/unknown/unknown_expression.rs index fa8b3386e7d..8a729452f1b 100644 --- a/crates/rome_js_formatter/src/js/unknown/unknown_expression.rs +++ b/crates/rome_js_formatter/src/js/unknown/unknown_expression.rs @@ -19,6 +19,10 @@ impl FormatNodeRule for FormatJsUnknownExpression { fn needs_parentheses(&self, item: &JsUnknownExpression) -> bool { item.needs_parentheses() } + + fn prints_comments(&self, _item: &JsUnknownExpression) -> bool { + true + } } impl NeedsParentheses for JsUnknownExpression { diff --git a/crates/rome_js_formatter/src/js/unknown/unknown_import_assertion_entry.rs b/crates/rome_js_formatter/src/js/unknown/unknown_import_assertion_entry.rs index f2f9fe7af37..84d24abb2be 100644 --- a/crates/rome_js_formatter/src/js/unknown/unknown_import_assertion_entry.rs +++ b/crates/rome_js_formatter/src/js/unknown/unknown_import_assertion_entry.rs @@ -14,4 +14,8 @@ impl FormatNodeRule for FormatJsUnknownImportAsse ) -> FormatResult<()> { format_unknown_node(node.syntax()).fmt(formatter) } + + fn prints_comments(&self, _item: &JsUnknownImportAssertionEntry) -> bool { + true + } } diff --git a/crates/rome_js_formatter/src/js/unknown/unknown_member.rs b/crates/rome_js_formatter/src/js/unknown/unknown_member.rs index 0ef6be8648e..a6f760484aa 100644 --- a/crates/rome_js_formatter/src/js/unknown/unknown_member.rs +++ b/crates/rome_js_formatter/src/js/unknown/unknown_member.rs @@ -10,4 +10,8 @@ impl FormatNodeRule for FormatJsUnknownMember { fn fmt_fields(&self, node: &JsUnknownMember, formatter: &mut JsFormatter) -> FormatResult<()> { format_unknown_node(node.syntax()).fmt(formatter) } + + fn prints_comments(&self, _item: &JsUnknownMember) -> bool { + true + } } diff --git a/crates/rome_js_formatter/src/js/unknown/unknown_named_import_specifier.rs b/crates/rome_js_formatter/src/js/unknown/unknown_named_import_specifier.rs index da60c89758b..32ae3ef8e79 100644 --- a/crates/rome_js_formatter/src/js/unknown/unknown_named_import_specifier.rs +++ b/crates/rome_js_formatter/src/js/unknown/unknown_named_import_specifier.rs @@ -14,4 +14,8 @@ impl FormatNodeRule for FormatJsUnknownNamedImpor ) -> FormatResult<()> { format_unknown_node(node.syntax()).fmt(formatter) } + + fn prints_comments(&self, _item: &JsUnknownNamedImportSpecifier) -> bool { + true + } } diff --git a/crates/rome_js_formatter/src/js/unknown/unknown_parameter.rs b/crates/rome_js_formatter/src/js/unknown/unknown_parameter.rs index bb7f282ccbf..14eeb453669 100644 --- a/crates/rome_js_formatter/src/js/unknown/unknown_parameter.rs +++ b/crates/rome_js_formatter/src/js/unknown/unknown_parameter.rs @@ -14,4 +14,8 @@ impl FormatNodeRule for FormatJsUnknownParameter { ) -> FormatResult<()> { format_unknown_node(node.syntax()).fmt(formatter) } + + fn prints_comments(&self, _item: &JsUnknownParameter) -> bool { + true + } } diff --git a/crates/rome_js_formatter/src/js/unknown/unknown_statement.rs b/crates/rome_js_formatter/src/js/unknown/unknown_statement.rs index 996609ac4a3..33e79ae99fe 100644 --- a/crates/rome_js_formatter/src/js/unknown/unknown_statement.rs +++ b/crates/rome_js_formatter/src/js/unknown/unknown_statement.rs @@ -14,4 +14,8 @@ impl FormatNodeRule for FormatJsUnknownStatement { ) -> FormatResult<()> { format_unknown_node(node.syntax()).fmt(formatter) } + + fn prints_comments(&self, _item: &JsUnknownStatement) -> bool { + true + } } diff --git a/crates/rome_js_formatter/src/lib.rs b/crates/rome_js_formatter/src/lib.rs index 144905a775a..78c4f2cb1dc 100644 --- a/crates/rome_js_formatter/src/lib.rs +++ b/crates/rome_js_formatter/src/lib.rs @@ -271,7 +271,7 @@ use rome_rowan::SyntaxResult; use rome_rowan::TextRange; use rome_rowan::{AstNode, SyntaxNode}; -use crate::builders::{format_parenthesize, format_suppressed_node}; +use crate::builders::format_suppressed_node; use crate::comments::JsCommentStyle; use crate::context::{JsFormatContext, JsFormatOptions}; use crate::cst::FormatJsSyntaxNode; @@ -427,19 +427,31 @@ where let syntax = node.syntax(); if f.context().comments().is_suppressed(syntax) { - write!(f, [format_suppressed_node(syntax)]) - } else if self.needs_parentheses(node) { + return write!(f, [format_suppressed_node(syntax)]); + } + + if !self.prints_comments(node) { + write!(f, [format_leading_comments(node.syntax())])?; + } + + if self.needs_parentheses(node) { write!( f, - [format_parenthesize( - node.syntax().first_token().as_ref(), - &format_once(|f| self.fmt_fields(node, f)), - node.syntax().last_token().as_ref(), - )] - ) + [ + text("("), + format_once(|f| self.fmt_fields(node, f)), + text(")"), + ] + )?; } else { - self.fmt_fields(node, f) + self.fmt_fields(node, f)?; + } + + if !self.prints_comments(node) { + write!(f, [format_trailing_comments(node.syntax())])?; } + + Ok(()) } /// Formats the node's fields. @@ -450,6 +462,10 @@ where let _ = item; false } + + fn prints_comments(&self, _item: &N) -> bool { + return false; + } } /// Format implementation specific to JavaScript tokens. @@ -463,11 +479,7 @@ impl FormatRule for FormatJsSyntaxToken { write!( f, - [ - format_leading_trivia(token), - format_trimmed_token(token), - format_trailing_trivia(token), - ] + [format_dangling_trivia(token), format_trimmed_token(token),] ) } } @@ -503,10 +515,6 @@ impl FormatLanguage for JsFormatLanguage { type CommentStyle = JsCommentStyle; type FormatRule = FormatJsSyntaxNode; - fn comment_style(&self) -> Self::CommentStyle { - JsCommentStyle - } - fn transform( &self, root: &SyntaxNode, @@ -750,8 +758,10 @@ function() { // use this test check if your snippet prints as you wish, without using a snapshot fn quick_test() { let src = r#" - type C = B & (C | A) & B; + b; +// https://github.com/babel/babel/pull/11640 +a + , c; "#; let syntax = SourceType::tsx(); let tree = parse(src, 0, syntax); @@ -760,13 +770,13 @@ function() { let result = format_node(options.clone(), &tree.syntax()) .unwrap() .print(); - check_reformat(CheckReformatParams { - root: &tree.syntax(), - text: result.as_code(), - source_type: syntax, - file_name: "quick_test", - options, - }); + // check_reformat(CheckReformatParams { + // root: &tree.syntax(), + // text: result.as_code(), + // source_type: syntax, + // file_name: "quick_test", + // options, + // }); assert_eq!( result.as_code(), "type Example = {\n\t[A in B]: T;\n} & {\n\t[A in B]: T;\n};\n" diff --git a/crates/rome_js_formatter/src/prelude.rs b/crates/rome_js_formatter/src/prelude.rs index 7198ad90739..eda4fcc6f37 100644 --- a/crates/rome_js_formatter/src/prelude.rs +++ b/crates/rome_js_formatter/src/prelude.rs @@ -8,9 +8,7 @@ pub use rome_formatter::prelude::*; pub use rome_rowan::{AstNode as _, AstNodeList as _, AstSeparatedList as _}; pub use crate::builders::{ - format_delimited, format_inserted, format_inserted_close_paren, format_inserted_open_paren, - format_or_verbatim, format_parenthesize, format_suppressed_node, format_unknown_node, - format_verbatim_node, + format_or_verbatim, format_suppressed_node, format_unknown_node, format_verbatim_node, }; pub use crate::separated::{ diff --git a/crates/rome_js_formatter/src/separated.rs b/crates/rome_js_formatter/src/separated.rs index f0f207c0271..d6584a3a2d6 100644 --- a/crates/rome_js_formatter/src/separated.rs +++ b/crates/rome_js_formatter/src/separated.rs @@ -1,7 +1,7 @@ use crate::prelude::*; use crate::AsFormat; use rome_formatter::{write, GroupId}; -use rome_js_syntax::{JsLanguage, JsSyntaxKind}; +use rome_js_syntax::JsLanguage; use rome_rowan::{ AstNode, AstSeparatedElement, AstSeparatedList, AstSeparatedListElementsIterator, Language, }; @@ -13,7 +13,7 @@ pub struct FormatSeparatedElement { element: AstSeparatedElement, is_last: bool, /// The separator to write if the element has no separator yet. - separator: JsSyntaxKind, + separator: &'static str, options: FormatSeparatedOptions, } @@ -63,12 +63,12 @@ where TrailingSeparator::Allowed => { write!( f, - [if_group_breaks(&format_inserted(self.separator)) + [if_group_breaks(&text(self.separator)) .with_group_id(self.options.group_id)] )?; } TrailingSeparator::Mandatory => { - format_inserted(self.separator).fmt(f)?; + text(self.separator).fmt(f)?; } TrailingSeparator::Omit | TrailingSeparator::Disallowed => { /* no op */ } } @@ -90,7 +90,7 @@ where { next: Option>, inner: I, - separator: JsSyntaxKind, + separator: &'static str, options: FormatSeparatedOptions, } @@ -98,7 +98,7 @@ impl FormatSeparatedIter where L: Language, { - fn new(inner: I, separator: JsSyntaxKind) -> Self { + fn new(inner: I, separator: &'static str) -> Self { Self { inner, separator, @@ -166,7 +166,7 @@ pub trait FormatAstSeparatedListExtension: AstSeparatedList FormatSeparatedIter< AstSeparatedListElementsIterator, JsLanguage, diff --git a/crates/rome_js_formatter/src/ts/assignments/type_assertion_assignment.rs b/crates/rome_js_formatter/src/ts/assignments/type_assertion_assignment.rs index 4187c061d4b..c976a5b411a 100644 --- a/crates/rome_js_formatter/src/ts/assignments/type_assertion_assignment.rs +++ b/crates/rome_js_formatter/src/ts/assignments/type_assertion_assignment.rs @@ -2,6 +2,7 @@ use crate::prelude::*; use rome_formatter::write; use rome_js_syntax::{JsSyntaxKind, JsSyntaxNode, TsTypeAssertionAssignmentFields}; +use crate::builders::format_delimited; use crate::parentheses::NeedsParentheses; use rome_js_syntax::TsTypeAssertionAssignment; diff --git a/crates/rome_js_formatter/src/ts/auxiliary/module_block.rs b/crates/rome_js_formatter/src/ts/auxiliary/module_block.rs index 0af608dc922..4810a998eb8 100644 --- a/crates/rome_js_formatter/src/ts/auxiliary/module_block.rs +++ b/crates/rome_js_formatter/src/ts/auxiliary/module_block.rs @@ -1,5 +1,6 @@ use crate::prelude::*; +use crate::builders::format_delimited; use rome_formatter::write; use rome_js_syntax::TsModuleBlock; use rome_js_syntax::TsModuleBlockFields; diff --git a/crates/rome_js_formatter/src/ts/bindings/type_parameters.rs b/crates/rome_js_formatter/src/ts/bindings/type_parameters.rs index 6000e61f071..fdc8b242a56 100644 --- a/crates/rome_js_formatter/src/ts/bindings/type_parameters.rs +++ b/crates/rome_js_formatter/src/ts/bindings/type_parameters.rs @@ -1,5 +1,6 @@ use crate::prelude::*; +use crate::builders::format_delimited; use rome_formatter::{write, FormatRuleWithOptions, GroupId}; use rome_js_syntax::{TsTypeParameters, TsTypeParametersFields}; diff --git a/crates/rome_js_formatter/src/ts/declarations/enum_declaration.rs b/crates/rome_js_formatter/src/ts/declarations/enum_declaration.rs index 9e818212d00..d2ee85e1c83 100644 --- a/crates/rome_js_formatter/src/ts/declarations/enum_declaration.rs +++ b/crates/rome_js_formatter/src/ts/declarations/enum_declaration.rs @@ -1,6 +1,7 @@ use crate::prelude::*; use rome_formatter::write; +use crate::builders::format_delimited; use rome_js_syntax::{TsEnumDeclaration, TsEnumDeclarationFields}; #[derive(Debug, Clone, Default)] diff --git a/crates/rome_js_formatter/src/ts/declarations/interface_declaration.rs b/crates/rome_js_formatter/src/ts/declarations/interface_declaration.rs index b157191d618..df9aa6ede7f 100644 --- a/crates/rome_js_formatter/src/ts/declarations/interface_declaration.rs +++ b/crates/rome_js_formatter/src/ts/declarations/interface_declaration.rs @@ -1,5 +1,6 @@ use crate::prelude::*; +use crate::builders::format_delimited; use rome_formatter::{format_args, write}; use rome_js_syntax::{TsInterfaceDeclaration, TsInterfaceDeclarationFields}; @@ -78,16 +79,17 @@ impl FormatNodeRule for FormatTsInterfaceDeclaration { let last_token = last_node.last_token(); let mut has_trailing_comments = false; - if let Some(last_token) = &last_token { - for comment in last_token - .trailing_trivia() - .pieces() - .filter_map(|piece| piece.as_comments()) - { - has_trailing_comments = true; - f.state_mut().mark_comment_as_formatted(&comment); - } - } + // FIXME + // if let Some(last_token) = &last_token { + // for comment in last_token + // .trailing_trivia() + // .pieces() + // .filter_map(|piece| piece.as_comments()) + // { + // has_trailing_comments = true; + // f.state_mut().mark_token_trivia_formatted(&comment); + // } + // } let id_has_trailing_comments = id.syntax().has_trailing_comments(); if id_has_trailing_comments || extends_clause.is_some() { @@ -114,17 +116,10 @@ impl FormatNodeRule for FormatTsInterfaceDeclaration { &l_curly_token, &format_args![ format_with(|f| { - // Write the manual handled comments + // TODO: See PR Write the manual handled comments if let Some(last_token) = &last_token { if has_trailing_comments { - write!( - f, - [ - format_trailing_trivia(last_token) - .skip_formatted_check(), - hard_line_break() - ] - )?; + write!(f, [hard_line_break()])?; } } diff --git a/crates/rome_js_formatter/src/ts/expressions/type_arguments.rs b/crates/rome_js_formatter/src/ts/expressions/type_arguments.rs index 99aaf01b1d2..a493fcf2299 100644 --- a/crates/rome_js_formatter/src/ts/expressions/type_arguments.rs +++ b/crates/rome_js_formatter/src/ts/expressions/type_arguments.rs @@ -1,3 +1,4 @@ +use crate::builders::format_delimited; use crate::utils::should_hug_type; use crate::{prelude::*, utils::is_object_like_type}; use rome_formatter::write; diff --git a/crates/rome_js_formatter/src/ts/expressions/type_assertion_expression.rs b/crates/rome_js_formatter/src/ts/expressions/type_assertion_expression.rs index 7e81376a0fc..e799fc6b774 100644 --- a/crates/rome_js_formatter/src/ts/expressions/type_assertion_expression.rs +++ b/crates/rome_js_formatter/src/ts/expressions/type_assertion_expression.rs @@ -1,5 +1,6 @@ use crate::prelude::*; +use crate::builders::format_delimited; use crate::parentheses::{is_callee, is_member_object, is_spread, is_tag, NeedsParentheses}; use rome_formatter::write; use rome_js_syntax::JsSyntaxNode; diff --git a/crates/rome_js_formatter/src/ts/lists/enum_member_list.rs b/crates/rome_js_formatter/src/ts/lists/enum_member_list.rs index 47ea34b7581..5c5efd577db 100644 --- a/crates/rome_js_formatter/src/ts/lists/enum_member_list.rs +++ b/crates/rome_js_formatter/src/ts/lists/enum_member_list.rs @@ -1,6 +1,6 @@ use crate::prelude::*; use crate::utils::node_has_leading_newline; -use rome_js_syntax::{JsSyntaxKind, TsEnumMemberList}; +use rome_js_syntax::TsEnumMemberList; #[derive(Debug, Clone, Default)] pub struct FormatTsEnumMemberList; @@ -16,7 +16,7 @@ impl FormatRule for FormatTsEnumMemberList { } else { soft_line_break_or_space() }) - .entries(node.format_separated(JsSyntaxKind::COMMA).nodes_grouped()) + .entries(node.format_separated(",").nodes_grouped()) .finish() } } diff --git a/crates/rome_js_formatter/src/ts/lists/tuple_type_element_list.rs b/crates/rome_js_formatter/src/ts/lists/tuple_type_element_list.rs index fad9b26bdf5..ae6214c9292 100644 --- a/crates/rome_js_formatter/src/ts/lists/tuple_type_element_list.rs +++ b/crates/rome_js_formatter/src/ts/lists/tuple_type_element_list.rs @@ -1,5 +1,5 @@ use crate::prelude::*; -use rome_js_syntax::{JsSyntaxKind, TsTupleTypeElementList}; +use rome_js_syntax::TsTupleTypeElementList; #[derive(Debug, Clone, Default)] pub struct FormatTsTupleTypeElementList; @@ -9,7 +9,7 @@ impl FormatRule for FormatTsTupleTypeElementList { fn fmt(&self, node: &TsTupleTypeElementList, f: &mut JsFormatter) -> FormatResult<()> { f.join_with(&soft_line_break_or_space()) - .entries(node.format_separated(JsSyntaxKind::COMMA).nodes_grouped()) + .entries(node.format_separated(",").nodes_grouped()) .finish() } } diff --git a/crates/rome_js_formatter/src/ts/lists/type_argument_list.rs b/crates/rome_js_formatter/src/ts/lists/type_argument_list.rs index 7b1f9b4bcbf..448098e2462 100644 --- a/crates/rome_js_formatter/src/ts/lists/type_argument_list.rs +++ b/crates/rome_js_formatter/src/ts/lists/type_argument_list.rs @@ -1,5 +1,5 @@ use crate::prelude::*; -use rome_js_syntax::{JsSyntaxKind, TsTypeArgumentList}; +use rome_js_syntax::TsTypeArgumentList; #[derive(Debug, Clone, Default)] pub struct FormatTsTypeArgumentList; @@ -10,7 +10,7 @@ impl FormatRule for FormatTsTypeArgumentList { fn fmt(&self, node: &TsTypeArgumentList, f: &mut JsFormatter) -> FormatResult<()> { f.join_with(&soft_line_break_or_space()) .entries( - node.format_separated(JsSyntaxKind::COMMA) + node.format_separated(",") .with_trailing_separator(TrailingSeparator::Disallowed), ) .finish() diff --git a/crates/rome_js_formatter/src/ts/lists/type_list.rs b/crates/rome_js_formatter/src/ts/lists/type_list.rs index 3cefbbebfc4..afeaa010d0e 100644 --- a/crates/rome_js_formatter/src/ts/lists/type_list.rs +++ b/crates/rome_js_formatter/src/ts/lists/type_list.rs @@ -1,5 +1,5 @@ use crate::prelude::*; -use rome_js_syntax::{JsSyntaxKind, TsTypeList}; +use rome_js_syntax::TsTypeList; #[derive(Debug, Clone, Default)] pub struct FormatTsTypeList; @@ -11,7 +11,7 @@ impl FormatRule for FormatTsTypeList { // the grouping will be applied by the parent f.join_with(&soft_line_break_or_space()) .entries( - node.format_separated(JsSyntaxKind::COMMA) + node.format_separated(",") .with_trailing_separator(TrailingSeparator::Disallowed), ) .finish() diff --git a/crates/rome_js_formatter/src/ts/lists/type_member_list.rs b/crates/rome_js_formatter/src/ts/lists/type_member_list.rs index 8c295c32e31..912efe6fa91 100644 --- a/crates/rome_js_formatter/src/ts/lists/type_member_list.rs +++ b/crates/rome_js_formatter/src/ts/lists/type_member_list.rs @@ -1,6 +1,6 @@ use crate::prelude::*; use rome_formatter::{write, Buffer}; -use rome_js_syntax::{JsSyntaxKind, TsAnyTypeMember, TsTypeMemberList}; +use rome_js_syntax::{TsAnyTypeMember, TsTypeMemberList}; use rome_rowan::AstNodeList; @@ -50,12 +50,9 @@ impl Format for TsTypeMemberItem { // Children don't format the separator on purpose, so it's up to the parent - this node, // to decide to print their separator if self.last { - write!( - f, - [if_group_breaks(&format_inserted(JsSyntaxKind::SEMICOLON))] - )?; + write!(f, [if_group_breaks(&text(";"))])?; } else { - format_inserted(JsSyntaxKind::SEMICOLON).fmt(f)?; + text(";").fmt(f)?; } } diff --git a/crates/rome_js_formatter/src/ts/lists/type_parameter_list.rs b/crates/rome_js_formatter/src/ts/lists/type_parameter_list.rs index 1f0a83a1fca..f7680999d4e 100644 --- a/crates/rome_js_formatter/src/ts/lists/type_parameter_list.rs +++ b/crates/rome_js_formatter/src/ts/lists/type_parameter_list.rs @@ -1,5 +1,5 @@ use crate::prelude::*; -use rome_js_syntax::{JsSyntaxKind, TsTypeParameterList}; +use rome_js_syntax::TsTypeParameterList; use rome_rowan::AstSeparatedList; #[derive(Debug, Clone, Default)] @@ -23,7 +23,7 @@ impl FormatRule for FormatTsTypeParameterList { f.join_with(&soft_line_break_or_space()) .entries( - node.format_separated(JsSyntaxKind::COMMA) + node.format_separated(",") .with_trailing_separator(trailing_separator), ) .finish() diff --git a/crates/rome_js_formatter/src/ts/types/mapped_type.rs b/crates/rome_js_formatter/src/ts/types/mapped_type.rs index 94780fba316..3515cc4b0b2 100644 --- a/crates/rome_js_formatter/src/ts/types/mapped_type.rs +++ b/crates/rome_js_formatter/src/ts/types/mapped_type.rs @@ -1,6 +1,7 @@ use crate::prelude::*; use crate::utils::FormatWithSemicolon; +use crate::builders::format_delimited; use crate::parentheses::NeedsParentheses; use rome_formatter::{format_args, write}; use rome_js_syntax::{JsSyntaxNode, TsMappedType, TsMappedTypeFields}; diff --git a/crates/rome_js_formatter/src/ts/types/tuple_type.rs b/crates/rome_js_formatter/src/ts/types/tuple_type.rs index cdaaae84963..7f160a6b91f 100644 --- a/crates/rome_js_formatter/src/ts/types/tuple_type.rs +++ b/crates/rome_js_formatter/src/ts/types/tuple_type.rs @@ -1,5 +1,6 @@ use crate::prelude::*; +use crate::builders::format_delimited; use crate::parentheses::NeedsParentheses; use rome_formatter::write; use rome_js_syntax::{JsSyntaxNode, TsTupleType, TsTupleTypeFields}; diff --git a/crates/rome_js_formatter/src/ts/types/union_type.rs b/crates/rome_js_formatter/src/ts/types/union_type.rs index 454223ac2a9..bdf774639bd 100644 --- a/crates/rome_js_formatter/src/ts/types/union_type.rs +++ b/crates/rome_js_formatter/src/ts/types/union_type.rs @@ -1,8 +1,8 @@ use crate::parentheses::NeedsParentheses; use crate::prelude::*; use crate::utils::{ - has_leading_own_line_comment, should_hug_type, union_or_intersection_type_needs_parentheses, - FormatTypeMemberSeparator, TsIntersectionOrUnionTypeList, + should_hug_type, union_or_intersection_type_needs_parentheses, FormatTypeMemberSeparator, + TsIntersectionOrUnionTypeList, }; use rome_formatter::{format_args, write, Buffer}; use rome_js_syntax::{JsSyntaxKind, JsSyntaxToken, TsTupleTypeElementList, TsUnionType}; @@ -62,7 +62,7 @@ impl FormatNodeRule for FormatTsUnionType { f, [ FormatTypeSetLeadingSeparator { - separator: JsSyntaxKind::PIPE, + separator: "|", leading_separator: leading_separator_token.as_ref(), leading_soft_line_break_or_space: should_indent && !has_leading_own_line_comment, @@ -123,7 +123,7 @@ impl NeedsParentheses for TsUnionType { } pub struct FormatTypeSetLeadingSeparator<'a> { - separator: JsSyntaxKind, + separator: &'static str, leading_separator: Option<&'a JsSyntaxToken>, leading_soft_line_break_or_space: bool, } @@ -145,7 +145,7 @@ impl Format for FormatTypeSetLeadingSeparator<'_> { if self.leading_soft_line_break_or_space { write!(f, [soft_line_break_or_space()])?; } - write!(f, [format_inserted(self.separator), space()]) + write!(f, [text(self.separator), space()]) }); write!(f, [if_group_breaks(&content)]) diff --git a/crates/rome_js_formatter/src/utils/array.rs b/crates/rome_js_formatter/src/utils/array.rs index 3b18f77e840..5a23bffd88a 100644 --- a/crates/rome_js_formatter/src/utils/array.rs +++ b/crates/rome_js_formatter/src/utils/array.rs @@ -4,7 +4,7 @@ use crate::AsFormat; use rome_formatter::write; use rome_js_syntax::{ JsAnyArrayAssignmentPatternElement, JsAnyArrayBindingPatternElement, JsAnyArrayElement, - JsLanguage, JsSyntaxKind, + JsLanguage, }; use rome_rowan::{AstNode, AstSeparatedList}; @@ -42,12 +42,12 @@ where // In forced separator mode or if this element is not the last in the list, print the separator match element.trailing_separator()? { Some(trailing) => write!(f, [trailing.format()])?, - None => format_inserted(JsSyntaxKind::COMMA).fmt(f)?, + None => text(",").fmt(f)?, }; } else if let Some(separator) = element.trailing_separator()? { write!(f, [format_only_if_breaks(separator, &separator.format())])?; } else { - write!(f, [if_group_breaks(&format_inserted(JsSyntaxKind::COMMA))])?; + write!(f, [if_group_breaks(&text(","))])?; }; Ok(()) diff --git a/crates/rome_js_formatter/src/utils/assignment_like.rs b/crates/rome_js_formatter/src/utils/assignment_like.rs index 86faf3821ad..591169c2767 100644 --- a/crates/rome_js_formatter/src/utils/assignment_like.rs +++ b/crates/rome_js_formatter/src/utils/assignment_like.rs @@ -4,23 +4,25 @@ use crate::prelude::*; use crate::utils::member_chain::is_member_call_chain; use crate::utils::object::write_member_name; use crate::utils::{JsAnyBinaryLikeExpression, JsAnyBinaryLikeLeftExpression}; -use rome_formatter::{format_args, write, CstFormatContext, FormatOptions, VecBuffer}; +use rome_formatter::{ + format_args, has_leading_own_line_comment, write, Comments, CstFormatContext, FormatOptions, + VecBuffer, +}; use rome_js_syntax::JsAnyLiteralExpression; use rome_js_syntax::{ JsAnyAssignmentPattern, JsAnyBindingPattern, JsAnyCallArgument, JsAnyClassMemberName, JsAnyExpression, JsAnyFunctionBody, JsAnyObjectAssignmentPatternMember, JsAnyObjectBindingPatternMember, JsAnyObjectMemberName, JsAnyTemplateElement, - JsAssignmentExpression, JsInitializerClause, JsLiteralMemberName, JsObjectAssignmentPattern, - JsObjectAssignmentPatternProperty, JsObjectBindingPattern, JsPropertyClassMember, - JsPropertyClassMemberFields, JsPropertyObjectMember, JsSyntaxKind, JsVariableDeclarator, - TsAnyVariableAnnotation, TsIdentifierBinding, TsPropertySignatureClassMember, - TsPropertySignatureClassMemberFields, TsType, TsTypeAliasDeclaration, TsTypeArguments, + JsAssignmentExpression, JsInitializerClause, JsLanguage, JsLiteralMemberName, + JsObjectAssignmentPattern, JsObjectAssignmentPatternProperty, JsObjectBindingPattern, + JsPropertyClassMember, JsPropertyClassMemberFields, JsPropertyObjectMember, JsSyntaxKind, + JsVariableDeclarator, TsAnyVariableAnnotation, TsIdentifierBinding, + TsPropertySignatureClassMember, TsPropertySignatureClassMemberFields, TsType, + TsTypeAliasDeclaration, TsTypeArguments, }; use rome_rowan::{declare_node_union, AstNode, SyntaxResult}; use std::iter; -use super::has_leading_own_line_comment; - declare_node_union! { pub(crate) JsAnyAssignmentLike = JsPropertyObjectMember | @@ -620,7 +622,7 @@ impl JsAnyAssignmentLike { return Ok(AssignmentLikeLayout::BreakLeftHandSide); } - if self.should_break_after_operator(&right)? { + if self.should_break_after_operator(&right, f.context().comments())? { return Ok(AssignmentLikeLayout::BreakAfterOperator); } @@ -818,11 +820,15 @@ impl JsAnyAssignmentLike { /// /// This function is small wrapper around [should_break_after_operator] because it has to work /// for nodes that belong to TypeScript too. - fn should_break_after_operator(&self, right: &RightAssignmentLike) -> SyntaxResult { + fn should_break_after_operator( + &self, + right: &RightAssignmentLike, + comments: &Comments, + ) -> SyntaxResult { let result = if let Some(expression) = right.as_expression() { - should_break_after_operator(&expression)? + should_break_after_operator(&expression, comments)? } else { - has_leading_own_line_comment(right.syntax()) + has_leading_own_line_comment(right.syntax(), comments) }; Ok(result) @@ -830,12 +836,15 @@ impl JsAnyAssignmentLike { } /// Checks if the function is entitled to be printed with layout [AssignmentLikeLayout::BreakAfterOperator] -pub(crate) fn should_break_after_operator(right: &JsAnyExpression) -> SyntaxResult { +pub(crate) fn should_break_after_operator( + right: &JsAnyExpression, + comments: &Comments, +) -> SyntaxResult { // Traverse from the right expression to the left most node and check if any has a leading comment // that causes a line break. let mut current: JsAnyBinaryLikeLeftExpression = right.clone().into(); loop { - if has_leading_own_line_comment(current.syntax()) { + if has_leading_own_line_comment(current.syntax(), comments) { return Ok(true); } diff --git a/crates/rome_js_formatter/src/utils/binary_like_expression.rs b/crates/rome_js_formatter/src/utils/binary_like_expression.rs index bc40af55320..6f874ec12df 100644 --- a/crates/rome_js_formatter/src/utils/binary_like_expression.rs +++ b/crates/rome_js_formatter/src/utils/binary_like_expression.rs @@ -54,7 +54,6 @@ //! and right side of each Right side. use crate::prelude::*; -use crate::utils::{has_token_trailing_line_comment, has_trailing_line_comment}; use rome_formatter::{format_args, write, Buffer, CstFormatContext}; use rome_js_syntax::{ JsAnyExpression, JsAnyInProperty, JsBinaryExpression, JsBinaryOperator, JsDoWhileStatement, @@ -73,8 +72,6 @@ use std::fmt::Debug; use std::hash::Hash; use std::iter::FusedIterator; -use super::has_leading_own_line_comment; - declare_node_union! { pub(crate) JsAnyBinaryLikeExpression = JsLogicalExpression | JsBinaryExpression | JsInstanceofExpression | JsInExpression } @@ -197,6 +194,7 @@ fn split_into_left_and_right_sides( } } VisitEvent::Exit(expression) => items.push(BinaryLeftOrRightSide::Right { + print_parent_comments: expression.syntax() != root.syntax(), parent: expression, inside_condition, }), @@ -268,6 +266,10 @@ enum BinaryLeftOrRightSide { parent: JsAnyBinaryLikeExpression, /// Is the parent the condition of a `if` / `while` / `do-while` / `for` statement? inside_condition: bool, + + /// Indicates if the comments of the parent should be printed or not. + /// Must be true if `parent` isn't the root `JsAnyBinaryLike` for which `format` is called. + print_parent_comments: bool, }, } @@ -297,6 +299,7 @@ impl Format for BinaryLeftOrRightSide { BinaryLeftOrRightSide::Right { parent: binary_like_expression, inside_condition: inside_parenthesis, + print_parent_comments, } => { // It's only possible to suppress the formatting of the whole binary expression formatting OR // the formatting of the right hand side value but not of a nested binary expression. @@ -341,13 +344,12 @@ impl Format for BinaryLeftOrRightSide { let right_has_same_kind = is_same_binary_expression_kind(binary_like_expression, right.syntax()); - let should_break = { - if has_token_trailing_line_comment(&operator_token) { - true - } else { - has_leading_own_line_comment(binary_like_expression.left()?.syntax()) - } - }; + let should_break = f + .context() + .comments() + .trailing_comments(binary_like_expression.left()?.syntax()) + .iter() + .any(|comment| comment.kind().is_line()); let should_group = !(parent_has_same_kind || left_has_same_kind @@ -358,20 +360,24 @@ impl Format for BinaryLeftOrRightSide { JsAnyBinaryLikeExpression::JsLogicalExpression(_) ))); + if *print_parent_comments { + write!( + f, + [format_leading_comments(binary_like_expression.syntax())] + )?; + } + if !should_break && should_group { write!(f, [group(&operator_and_right_expression)])?; } else { write!(f, [operator_and_right_expression])?; } - // This is not ideal but it fixes an instability issue when the right hand side has a trailing line comment - // `a && b // comment` -> `a &&\n b; // comment` (see how the comment moved after the `;`) - if has_trailing_line_comment(right.syntax()) - && parent.map_or(false, |parent| { - parent.kind() == JsSyntaxKind::JS_EXPRESSION_STATEMENT - }) - { - write!(f, [line_suffix_boundary()])?; + if *print_parent_comments { + write!( + f, + [format_trailing_comments(binary_like_expression.syntax())] + )?; } Ok(()) diff --git a/crates/rome_js_formatter/src/utils/conditional.rs b/crates/rome_js_formatter/src/utils/conditional.rs index 0bf70651994..308df283ca9 100644 --- a/crates/rome_js_formatter/src/utils/conditional.rs +++ b/crates/rome_js_formatter/src/utils/conditional.rs @@ -1,6 +1,7 @@ use crate::prelude::*; use rome_formatter::{ - write, FormatContext, FormatOwnedWithRule, FormatRefWithRule, FormatRuleWithOptions, + write, CstFormatContext, FormatContext, FormatOwnedWithRule, FormatRefWithRule, + FormatRuleWithOptions, }; use crate::{AsFormat, IntoFormat}; @@ -187,8 +188,7 @@ impl FormatRule for FormatJsAnyConditionalRule { write!(f, [soft_line_break()])?; } - // Make sure that line suffix comments to not escape - write!(f, [line_suffix_boundary()]) + Ok(()) }); let grouped = format_with(|f| { @@ -199,9 +199,39 @@ impl FormatRule for FormatJsAnyConditionalRule { } }); + let has_multiline_comment = { + let comments = f.context().comments(); + + let has_block_comment = |syntax: &JsSyntaxNode| { + comments + .node_comments(syntax) + .any(|comment| comment.kind().is_block()) + }; + + let test_has_block_comments = match conditional { + JsAnyConditional::JsConditionalExpression(expression) => { + has_block_comment(expression.test()?.syntax()) + } + JsAnyConditional::TsConditionalType(ty) => { + has_block_comment(ty.check_type()?.syntax()) + || has_block_comment(ty.extends_type()?.syntax()) + } + }; + + test_has_block_comments + || has_block_comment(consequent.syntax()) + || has_block_comment(alternate.syntax()) + }; + if layout.is_nested_test() || should_extra_indent { - group(&soft_block_indent(&grouped)).fmt(f) + group(&soft_block_indent(&grouped)) + .should_expand(has_multiline_comment) + .fmt(f) } else { + if has_multiline_comment { + write!(f, [expand_parent()])?; + } + grouped.fmt(f) } } diff --git a/crates/rome_js_formatter/src/utils/format_class.rs b/crates/rome_js_formatter/src/utils/format_class.rs index d2834b209f6..80cb27a1830 100644 --- a/crates/rome_js_formatter/src/utils/format_class.rs +++ b/crates/rome_js_formatter/src/utils/format_class.rs @@ -1,3 +1,4 @@ +use crate::builders::format_delimited; use crate::prelude::*; use rome_formatter::write; use rome_js_syntax::JsAnyClass; diff --git a/crates/rome_js_formatter/src/utils/mod.rs b/crates/rome_js_formatter/src/utils/mod.rs index 4403ea25e7e..95f4e1048a8 100644 --- a/crates/rome_js_formatter/src/utils/mod.rs +++ b/crates/rome_js_formatter/src/utils/mod.rs @@ -16,7 +16,6 @@ mod typescript; pub(crate) use crate::parentheses::resolve_left_most_expression; use crate::prelude::*; -use crate::JsCommentStyle; pub(crate) use assignment_like::{ with_assignment_layout, AssignmentLikeLayout, JsAnyAssignmentLike, }; @@ -27,9 +26,9 @@ pub(crate) use conditional::{ConditionalJsxChain, JsAnyConditional}; pub(crate) use member_chain::get_member_chain; pub(crate) use object_like::JsObjectLike; pub(crate) use object_pattern_like::JsObjectPatternLike; -use rome_formatter::{format_args, write, Buffer, CommentStyle}; +use rome_formatter::{format_args, write, Buffer}; use rome_js_syntax::{JsAnyExpression, JsAnyStatement, JsInitializerClause, JsLanguage, Modifiers}; -use rome_js_syntax::{JsSyntaxKind, JsSyntaxNode, JsSyntaxToken}; +use rome_js_syntax::{JsSyntaxNode, JsSyntaxToken}; use rome_rowan::{AstNode, AstNodeList}; pub(crate) use string_utils::*; pub(crate) use typescript::{ @@ -208,7 +207,7 @@ impl Format for FormatWithSemicolon<'_> { if let Some(semicolon) = self.semicolon { write!(f, [semicolon.format()])?; } else if !is_unknown { - format_inserted(JsSyntaxKind::SEMICOLON).fmt(f)?; + text(";").fmt(f)?; } Ok(()) } @@ -251,45 +250,3 @@ where join_with.finish() } - -pub(crate) fn has_trailing_line_comment(node: &JsSyntaxNode) -> bool { - node.last_token() - .map_or(false, |token| has_token_trailing_line_comment(&token)) -} - -pub(crate) fn has_token_trailing_line_comment(token: &JsSyntaxToken) -> bool { - token - .trailing_trivia() - .pieces() - .filter_map(|piece| piece.as_comments()) - .any(|comment| JsCommentStyle.get_comment_kind(&comment).is_line()) -} - -/// Tests if the node has any leading comment that will be placed on its own line. -pub(crate) fn has_leading_own_line_comment(node: &JsSyntaxNode) -> bool { - if let Some(leading_trivia) = node.first_leading_trivia() { - let mut first_comment = true; - let mut after_comment = false; - let mut after_new_line = false; - - for piece in leading_trivia.pieces() { - if piece.is_comments() { - if after_new_line && first_comment { - return true; - } else { - first_comment = false; - after_comment = true; - } - } else if piece.is_newline() { - if after_comment { - return true; - } else { - after_new_line = true; - } - } else if piece.is_skipped() { - return false; - } - } - } - false -} diff --git a/crates/rome_js_formatter/src/utils/object_like.rs b/crates/rome_js_formatter/src/utils/object_like.rs index dd1a4ba0c9f..994047c9233 100644 --- a/crates/rome_js_formatter/src/utils/object_like.rs +++ b/crates/rome_js_formatter/src/utils/object_like.rs @@ -1,3 +1,4 @@ +use crate::builders::format_delimited; use crate::prelude::*; use crate::utils::node_has_leading_newline; use crate::JsFormatContext; @@ -53,11 +54,13 @@ impl Format for JsObjectLike { fn fmt(&self, f: &mut JsFormatter) -> FormatResult<()> { let members = format_with(|f| self.write_members(f)); if self.members_are_empty() { + let r_curly = self.r_curly_token()?; write!( f, [ - format_delimited(&self.l_curly_token()?, &members, &self.r_curly_token()?) - .soft_block_indent() + self.l_curly_token().format(), + format_dangling_trivia(&r_curly).indented(), + r_curly.format() ] ) } else if self.members_have_leading_newline() { diff --git a/crates/rome_js_formatter/src/utils/object_pattern_like.rs b/crates/rome_js_formatter/src/utils/object_pattern_like.rs index 7a7e8d1ea42..0ca3d4494e2 100644 --- a/crates/rome_js_formatter/src/utils/object_pattern_like.rs +++ b/crates/rome_js_formatter/src/utils/object_pattern_like.rs @@ -1,3 +1,4 @@ +use crate::builders::format_delimited; use crate::prelude::*; use crate::JsFormatContext; use rome_formatter::formatter::Formatter; diff --git a/crates/rome_js_formatter/tests/specs/js/module/class/class_comments.js.snap b/crates/rome_js_formatter/tests/specs/js/module/class/class_comments.js.snap index d80114811e0..73ac6426c2d 100644 --- a/crates/rome_js_formatter/tests/specs/js/module/class/class_comments.js.snap +++ b/crates/rome_js_formatter/tests/specs/js/module/class/class_comments.js.snap @@ -24,6 +24,7 @@ class A extends B { constructor() { super(); } + // trailing comment } diff --git a/crates/rome_js_formatter/tests/specs/js/module/expression/logical_expression.js.snap b/crates/rome_js_formatter/tests/specs/js/module/expression/logical_expression.js.snap index 0975e69a094..268598dc1ac 100644 --- a/crates/rome_js_formatter/tests/specs/js/module/expression/logical_expression.js.snap +++ b/crates/rome_js_formatter/tests/specs/js/module/expression/logical_expression.js.snap @@ -269,12 +269,7 @@ const a = lorem && // foo ipsum; -lorem && - call_function( - 1, - 3, - ) // foo - ; +lorem && call_function(1, 3); // foo lorem && // foo diff --git a/crates/rome_js_formatter/tests/specs/jsx/element.jsx.snap b/crates/rome_js_formatter/tests/specs/jsx/element.jsx.snap index 7eac0eacc56..9878595dcc7 100644 --- a/crates/rome_js_formatter/tests/specs/jsx/element.jsx.snap +++ b/crates/rome_js_formatter/tests/specs/jsx/element.jsx.snap @@ -486,7 +486,8 @@ let a = ( { " " - /* comment */} + /* comment */ + } ); let a = {/* comment */ " "}; diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/arrays/numbers-negative-comment-after-minus.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/arrays/numbers-negative-comment-after-minus.js.snap deleted file mode 100644 index 1d330f9dcde..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/js/arrays/numbers-negative-comment-after-minus.js.snap +++ /dev/null @@ -1,121 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -const numbers = [-2017,-506252,-744011292,-7224,-70.4,-83353.6,-708.4,-174023963.52,-40385,-// comment1 -380014, --253951682,-728,-15.84,-2058467564.56,-43,-33,-85134845,-67092,-1,-78820379,-2371.6,-16,7, -// comment2 --62454,-4282239912, --10816495.36,0.88,-100622682,8.8,-67087.68000000001,-3758276,-25.5211,-54,-1184265243,-46073628,-280423.44, --41833463,-27961.12,-305.36,-199875.28]; - -c = [ - - /**/ 66, 66, 57, 45, 47, 33, 53, 82, 81, 76, 66, 57, 45, 47, 33, 53, 82, 81, 223323 -]; -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -8,10 +8,7 @@ - -708.4, - -174023963.52, - -40385, -- -( -- // comment1 -- 380014 -- ), -+ -380014, // comment1 - -253951682, - -728, - -15.84, -@@ -46,7 +43,7 @@ - ]; - - c = [ -- -(/**/ 66), -+ - /**/ 66, - 66, - 57, - 45, -``` - -# Output - -```js -const numbers = [ - -2017, - -506252, - -744011292, - -7224, - -70.4, - -83353.6, - -708.4, - -174023963.52, - -40385, - -380014, // comment1 - -253951682, - -728, - -15.84, - -2058467564.56, - -43, - -33, - -85134845, - -67092, - -1, - -78820379, - -2371.6, - -16, - 7, - // comment2 - -62454, - -4282239912, - -10816495.36, - 0.88, - -100622682, - 8.8, - -67087.68000000001, - -3758276, - -25.5211, - -54, - -1184265243, - -46073628, - -280423.44, - -41833463, - -27961.12, - -305.36, - -199875.28, -]; - -c = [ - - /**/ 66, - 66, - 57, - 45, - 47, - 33, - 53, - 82, - 81, - 76, - 66, - 57, - 45, - 47, - 33, - 53, - 82, - 81, - 223323, -]; -``` - - - diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/arrays/numbers-with-tricky-comments.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/arrays/numbers-with-tricky-comments.js.snap index ef939affd5f..b795c7fb62c 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/arrays/numbers-with-tricky-comments.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/arrays/numbers-with-tricky-comments.js.snap @@ -1,5 +1,7 @@ --- source: crates/rome_js_formatter/tests/prettier_tests.rs +info: + test_file: js/arrays/numbers-with-tricky-comments.js --- # Input @@ -20,11 +22,9 @@ const lazyCatererNumbers = [1, 2, 4, 7, 11, 16, 22, 29, 37, 46, ```diff --- Prettier +++ Rome -@@ -1,9 +1,11 @@ - const lazyCatererNumbers = [ +@@ -2,8 +2,10 @@ 1, 2, 4, 7, 11, 16, 22, 29, 37, 46, 56, 67, 79, 92, 106, 121, 137, 154, 172, -- 191, 211, 232, 254, 277, 301, 326, 352, 379, 407, 436, 466 /*block*/, -+ 191, 211, 232, 254, 277, 301, 326, 352, 379, 407, 436, 466, /*block*/ + 191, 211, 232, 254, 277, 301, 326, 352, 379, 407, 436, 466 /*block*/, // line - 497, 529, 562, 596, 631, 667, 704, 742, 781, 821, 862, 904, 947, 991, 1036, - 1082, 1129, 1177, 1226, @@ -43,7 +43,7 @@ const lazyCatererNumbers = [1, 2, 4, 7, 11, 16, 22, 29, 37, 46, ```js const lazyCatererNumbers = [ 1, 2, 4, 7, 11, 16, 22, 29, 37, 46, 56, 67, 79, 92, 106, 121, 137, 154, 172, - 191, 211, 232, 254, 277, 301, 326, 352, 379, 407, 436, 466, /*block*/ + 191, 211, 232, 254, 277, 301, 326, 352, 379, 407, 436, 466 /*block*/, // line 497, 529, 562, 596, 631, 667, 704, 742, 781, 821, 862, 904, 947, 991, 1036, 1082, diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/assignment-comments/number.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/assignment-comments/number.js.snap deleted file mode 100644 index 2ca58c401c6..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/js/assignment-comments/number.js.snap +++ /dev/null @@ -1,125 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -fnNumber = - // Comment - 3; - -fnNumber = - - // Comment - - 3; - -fnNumber = - // Comment0 - // Comment1 - 3; - -fnNumber = /* comment */ - 3; - -fnNumber = /* comments0 */ - /* comments1 */ - 3; - -fnNumber = - // Comment - 3; - -var fnNumber = - - // Comment - - 3; - -var fnNumber = - // Comment0 - // Comment1 - 3; - -var fnNumber = /* comment */ - 3; - -var fnNumber = /* comments0 */ - /* comments1 */ - 3; -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -14,8 +14,7 @@ - - fnNumber = /* comment */ 3; - --fnNumber = -- /* comments0 */ -+fnNumber = /* comments0 */ - /* comments1 */ - 3; - -@@ -35,7 +34,6 @@ - - var fnNumber = /* comment */ 3; - --var fnNumber = -- /* comments0 */ -+var fnNumber = /* comments0 */ - /* comments1 */ - 3; -``` - -# Output - -```js -fnNumber = - // Comment - 3; - -fnNumber = - // Comment - - 3; - -fnNumber = - // Comment0 - // Comment1 - 3; - -fnNumber = /* comment */ 3; - -fnNumber = /* comments0 */ - /* comments1 */ - 3; - -fnNumber = - // Comment - 3; - -var fnNumber = - // Comment - - 3; - -var fnNumber = - // Comment0 - // Comment1 - 3; - -var fnNumber = /* comment */ 3; - -var fnNumber = /* comments0 */ - /* comments1 */ - 3; -``` - - - diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/babel-plugins/bigint.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/babel-plugins/bigint.js.snap deleted file mode 100644 index 40b6a48aaa1..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/js/babel-plugins/bigint.js.snap +++ /dev/null @@ -1,293 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -// https://github.com/tc39/proposal-bigint - -const previousMaxSafe = BigInt(Number.MAX_SAFE_INTEGER); -// ↪ 9007199254740991 - -const maxPlusOne = previousMaxSafe + 1n; -// ↪ 9007199254740992n - -const theFuture = previousMaxSafe + 2n; -// ↪ 9007199254740993n, this works now! - -const multi = previousMaxSafe * 2n; -// ↪ 18014398509481982n - -// `–` is not minus sign, -// SIC https://github.com/tc39/proposal-bigint#operators -// const subtr = multi – 10n; -// ↪ 18014398509481972n - -const mod = multi % 10n; -// ↪ 2n - -const bigN = 2n ** 54n; -// ↪ 18014398509481984n - -bigN * -1n -// ↪ –18014398509481984n - -0n === 0 -// ↪ false - -0n == 0 -// ↪ true - -1n < 2 -// ↪ true - -2n > 1 -// ↪ true - -2 > 2 -// ↪ false - -2n > 2 -// ↪ false - -2n >= 2 -// ↪ true - -const mixed = [4n, 6, -12n, 10, 4, 0, 0n]; -// ↪ [4n, 6, -12n, 10, 4, 0, 0n] - -mixed.sort(); -// ↪ [-12n, 0, 0n, 10, 4n, 4, 6] - -if (0n) { - console.log('Hello from the if!'); -} else { - console.log('Hello from the else!'); -} - -// ↪ "Hello from the else!" - -0n || 12n -// ↪ 12n - -0n && 12n -// ↪ 0n - -Boolean(0n) -// ↪ false - -Boolean(12n) -// ↪ true - -!12n -// ↪ false - -!0n -// ↪ true - -const view = new BigInt64Array(4); -// ↪ [0n, 0n, 0n, 0n] -view.length; -// ↪ 4 -view[0]; -// ↪ 0n -view[0] = 42n; -view[0]; -// ↪ 42n - -// Highest possible BigInt value that can be represented as a -// signed 64-bit integer. -const max = 2n ** (64n - 1n) - 1n; -view[0] = max; -view[0]; -// ↪ 9_223_372_036_854_775_807n -view[0] = max + 1n; -view[0]; -// ↪ -9_223_372_036_854_775_808n -// ^ negative because of overflow - -1n + 2 -// ↪ TypeError: Cannot mix BigInt and other types, use explicit conversions - -1n * 2 -// ↪ TypeError: Cannot mix BigInt and other types, use explicit conversions - -+1n -// ↪ TypeError: Cannot convert a BigInt value to a number - -Number(1n) -// ↪ 1 - -1n + '2' -// ↪ "12" - -'2' + 1n -// ↪ "21" - -const badPrecision = BigInt(9007199254740993); -// ↪9007199254740992n - -const goodPrecision = BigInt('9007199254740993'); -// ↪9007199254740993n - -const alsoGoodPrecision = 9007199254740993n; -// ↪9007199254740993n -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -103,9 +103,10 @@ - 1n + 2; - // ↪ TypeError: Cannot mix BigInt and other types, use explicit conversions - --1n * 2 + -+1n * 2 - // ↪ TypeError: Cannot mix BigInt and other types, use explicit conversions - -+ + - 1n; - // ↪ TypeError: Cannot convert a BigInt value to a number - -``` - -# Output - -```js -// https://github.com/tc39/proposal-bigint - -const previousMaxSafe = BigInt(Number.MAX_SAFE_INTEGER); -// ↪ 9007199254740991 - -const maxPlusOne = previousMaxSafe + 1n; -// ↪ 9007199254740992n - -const theFuture = previousMaxSafe + 2n; -// ↪ 9007199254740993n, this works now! - -const multi = previousMaxSafe * 2n; -// ↪ 18014398509481982n - -// `–` is not minus sign, -// SIC https://github.com/tc39/proposal-bigint#operators -// const subtr = multi – 10n; -// ↪ 18014398509481972n - -const mod = multi % 10n; -// ↪ 2n - -const bigN = 2n ** 54n; -// ↪ 18014398509481984n - -bigN * -1n; -// ↪ –18014398509481984n - -0n === 0; -// ↪ false - -0n == 0; -// ↪ true - -1n < 2; -// ↪ true - -2n > 1; -// ↪ true - -2 > 2; -// ↪ false - -2n > 2; -// ↪ false - -2n >= 2; -// ↪ true - -const mixed = [4n, 6, -12n, 10, 4, 0, 0n]; -// ↪ [4n, 6, -12n, 10, 4, 0, 0n] - -mixed.sort(); -// ↪ [-12n, 0, 0n, 10, 4n, 4, 6] - -if (0n) { - console.log("Hello from the if!"); -} else { - console.log("Hello from the else!"); -} - -// ↪ "Hello from the else!" - -0n || 12n; -// ↪ 12n - -0n && 12n; -// ↪ 0n - -Boolean(0n); -// ↪ false - -Boolean(12n); -// ↪ true - -!12n; -// ↪ false - -!0n; -// ↪ true - -const view = new BigInt64Array(4); -// ↪ [0n, 0n, 0n, 0n] -view.length; -// ↪ 4 -view[0]; -// ↪ 0n -view[0] = 42n; -view[0]; -// ↪ 42n - -// Highest possible BigInt value that can be represented as a -// signed 64-bit integer. -const max = 2n ** (64n - 1n) - 1n; -view[0] = max; -view[0]; -// ↪ 9_223_372_036_854_775_807n -view[0] = max + 1n; -view[0]; -// ↪ -9_223_372_036_854_775_808n -// ^ negative because of overflow - -1n + 2; -// ↪ TypeError: Cannot mix BigInt and other types, use explicit conversions - -1n * 2 - // ↪ TypeError: Cannot mix BigInt and other types, use explicit conversions - - + - 1n; -// ↪ TypeError: Cannot convert a BigInt value to a number - -Number(1n); -// ↪ 1 - -1n + "2"; -// ↪ "12" - -"2" + 1n; -// ↪ "21" - -const badPrecision = BigInt(9007199254740993); -// ↪9007199254740992n - -const goodPrecision = BigInt("9007199254740993"); -// ↪9007199254740993n - -const alsoGoodPrecision = 9007199254740993n; -// ↪9007199254740993n -``` - - - diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/babel-plugins/optional-chaining.js b/crates/rome_js_formatter/tests/specs/prettier/js/babel-plugins/optional-chaining.js index f013537cdce..d0d5842054e 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/babel-plugins/optional-chaining.js +++ b/crates/rome_js_formatter/tests/specs/prettier/js/babel-plugins/optional-chaining.js @@ -17,7 +17,6 @@ obj?.foo.bar?.baz; // Only access `foo` if `obj` exists, and `baz` if // `bar` exists // Example usage with bracket notation: -// rome-ignore format: https://github.com/rome/tools/issues/2768 obj?.['foo']?.bar?.baz // 42 const obj2 = { diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/babel-plugins/optional-chaining.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/babel-plugins/optional-chaining.js.snap deleted file mode 100644 index aef3c7ebeba..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/js/babel-plugins/optional-chaining.js.snap +++ /dev/null @@ -1,159 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -// https://babeljs.io/docs/en/babel-plugin-proposal-optional-chaining - -const obj = { - foo: { - bar: { - baz: 42, - }, - }, -}; - -const baz = obj?.foo?.bar?.baz; // 42 - -const safe = obj?.qux?.baz; // undefined - -// Optional chaining and normal chaining can be intermixed -obj?.foo.bar?.baz; // Only access `foo` if `obj` exists, and `baz` if - // `bar` exists - -// Example usage with bracket notation: -// rome-ignore format: https://github.com/rome/tools/issues/2768 -obj?.['foo']?.bar?.baz // 42 - -const obj2 = { - foo: { - bar: { - baz() { - return 42; - }, - }, - }, -}; - -const baz2 = obj?.foo?.bar?.baz(); // 42 - -const safe3 = obj?.qux?.baz(); // undefined -const safe4 = obj?.foo.bar.qux?.(); // undefined - -const willThrow = obj?.foo.bar.qux(); // Error: not a function - -// Top function can be called directly, too. -function test() { - return 42; -} -test?.(); // 42 - -exists?.(); // undefined - -const obj3 = { - foo: { - bar: { - baz: class { - }, - }, - }, -}; - -const obj4 = { - foo: { - bar: {} - }, -}; - -const ret = delete obj?.foo?.bar?.baz; // true -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -17,7 +17,8 @@ - // `bar` exists - - // Example usage with bracket notation: --obj?.["foo"]?.bar?.baz; // 42 -+// rome-ignore format: https://github.com/rome/tools/issues/2768 -+obj?.['foo']?.bar?.baz // 42 - - const obj2 = { - foo: { -``` - -# Output - -```js -// https://babeljs.io/docs/en/babel-plugin-proposal-optional-chaining - -const obj = { - foo: { - bar: { - baz: 42, - }, - }, -}; - -const baz = obj?.foo?.bar?.baz; // 42 - -const safe = obj?.qux?.baz; // undefined - -// Optional chaining and normal chaining can be intermixed -obj?.foo.bar?.baz; // Only access `foo` if `obj` exists, and `baz` if -// `bar` exists - -// Example usage with bracket notation: -// rome-ignore format: https://github.com/rome/tools/issues/2768 -obj?.['foo']?.bar?.baz // 42 - -const obj2 = { - foo: { - bar: { - baz() { - return 42; - }, - }, - }, -}; - -const baz2 = obj?.foo?.bar?.baz(); // 42 - -const safe3 = obj?.qux?.baz(); // undefined -const safe4 = obj?.foo.bar.qux?.(); // undefined - -const willThrow = obj?.foo.bar.qux(); // Error: not a function - -// Top function can be called directly, too. -function test() { - return 42; -} -test?.(); // 42 - -exists?.(); // undefined - -const obj3 = { - foo: { - bar: { - baz: class {}, - }, - }, -}; - -const obj4 = { - foo: { - bar: {}, - }, -}; - -const ret = delete obj?.foo?.bar?.baz; // true -``` - - - diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/babel-plugins/private-fields-in-in.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/babel-plugins/private-fields-in-in.js.snap deleted file mode 100644 index d4651c39ac0..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/js/babel-plugins/private-fields-in-in.js.snap +++ /dev/null @@ -1,141 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -// https://github.com/tc39/proposal-private-fields-in-in - -class C { - #brand; - - static isC(obj) { - try { - obj.#brand; - return true; - } catch { - return false; - } - } -} - -class C2 { - #data = null; // populated later - - get #getter() { - if (!this.#data) { - throw new Error('no data yet!'); - } - return this.#data; - } - - static isC(obj) { - try { - obj.#getter; - return true; - } catch { - return false; // oops! might have gotten here because `#getter` threw :-( - } - } -} - -class C3 { - #brand; - - #method() {} - - get #getter() {} - - static isC(obj) { - return #brand in obj && #method in obj && #getter in obj; - } -} - -// Invalid https://github.com/tc39/proposal-private-fields-in-in#try-statement -// class C { -// #brand; - -// static isC(obj) { -// return try obj.#brand; -// } -// } -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -44,7 +44,6 @@ - return #brand in obj && #method in obj && #getter in obj; - } - } -- - // Invalid https://github.com/tc39/proposal-private-fields-in-in#try-statement - // class C { - // #brand; -``` - -# Output - -```js -// https://github.com/tc39/proposal-private-fields-in-in - -class C { - #brand; - - static isC(obj) { - try { - obj.#brand; - return true; - } catch { - return false; - } - } -} - -class C2 { - #data = null; // populated later - - get #getter() { - if (!this.#data) { - throw new Error("no data yet!"); - } - return this.#data; - } - - static isC(obj) { - try { - obj.#getter; - return true; - } catch { - return false; // oops! might have gotten here because `#getter` threw :-( - } - } -} - -class C3 { - #brand; - - #method() {} - - get #getter() {} - - static isC(obj) { - return #brand in obj && #method in obj && #getter in obj; - } -} -// Invalid https://github.com/tc39/proposal-private-fields-in-in#try-statement -// class C { -// #brand; - -// static isC(obj) { -// return try obj.#brand; -// } -// } -``` - - - diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/classes/method.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/classes/method.js.snap deleted file mode 100644 index 9459e40814a..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/js/classes/method.js.snap +++ /dev/null @@ -1,52 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js - -class C { - name/*comment*/() { - } -}; - - -({ - name/*comment*/() { - } -}); -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -1,7 +1,7 @@ - class C { -- name /*comment*/() {} -+ name /*comment*/ () {} - } - - ({ -- name /*comment*/() {}, -+ name /*comment*/ () {}, - }); -``` - -# Output - -```js -class C { - name /*comment*/ () {} -} - -({ - name /*comment*/ () {}, -}); -``` - - - diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/closure-compiler-type-cast.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/closure-compiler-type-cast.js.snap deleted file mode 100644 index 53686ecb0e7..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/closure-compiler-type-cast.js.snap +++ /dev/null @@ -1,154 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -// test to make sure comments are attached correctly -let inlineComment = /* some comment */ ( - someReallyLongFunctionCall(withLots, ofArguments)); - -let object = { - key: /* some comment */ (someReallyLongFunctionCall(withLots, ofArguments)) -}; - -// preserve parens only for type casts -let assignment = /** @type {string} */ (getValue()); -let value = /** @type {string} */ (this.members[0]).functionCall(); - -functionCall(1 + /** @type {string} */ (value), /** @type {!Foo} */ ({})); - -function returnValue() { - return /** @type {!Array.} */ (['hello', 'you']); -} - -// Only numberOrString is typecast -var newArray = /** @type {array} */ (numberOrString).map(x => x); -var newArray = /** @type {array} */ ((numberOrString)).map(x => x); -var newArray = test(/** @type {array} */ (numberOrString).map(x => x)); -var newArray = test(/** @type {array} */ ((numberOrString)).map(x => x)); - -// The numberOrString.map CallExpression is typecast -var newArray = /** @type {array} */ (numberOrString.map(x => x)); -var newArray = /** @type {array} */ ((numberOrString).map(x => x)); -var newArray = test(/** @type {array} */ (numberOrString.map(x => x))); -var newArray = test(/** @type {array} */ ((numberOrString).map(x => x))); - -test(/** @type {number} */(num) + 1); -test(/** @type {!Array} */(arrOrString).length + 1); -test(/** @type {!Array} */((arrOrString)).length + 1); - -const data = functionCall( - arg1, - arg2, - /** @type {{height: number, width: number}} */ (arg3)); - -const style = /** @type {{ - width: number, - height: number, - marginTop: number, - marginLeft: number, - marginRight: number, - marginBottom: number, -}} */ ({ - width, - height, - ...margins, -}); - -const style2 =/** - * @type {{ - * width: number, - * }} -*/({ - width, -}); - -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -57,6 +57,6 @@ - * @type {{ - * width: number, - * }} -- */ ({ -+*/ ({ - width, - }); -``` - -# Output - -```js -// test to make sure comments are attached correctly -let inlineComment = /* some comment */ someReallyLongFunctionCall( - withLots, - ofArguments, -); - -let object = { - key: /* some comment */ someReallyLongFunctionCall(withLots, ofArguments), -}; - -// preserve parens only for type casts -let assignment = /** @type {string} */ (getValue()); -let value = /** @type {string} */ (this.members[0]).functionCall(); - -functionCall(1 + /** @type {string} */ (value), /** @type {!Foo} */ ({})); - -function returnValue() { - return /** @type {!Array.} */ (["hello", "you"]); -} - -// Only numberOrString is typecast -var newArray = /** @type {array} */ (numberOrString).map((x) => x); -var newArray = /** @type {array} */ (numberOrString).map((x) => x); -var newArray = test(/** @type {array} */ (numberOrString).map((x) => x)); -var newArray = test(/** @type {array} */ (numberOrString).map((x) => x)); - -// The numberOrString.map CallExpression is typecast -var newArray = /** @type {array} */ (numberOrString.map((x) => x)); -var newArray = /** @type {array} */ (numberOrString.map((x) => x)); -var newArray = test(/** @type {array} */ (numberOrString.map((x) => x))); -var newArray = test(/** @type {array} */ (numberOrString.map((x) => x))); - -test(/** @type {number} */ (num) + 1); -test(/** @type {!Array} */ (arrOrString).length + 1); -test(/** @type {!Array} */ (arrOrString).length + 1); - -const data = functionCall( - arg1, - arg2, - /** @type {{height: number, width: number}} */ (arg3), -); - -const style = /** @type {{ - width: number, - height: number, - marginTop: number, - marginLeft: number, - marginRight: number, - marginBottom: number, -}} */ ({ - width, - height, - ...margins, -}); - -const style2 = /** - * @type {{ - * width: number, - * }} -*/ ({ - width, -}); -``` - - - diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/comment-placement.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/comment-placement.js.snap deleted file mode 100644 index 053c9cf6526..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/comment-placement.js.snap +++ /dev/null @@ -1,70 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -const foo1 = /** @type {string} */ - (value); - -const foo2 = - /** @type {string} */ - (value); - -const foo3 = - - /** @type {string} */ - (value); - - -const foo4 = - /** @type {string} */(value); - -const foo5 = - /** @type {string} */ ( - value - ); -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -8,6 +8,8 @@ - /** @type {string} */ - (value); - --const foo4 = /** @type {string} */ (value); -+const foo4 = -+ /** @type {string} */ (value); - --const foo5 = /** @type {string} */ (value); -+const foo5 = -+ /** @type {string} */ (value); -``` - -# Output - -```js -const foo1 = /** @type {string} */ (value); - -const foo2 = - /** @type {string} */ - (value); - -const foo3 = - /** @type {string} */ - (value); - -const foo4 = - /** @type {string} */ (value); - -const foo5 = - /** @type {string} */ (value); -``` - - - diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/issue-4124.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/issue-4124.js.snap deleted file mode 100644 index 96b30f08c0c..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/issue-4124.js.snap +++ /dev/null @@ -1,71 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -/** @type {Object} */(myObject.property).someProp = true; -(/** @type {Object} */(myObject.property)).someProp = true; - -const prop = /** @type {Object} */(myObject.property).someProp; - -const test = /** @type (function (*): ?|undefined) */ - (goog.partial(NewThing.onTemplateChange, rationaleField, typeField)); - -const test = /** @type (function (*): ?|undefined) */ (goog.partial(NewThing.onTemplateChange, rationaleField, typeField)); - -const model = /** @type {?{getIndex: Function}} */ (model); - -const foo = /** @type {string} */ - (bar); - -const test = /** @type (function (*): ?|undefined) */ (foo); -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -3,9 +3,9 @@ - - const prop = /** @type {Object} */ (myObject.property).someProp; - --const test = -- /** @type (function (*): ?|undefined) */ -- (goog.partial(NewThing.onTemplateChange, rationaleField, typeField)); -+const test = /** @type (function (*): ?|undefined) */ ( -+ goog.partial(NewThing.onTemplateChange, rationaleField, typeField) -+); - - const test = /** @type (function (*): ?|undefined) */ ( - goog.partial(NewThing.onTemplateChange, rationaleField, typeField) -``` - -# Output - -```js -/** @type {Object} */ (myObject.property).someProp = true; -/** @type {Object} */ (myObject.property).someProp = true; - -const prop = /** @type {Object} */ (myObject.property).someProp; - -const test = /** @type (function (*): ?|undefined) */ ( - goog.partial(NewThing.onTemplateChange, rationaleField, typeField) -); - -const test = /** @type (function (*): ?|undefined) */ ( - goog.partial(NewThing.onTemplateChange, rationaleField, typeField) -); - -const model = /** @type {?{getIndex: Function}} */ (model); - -const foo = /** @type {string} */ (bar); - -const test = /** @type (function (*): ?|undefined) */ (foo); -``` - - - diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/issue-8045.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/issue-8045.js.snap index 94bf9a77f64..d61fab8d3ff 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/issue-8045.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/issue-8045.js.snap @@ -34,22 +34,7 @@ function jsdocCastInReturn() { ```diff --- Prettier +++ Rome -@@ -1,5 +1,5 @@ --const myLongVariableName = -- /** @type {ThisIsAVeryLongTypeThatShouldTriggerLineWrapping} */ (fooBarBaz); -+const myLongVariableName = /** @type {ThisIsAVeryLongTypeThatShouldTriggerLineWrapping} */ -+ (fooBarBaz); - - function jsdocCastInReturn() { - return /** @type {ThisIsAVeryLongTypeThatShouldTriggerLineWrapping} */ ( -@@ -7,24 +7,20 @@ - ); - } - --const myLongVariableName = -- /** @type {ThisIsAVeryLongTypeThatShouldTriggerLineWrapping} */ -+const myLongVariableName = /** @type {ThisIsAVeryLongTypeThatShouldTriggerLineWrapping} */ - (fooBarBaz); +@@ -13,8 +13,7 @@ function jsdocCastInReturn() { return ( @@ -59,10 +44,7 @@ function jsdocCastInReturn() { ); } --const myLongVariableName = -- /** @type {ThisIsAVeryLongTypeThatShouldTriggerLineWrapping} */ -+const myLongVariableName = /** @type {ThisIsAVeryLongTypeThatShouldTriggerLineWrapping} */ - (fooBarBaz); +@@ -24,7 +23,6 @@ function jsdocCastInReturn() { return ( @@ -76,8 +58,8 @@ function jsdocCastInReturn() { # Output ```js -const myLongVariableName = /** @type {ThisIsAVeryLongTypeThatShouldTriggerLineWrapping} */ - (fooBarBaz); +const myLongVariableName = + /** @type {ThisIsAVeryLongTypeThatShouldTriggerLineWrapping} */ (fooBarBaz); function jsdocCastInReturn() { return /** @type {ThisIsAVeryLongTypeThatShouldTriggerLineWrapping} */ ( @@ -85,7 +67,8 @@ function jsdocCastInReturn() { ); } -const myLongVariableName = /** @type {ThisIsAVeryLongTypeThatShouldTriggerLineWrapping} */ +const myLongVariableName = + /** @type {ThisIsAVeryLongTypeThatShouldTriggerLineWrapping} */ (fooBarBaz); function jsdocCastInReturn() { @@ -94,7 +77,8 @@ function jsdocCastInReturn() { ); } -const myLongVariableName = /** @type {ThisIsAVeryLongTypeThatShouldTriggerLineWrapping} */ +const myLongVariableName = + /** @type {ThisIsAVeryLongTypeThatShouldTriggerLineWrapping} */ (fooBarBaz); function jsdocCastInReturn() { @@ -105,10 +89,4 @@ function jsdocCastInReturn() { ``` -# Lines exceeding max width of 80 characters -``` - 1: const myLongVariableName = /** @type {ThisIsAVeryLongTypeThatShouldTriggerLineWrapping} */ - 10: const myLongVariableName = /** @type {ThisIsAVeryLongTypeThatShouldTriggerLineWrapping} */ - 19: const myLongVariableName = /** @type {ThisIsAVeryLongTypeThatShouldTriggerLineWrapping} */ -``` diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/object-with-comment.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/object-with-comment.js.snap deleted file mode 100644 index 40b34891364..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/object-with-comment.js.snap +++ /dev/null @@ -1,58 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -const objectWithComment = /** @type MyType */ ( - /* comment */ - { - foo: bar - } -); - -const objectWithComment2 = /** @type MyType */ ( /* comment */ { - foo: bar - } -); -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -5,8 +5,8 @@ - } - ); - --const objectWithComment2 = /** @type MyType */ ( -- /* comment */ { -+const objectWithComment2 = /** @type MyType */ (/* comment */ -+ { - foo: bar, - } - ); -``` - -# Output - -```js -const objectWithComment = /** @type MyType */ ( - /* comment */ - { - foo: bar, - } -); - -const objectWithComment2 = /** @type MyType */ (/* comment */ - { - foo: bar, - } -); -``` - - - diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/comments/binary-expressions-block-comments.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/comments/binary-expressions-block-comments.js.snap deleted file mode 100644 index 03cf52e33b0..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/js/comments/binary-expressions-block-comments.js.snap +++ /dev/null @@ -1,183 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -a = b || /** Comment */ -c; - -a = b /** Comment */ || -c; - -a = b || /** TODO this is a very very very very long comment that makes it go > 80 columns */ -c; - -a = b /** TODO this is a very very very very long comment that makes it go > 80 columns */ || -c; - -a = b || /** TODO this is a very very very very long comment that makes it go > 80 columns */ c; - -a = b && /** Comment */ -c; - -a = b /** Comment */ && -c; - -a = b && /** TODO this is a very very very very long comment that makes it go > 80 columns */ -c; - -a = b /** TODO this is a very very very very long comment that makes it go > 80 columns */ && -c; - -a = b && /** TODO this is a very very very very long comment that makes it go > 80 columns */ c; - -a = b + /** Comment */ -c; - -a = b /** Comment */ + -c; - -a = b + /** TODO this is a very very very very long comment that makes it go > 80 columns */ -c; - -a = b /** TODO this is a very very very very long comment that makes it go > 80 columns */ + -c; - -a = b + /** TODO this is a very very very very long comment that makes it go > 80 columns */ c;``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -1,9 +1,9 @@ --a = b /** Comment */ || c; -+a = b || /** Comment */ c; - - a = b /** Comment */ || c; - - a = -- b /** TODO this is a very very very very long comment that makes it go > 80 columns */ || -+ b || /** TODO this is a very very very very long comment that makes it go > 80 columns */ - c; - - a = -@@ -11,15 +11,15 @@ - c; - - a = -- b || -- /** TODO this is a very very very very long comment that makes it go > 80 columns */ c; -+ b || /** TODO this is a very very very very long comment that makes it go > 80 columns */ -+ c; - --a = b /** Comment */ && c; -+a = b && /** Comment */ c; - - a = b /** Comment */ && c; - - a = -- b /** TODO this is a very very very very long comment that makes it go > 80 columns */ && -+ b && /** TODO this is a very very very very long comment that makes it go > 80 columns */ - c; - - a = -@@ -27,15 +27,15 @@ - c; - - a = -- b && -- /** TODO this is a very very very very long comment that makes it go > 80 columns */ c; -+ b && /** TODO this is a very very very very long comment that makes it go > 80 columns */ -+ c; - --a = b /** Comment */ + c; -+a = b + /** Comment */ c; - - a = b /** Comment */ + c; - - a = -- b /** TODO this is a very very very very long comment that makes it go > 80 columns */ + -+ b + /** TODO this is a very very very very long comment that makes it go > 80 columns */ - c; - - a = -@@ -43,5 +43,5 @@ - c; - - a = -- b + -- /** TODO this is a very very very very long comment that makes it go > 80 columns */ c; -+ b + /** TODO this is a very very very very long comment that makes it go > 80 columns */ -+ c; -``` - -# Output - -```js -a = b || /** Comment */ c; - -a = b /** Comment */ || c; - -a = - b || /** TODO this is a very very very very long comment that makes it go > 80 columns */ - c; - -a = - b /** TODO this is a very very very very long comment that makes it go > 80 columns */ || - c; - -a = - b || /** TODO this is a very very very very long comment that makes it go > 80 columns */ - c; - -a = b && /** Comment */ c; - -a = b /** Comment */ && c; - -a = - b && /** TODO this is a very very very very long comment that makes it go > 80 columns */ - c; - -a = - b /** TODO this is a very very very very long comment that makes it go > 80 columns */ && - c; - -a = - b && /** TODO this is a very very very very long comment that makes it go > 80 columns */ - c; - -a = b + /** Comment */ c; - -a = b /** Comment */ + c; - -a = - b + /** TODO this is a very very very very long comment that makes it go > 80 columns */ - c; - -a = - b /** TODO this is a very very very very long comment that makes it go > 80 columns */ + - c; - -a = - b + /** TODO this is a very very very very long comment that makes it go > 80 columns */ - c; -``` - - -# Lines exceeding max width of 80 characters -``` - 6: b || /** TODO this is a very very very very long comment that makes it go > 80 columns */ - 10: b /** TODO this is a very very very very long comment that makes it go > 80 columns */ || - 14: b || /** TODO this is a very very very very long comment that makes it go > 80 columns */ - 22: b && /** TODO this is a very very very very long comment that makes it go > 80 columns */ - 26: b /** TODO this is a very very very very long comment that makes it go > 80 columns */ && - 30: b && /** TODO this is a very very very very long comment that makes it go > 80 columns */ - 38: b + /** TODO this is a very very very very long comment that makes it go > 80 columns */ - 42: b /** TODO this is a very very very very long comment that makes it go > 80 columns */ + - 46: b + /** TODO this is a very very very very long comment that makes it go > 80 columns */ -``` - diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/comments/binary-expressions.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/comments/binary-expressions.js.snap deleted file mode 100644 index b3bcd2411f5..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/js/comments/binary-expressions.js.snap +++ /dev/null @@ -1,274 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -function addition() { - 0 - // Comment - + x -} - -function multiplication() { - 0 - // Comment - * x -} - -function division() { - 0 - // Comment - / x -} - -function substraction() { - 0 - // Comment - - x -} - -function remainder() { - 0 - // Comment - % x -} - -function exponentiation() { - 0 - // Comment - ** x -} - -function leftShift() { - 0 - // Comment - << x -} - -function rightShift() { - 0 - // Comment - >> x -} - -function unsignedRightShift() { - 0 - // Comment - >>> x -} - -function bitwiseAnd() { - 0 - // Comment - & x -} - -function bitwiseOr() { - 0 - // Comment - | x -} - -function bitwiseXor() { - 0 - // Comment - ^ x -} -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -1,71 +1,83 @@ - function addition() { -- 0 + -+ 0 - // Comment -+ + - x; - } - - function multiplication() { -- 0 * -+ 0 - // Comment -+ * - x; - } - - function division() { -- 0 / -+ 0 - // Comment -+ / - x; - } - - function substraction() { -- 0 - -+ 0 - // Comment -+ - - x; - } - - function remainder() { -- 0 % -+ 0 - // Comment -+ % - x; - } - - function exponentiation() { -- 0 ** -+ 0 - // Comment -+ ** - x; - } - - function leftShift() { -- 0 << -+ 0 - // Comment -+ << - x; - } - - function rightShift() { -- 0 >> -+ 0 - // Comment -+ >> - x; - } - - function unsignedRightShift() { -- 0 >>> -+ 0 - // Comment -+ >>> - x; - } - - function bitwiseAnd() { -- 0 & -+ 0 - // Comment -+ & - x; - } - - function bitwiseOr() { -- 0 | -+ 0 - // Comment -+ | - x; - } - - function bitwiseXor() { -- 0 ^ -+ 0 - // Comment -+ ^ - x; - } -``` - -# Output - -```js -function addition() { - 0 - // Comment - + - x; -} - -function multiplication() { - 0 - // Comment - * - x; -} - -function division() { - 0 - // Comment - / - x; -} - -function substraction() { - 0 - // Comment - - - x; -} - -function remainder() { - 0 - // Comment - % - x; -} - -function exponentiation() { - 0 - // Comment - ** - x; -} - -function leftShift() { - 0 - // Comment - << - x; -} - -function rightShift() { - 0 - // Comment - >> - x; -} - -function unsignedRightShift() { - 0 - // Comment - >>> - x; -} - -function bitwiseAnd() { - 0 - // Comment - & - x; -} - -function bitwiseOr() { - 0 - // Comment - | - x; -} - -function bitwiseXor() { - 0 - // Comment - ^ - x; -} -``` - - - diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/comments/break-continue-statements.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/comments/break-continue-statements.js.snap deleted file mode 100644 index e03bfa65175..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/js/comments/break-continue-statements.js.snap +++ /dev/null @@ -1,55 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -for (;;) { - break /* comment */; - continue /* comment */; -} - -loop: for (;;) { - break /* comment */ loop; - break loop /* comment */; - continue /* comment */ loop; - continue loop /* comment */; -} -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -1,6 +1,6 @@ - for (;;) { -- break; /* comment */ -- continue; /* comment */ -+ break /* comment */; -+ continue /* comment */; - } - - loop: for (;;) { -``` - -# Output - -```js -for (;;) { - break /* comment */; - continue /* comment */; -} - -loop: for (;;) { - break /* comment */ loop; - break loop /* comment */; - continue /* comment */ loop; - continue loop /* comment */; -} -``` - - - diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/comments/call_comment.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/comments/call_comment.js.snap deleted file mode 100644 index c778acfd7af..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/js/comments/call_comment.js.snap +++ /dev/null @@ -1,73 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -render( // Warm any cache - , - container -); - -React.render( // Warm any cache - , - container -); - -render?.( // Warm any cache - , - container -); -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -1,17 +1,14 @@ --render( -- // Warm any cache -+render( // Warm any cache - , - container, - ); - --React.render( -- // Warm any cache -+React.render( // Warm any cache - , - container, - ); - --render?.( -- // Warm any cache -+render?.( // Warm any cache - , - container, - ); -``` - -# Output - -```js -render( // Warm any cache - , - container, -); - -React.render( // Warm any cache - , - container, -); - -render?.( // Warm any cache - , - container, -); -``` - - - diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/comments/dangling.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/comments/dangling.js.snap deleted file mode 100644 index 873113da5c8..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/js/comments/dangling.js.snap +++ /dev/null @@ -1,61 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -var a = {/* dangling */}; -var b = { - // dangling -}; -var b = [/* dangling */]; -function d() { - /* dangling */ -} -new Thing(/* dangling */); -Thing(/* dangling */); -export /* dangling */{}; -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -1,12 +1,8 @@ --var a = { -- /* dangling */ --}; -+var a = {/* dangling */}; - var b = { - // dangling - }; --var b = [ -- /* dangling */ --]; -+var b = [/* dangling */]; - function d() { - /* dangling */ - } -``` - -# Output - -```js -var a = {/* dangling */}; -var b = { - // dangling -}; -var b = [/* dangling */]; -function d() { - /* dangling */ -} -new Thing(/* dangling */); -Thing(/* dangling */); -export /* dangling */ {}; -``` - - - diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/comments/dangling_for.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/comments/dangling_for.js.snap deleted file mode 100644 index 72b12a3b226..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/js/comments/dangling_for.js.snap +++ /dev/null @@ -1,39 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -for // comment -(;;); - -for /* comment */(;;); -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -1,5 +1,3 @@ --// comment --for (;;); -+for (;;); // comment - --/* comment */ --for (;;); -+for /* comment */ (;;); -``` - -# Output - -```js -for (;;); // comment - -for /* comment */ (;;); -``` - - - diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/comments/preserve-new-line-last.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/comments/preserve-new-line-last.js.snap deleted file mode 100644 index 235205204d3..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/js/comments/preserve-new-line-last.js.snap +++ /dev/null @@ -1,80 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -function f() { - a - /* eslint-disable */ -} - -function f() { - a - - /* eslint-disable */ -} - -function name() { - // comment1 - func1() - - // comment2 - func2() - - // comment3 why func3 commented - // func3() -} -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -5,7 +5,6 @@ - - function f() { - a; -- - /* eslint-disable */ - } - -@@ -15,7 +14,6 @@ - - // comment2 - func2(); -- - // comment3 why func3 commented - // func3() - } -``` - -# Output - -```js -function f() { - a; - /* eslint-disable */ -} - -function f() { - a; - /* eslint-disable */ -} - -function name() { - // comment1 - func1(); - - // comment2 - func2(); - // comment3 why func3 commented - // func3() -} -``` - - - diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/comments/return-statement.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/comments/return-statement.js.snap index 2c9d24ea4c5..1275470ed15 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/comments/return-statement.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/comments/return-statement.js.snap @@ -1,5 +1,7 @@ --- source: crates/rome_js_formatter/tests/prettier_tests.rs +info: + test_file: js/comments/return-statement.js --- # Input @@ -151,15 +153,6 @@ function inlineComment() { } function excessiveEverything() { -@@ -116,5 +118,7 @@ - } - - function inlineComment() { -- return /* hi */ 42 || 42; -+ return ( -+ /* hi */ 42 || 42 -+ ); - } ``` # Output @@ -285,9 +278,7 @@ function taggedTemplate() { } function inlineComment() { - return ( - /* hi */ 42 || 42 - ); + return /* hi */ 42 || 42; } ``` diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/comments/single-star-jsdoc.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/comments/single-star-jsdoc.js.snap deleted file mode 100644 index 480a21d665c..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/js/comments/single-star-jsdoc.js.snap +++ /dev/null @@ -1,95 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -/* - * Looking good! - */ - -if(true) { - /* - * Oh no - */ -} - - /** first line -* second line - * third line */ - - /* first line -* second line - * third line */ - - /*! first line -*second line - * third line */ - -/*! -* Extracted from vue codebase -* https://github.com/vuejs/vue/blob/cfd73c2386623341fdbb3ac636c4baf84ea89c2c/src/compiler/parser/html-parser.js -* HTML Parser By John Resig (ejohn.org) -* Modified by Juriy "kangax" Zaytsev -* Original code by Erik Arvidsson, Mozilla Public License -* http://erik.eae.net/simplehtmlparser/simplehtmlparser.js -*/ -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -7,7 +7,6 @@ - * Oh no - */ - } -- - /** first line - * second line - * third line */ -``` - -# Output - -```js -/* - * Looking good! - */ - -if (true) { - /* - * Oh no - */ -} -/** first line - * second line - * third line */ - -/* first line - * second line - * third line */ - -/*! first line - *second line - * third line */ - -/*! - * Extracted from vue codebase - * https://github.com/vuejs/vue/blob/cfd73c2386623341fdbb3ac636c4baf84ea89c2c/src/compiler/parser/html-parser.js - * HTML Parser By John Resig (ejohn.org) - * Modified by Juriy "kangax" Zaytsev - * Original code by Erik Arvidsson, Mozilla Public License - * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js - */ -``` - - -# Lines exceeding max width of 80 characters -``` - 24: * https://github.com/vuejs/vue/blob/cfd73c2386623341fdbb3ac636c4baf84ea89c2c/src/compiler/parser/html-parser.js -``` - diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/comments/switch.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/comments/switch.js.snap index 30252b9953e..0637465200c 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/comments/switch.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/comments/switch.js.snap @@ -51,19 +51,7 @@ switch (foo) { ```diff --- Prettier +++ Rome -@@ -7,19 +7,18 @@ - case "MemberExpression": - prop = node.property; - break; -- - // no default - } - - switch (foo) { - case "bar": - doThing(); -- - // no default +@@ -19,7 +19,8 @@ } switch (foo) { @@ -87,12 +75,14 @@ switch (node && node.type) { case "MemberExpression": prop = node.property; break; + // no default } switch (foo) { case "bar": doThing(); + // no default } diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/comments/template-literal.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/comments/template-literal.js.snap deleted file mode 100644 index 457e8fc50f4..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/js/comments/template-literal.js.snap +++ /dev/null @@ -1,64 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -` -${a // comment -} - -${b /* comment */} - -${/* comment */ c /* comment */} - -${// comment -d //comment -}; -` -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -1,14 +1,11 @@ - ` --${ -- a // comment -+${a // comment - } - - ${b /* comment */} - - ${/* comment */ c /* comment */} - --${ -- // comment -- d //comment -+${d // comment //comment - }; - `; -``` - -# Output - -```js -` -${a // comment -} - -${b /* comment */} - -${/* comment */ c /* comment */} - -${d // comment //comment -}; -`; -``` - - - diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/comments/trailing-jsdocs.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/comments/trailing-jsdocs.js.snap deleted file mode 100644 index 9a97acc9e76..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/js/comments/trailing-jsdocs.js.snap +++ /dev/null @@ -1,101 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -const CONNECTION_STATUS = exports.CONNECTION_STATUS = { - CLOSED: Object.freeze({ kind: 'CLOSED' }), - CONNECTED: Object.freeze({ kind: 'CONNECTED' }), - CONNECTING: Object.freeze({ kind: 'CONNECTING' }), - NOT_CONNECTED: Object.freeze({ kind: 'NOT_CONNECTED' }) }; - -/* A comment */ /** -* A type that can be written to a buffer. -*/ /** -* Describes the connection status of a ReactiveSocket/DuplexConnection. -* - NOT_CONNECTED: no connection established or pending. -* - CONNECTING: when `connect()` has been called but a connection is not yet -* established. -* - CONNECTED: when a connection is established. -* - CLOSED: when the connection has been explicitly closed via `close()`. -* - ERROR: when the connection has been closed for any other reason. -*/ /** -* A contract providing different interaction models per the [ReactiveSocket protocol] -* (https://github.com/ReactiveSocket/reactivesocket/blob/master/Protocol.md). -*/ /** -* A single unit of data exchanged between the peers of a `ReactiveSocket`. -*/ -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -4,12 +4,9 @@ - CONNECTING: Object.freeze({ kind: "CONNECTING" }), - NOT_CONNECTED: Object.freeze({ kind: "NOT_CONNECTED" }), - }); -- --/* A comment */ --/** -+/* A comment */ /** - * A type that can be written to a buffer. -- */ --/** -+ */ /** - * Describes the connection status of a ReactiveSocket/DuplexConnection. - * - NOT_CONNECTED: no connection established or pending. - * - CONNECTING: when `connect()` has been called but a connection is not yet -@@ -17,11 +14,9 @@ - * - CONNECTED: when a connection is established. - * - CLOSED: when the connection has been explicitly closed via `close()`. - * - ERROR: when the connection has been closed for any other reason. -- */ --/** -+ */ /** - * A contract providing different interaction models per the [ReactiveSocket protocol] - * (https://github.com/ReactiveSocket/reactivesocket/blob/master/Protocol.md). -- */ --/** -+ */ /** - * A single unit of data exchanged between the peers of a `ReactiveSocket`. - */ -``` - -# Output - -```js -const CONNECTION_STATUS = (exports.CONNECTION_STATUS = { - CLOSED: Object.freeze({ kind: "CLOSED" }), - CONNECTED: Object.freeze({ kind: "CONNECTED" }), - CONNECTING: Object.freeze({ kind: "CONNECTING" }), - NOT_CONNECTED: Object.freeze({ kind: "NOT_CONNECTED" }), -}); -/* A comment */ /** - * A type that can be written to a buffer. - */ /** - * Describes the connection status of a ReactiveSocket/DuplexConnection. - * - NOT_CONNECTED: no connection established or pending. - * - CONNECTING: when `connect()` has been called but a connection is not yet - * established. - * - CONNECTED: when a connection is established. - * - CLOSED: when the connection has been explicitly closed via `close()`. - * - ERROR: when the connection has been closed for any other reason. - */ /** - * A contract providing different interaction models per the [ReactiveSocket protocol] - * (https://github.com/ReactiveSocket/reactivesocket/blob/master/Protocol.md). - */ /** - * A single unit of data exchanged between the peers of a `ReactiveSocket`. - */ -``` - - -# Lines exceeding max width of 80 characters -``` - 18: * A contract providing different interaction models per the [ReactiveSocket protocol] -``` - diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/comments/while.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/comments/while.js.snap deleted file mode 100644 index 5ab99aaa8b2..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/js/comments/while.js.snap +++ /dev/null @@ -1,88 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -while( - true - // Comment - ) {} - -while(true)// Comment -{} - -while(true){}// Comment - -while(true)/*Comment*/{} - -while( - true // Comment - && true // Comment - ){} - -while(true) {} // comment - -while(true) /* comment */ ++x; - -while(1) // Comment - foo(); -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -3,15 +3,11 @@ - // Comment - ) {} - --while (true) { -- // Comment --} -+while (true) {} // Comment - - while (true) {} // Comment - --while (true) { -- /*Comment*/ --} -+while (true) /*Comment*/ {} - - while ( - true && // Comment -``` - -# Output - -```js -while ( - true - // Comment -) {} - -while (true) {} // Comment - -while (true) {} // Comment - -while (true) /*Comment*/ {} - -while ( - true && // Comment - true // Comment -) {} - -while (true) {} // comment - -while (true) /* comment */ ++x; - -while (1) - // Comment - foo(); -``` - - - diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/conditional/comments.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/conditional/comments.js.snap index 973a812cc58..81e68111bc2 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/conditional/comments.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/conditional/comments.js.snap @@ -1,5 +1,7 @@ --- source: crates/rome_js_formatter/tests/prettier_tests.rs +info: + test_file: js/conditional/comments.js --- # Input @@ -117,112 +119,17 @@ c */? foo : bar : bar; ```diff --- Prettier +++ Rome -@@ -1,36 +1,39 @@ - var inspect = - 4 === util.inspect.length -- ? // node <= 0.8.x -- function (v, colors) { -+ ? function (v, colors) { -+ // node <= 0.8.x - return util.inspect(v, void 0, void 0, colors); - } -- : // node > 0.8.x -- function (v, colors) { -+ : function (v, colors) { -+ // node > 0.8.x - return util.inspect(v, { colors: colors }); - }; - - var inspect = - 4 === util.inspect.length -- ? // node <= 0.8.x -- function (v, colors) { -+ ? function (v, colors) { -+ // node <= 0.8.x - return util.inspect(v, void 0, void 0, colors); - } -- : // node > 0.8.x -- function (v, colors) { -+ : function (v, colors) { -+ // node > 0.8.x - return util.inspect(v, { colors: colors }); +@@ -21,8 +21,8 @@ }; const extractTextPluginOptions = shouldUseRelativeAssetPaths - ? // Making sure that the publicPath goes back to to build folder. - { publicPath: Array(cssFilename.split("/").length).join("../") } -+ // Making sure that the publicPath goes back to to build folder. ++// Making sure that the publicPath goes back to to build folder. + ? { publicPath: Array(cssFilename.split("/").length).join("../") } : {}; const extractTextPluginOptions2 = shouldUseRelativeAssetPaths -- ? // Making sure that the publicPath goes back to to build folder. -- { publicPath: Array(cssFilename.split("/").length).join("../") } -+ ? { -+ // Making sure that the publicPath goes back to to build folder. -+ publicPath: Array(cssFilename.split("/").length).join("../"), -+ } - : {}; - --const extractTextPluginOptions3 = shouldUseRelativeAssetPaths // Making sure that the publicPath goes back to to build folder. -+const extractTextPluginOptions3 = shouldUseRelativeAssetPaths -+ // Making sure that the publicPath goes back to to build folder. - ? { publicPath: Array(cssFilename.split("/").length).join("../") } - : {}; - -@@ -51,8 +54,7 @@ - comment - comment - comment -- */ -- foo -+ */ foo - : bar; - - test -@@ -60,13 +62,11 @@ - comment - comment - comment -- */ -- foo -+ */ foo - : test - ? /* comment - comment -- comment */ -- foo -+ comment */ foo - : bar; - - test ? /* comment */ foo : bar; -@@ -77,8 +77,7 @@ - comment - comment - comment -- */ -- bar; -+ */ bar; - - test - ? foo -@@ -86,14 +85,12 @@ - comment - comment - comment -- */ -- test -+ */ test - ? foo - : /* comment - comment - comment -- */ -- bar; -+ */ bar; - - test ? foo : /* comment */ bar; - ``` # Output @@ -230,40 +137,37 @@ c */? foo : bar : bar; ```js var inspect = 4 === util.inspect.length - ? function (v, colors) { - // node <= 0.8.x + ? // node <= 0.8.x + function (v, colors) { return util.inspect(v, void 0, void 0, colors); } - : function (v, colors) { - // node > 0.8.x + : // node > 0.8.x + function (v, colors) { return util.inspect(v, { colors: colors }); }; var inspect = 4 === util.inspect.length - ? function (v, colors) { - // node <= 0.8.x + ? // node <= 0.8.x + function (v, colors) { return util.inspect(v, void 0, void 0, colors); } - : function (v, colors) { - // node > 0.8.x + : // node > 0.8.x + function (v, colors) { return util.inspect(v, { colors: colors }); }; const extractTextPluginOptions = shouldUseRelativeAssetPaths - // Making sure that the publicPath goes back to to build folder. +// Making sure that the publicPath goes back to to build folder. ? { publicPath: Array(cssFilename.split("/").length).join("../") } : {}; const extractTextPluginOptions2 = shouldUseRelativeAssetPaths - ? { - // Making sure that the publicPath goes back to to build folder. - publicPath: Array(cssFilename.split("/").length).join("../"), - } + ? // Making sure that the publicPath goes back to to build folder. + { publicPath: Array(cssFilename.split("/").length).join("../") } : {}; -const extractTextPluginOptions3 = shouldUseRelativeAssetPaths - // Making sure that the publicPath goes back to to build folder. +const extractTextPluginOptions3 = shouldUseRelativeAssetPaths // Making sure that the publicPath goes back to to build folder. ? { publicPath: Array(cssFilename.split("/").length).join("../") } : {}; @@ -284,7 +188,8 @@ test comment comment comment - */ foo + */ + foo : bar; test @@ -292,11 +197,13 @@ test comment comment comment - */ foo + */ + foo : test ? /* comment comment - comment */ foo + comment */ + foo : bar; test ? /* comment */ foo : bar; @@ -307,7 +214,8 @@ test comment comment comment - */ bar; + */ + bar; test ? foo @@ -315,12 +223,14 @@ test comment comment comment - */ test + */ + test ? foo : /* comment comment comment - */ bar; + */ + bar; test ? foo : /* comment */ bar; @@ -333,4 +243,8 @@ c */ ``` +# Lines exceeding max width of 80 characters +``` + 33: const extractTextPluginOptions3 = shouldUseRelativeAssetPaths // Making sure that the publicPath goes back to to build folder. +``` diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/for/continue-and-break-comment-2.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/for/continue-and-break-comment-2.js.snap deleted file mode 100644 index 7a36ef2e95d..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/js/for/continue-and-break-comment-2.js.snap +++ /dev/null @@ -1,480 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -for(;;) { - continue - // comment - ; -} - -for (;;) { - break - // comment - ; -} - -for (const f of []) { - continue - // comment - ; -} - -for (const f of []) { - break - // comment - ; -} - -for (const f in {}) { - continue - // comment - ; -} - -for (const f in {}) { - break - // comment - ; -} - -while(true) { - continue - // comment - ; -} - -while (true) { - break - // comment - ; -} - -do { - continue - // comment - ; -} while(true); - - -do { - break - // comment - ; -} while(true); - -label1: for (;;) { - continue label1 - // comment - ; -} - -label2: { - break label2 - // comment - ; -}; - -for(;;) { - continue - /* comment */ - ; -} - -for (;;) { - break - /* comment */ - ; -} - -for (const f of []) { - continue - /* comment */ - ; -} - -for (const f of []) { - break - /* comment */ - ; -} - -for (const f in {}) { - continue - /* comment */ - ; -} - -for (const f in {}) { - break - /* comment */ - ; -} - -while(true) { - continue - /* comment */ - ; -} - -while (true) { - break - /* comment */ - ; -} - -do { - continue - /* comment */ - ; -} while(true); - - -do { - break - /* comment */ - ; -} while(true); - -label1: for (;;) { - continue label1 - /* comment */ - ; -} - -label2: { - break label2 - /* comment */ - ; -}; -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -1,119 +1,143 @@ - for (;;) { -- continue; -+ continue - // comment -+ ; - } - - for (;;) { -- break; -+ break - // comment -+ ; - } - - for (const f of []) { -- continue; -+ continue - // comment -+ ; - } - - for (const f of []) { -- break; -+ break - // comment -+ ; - } - - for (const f in {}) { -- continue; -+ continue - // comment -+ ; - } - - for (const f in {}) { -- break; -+ break - // comment -+ ; - } - - while (true) { -- continue; -+ continue - // comment -+ ; - } - - while (true) { -- break; -+ break - // comment -+ ; - } - - do { -- continue; -+ continue - // comment -+ ; - } while (true); - - do { -- break; -+ break - // comment -+ ; - } while (true); - - label1: for (;;) { -- continue label1; -+ continue label1 - // comment -+ ; - } - - label2: { -- break label2; -+ break label2 - // comment -+ ; - } - - for (;;) { -- continue; -+ continue - /* comment */ -+ ; - } - - for (;;) { -- break; -+ break - /* comment */ -+ ; - } - - for (const f of []) { -- continue; -+ continue - /* comment */ -+ ; - } - - for (const f of []) { -- break; -+ break - /* comment */ -+ ; - } - - for (const f in {}) { -- continue; -+ continue - /* comment */ -+ ; - } - - for (const f in {}) { -- break; -+ break - /* comment */ -+ ; - } - - while (true) { -- continue; -+ continue - /* comment */ -+ ; - } - - while (true) { -- break; -+ break - /* comment */ -+ ; - } - - do { -- continue; -+ continue - /* comment */ -+ ; - } while (true); - - do { -- break; -+ break - /* comment */ -+ ; - } while (true); - - label1: for (;;) { -- continue label1; -+ continue label1 - /* comment */ -+ ; - } - - label2: { -- break label2; -+ break label2 - /* comment */ -+ ; - } -``` - -# Output - -```js -for (;;) { - continue - // comment - ; -} - -for (;;) { - break - // comment - ; -} - -for (const f of []) { - continue - // comment - ; -} - -for (const f of []) { - break - // comment - ; -} - -for (const f in {}) { - continue - // comment - ; -} - -for (const f in {}) { - break - // comment - ; -} - -while (true) { - continue - // comment - ; -} - -while (true) { - break - // comment - ; -} - -do { - continue - // comment - ; -} while (true); - -do { - break - // comment - ; -} while (true); - -label1: for (;;) { - continue label1 - // comment - ; -} - -label2: { - break label2 - // comment - ; -} - -for (;;) { - continue - /* comment */ - ; -} - -for (;;) { - break - /* comment */ - ; -} - -for (const f of []) { - continue - /* comment */ - ; -} - -for (const f of []) { - break - /* comment */ - ; -} - -for (const f in {}) { - continue - /* comment */ - ; -} - -for (const f in {}) { - break - /* comment */ - ; -} - -while (true) { - continue - /* comment */ - ; -} - -while (true) { - break - /* comment */ - ; -} - -do { - continue - /* comment */ - ; -} while (true); - -do { - break - /* comment */ - ; -} while (true); - -label1: for (;;) { - continue label1 - /* comment */ - ; -} - -label2: { - break label2 - /* comment */ - ; -} -``` - - - diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/if/if_comments.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/if/if_comments.js.snap deleted file mode 100644 index ce9dac60a1e..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/js/if/if_comments.js.snap +++ /dev/null @@ -1,191 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -async function f1() { - if (untrackedChoice === 0) /* Cancel */ { - return null; - } else if (untrackedChoice === 1) /* Add */ { - await repository.addAll(Array.from(untrackedChanges.keys())); - shouldAmend = true; - } else if (untrackedChoice === 2) /* Allow Untracked */ { - allowUntracked = true; - } -} - -async function f2() { - if (untrackedChoice === 0) /* Cancel */ - null; - else if (untrackedChoice === 1) /* Add */ - shouldAmend = true; - else if (untrackedChoice === 2) /* Allow Untracked */ - allowUntracked = true; -} - -async function f3() { - if (untrackedChoice === 0) /* Cancel */ // Cancel - null; - else if (untrackedChoice === 1) /* Add */ // Add - shouldAmend = true; - else if (untrackedChoice === 2) /* Allow Untracked */ // Allow Untracked - allowUntracked = true; -} - -async function f4() { - if (untrackedChoice === 0) - /* Cancel */ { - return null; - } - else if (untrackedChoice === 1) - /* Add */ { - await repository.addAll(Array.from(untrackedChanges.keys())); - shouldAmend = true; - } - else if (untrackedChoice === 2) - /* Allow Untracked */ { - allowUntracked = true; - } -} - -async function f5() { - if (untrackedChoice === 0) { - /* Cancel */ return null; - } else if (untrackedChoice === 1) { - /* Add */ await repository.addAll(Array.from(untrackedChanges.keys())); - shouldAmend = true; - } else if (untrackedChoice === 2) { - /* Allow Untracked */ allowUntracked = true; - } -} -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -1,11 +1,11 @@ - async function f1() { -- if (untrackedChoice === 0) { -- /* Cancel */ return null; -- } else if (untrackedChoice === 1) { -- /* Add */ await repository.addAll(Array.from(untrackedChanges.keys())); -+ if (untrackedChoice === 0) /* Cancel */ { -+ return null; -+ } else if (untrackedChoice === 1) /* Add */ { -+ await repository.addAll(Array.from(untrackedChanges.keys())); - shouldAmend = true; -- } else if (untrackedChoice === 2) { -- /* Allow Untracked */ allowUntracked = true; -+ } else if (untrackedChoice === 2) /* Allow Untracked */ { -+ allowUntracked = true; - } - } - -@@ -16,25 +16,28 @@ - } - - async function f3() { -- if (untrackedChoice === 0) -- /* Cancel */ // Cancel -+ if (untrackedChoice === 0) /* Cancel */ -+ // Cancel - null; -- else if (untrackedChoice === 1) -- /* Add */ // Add -+ else if (untrackedChoice === 1) /* Add */ -+ // Add - shouldAmend = true; -- else if (untrackedChoice === 2) -- /* Allow Untracked */ // Allow Untracked -+ else if (untrackedChoice === 2) /* Allow Untracked */ -+ // Allow Untracked - allowUntracked = true; - } - - async function f4() { -- if (untrackedChoice === 0) { -- /* Cancel */ return null; -- } else if (untrackedChoice === 1) { -- /* Add */ await repository.addAll(Array.from(untrackedChanges.keys())); -+ if (untrackedChoice === 0) -+ /* Cancel */ { -+ return null; -+ } else if (untrackedChoice === 1) -+ /* Add */ { -+ await repository.addAll(Array.from(untrackedChanges.keys())); - shouldAmend = true; -- } else if (untrackedChoice === 2) { -- /* Allow Untracked */ allowUntracked = true; -+ } else if (untrackedChoice === 2) -+ /* Allow Untracked */ { -+ allowUntracked = true; - } - } - -``` - -# Output - -```js -async function f1() { - if (untrackedChoice === 0) /* Cancel */ { - return null; - } else if (untrackedChoice === 1) /* Add */ { - await repository.addAll(Array.from(untrackedChanges.keys())); - shouldAmend = true; - } else if (untrackedChoice === 2) /* Allow Untracked */ { - allowUntracked = true; - } -} - -async function f2() { - if (untrackedChoice === 0) /* Cancel */ null; - else if (untrackedChoice === 1) /* Add */ shouldAmend = true; - else if (untrackedChoice === 2) /* Allow Untracked */ allowUntracked = true; -} - -async function f3() { - if (untrackedChoice === 0) /* Cancel */ - // Cancel - null; - else if (untrackedChoice === 1) /* Add */ - // Add - shouldAmend = true; - else if (untrackedChoice === 2) /* Allow Untracked */ - // Allow Untracked - allowUntracked = true; -} - -async function f4() { - if (untrackedChoice === 0) - /* Cancel */ { - return null; - } else if (untrackedChoice === 1) - /* Add */ { - await repository.addAll(Array.from(untrackedChanges.keys())); - shouldAmend = true; - } else if (untrackedChoice === 2) - /* Allow Untracked */ { - allowUntracked = true; - } -} - -async function f5() { - if (untrackedChoice === 0) { - /* Cancel */ return null; - } else if (untrackedChoice === 1) { - /* Add */ await repository.addAll(Array.from(untrackedChanges.keys())); - shouldAmend = true; - } else if (untrackedChoice === 2) { - /* Allow Untracked */ allowUntracked = true; - } -} -``` - - - diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/line-suffix-boundary/boundary.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/line-suffix-boundary/boundary.js.snap deleted file mode 100644 index 810ee274d56..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/js/line-suffix-boundary/boundary.js.snap +++ /dev/null @@ -1,107 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -`${ -a + // a - a -} - -${a // comment -} - -${b /* comment */} - -${/* comment */ c /* comment */} - -${// comment -d //comment -} - -${// $FlowFixMe found when converting React.createClass to ES6 -ExampleStory.getFragment('story')} -`; - -
-{ExampleStory.getFragment('story') // $FlowFixMe found when converting React.createClass to ES6 -} -
; -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -3,23 +3,20 @@ - a - } - --${ -- a // comment -+${a // comment - } - - ${b /* comment */} - - ${/* comment */ c /* comment */} - --${ -- // comment -- d //comment -+${d // comment //comment - } - --${ -+${ExampleStory.getFragment( - // $FlowFixMe found when converting React.createClass to ES6 -- ExampleStory.getFragment("story") --} -+ "story", -+)} - `; - -
-``` - -# Output - -```js -`${ - a + // a - a -} - -${a // comment -} - -${b /* comment */} - -${/* comment */ c /* comment */} - -${d // comment //comment -} - -${ExampleStory.getFragment( - // $FlowFixMe found when converting React.createClass to ES6 - "story", -)} -`; - -
- { - ExampleStory.getFragment("story") // $FlowFixMe found when converting React.createClass to ES6 - } -
; -``` - - -# Lines exceeding max width of 80 characters -``` - 24: ExampleStory.getFragment("story") // $FlowFixMe found when converting React.createClass to ES6 -``` - diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/return/comment.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/return/comment.js.snap index 2808b3c7ed4..8cbdf6cdf7a 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/return/comment.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/return/comment.js.snap @@ -1,5 +1,7 @@ --- source: crates/rome_js_formatter/tests/prettier_tests.rs +info: + test_file: js/return/comment.js --- # Input @@ -54,14 +56,7 @@ fn(function f() { ```diff --- Prettier +++ Rome -@@ -12,30 +12,25 @@ - } - - function f() { -- return; /* a */ -+ return /* a */ - // b -+ ; +@@ -17,25 +17,19 @@ } function x() { @@ -115,9 +110,8 @@ function f() { } function f() { - return /* a */ + return; /* a */ // b - ; } function x() { diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/template/comment.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/template/comment.js.snap deleted file mode 100644 index 4745afa1e1e..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/js/template/comment.js.snap +++ /dev/null @@ -1,60 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -` -(?:${escapeChar}[\\S\\s]|(?:(?!${// Using `XRegExp.union` safely rewrites backreferences in `left` and `right`. -// Intentionally not passing `basicFlags` to `XRegExp.union` since any syntax -// transformation resulting from those flags was already applied to `left` and -// `right` when they were passed through the XRegExp constructor above. -XRegExp.union([left, right], '', {conjunction: 'or'}).source})[^${escapeChar}])+)+ -`; - -`a${/* b */c/* d */}e${// f -g -// h -}`; -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -8,8 +8,6 @@ - })[^${escapeChar}])+)+ - `; - --`a${/* b */ c /* d */}e${ -- // f -- g -- // h -+`a${/* b */ c /* d */}e${g // f -+// h - }`; -``` - -# Output - -```js -` -(?:${escapeChar}[\\S\\s]|(?:(?!${ - // Using `XRegExp.union` safely rewrites backreferences in `left` and `right`. - // Intentionally not passing `basicFlags` to `XRegExp.union` since any syntax - // transformation resulting from those flags was already applied to `left` and - // `right` when they were passed through the XRegExp constructor above. - XRegExp.union([left, right], "", { conjunction: "or" }).source -})[^${escapeChar}])+)+ -`; - -`a${/* b */ c /* d */}e${g // f -// h -}`; -``` - - - diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/ternaries/nested.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/ternaries/nested.js.snap deleted file mode 100644 index 5b3a410ba4b..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/js/ternaries/nested.js.snap +++ /dev/null @@ -1,226 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -let icecream = what == "cone" - ? p => !!p ? `here's your ${p} cone` : `just the empty cone for you` - : p => `here's your ${p} ${what}`; - -const value = condition1 -? value1 -: condition2 - ? value2 - : condition3 - ? value3 - : value4; - - -const StorybookLoader = ({ match }) => ( - match.params.storyId === "button" - ? - : match.params.storyId === "color" - ? - : match.params.storyId === "typography" - ? - : match.params.storyId === "loading" - ? - : match.params.storyId === "deal-list" - ? - : ( - - {'Missing story book'} - - - - - ) -) - -const message = - i % 3 === 0 && i % 5 === 0 ? - 'fizzbuzz' - : i % 3 === 0 ? - 'fizz' - : i % 5 === 0 ? - 'buzz' - : - String(i) - -const paymentMessage = state == 'success' - ? 'Payment completed successfully' - -: state == 'processing' - ? 'Payment processing' - -: state == 'invalid_cvc' - ? 'There was an issue with your CVC number' - -: state == 'invalid_expiry' - ? 'Expiry must be sometime in the past.' - - : 'There was an issue with the payment. Please contact support.' - -const paymentMessage2 = state == 'success' - ? 1 //'Payment completed successfully' - -: state == 'processing' - ? 2 //'Payment processing' - -: state == 'invalid_cvc' - ? 3 //'There was an issue with your CVC number' - -: true //state == 'invalid_expiry' - ? 4 //'Expiry must be sometime in the past.' - - : 5 // 'There was an issue with the payment. Please contact support.' - -const foo =
- {medals[0].record ? ( - i18n('Record') - ) : medals[0].unique ? ( - i18n('Unique') - ) : medals[0].type === 0 ? ( - i18n('Silver') - ) : medals[0].type === 1 ? ( - i18n('Gold') - ) : medals[0].type === 2 ? ( - i18n('Platinum') - ) : ( - i18n('Theme') - )} -
- -a - ? literalline - : { - 123: 12 - } - ? line - : softline -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -60,7 +60,8 @@ - ? 3 //'There was an issue with your CVC number' - : true //state == 'invalid_expiry' - ? 4 //'Expiry must be sometime in the past.' -- : 5; // 'There was an issue with the payment. Please contact support.' -+ : 5 // 'There was an issue with the payment. Please contact support.' -+ ; - - const foo = ( -
(!!p ? `here's your ${p} cone` : `just the empty cone for you`) - : (p) => `here's your ${p} ${what}`; - -const value = condition1 - ? value1 - : condition2 - ? value2 - : condition3 - ? value3 - : value4; - -const StorybookLoader = ({ match }) => - match.params.storyId === "button" ? ( - - ) : match.params.storyId === "color" ? ( - - ) : match.params.storyId === "typography" ? ( - - ) : match.params.storyId === "loading" ? ( - - ) : match.params.storyId === "deal-list" ? ( - - ) : ( - - {"Missing story book"} - - - - - ); - -const message = - i % 3 === 0 && i % 5 === 0 - ? "fizzbuzz" - : i % 3 === 0 - ? "fizz" - : i % 5 === 0 - ? "buzz" - : String(i); - -const paymentMessage = - state == "success" - ? "Payment completed successfully" - : state == "processing" - ? "Payment processing" - : state == "invalid_cvc" - ? "There was an issue with your CVC number" - : state == "invalid_expiry" - ? "Expiry must be sometime in the past." - : "There was an issue with the payment. Please contact support."; - -const paymentMessage2 = - state == "success" - ? 1 //'Payment completed successfully' - : state == "processing" - ? 2 //'Payment processing' - : state == "invalid_cvc" - ? 3 //'There was an issue with your CVC number' - : true //state == 'invalid_expiry' - ? 4 //'Expiry must be sometime in the past.' - : 5 // 'There was an issue with the payment. Please contact support.' - ; - -const foo = ( -
- {medals[0].record - ? i18n("Record") - : medals[0].unique - ? i18n("Unique") - : medals[0].type === 0 - ? i18n("Silver") - : medals[0].type === 1 - ? i18n("Gold") - : medals[0].type === 2 - ? i18n("Platinum") - : i18n("Theme")} -
-); - -a - ? literalline - : { - 123: 12, - } - ? line - : softline; -``` - - - diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/try/catch.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/try/catch.js.snap deleted file mode 100644 index 8cf97185203..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/js/try/catch.js.snap +++ /dev/null @@ -1,84 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -try {} -catch( - // comment - foo -) {} - -try {} -catch(foo //comment -) {} - -try {} -catch( - /* comment */ foo -) {} - -try {} -catch( - foo /* comment */ -) {} - -try {} -catch( - foo - /* comment */ -) {} -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -10,7 +10,9 @@ - ) {} - - try { --} catch (/* comment */ foo) {} -+} catch ( -+ /* comment */ foo -+) {} - - try { - } catch (foo /* comment */) {} -``` - -# Output - -```js -try { -} catch ( - // comment - foo -) {} - -try { -} catch ( - foo //comment -) {} - -try { -} catch ( - /* comment */ foo -) {} - -try { -} catch (foo /* comment */) {} - -try { -} catch ( - foo - /* comment */ -) {} -``` - - - diff --git a/crates/rome_js_formatter/tests/specs/prettier/typescript/arrow/comments.ts.snap b/crates/rome_js_formatter/tests/specs/prettier/typescript/arrow/comments.ts.snap index fad26202e0f..14d52ed8780 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/typescript/arrow/comments.ts.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/typescript/arrow/comments.ts.snap @@ -23,7 +23,7 @@ const fn2 = () => { ```diff --- Prettier +++ Rome -@@ -1,9 +1,9 @@ +@@ -1,6 +1,6 @@ const fn1 = () => { return; -}; /* foo */ @@ -31,11 +31,6 @@ const fn2 = () => { const fn2 = () => { return; --}; -- -+} - // foo -+; ``` # Output @@ -47,9 +42,9 @@ const fn1 = () => { const fn2 = () => { return; -} +}; + // foo -; ``` diff --git a/crates/rome_js_formatter/tests/specs/prettier/typescript/class/generics.ts.snap b/crates/rome_js_formatter/tests/specs/prettier/typescript/class/generics.ts.snap deleted file mode 100644 index d91ddcf9d4d..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/typescript/class/generics.ts.snap +++ /dev/null @@ -1,52 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -class implements Map {} - -interface AudioBufferList { - mBuffers: interop.Reference; -} -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -1,5 +1,5 @@ - class implements Map {} - - interface AudioBufferList { -- mBuffers: interop.Reference; -+ mBuffers: interop.Reference; - } -``` - -# Output - -```js -class implements Map {} - -interface AudioBufferList { - mBuffers: interop.Reference; -} -``` - - -# Errors -``` -error[SyntaxError]: class declarations must have a name - ┌─ generics.ts:1:1 - │ -1 │ class implements Map {} - │ ^^^^^ - - -``` - - diff --git a/crates/rome_js_formatter/tests/specs/prettier/typescript/comments/after_jsx_generic.ts.snap b/crates/rome_js_formatter/tests/specs/prettier/typescript/comments/after_jsx_generic.ts.snap deleted file mode 100644 index 36413b8d1b1..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/typescript/comments/after_jsx_generic.ts.snap +++ /dev/null @@ -1,93 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -let comp = ( - <> - /* comment1 */> - foo /* comment2 */> - /* comment3 */ bar> - foo /* comment4 */ bar> - - - // comment5 - > - - foo - // comment6 - > - - // comment7 - foo - > - - foo - // comment8 - bar - > - -); -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -1,7 +1,7 @@ - let comp = ( - <> -- /* comment1 */> -- foo /* comment2 */> -+ /* comment1 */ > -+ foo /* comment2 */ > - /* comment3 */ bar> - foo /* comment4 */ bar> - -@@ -10,7 +10,7 @@ - > - - foo -- // comment6 -+ // comment6 - > - - // comment7 -``` - -# Output - -```js -let comp = ( - <> - /* comment1 */ > - foo /* comment2 */ > - /* comment3 */ bar> - foo /* comment4 */ bar> - - - // comment5 - > - - foo - // comment6 - > - - // comment7 - foo - > - - foo - // comment8 - bar - > - -); -``` - - - diff --git a/crates/rome_js_formatter/tests/specs/prettier/typescript/comments/methods.ts.snap b/crates/rome_js_formatter/tests/specs/prettier/typescript/comments/methods.ts.snap deleted file mode 100644 index 198dd71b9bf..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/typescript/comments/methods.ts.snap +++ /dev/null @@ -1,131 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -export class Point { -/** - * Does something. - */ - foo() {} - - /** - * Does something else. - */ - bar() {} - - /** - * Does - * something - * much - * better - * than - * the - * rest. - */ - baz() {} - - /** - * Buzz-Fizz. - * Note: This is indented too far. - */ - fizzBuzz() {} - - /** - * Turns the given string into pig-latin. - */ - pigLatinize(value: string) { -/** - * This is a block comment inside of a method. - */ - } - - /** - * One - * Two - * Three -* Four - */ - mismatchedIndentation() {} - - inline /* foo*/ (/* bar */) /* baz */ {} - - noBody(/* comment */ arg); -} -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -43,7 +43,7 @@ - */ - mismatchedIndentation() {} - -- inline /* foo*/(/* bar */) /* baz */ {} -+ inline /* foo*/ (/* bar */) /* baz */ {} - - noBody(/* comment */ arg); - } -``` - -# Output - -```js -export class Point { - /** - * Does something. - */ - foo() {} - - /** - * Does something else. - */ - bar() {} - - /** - * Does - * something - * much - * better - * than - * the - * rest. - */ - baz() {} - - /** - * Buzz-Fizz. - * Note: This is indented too far. - */ - fizzBuzz() {} - - /** - * Turns the given string into pig-latin. - */ - pigLatinize(value: string) { - /** - * This is a block comment inside of a method. - */ - } - - /** - * One - * Two - * Three - * Four - */ - mismatchedIndentation() {} - - inline /* foo*/ (/* bar */) /* baz */ {} - - noBody(/* comment */ arg); -} -``` - - - diff --git a/crates/rome_js_formatter/tests/specs/prettier/typescript/conditional-types/comments.ts.snap b/crates/rome_js_formatter/tests/specs/prettier/typescript/conditional-types/comments.ts.snap deleted file mode 100644 index 738b3642f4b..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/typescript/conditional-types/comments.ts.snap +++ /dev/null @@ -1,218 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -type A = B extends T - ? // comment - foo - : bar; - -type A = B extends test /* comment - comment - comment -*/ - ? foo - : bar; - -type T = test extends B - ? /* comment - comment - comment - comment - */ - foo - : bar; - -type T = test extends B - ? /* comment - comment - comment - comment - */ - foo - : test extends B - ? /* comment - comment - comment */ - foo - : bar; - -type T = test extends B - ? /* comment */ - foo - : bar; - -type T = test extends B - ? foo - : /* comment - comment - comment - comment - */ - bar; - -type T = test extends B - ? foo - : /* comment - comment - comment - comment - */ - test extends B - ? foo - : /* comment - comment - comment - */ - bar; - -type T = test extends B - ? foo - : /* comment */ - bar; - -type T = test extends B ? test extends B /* c -c */? foo : bar : bar; -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -1,6 +1,5 @@ - type A = B extends T -- ? // comment -- foo -+ ? foo // comment - : bar; - - type A = B extends test /* comment -@@ -15,8 +14,7 @@ - comment - comment - comment -- */ -- foo -+ */ foo - : bar; - - type T = test extends B -@@ -24,13 +22,11 @@ - comment - comment - comment -- */ -- foo -+ */ foo - : test extends B - ? /* comment - comment -- comment */ -- foo -+ comment */ foo - : bar; - - type T = test extends B ? /* comment */ foo : bar; -@@ -41,8 +37,7 @@ - comment - comment - comment -- */ -- bar; -+ */ bar; - - type T = test extends B - ? foo -@@ -50,14 +45,12 @@ - comment - comment - comment -- */ -- test extends B -+ */ test extends B - ? foo - : /* comment - comment - comment -- */ -- bar; -+ */ bar; - - type T = test extends B ? foo : /* comment */ bar; - -``` - -# Output - -```js -type A = B extends T - ? foo // comment - : bar; - -type A = B extends test /* comment - comment - comment -*/ - ? foo - : bar; - -type T = test extends B - ? /* comment - comment - comment - comment - */ foo - : bar; - -type T = test extends B - ? /* comment - comment - comment - comment - */ foo - : test extends B - ? /* comment - comment - comment */ foo - : bar; - -type T = test extends B ? /* comment */ foo : bar; - -type T = test extends B - ? foo - : /* comment - comment - comment - comment - */ bar; - -type T = test extends B - ? foo - : /* comment - comment - comment - comment - */ test extends B - ? foo - : /* comment - comment - comment - */ bar; - -type T = test extends B ? foo : /* comment */ bar; - -type T = test extends B - ? test extends B /* c -c */ - ? foo - : bar - : bar; -``` - - - diff --git a/crates/rome_js_formatter/tests/specs/prettier/typescript/export/comment.ts.snap b/crates/rome_js_formatter/tests/specs/prettier/typescript/export/comment.ts.snap deleted file mode 100644 index cef2de770b9..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/typescript/export/comment.ts.snap +++ /dev/null @@ -1,32 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -export function match(): string /* the matching pattern */ -a -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -1,2 +1,2 @@ --export function match(): string; /* the matching pattern */ -+export function match(): string /* the matching pattern */; - a; -``` - -# Output - -```js -export function match(): string /* the matching pattern */; -a; -``` - - - diff --git a/crates/rome_js_formatter/tests/specs/prettier/typescript/keyword-types/conditional-types.ts.snap b/crates/rome_js_formatter/tests/specs/prettier/typescript/keyword-types/conditional-types.ts.snap deleted file mode 100644 index e1a8d4a8207..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/typescript/keyword-types/conditional-types.ts.snap +++ /dev/null @@ -1,56 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -export type UnwrappedResultRow = { - [P in keyof T]: ( - T[P] extends Req ? ( - a - ) : ( - T[P] extends Opt ? ( - b - ) : ( - // TEST - never - ) - ) - ); -}; -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -3,6 +3,7 @@ - ? a - : T[P] extends Opt - ? b -- : // TEST -+ : -+ // TEST - never; - }; -``` - -# Output - -```js -export type UnwrappedResultRow = { - [P in keyof T]: T[P] extends Req - ? a - : T[P] extends Opt - ? b - : - // TEST - never; -}; -``` - - - diff --git a/crates/rome_js_formatter/tests/specs/prettier/typescript/keyword-types/keyword-types-with-parens-comments.ts.snap b/crates/rome_js_formatter/tests/specs/prettier/typescript/keyword-types/keyword-types-with-parens-comments.ts.snap deleted file mode 100644 index d321dd2b756..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/typescript/keyword-types/keyword-types-with-parens-comments.ts.snap +++ /dev/null @@ -1,168 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -let foo: ( - // comment - any -); -let foo: ( - // comment - null -); -let foo: ( - // comment - this -); -let foo: ( - // comment - number -); -let foo: ( - // comment - void -); -let foo: ( - // comment - boolean -); -let foo: ( - // comment - bigint -); -let foo: ( - // comment - symbol -); -let foo: ( - // comment - string -); -let foo: ( - // comment - never -); -let foo: ( - // comment - object -); -let foo: ( - // comment - undefined -); -let foo: ( - // comment - unknown -); -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -1,26 +1,39 @@ --let foo: // comment -+let foo: -+// comment - any; --let foo: // comment -+let foo: -+// comment - null; --let foo: // comment -+let foo: -+// comment - this; --let foo: // comment -+let foo: -+// comment - number; --let foo: // comment -+let foo: -+// comment - void; --let foo: // comment -+let foo: -+// comment - boolean; --let foo: // comment -+let foo: -+// comment - bigint; --let foo: // comment -+let foo: -+// comment - symbol; --let foo: // comment -+let foo: -+// comment - string; --let foo: // comment -+let foo: -+// comment - never; --let foo: // comment -+let foo: -+// comment - object; --let foo: // comment -+let foo: -+// comment - undefined; --let foo: // comment -+let foo: -+// comment - unknown; -``` - -# Output - -```js -let foo: -// comment -any; -let foo: -// comment -null; -let foo: -// comment -this; -let foo: -// comment -number; -let foo: -// comment -void; -let foo: -// comment -boolean; -let foo: -// comment -bigint; -let foo: -// comment -symbol; -let foo: -// comment -string; -let foo: -// comment -never; -let foo: -// comment -object; -let foo: -// comment -undefined; -let foo: -// comment -unknown; -``` - - - From 0a494b57de14cbebdbba3e0b937641ed74203e3b Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Wed, 7 Sep 2022 08:00:59 +0200 Subject: [PATCH 02/45] Some more improvements --- crates/rome_formatter/src/comments.rs | 74 +++++++++++++++---- crates/rome_formatter/src/lib.rs | 4 +- crates/rome_js_formatter/src/builders.rs | 8 +- crates/rome_js_formatter/src/comments.rs | 64 +++++++++++++++- .../expressions/arrow_function_expression.rs | 8 +- .../src/js/statements/return_statement.rs | 6 +- .../attribute/expression_attribute_value.rs | 16 ++-- .../src/jsx/auxiliary/expression_child.rs | 4 +- .../src/jsx/tag/opening_element.rs | 21 +++--- crates/rome_js_formatter/src/lib.rs | 7 +- .../rome_js_formatter/src/syntax_rewriter.rs | 23 +----- .../src/utils/assignment_like.rs | 41 ++++------ .../src/utils/conditional.rs | 2 +- .../prettier/js/assignment/lone-arg.js.snap | 18 ++--- .../prettier/js/break-calls/react.js.snap | 48 +++++------- 15 files changed, 201 insertions(+), 143 deletions(-) diff --git a/crates/rome_formatter/src/comments.rs b/crates/rome_formatter/src/comments.rs index a9eed0dbbeb..acccd7a7271 100644 --- a/crates/rome_formatter/src/comments.rs +++ b/crates/rome_formatter/src/comments.rs @@ -9,17 +9,6 @@ use std::cell::RefCell; use std::collections::{HashMap, HashSet}; use std::rc::Rc; -/// Tests if the node has any leading comment that will be placed on its own line. -pub fn has_leading_own_line_comment( - node: &SyntaxNode, - comments: &Comments, -) -> bool { - comments - .leading_comments(node) - .iter() - .any(|comment| comment.lines_after() > 0) -} - #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub enum CommentKind { /// An inline comment that can appear between any two tokens and doesn't contain any line breaks. @@ -189,6 +178,10 @@ impl DecoratedComment { .expect("Expected token to have a parent node.") } + pub fn piece(&self) -> &SyntaxTriviaPieceComments { + &self.comment + } + pub fn enclosing_token(&self) -> SyntaxToken { self.comment.as_piece().token() } @@ -277,6 +270,8 @@ pub trait CommentStyle { /// Returns `true` if a comment with the given `text` is a `rome-ignore format:` suppression comment. fn is_suppression(text: &str) -> bool; + fn is_open_parentheses(kind: ::Kind) -> bool; + /// Returns the (kind)[CommentKind] of the comment fn get_comment_kind(comment: &SyntaxTriviaPieceComments) -> CommentKind; @@ -355,7 +350,9 @@ impl Comments { /// Returns `true` if the given `node` has any leading or trailing comments. #[inline] pub fn has_comments(&self, node: &SyntaxNode) -> bool { - self.has_leading_comments(node) || self.has_trailing_comments(node) + self.has_leading_comments(node) + || self.has_trailing_comments(node) + || self.has_node_dangling_trivia(node) } /// Returns `true` if the given [node] has any leading comments. @@ -367,6 +364,13 @@ impl Comments { !self.leading_comments(node).is_empty() } + /// Tests if the node has any leading comment that will be placed on its own line. + pub fn has_leading_own_line_comment(&self, node: &SyntaxNode) -> bool { + self.leading_comments(node) + .iter() + .any(|comment| comment.lines_after() > 0) + } + /// Returns `true` if the given [node] has any trailing comments. /// By default, a comment is a node's trailing comment if: /// * the next sibling is a token @@ -381,6 +385,14 @@ impl Comments { node.tokens().any(|token| self.has_dangling_trivia(&token)) } + pub fn node_dangling_comments<'a>( + &'a self, + node: &'a SyntaxNode, + ) -> impl Iterator> + 'a { + node.tokens() + .flat_map(|token| self.dangling_comments(&token)) + } + /// Returns the [node]'s leading comments. #[inline] pub fn leading_comments(&self, node: &SyntaxNode) -> &[SourceComment] { @@ -393,9 +405,22 @@ impl Comments { self.data.trailing_comments.get(node) } - pub fn node_comments(&self, node: &SyntaxNode) -> impl Iterator> { + pub fn leading_trailing_comments( + &self, + node: &SyntaxNode, + ) -> impl Iterator> { + self.leading_comments(node) + .iter() + .chain(self.trailing_comments(node).iter()) + } + + pub fn node_comments<'a>( + &'a self, + node: &'a SyntaxNode, + ) -> impl Iterator> + 'a { self.leading_comments(node) .iter() + .chain(self.node_dangling_comments(node)) .chain(self.trailing_comments(node).iter()) } @@ -749,6 +774,15 @@ where } // Any comment following now is preceded by 'token' and not a node. + + // TODO: Difference to prettier: + // - Prettier keeps the preceding node around, even if there has been a token in between + // - Prettier keeps the following node around, even if there has been a token in between + // - a = b; a is still the preceding even if positioned at b; + // - same with following. It takes the first node that follows (and belongs to the same parent) + // They then use a breakTie in situations where there are preceding and following nodes set. + // Emphasis the importance of nodes even more. Reduces the places where dangling comments can appear + // Has mainly become relevant for trailing comments. Is there also a noticable difference for leading? self.preceding_node = None; self.following_node = None; self.last_token = Some(token); @@ -798,10 +832,17 @@ where match (comment.take_preceding_node(), comment.take_following_node()) { (Some(preceding), Some(following)) => { + // Always attach suppression with the next node. if Language::CommentStyle::is_suppression(comment.comment.text()) { self.node_comments .insert_leading_comment(following, comment.into()); } else { + // Attach comments with both preceding and following node to the preceding + // because there's a line break separating it from the following node. + // ```javascript + // a; // comment + // b + // ``` self.node_comments .insert_trailing_comment(preceding, comment.into()); } @@ -823,6 +864,13 @@ where } } else { match (comment.take_following_node(), comment.take_preceding_node()) { + // Following always wins for a leading comment + // ```javascript + // a; + // // comment + // b + // ``` + // attach the comment to the `b` expression statement (Some(following), _) => { self.node_comments .insert_leading_comment(following, comment.into()); diff --git a/crates/rome_formatter/src/lib.rs b/crates/rome_formatter/src/lib.rs index f6ada102607..8595e3d2a23 100644 --- a/crates/rome_formatter/src/lib.rs +++ b/crates/rome_formatter/src/lib.rs @@ -53,8 +53,8 @@ pub use builders::{ soft_line_break_or_space, soft_line_indent_or_space, space, text, BestFitting, }; pub use comments::{ - has_leading_own_line_comment, CommentKind, CommentPosition, CommentStyle, Comments, - DanglingTrivia, DecoratedComment, SourceComment, + CommentKind, CommentPosition, CommentStyle, Comments, DanglingTrivia, DecoratedComment, + SourceComment, }; pub use format_element::{normalize_newlines, FormatElement, Text, Verbatim, LINE_TERMINATORS}; pub use group_id::GroupId; diff --git a/crates/rome_js_formatter/src/builders.rs b/crates/rome_js_formatter/src/builders.rs index 5498f619356..a8830ae4e9a 100644 --- a/crates/rome_js_formatter/src/builders.rs +++ b/crates/rome_js_formatter/src/builders.rs @@ -98,12 +98,10 @@ impl Format for FormatVerbatimNode<'_> { let leading_comments = comments.leading_comments(self.node); let outside_trimmed_range = leading_comments.partition_point(|comment| { - dbg!(trimmed_source_range) - .contains_range(dbg!(source_range(f, comment.piece().text_range()))) + trimmed_source_range + .contains_range(source_range(f, comment.piece().text_range())) }); - dbg!(outside_trimmed_range); - write!( f, [FormatLeadingComments::Comments( @@ -138,8 +136,6 @@ impl Format for FormatVerbatimNode<'_> { }, ); - dbg!(&original_source); - dynamic_text( &normalize_newlines(&original_source, LINE_TERMINATORS), self.node.text_trimmed_range().start(), diff --git a/crates/rome_js_formatter/src/comments.rs b/crates/rome_js_formatter/src/comments.rs index c0cb0c2dfa4..e72827bb22b 100644 --- a/crates/rome_js_formatter/src/comments.rs +++ b/crates/rome_js_formatter/src/comments.rs @@ -6,11 +6,12 @@ use rome_js_syntax::suppression::{parse_suppression_comment, SuppressionCategory use rome_js_syntax::{ JsAnyStatement, JsArrayAssignmentPattern, JsArrayBindingPattern, JsArrayExpression, JsArrayHole, JsBlockStatement, JsBreakStatement, JsCallArgumentList, JsCallArguments, - JsContinueStatement, JsDefaultClause, JsFunctionBody, JsLanguage, JsSyntaxKind, JsSyntaxNode, - JsSyntaxToken, + JsContinueStatement, JsDefaultClause, JsFunctionBody, JsLanguage, JsModule, JsScript, + JsSyntaxKind, JsSyntaxNode, JsSyntaxToken, }; use rome_rowan::{ - declare_node_union, match_ast, SyntaxKind, SyntaxResult, SyntaxTriviaPieceComments, TextLen, + declare_node_union, match_ast, Language, SyntaxKind, SyntaxResult, SyntaxSlot, + SyntaxTriviaPieceComments, TextLen, }; #[derive(Default)] @@ -133,6 +134,10 @@ impl CommentStyle for JsCommentStyle { .any(|(category, _)| category == SuppressionCategory::Format) } + fn is_open_parentheses(kind: ::Kind) -> bool { + kind == JsSyntaxKind::L_PAREN + } + fn get_comment_kind(comment: &SyntaxTriviaPieceComments) -> CommentKind { if comment.text().starts_with("/*") { if comment.has_newline() { @@ -151,7 +156,39 @@ impl CommentStyle for JsCommentStyle { let enclosing_node = comment.enclosing_node(); if let Some(following_node) = comment.following_node() { + if comment.is_trailing_token_trivia() && is_type_comment(comment.piece()) { + return CommentPosition::Leading { + node: following_node.clone(), + comment, + }; + } + match following_node.kind() { + JsSyntaxKind::JS_SCRIPT | JsSyntaxKind::JS_MODULE => { + let (directives, statements) = match_ast! { + match following_node { + JsScript(script) => { + (script.directives().into_syntax_list(), script.statements().into_syntax_list()) + }, + JsModule(module) => { + (module.directives().into_syntax_list(), module.items().into_syntax_list()) + }, + _ => unreachable!() + } + }; + + let first_inner = directives.first().or_else(|| statements.first()); + + return match first_inner { + // Attach any leading comments to the first statement or directive in a module or script to prevent + // that a rome-ignore comment on the first statement ignores the whole file. + Some(SyntaxSlot::Node(node)) => CommentPosition::Leading { node, comment }, + Some(SyntaxSlot::Token(_)) | Some(SyntaxSlot::Empty) | None => { + CommentPosition::Default(comment) + } + }; + } + // Move leading comments in front of the `{` inside of the block // ``` // if (test) /* comment */ { @@ -323,7 +360,7 @@ impl CommentStyle for JsCommentStyle { } } - match dbg!(comment.following_token().kind()) { + match comment.following_token().kind() { JsSyntaxKind::R_BRACK => { // Handles comments before the `]` token of an array // @@ -420,6 +457,25 @@ impl CommentStyle for JsCommentStyle { } } +/// Returns `true` if `comment` is a [Closure type comment](https://github.com/google/closure-compiler/wiki/Types-in-the-Closure-Type-System) +/// or [TypeScript type comment](https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html#type) +pub(crate) fn is_type_comment(comment: &SyntaxTriviaPieceComments) -> bool { + let text = comment.text(); + + // Must be a `/**` comment + if !text.starts_with("/**") { + return false; + } + + text.trim_start_matches("/**") + .trim_end_matches("*/") + .split_whitespace() + .any(|word| match word.strip_prefix("@type") { + Some(after) => after.is_empty() || after.starts_with('{'), + None => false, + }) +} + declare_node_union! { JsAnyArrayLike = JsArrayExpression | JsArrayAssignmentPattern diff --git a/crates/rome_js_formatter/src/js/expressions/arrow_function_expression.rs b/crates/rome_js_formatter/src/js/expressions/arrow_function_expression.rs index 92d2ec6af88..5ae824ab9f3 100644 --- a/crates/rome_js_formatter/src/js/expressions/arrow_function_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/arrow_function_expression.rs @@ -107,10 +107,10 @@ impl FormatNodeRule for FormatJsArrowFunctionExpressi _ => false, }, }; - let body_has_leading_line_comment = - dbg!(f.context().comments().leading_comments(body.syntax())) - .iter() - .any(|comment| comment.lines_after() > 0); + let body_has_leading_line_comment = f + .context() + .comments() + .has_leading_own_line_comment(body.syntax()); // Add parentheses to avoid confusion between `a => b ? c : d` and `a <= b ? c : d` // but only if the body isn't an object/function or class expression because parentheses are always required in that diff --git a/crates/rome_js_formatter/src/js/statements/return_statement.rs b/crates/rome_js_formatter/src/js/statements/return_statement.rs index 6ce3f0fd665..469cee82d28 100644 --- a/crates/rome_js_formatter/src/js/statements/return_statement.rs +++ b/crates/rome_js_formatter/src/js/statements/return_statement.rs @@ -1,9 +1,7 @@ use crate::prelude::*; use crate::utils::{FormatWithSemicolon, JsAnyBinaryLikeExpression, JsAnyBinaryLikeLeftExpression}; -use rome_formatter::{ - format_args, has_leading_own_line_comment, write, Comments, CstFormatContext, -}; +use rome_formatter::{format_args, write, Comments, CstFormatContext}; use crate::parentheses::get_expression_left_side; use rome_js_syntax::{ @@ -145,7 +143,7 @@ fn has_argument_leading_comments( let mut current: Option = Some(argument.clone().into()); while let Some(expression) = current { - if has_leading_own_line_comment(expression.syntax(), comments) { + if comments.has_leading_own_line_comment(expression.syntax()) { return true; } diff --git a/crates/rome_js_formatter/src/jsx/attribute/expression_attribute_value.rs b/crates/rome_js_formatter/src/jsx/attribute/expression_attribute_value.rs index 6563f02ea82..47ec8b2b677 100644 --- a/crates/rome_js_formatter/src/jsx/attribute/expression_attribute_value.rs +++ b/crates/rome_js_formatter/src/jsx/attribute/expression_attribute_value.rs @@ -1,8 +1,9 @@ use crate::prelude::*; -use rome_formatter::{format_args, write}; +use rome_formatter::{format_args, write, Comments, CstFormatContext}; use rome_js_syntax::{ - JsAnyExpression, JsxAnyTag, JsxExpressionAttributeValue, JsxExpressionAttributeValueFields, + JsAnyExpression, JsLanguage, JsxAnyTag, JsxExpressionAttributeValue, + JsxExpressionAttributeValueFields, }; #[derive(Debug, Clone, Default)] @@ -22,7 +23,7 @@ impl FormatNodeRule for FormatJsxExpressionAttribut let expression = expression?; - let should_inline = should_inline_jsx_expression(&expression); + let should_inline = should_inline_jsx_expression(&expression, f.context().comments()); if should_inline { write!( @@ -74,10 +75,13 @@ impl FormatNodeRule for FormatJsxExpressionAttribut /// ] /// } /> /// ``` -pub(crate) fn should_inline_jsx_expression(expression: &JsAnyExpression) -> bool { +pub(crate) fn should_inline_jsx_expression( + expression: &JsAnyExpression, + comments: &Comments, +) -> bool { use JsAnyExpression::*; - if expression.syntax().has_comments_direct() { + if comments.has_comments(expression.syntax()) { return false; } @@ -94,7 +98,7 @@ pub(crate) fn should_inline_jsx_expression(expression: &JsAnyExpression) -> bool JsAwaitExpression(await_expression) => match await_expression.argument() { Ok(JsxTagExpression(argument)) => { matches!(argument.tag(), Ok(JsxAnyTag::JsxElement(_))) - && should_inline_jsx_expression(&argument.into()) + && should_inline_jsx_expression(&argument.into(), comments) } _ => false, }, diff --git a/crates/rome_js_formatter/src/jsx/auxiliary/expression_child.rs b/crates/rome_js_formatter/src/jsx/auxiliary/expression_child.rs index c43175a0a21..d3a49de16db 100644 --- a/crates/rome_js_formatter/src/jsx/auxiliary/expression_child.rs +++ b/crates/rome_js_formatter/src/jsx/auxiliary/expression_child.rs @@ -3,7 +3,7 @@ use crate::prelude::*; use crate::prelude::{format_args, write}; use crate::utils::JsAnyBinaryLikeExpression; -use rome_formatter::{group, FormatResult}; +use rome_formatter::{group, CstFormatContext, FormatResult}; use rome_js_syntax::{JsAnyExpression, JsxExpressionChild, JsxExpressionChildFields}; #[derive(Debug, Clone, Default)] @@ -23,7 +23,7 @@ impl FormatNodeRule for FormatJsxExpressionChild { { true } else { - should_inline_jsx_expression(expression) + should_inline_jsx_expression(expression, f.context().comments()) } }); diff --git a/crates/rome_js_formatter/src/jsx/tag/opening_element.rs b/crates/rome_js_formatter/src/jsx/tag/opening_element.rs index 27358b9a9c6..8f966190de8 100644 --- a/crates/rome_js_formatter/src/jsx/tag/opening_element.rs +++ b/crates/rome_js_formatter/src/jsx/tag/opening_element.rs @@ -1,9 +1,9 @@ use crate::prelude::*; -use rome_formatter::write; +use rome_formatter::{write, Comments, CstFormatContext}; use rome_js_syntax::{ - JsSyntaxToken, JsxAnyAttribute, JsxAnyAttributeValue, JsxAnyElementName, JsxAttributeList, - JsxOpeningElement, JsxSelfClosingElement, JsxString, TsTypeArguments, + JsLanguage, JsSyntaxToken, JsxAnyAttribute, JsxAnyAttributeValue, JsxAnyElementName, + JsxAttributeList, JsxOpeningElement, JsxSelfClosingElement, JsxString, TsTypeArguments, }; use rome_rowan::{declare_node_union, SyntaxResult}; @@ -22,7 +22,7 @@ declare_node_union! { impl Format for JsxAnyOpeningElement { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { - let layout = self.compute_layout()?; + let layout = self.compute_layout(f.context().comments())?; let l_angle_token = self.l_angle_token()?; let name = self.name()?; @@ -150,23 +150,24 @@ impl JsxAnyOpeningElement { matches!(self, JsxAnyOpeningElement::JsxSelfClosingElement(_)) } - fn compute_layout(&self) -> SyntaxResult { + fn compute_layout( + &self, + comments: &Comments, + ) -> SyntaxResult { let attributes = self.attributes(); - let l_angle_token = self.l_angle_token()?; let name = self.name()?; - let name_has_comments = l_angle_token.has_trailing_comments() - || name.syntax().has_comments_direct() + let name_has_comments = comments.has_comments(name.syntax()) || self .type_arguments() - .map_or(false, |arguments| arguments.syntax().has_comments_direct()); + .map_or(false, |arguments| comments.has_comments(arguments.syntax())); let layout = if self.is_self_closing() && attributes.is_empty() && !name_has_comments { OpeningElementLayout::Inline } else if attributes.len() == 1 && attributes.iter().all(|attribute| { is_single_line_string_literal_attribute(&attribute) - && !attribute.syntax().has_comments_direct() + && !comments.has_comments(attribute.syntax()) }) && !name_has_comments { diff --git a/crates/rome_js_formatter/src/lib.rs b/crates/rome_js_formatter/src/lib.rs index 78c4f2cb1dc..2b3061c3c24 100644 --- a/crates/rome_js_formatter/src/lib.rs +++ b/crates/rome_js_formatter/src/lib.rs @@ -758,10 +758,9 @@ function() { // use this test check if your snippet prints as you wish, without using a snapshot fn quick_test() { let src = r#" - b; -// https://github.com/babel/babel/pull/11640 - -a + , c; +fnString = // Comment0 + // Comment1 + 'some' + 'long' + 'string'; "#; let syntax = SourceType::tsx(); let tree = parse(src, 0, syntax); diff --git a/crates/rome_js_formatter/src/syntax_rewriter.rs b/crates/rome_js_formatter/src/syntax_rewriter.rs index 79e1040a754..4626b68a996 100644 --- a/crates/rome_js_formatter/src/syntax_rewriter.rs +++ b/crates/rome_js_formatter/src/syntax_rewriter.rs @@ -1,3 +1,4 @@ +use crate::comments::is_type_comment; use crate::parentheses::JsAnyParenthesized; use crate::TextRange; use rome_formatter::{TransformSourceMap, TransformSourceMapBuilder}; @@ -7,8 +8,7 @@ use rome_js_syntax::{ }; use rome_rowan::syntax::SyntaxTrivia; use rome_rowan::{ - AstNode, SyntaxKind, SyntaxRewriter, SyntaxToken, SyntaxTriviaPiece, SyntaxTriviaPieceComments, - VisitNodeSignal, + AstNode, SyntaxKind, SyntaxRewriter, SyntaxToken, SyntaxTriviaPiece, VisitNodeSignal, }; use std::iter::FusedIterator; @@ -380,25 +380,6 @@ fn has_type_cast_comment_or_skipped(trivia: &SyntaxTrivia) -> bool { }) } -/// Returns `true` if `comment` is a [Closure type comment](https://github.com/google/closure-compiler/wiki/Types-in-the-Closure-Type-System) -/// or [TypeScript type comment](https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html#type) -fn is_type_comment(comment: &SyntaxTriviaPieceComments) -> bool { - let text = comment.text(); - - // Must be a `/**` comment - if !text.starts_with("/**") { - return false; - } - - text.trim_start_matches("/**") - .trim_end_matches("*/") - .split_whitespace() - .any(|word| match word.strip_prefix("@type") { - Some(after) => after.is_empty() || after.starts_with('{'), - None => false, - }) -} - fn chain_pieces(first: F, second: S) -> ChainTriviaPiecesIterator where F: Iterator>, diff --git a/crates/rome_js_formatter/src/utils/assignment_like.rs b/crates/rome_js_formatter/src/utils/assignment_like.rs index 591169c2767..95cc6c42cab 100644 --- a/crates/rome_js_formatter/src/utils/assignment_like.rs +++ b/crates/rome_js_formatter/src/utils/assignment_like.rs @@ -1,13 +1,9 @@ use crate::js::auxiliary::initializer_clause::FormatJsInitializerClauseOptions; -use crate::parentheses::get_expression_left_side; use crate::prelude::*; use crate::utils::member_chain::is_member_call_chain; use crate::utils::object::write_member_name; -use crate::utils::{JsAnyBinaryLikeExpression, JsAnyBinaryLikeLeftExpression}; -use rome_formatter::{ - format_args, has_leading_own_line_comment, write, Comments, CstFormatContext, FormatOptions, - VecBuffer, -}; +use crate::utils::JsAnyBinaryLikeExpression; +use rome_formatter::{format_args, write, Comments, CstFormatContext, FormatOptions, VecBuffer}; use rome_js_syntax::JsAnyLiteralExpression; use rome_js_syntax::{ JsAnyAssignmentPattern, JsAnyBindingPattern, JsAnyCallArgument, JsAnyClassMemberName, @@ -828,7 +824,7 @@ impl JsAnyAssignmentLike { let result = if let Some(expression) = right.as_expression() { should_break_after_operator(&expression, comments)? } else { - has_leading_own_line_comment(right.syntax(), comments) + comments.has_leading_own_line_comment(right.syntax()) }; Ok(result) @@ -840,21 +836,8 @@ pub(crate) fn should_break_after_operator( right: &JsAnyExpression, comments: &Comments, ) -> SyntaxResult { - // Traverse from the right expression to the left most node and check if any has a leading comment - // that causes a line break. - let mut current: JsAnyBinaryLikeLeftExpression = right.clone().into(); - loop { - if has_leading_own_line_comment(current.syntax(), comments) { - return Ok(true); - } - - if let JsAnyBinaryLikeLeftExpression::JsAnyExpression(expression) = current { - if let Some(left) = get_expression_left_side(&expression) { - current = left; - continue; - } - } - break; + if comments.has_leading_own_line_comment(right.syntax()) { + return Ok(true); } let result = match right { @@ -1059,7 +1042,9 @@ fn is_poorly_breakable_member_or_call_chain( let is_breakable_call = match args.len() { 0 => false, 1 => match args.iter().next() { - Some(first_argument) => !is_short_argument(first_argument?, threshold)?, + Some(first_argument) => { + !is_short_argument(first_argument?, threshold, f.context().comments())? + } None => false, }, _ => true, @@ -1086,8 +1071,12 @@ fn is_poorly_breakable_member_or_call_chain( /// We need it to decide if `JsCallExpression` with the argument is breakable or not /// If the argument is short the function call isn't breakable /// [Prettier applies]: https://github.com/prettier/prettier/blob/a043ac0d733c4d53f980aa73807a63fc914f23bd/src/language-js/print/assignment.js#L374 -fn is_short_argument(argument: JsAnyCallArgument, threshold: u16) -> SyntaxResult { - if argument.syntax().has_comments_direct() { +fn is_short_argument( + argument: JsAnyCallArgument, + threshold: u16, + comments: &Comments, +) -> SyntaxResult { + if comments.has_comments(argument.syntax()) { return Ok(false); } @@ -1098,7 +1087,7 @@ fn is_short_argument(argument: JsAnyCallArgument, threshold: u16) -> SyntaxResul identifier.name()?.value_token()?.text_trimmed().len() <= threshold as usize } JsAnyExpression::JsUnaryExpression(unary_expression) => { - let has_comments = unary_expression.argument()?.syntax().has_comments_direct(); + let has_comments = comments.has_comments(unary_expression.argument()?.syntax()); unary_expression.is_signed_numeric_literal()? && !has_comments } diff --git a/crates/rome_js_formatter/src/utils/conditional.rs b/crates/rome_js_formatter/src/utils/conditional.rs index 308df283ca9..c00538fbf91 100644 --- a/crates/rome_js_formatter/src/utils/conditional.rs +++ b/crates/rome_js_formatter/src/utils/conditional.rs @@ -204,7 +204,7 @@ impl FormatRule for FormatJsAnyConditionalRule { let has_block_comment = |syntax: &JsSyntaxNode| { comments - .node_comments(syntax) + .leading_trailing_comments(syntax) .any(|comment| comment.kind().is_block()) }; diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/assignment/lone-arg.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/assignment/lone-arg.js.snap index 4878cd4a8d8..9598c1f063b 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/assignment/lone-arg.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/assignment/lone-arg.js.snap @@ -1,5 +1,7 @@ --- source: crates/rome_js_formatter/tests/prettier_tests.rs +info: + test_file: js/assignment/lone-arg.js --- # Input @@ -33,15 +35,8 @@ const bifornCringerMoshedPerplexSawderGlyphsHb = someBigFunctionName(`foo ```diff --- Prettier +++ Rome -@@ -10,10 +10,11 @@ - someBigFunctionName("foo")("bar"); - - if (true) { -- node.id = this.flowParseTypeAnnotatableIdentifier( -- /*allowPrimitiveOverride*/ true, -- ); -+ node.id = -+ this.flowParseTypeAnnotatableIdentifier(/*allowPrimitiveOverride*/ true); +@@ -15,5 +15,7 @@ + ); } -const bifornCringerMoshedPerplexSawderGlyphsHb = someBigFunctionName(`foo @@ -67,8 +62,9 @@ const bifornCringerMoshedPerplexSawderGlyphsHa = someBigFunctionName("foo")("bar"); if (true) { - node.id = - this.flowParseTypeAnnotatableIdentifier(/*allowPrimitiveOverride*/ true); + node.id = this.flowParseTypeAnnotatableIdentifier( + /*allowPrimitiveOverride*/ true, + ); } const bifornCringerMoshedPerplexSawderGlyphsHb = someBigFunctionName( diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/break-calls/react.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/break-calls/react.js.snap index 88f82d61260..62cbdfb792e 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/break-calls/react.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/break-calls/react.js.snap @@ -1,5 +1,7 @@ --- source: crates/rome_js_formatter/tests/prettier_tests.rs +info: + test_file: js/break-calls/react.js --- # Input @@ -77,29 +79,15 @@ function Comp5() { ```diff --- Prettier +++ Rome -@@ -41,64 +41,66 @@ - } - - function MyComponent(props) { -- useEffect( -- () => { -- console.log("some code", props.foo); -- }, +@@ -45,7 +45,6 @@ + () => { + console.log("some code", props.foo); + }, - -- // We need to disable the eslint warning here, -- // because of some complicated reason. -- // eslint-disable line react-hooks/exhaustive-deps -- [], -- ); -+ useEffect(() => { -+ console.log("some code", props.foo); -+ }, -+ // We need to disable the eslint warning here, -+ // because of some complicated reason. -+ // eslint-disable line react-hooks/exhaustive-deps -+ []); - - return null; + // We need to disable the eslint warning here, + // because of some complicated reason. + // eslint-disable line react-hooks/exhaustive-deps +@@ -56,49 +55,54 @@ } function Comp1() { @@ -236,13 +224,15 @@ function helloWorldWithReact() { } function MyComponent(props) { - useEffect(() => { - console.log("some code", props.foo); - }, - // We need to disable the eslint warning here, - // because of some complicated reason. - // eslint-disable line react-hooks/exhaustive-deps - []); + useEffect( + () => { + console.log("some code", props.foo); + }, + // We need to disable the eslint warning here, + // because of some complicated reason. + // eslint-disable line react-hooks/exhaustive-deps + [], + ); return null; } From a3a20bd53a9018230a40a67a96b5a1b323486e15 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Wed, 7 Sep 2022 08:51:22 +0200 Subject: [PATCH 03/45] refactor(formatter): Member chain part 1 --- crates/rome_formatter/src/comments.rs | 18 ++- crates/rome_formatter/src/formatter.rs | 12 +- .../src/js/bindings/parameters.rs | 37 +++-- .../src/js/classes/extends_clause.rs | 7 +- .../src/js/expressions/call_expression.rs | 4 + .../expressions/parenthesized_expression.rs | 6 +- .../src/js/lists/parameter_list.rs | 9 +- .../src/js/lists/template_element_list.rs | 16 +- .../src/js/lists/variable_declarator_list.rs | 2 +- .../src/js/unknown/unknown.rs | 1 + .../src/jsx/lists/child_list.rs | 20 ++- .../ts/declarations/interface_declaration.rs | 8 +- .../src/utils/format_class.rs | 14 +- crates/rome_js_formatter/src/utils/jsx.rs | 42 +++--- .../src/utils/member_chain/chain_member.rs | 81 +++++----- .../src/utils/member_chain/groups.rs | 67 ++++----- .../src/utils/member_chain/mod.rs | 76 ++++++---- .../js/first-argument-expansion/test.js.snap | 29 +--- .../function_expression.js.snap | 141 ------------------ 19 files changed, 271 insertions(+), 319 deletions(-) delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/function-first-param/function_expression.js.snap diff --git a/crates/rome_formatter/src/comments.rs b/crates/rome_formatter/src/comments.rs index acccd7a7271..7c716e1da92 100644 --- a/crates/rome_formatter/src/comments.rs +++ b/crates/rome_formatter/src/comments.rs @@ -287,7 +287,7 @@ pub trait CommentStyle { /// * the dangling comments of a token /// /// Cloning `comments` is cheap as it only involves bumping a reference counter. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct Comments { /// The use of a [Rc] is necessary to achieve that [Comments] has a lifetime that is independent from the [crate::Formatter]. /// Having independent lifetimes is necessary to support the use case where a (formattable object)[crate::Format] @@ -547,6 +547,22 @@ struct CommentsData { checked_suppressions: RefCell>>, } +impl Default for CommentsData +where + L: Language, +{ + fn default() -> Self { + Self { + is_suppression: |_| false, + leading_comments: Default::default(), + trailing_comments: Default::default(), + dangling_trivia: Default::default(), + #[cfg(debug_assertions)] + checked_suppressions: Default::default(), + } + } +} + impl std::fmt::Debug for CommentsData { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let mut debug_comments: Vec> = Vec::new(); diff --git a/crates/rome_formatter/src/formatter.rs b/crates/rome_formatter/src/formatter.rs index a2b816f0acd..897433cd0cb 100644 --- a/crates/rome_formatter/src/formatter.rs +++ b/crates/rome_formatter/src/formatter.rs @@ -2,7 +2,8 @@ use crate::buffer::BufferSnapshot; use crate::builders::{FillBuilder, JoinBuilder, JoinNodesBuilder, Line}; use crate::prelude::*; use crate::{ - Arguments, Buffer, FormatContext, FormatState, FormatStateSnapshot, GroupId, VecBuffer, + Arguments, Buffer, Comments, CstFormatContext, FormatContext, FormatState, FormatStateSnapshot, + GroupId, VecBuffer, }; /// Handles the formatting of a CST and stores the context how the CST should be formatted (user preferences). @@ -213,6 +214,15 @@ where } } +impl Formatter<'_, Context> +where + Context: CstFormatContext, +{ + pub fn comments(&self) -> &Comments { + self.context().comments() + } +} + impl Buffer for Formatter<'_, Context> { type Context = Context; diff --git a/crates/rome_js_formatter/src/js/bindings/parameters.rs b/crates/rome_js_formatter/src/js/bindings/parameters.rs index ea01247a909..0b5ffc37a69 100644 --- a/crates/rome_js_formatter/src/js/bindings/parameters.rs +++ b/crates/rome_js_formatter/src/js/bindings/parameters.rs @@ -1,5 +1,5 @@ use crate::prelude::*; -use rome_formatter::write; +use rome_formatter::{write, Comments, CstFormatContext}; use crate::js::expressions::call_arguments::is_test_call_expression; use crate::js::lists::parameter_list::{ @@ -9,7 +9,7 @@ use crate::js::lists::parameter_list::{ use crate::builders::format_delimited; use rome_js_syntax::{ JsAnyConstructorParameter, JsAnyFormalParameter, JsCallExpression, JsConstructorParameters, - JsParameters, JsSyntaxKind, JsSyntaxToken, TsType, + JsLanguage, JsParameters, JsSyntaxKind, JsSyntaxToken, TsType, }; use rome_rowan::{declare_node_union, SyntaxResult}; @@ -40,9 +40,12 @@ impl Format for FormatJsAnyParameters { Err(_) => false, }); - let can_hug = should_hug_function_parameters(self)? && !has_any_decorated_parameter; + let can_hug = should_hug_function_parameters(self, f.context().comments())? + && !has_any_decorated_parameter; - let layout = if can_hug || self.is_in_test_call()? { + let layout = if list.is_empty() { + ParameterLayout::NoParameters + } else if can_hug || self.is_in_test_call()? { ParameterLayout::Hug } else { ParameterLayout::Default @@ -52,6 +55,16 @@ impl Format for FormatJsAnyParameters { let r_paren_token = self.r_paren_token()?; match layout { + ParameterLayout::NoParameters => { + write!( + f, + [ + l_paren_token.format(), + format_dangling_trivia(&r_paren_token), + r_paren_token.format() + ] + ) + } ParameterLayout::Hug => { write!( f, @@ -139,6 +152,11 @@ impl FormatJsAnyParameters { #[derive(Copy, Debug, Clone, Eq, PartialEq)] pub enum ParameterLayout { + /// ```javascript + /// function test() {} + /// ``` + NoParameters, + /// Enforce that the opening and closing parentheses aren't separated from the first token of the parameter. /// For example, to enforce that the `{` and `}` of an object expression are formatted on the same line /// as the `(` and `)` tokens even IF the object expression itself breaks across multiple lines. @@ -164,7 +182,10 @@ pub enum ParameterLayout { Default, } -fn should_hug_function_parameters(parameters: &FormatJsAnyParameters) -> FormatResult { +fn should_hug_function_parameters( + parameters: &FormatJsAnyParameters, + comments: &Comments, +) -> FormatResult { use rome_js_syntax::{ JsAnyBinding::*, JsAnyBindingPattern::*, JsAnyExpression::*, JsAnyFormalParameter::*, JsAnyParameter::*, @@ -176,14 +197,10 @@ fn should_hug_function_parameters(parameters: &FormatJsAnyParameters) -> FormatR return Ok(false); } - if parameters.r_paren_token()?.has_leading_comments() { - return Ok(false); - } - // SAFETY: Safe because of the length check above let only_parameter = list.first().unwrap()?; - if only_parameter.syntax().has_comments_direct() { + if comments.has_comments(only_parameter.syntax()) { return Ok(false); } diff --git a/crates/rome_js_formatter/src/js/classes/extends_clause.rs b/crates/rome_js_formatter/src/js/classes/extends_clause.rs index f5be4a36388..8cdda609637 100644 --- a/crates/rome_js_formatter/src/js/classes/extends_clause.rs +++ b/crates/rome_js_formatter/src/js/classes/extends_clause.rs @@ -22,10 +22,11 @@ impl FormatNodeRule for FormatJsExtendsClause { let content = format_with(|f| write!(f, [super_class.format(), type_arguments.format()])); + let comments = f.comments(); let has_trailing_comments = if let Some(type_arguments) = &type_arguments { - type_arguments.syntax().has_trailing_comments() + comments.has_trailing_comments(type_arguments.syntax()) } else { - super_class.syntax().has_trailing_comments() + comments.has_trailing_comments(super_class.syntax()) }; if node @@ -33,7 +34,7 @@ impl FormatNodeRule for FormatJsExtendsClause { .grand_parent() .map_or(false, |p| p.kind() == JS_ASSIGNMENT_EXPRESSION) { - if super_class.syntax().has_leading_comments() || has_trailing_comments { + if comments.has_leading_comments(super_class.syntax()) || has_trailing_comments { write!(f, [text("("), &content, text(")")]) } else { let content = content.memoized(); diff --git a/crates/rome_js_formatter/src/js/expressions/call_expression.rs b/crates/rome_js_formatter/src/js/expressions/call_expression.rs index 3d1d3560d68..ca3700af23a 100644 --- a/crates/rome_js_formatter/src/js/expressions/call_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/call_expression.rs @@ -17,6 +17,10 @@ impl FormatNodeRule for FormatJsCallExpression { fn needs_parentheses(&self, item: &JsCallExpression) -> bool { item.needs_parentheses() } + + fn prints_comments(&self, _item: &JsCallExpression) -> bool { + true + } } impl NeedsParentheses for JsCallExpression { diff --git a/crates/rome_js_formatter/src/js/expressions/parenthesized_expression.rs b/crates/rome_js_formatter/src/js/expressions/parenthesized_expression.rs index 6b773b44a84..89cd60ea3c8 100644 --- a/crates/rome_js_formatter/src/js/expressions/parenthesized_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/parenthesized_expression.rs @@ -1,5 +1,5 @@ use crate::prelude::*; -use rome_formatter::{format_args, write}; +use rome_formatter::{format_args, write, CstFormatContext}; use crate::parentheses::NeedsParentheses; use rome_js_syntax::{ @@ -23,9 +23,9 @@ impl FormatNodeRule for FormatJsParenthesizedExpressi let l_paren_token = l_paren_token?; let expression = expression?; + let comments = f.context().comments(); - let should_hug = !(expression.syntax().has_comments_direct() - || l_paren_token.has_trailing_comments()) + let should_hug = !comments.has_comments(expression.syntax()) && (matches!( expression, JsAnyExpression::JsObjectExpression(_) | JsAnyExpression::JsArrayExpression(_) diff --git a/crates/rome_js_formatter/src/js/lists/parameter_list.rs b/crates/rome_js_formatter/src/js/lists/parameter_list.rs index 330846276e3..88cd070480b 100644 --- a/crates/rome_js_formatter/src/js/lists/parameter_list.rs +++ b/crates/rome_js_formatter/src/js/lists/parameter_list.rs @@ -40,7 +40,7 @@ impl<'a> FormatJsAnyParameterList<'a> { impl Format for FormatJsAnyParameterList<'_> { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { match self.layout { - None | Some(ParameterLayout::Default) => { + None | Some(ParameterLayout::Default) | Some(ParameterLayout::NoParameters) => { // The trailing separator is disallowed if the last element in the list is a rest parameter let has_trailing_rest = match self.list.last() { Some(elem) => matches!( @@ -132,6 +132,13 @@ impl JsAnyParameterList { } } + pub fn is_empty(&self) -> bool { + match self { + JsAnyParameterList::JsParameterList(parameters) => parameters.is_empty(), + JsAnyParameterList::JsConstructorParameterList(parameters) => parameters.is_empty(), + } + } + pub fn first(&self) -> Option> { Some(match self { JsAnyParameterList::JsParameterList(parameters) => { diff --git a/crates/rome_js_formatter/src/js/lists/template_element_list.rs b/crates/rome_js_formatter/src/js/lists/template_element_list.rs index bbeebef3dcc..82fdbeb6664 100644 --- a/crates/rome_js_formatter/src/js/lists/template_element_list.rs +++ b/crates/rome_js_formatter/src/js/lists/template_element_list.rs @@ -3,6 +3,7 @@ use crate::js::expressions::template_element::{AnyTemplateElement, TemplateEleme use crate::context::TabWidth; use crate::prelude::*; +use rome_formatter::{Comments, CstFormatContext}; use rome_js_syntax::{ JsAnyExpression, JsAnyLiteralExpression, JsAnyTemplateElement, JsLanguage, JsTemplateElementList, TsAnyTemplateElement, TsTemplateElementList, @@ -28,7 +29,7 @@ pub(crate) enum AnyTemplateElementList { impl Format for AnyTemplateElementList { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { - let layout = if self.is_simple() { + let layout = if self.is_simple(f.comments()) { TemplateElementLayout::SingleLine } else { TemplateElementLayout::Fit @@ -87,7 +88,7 @@ impl AnyTemplateElementList { /// Simple expressions are: /// * Identifiers: `this`, `a` /// * Members: `a.b`, `a[b]`, `a.b[c].d`, `a.b[5]`, `a.b["test"]` - fn is_simple(&self) -> bool { + fn is_simple(&self, comments: &Comments) -> bool { match self { AnyTemplateElementList::JsTemplateElementList(list) => { if list.is_empty() { @@ -101,7 +102,9 @@ impl AnyTemplateElementList { expression_elements.all(|expression_element| { match expression_element.expression() { - Ok(expression) => is_simple_member_expression(expression).unwrap_or(false), + Ok(expression) => { + is_simple_member_expression(expression, comments).unwrap_or(false) + } Err(_) => false, } }) @@ -142,11 +145,14 @@ declare_node_union! { AnyTemplateElementOrChunk = AnyTemplateElement | AnyTemplateChunkElement } -fn is_simple_member_expression(expression: JsAnyExpression) -> SyntaxResult { +fn is_simple_member_expression( + expression: JsAnyExpression, + comments: &Comments, +) -> SyntaxResult { let mut current = expression; loop { - if current.syntax().has_comments_direct() { + if comments.has_comments(current.syntax()) { return Ok(false); } diff --git a/crates/rome_js_formatter/src/js/lists/variable_declarator_list.rs b/crates/rome_js_formatter/src/js/lists/variable_declarator_list.rs index 025233ceec5..0e5778c814c 100644 --- a/crates/rome_js_formatter/src/js/lists/variable_declarator_list.rs +++ b/crates/rome_js_formatter/src/js/lists/variable_declarator_list.rs @@ -44,7 +44,7 @@ impl FormatRule for FormatJsVariableDeclaratorList { None => return Err(FormatError::SyntaxError), }; - if length == 1 && !first_declarator.syntax().has_leading_comments() { + if length == 1 && !f.comments().has_leading_comments(first_declarator.syntax()) { return write!(f, [format_first_declarator]); } diff --git a/crates/rome_js_formatter/src/js/unknown/unknown.rs b/crates/rome_js_formatter/src/js/unknown/unknown.rs index c6418e9531c..9ebb568714a 100644 --- a/crates/rome_js_formatter/src/js/unknown/unknown.rs +++ b/crates/rome_js_formatter/src/js/unknown/unknown.rs @@ -11,6 +11,7 @@ impl FormatNodeRule for FormatJsUnknown { format_unknown_node(node.syntax()).fmt(formatter) } + // FIXME verify if this is indeed necessary? fn prints_comments(&self, _item: &JsUnknown) -> bool { true } diff --git a/crates/rome_js_formatter/src/jsx/lists/child_list.rs b/crates/rome_js_formatter/src/jsx/lists/child_list.rs index ad9d75ad099..26d94d401b3 100644 --- a/crates/rome_js_formatter/src/jsx/lists/child_list.rs +++ b/crates/rome_js_formatter/src/jsx/lists/child_list.rs @@ -4,8 +4,10 @@ use crate::utils::jsx::{ JsxRawSpace, JsxSpace, }; use crate::JsFormatter; -use rome_formatter::{format_args, write, FormatRuleWithOptions, VecBuffer}; -use rome_js_syntax::{JsxAnyChild, JsxChildList}; +use rome_formatter::{ + format_args, write, Comments, CstFormatContext, FormatRuleWithOptions, VecBuffer, +}; +use rome_js_syntax::{JsLanguage, JsxAnyChild, JsxChildList}; #[derive(Debug, Clone, Default)] pub struct FormatJsxChildList { @@ -27,7 +29,7 @@ impl FormatRule for FormatJsxChildList { fn fmt(&self, list: &JsxChildList, f: &mut JsFormatter) -> FormatResult<()> { self.disarm_debug_assertions(list, f); - let children_meta = self.children_meta(list); + let children_meta = self.children_meta(list, f.context().comments()); let layout = self.layout(children_meta); let multiline_layout = if children_meta.meaningful_text { @@ -42,7 +44,7 @@ impl FormatRule for FormatJsxChildList { let mut last: Option = None; let mut force_multiline = layout.is_multiline(); - let mut children = jsx_split_children(list)?; + let mut children = jsx_split_children(list, f.context().comments())?; // Trim trailing new lines if let Some(JsxChild::EmptyLine | JsxChild::Newline) = children.last() { @@ -219,7 +221,9 @@ impl FormatJsxChildList { for child in node { match child { - JsxExpressionChild(expression) if is_whitespace_jsx_expression(&expression) => { + JsxExpressionChild(expression) + if is_whitespace_jsx_expression(&expression, f.context().comments()) => + { f.context() .comments() .mark_suppression_checked(expression.syntax()); @@ -275,7 +279,7 @@ impl FormatJsxChildList { } /// Computes additional meta data about the children by iterating once over all children. - fn children_meta(&self, list: &JsxChildList) -> ChildrenMeta { + fn children_meta(&self, list: &JsxChildList, comments: &Comments) -> ChildrenMeta { let mut has_expression = false; let mut meta = ChildrenMeta::default(); @@ -285,7 +289,9 @@ impl FormatJsxChildList { match child { JsxElement(_) | JsxFragment(_) | JsxSelfClosingElement(_) => meta.any_tag = true, - JsxExpressionChild(expression) if !is_whitespace_jsx_expression(&expression) => { + JsxExpressionChild(expression) + if !is_whitespace_jsx_expression(&expression, comments) => + { meta.multiple_expressions = has_expression; has_expression = true; } diff --git a/crates/rome_js_formatter/src/ts/declarations/interface_declaration.rs b/crates/rome_js_formatter/src/ts/declarations/interface_declaration.rs index df9aa6ede7f..9bf1cbefeca 100644 --- a/crates/rome_js_formatter/src/ts/declarations/interface_declaration.rs +++ b/crates/rome_js_formatter/src/ts/declarations/interface_declaration.rs @@ -41,9 +41,9 @@ impl FormatNodeRule for FormatTsInterfaceDeclaration { Ok(()) }); - let should_indent_extends_only = type_parameters - .as_ref() - .map_or(false, |params| !params.syntax().has_trailing_comments()); + let should_indent_extends_only = type_parameters.as_ref().map_or(false, |params| { + !f.comments().has_trailing_comments(params.syntax()) + }); let format_extends = format_with(|f| { if let Some(extends_clause) = &extends_clause { @@ -91,7 +91,7 @@ impl FormatNodeRule for FormatTsInterfaceDeclaration { // } // } - let id_has_trailing_comments = id.syntax().has_trailing_comments(); + let id_has_trailing_comments = f.comments().has_trailing_comments(id.syntax()); if id_has_trailing_comments || extends_clause.is_some() { if should_indent_extends_only { write!( diff --git a/crates/rome_js_formatter/src/utils/format_class.rs b/crates/rome_js_formatter/src/utils/format_class.rs index 80cb27a1830..9771628006d 100644 --- a/crates/rome_js_formatter/src/utils/format_class.rs +++ b/crates/rome_js_formatter/src/utils/format_class.rs @@ -1,28 +1,28 @@ use crate::builders::format_delimited; use crate::prelude::*; -use rome_formatter::write; -use rome_js_syntax::JsAnyClass; +use rome_formatter::{write, Comments}; +use rome_js_syntax::{JsAnyClass, JsLanguage}; pub struct FormatClass<'a> { class: &'a JsAnyClass, } impl FormatClass<'_> { - fn should_group(&self) -> FormatResult { + fn should_group(&self, comments: &Comments) -> FormatResult { if let Some(id) = self.class.id()? { - if id.syntax().has_trailing_comments() { + if comments.has_trailing_comments(id.syntax()) { return Ok(true); } } if let Some(type_parameters) = self.class.type_parameters() { - if type_parameters.syntax().has_trailing_comments() { + if comments.has_trailing_comments(type_parameters.syntax()) { return Ok(true); } } if let Some(extends) = self.class.extends_clause() { - if extends.syntax().has_trailing_comments() { + if comments.has_trailing_comments(extends.syntax()) { return Ok(true); } } @@ -51,7 +51,7 @@ impl Format for FormatClass<'_> { let class_token = self.class.class_token()?; let members = self.class.members(); - let group_mode = self.should_group()?; + let group_mode = self.should_group(f.comments())?; if let Some(abstract_token) = abstract_token { write!(f, [abstract_token.format(), space()])?; diff --git a/crates/rome_js_formatter/src/utils/jsx.rs b/crates/rome_js_formatter/src/utils/jsx.rs index 9c25c018cd0..ecc73215e77 100644 --- a/crates/rome_js_formatter/src/utils/jsx.rs +++ b/crates/rome_js_formatter/src/utils/jsx.rs @@ -1,9 +1,10 @@ use crate::context::QuoteStyle; use crate::prelude::*; -use rome_formatter::{format_args, write}; +use rome_formatter::{format_args, write, Comments}; use rome_js_syntax::{ - JsAnyExpression, JsAnyLiteralExpression, JsComputedMemberExpression, JsStaticMemberExpression, - JsSyntaxKind, JsxAnyChild, JsxExpressionChild, JsxTagExpression, TextLen, + JsAnyExpression, JsAnyLiteralExpression, JsComputedMemberExpression, JsLanguage, + JsStaticMemberExpression, JsSyntaxKind, JsxAnyChild, JsxExpressionChild, JsxTagExpression, + TextLen, }; use rome_rowan::{SyntaxResult, SyntaxTokenText, TextRange, TextSize}; use std::iter::{FusedIterator, Peekable}; @@ -142,7 +143,10 @@ impl Format for JsxRawSpace { } } -pub(crate) fn is_whitespace_jsx_expression(child: &JsxExpressionChild) -> bool { +pub(crate) fn is_whitespace_jsx_expression( + child: &JsxExpressionChild, + comments: &Comments, +) -> bool { match child.expression() { Some(JsAnyExpression::JsAnyLiteralExpression( JsAnyLiteralExpression::JsStringLiteralExpression(literal), @@ -155,10 +159,8 @@ pub(crate) fn is_whitespace_jsx_expression(child: &JsxExpressionChild) -> bool { (Ok(l_curly_token), Ok(value_token), Ok(r_curly_token)) => { let is_empty = matches!(value_token.text_trimmed(), "\" \"" | "' '"); - let has_comments = l_curly_token.has_trailing_comments() - || r_curly_token.has_leading_comments() - || value_token.has_leading_non_whitespace_trivia() - || value_token.has_trailing_comments(); + let has_comments = comments.has_dangling_trivia(&r_curly_token) + || comments.has_comments(literal.syntax()); is_empty && !has_comments } @@ -169,7 +171,10 @@ pub(crate) fn is_whitespace_jsx_expression(child: &JsxExpressionChild) -> bool { } } -pub(crate) fn jsx_split_children(children: I) -> SyntaxResult> +pub(crate) fn jsx_split_children( + children: I, + comments: &Comments, +) -> SyntaxResult> where I: IntoIterator, { @@ -244,7 +249,7 @@ where } JsxAnyChild::JsxExpressionChild(child) => { - if is_whitespace_jsx_expression(&child) { + if is_whitespace_jsx_expression(&child, comments) { match result.last() { Some(JsxChild::Whitespace) => { // Ignore @@ -402,6 +407,7 @@ impl FusedIterator for JsxSplitChunksIterator<'_> {} #[cfg(test)] mod tests { use crate::utils::jsx::{jsx_split_children, JsxChild, JsxSplitChunksIterator, JsxTextChunk}; + use rome_formatter::Comments; use rome_js_parser::parse; use rome_js_syntax::{JsxChildList, JsxText, SourceType}; use rome_rowan::{AstNode, TextSize}; @@ -497,7 +503,7 @@ mod tests { fn split_children_words_only() { let child_list = parse_jsx_children("a b c"); - let children = jsx_split_children(&child_list).unwrap(); + let children = jsx_split_children(&child_list, &Comments::default()).unwrap(); assert_eq!(3, children.len()); assert_word(&children[0], "a"); @@ -509,7 +515,7 @@ mod tests { fn split_non_meaningful_text() { let child_list = parse_jsx_children(" \n "); - let children = jsx_split_children(&child_list).unwrap(); + let children = jsx_split_children(&child_list, &Comments::default()).unwrap(); assert_eq!(children, vec![]); } @@ -518,7 +524,7 @@ mod tests { fn split_non_meaningful_leading_multiple_lines() { let child_list = parse_jsx_children(" \n \n "); - let children = jsx_split_children(&child_list).unwrap(); + let children = jsx_split_children(&child_list, &Comments::default()).unwrap(); assert_eq!(children, vec![JsxChild::EmptyLine]); } @@ -527,7 +533,7 @@ mod tests { fn split_meaningful_whitespace() { let child_list = parse_jsx_children(" "); - let children = jsx_split_children(&child_list).unwrap(); + let children = jsx_split_children(&child_list, &Comments::default()).unwrap(); assert_eq!(children, vec![JsxChild::Whitespace]); } @@ -536,7 +542,7 @@ mod tests { fn split_children_leading_newlines() { let child_list = parse_jsx_children(" \n a b"); - let children = jsx_split_children(&child_list).unwrap(); + let children = jsx_split_children(&child_list, &Comments::default()).unwrap(); assert_eq!(3, children.len()); assert_eq!(children[0], JsxChild::Newline); @@ -548,7 +554,7 @@ mod tests { fn split_children_trailing_whitespace() { let child_list = parse_jsx_children("a b \t "); - let children = jsx_split_children(&child_list).unwrap(); + let children = jsx_split_children(&child_list, &Comments::default()).unwrap(); assert_eq!(3, children.len()); assert_word(&children[0], "a"); @@ -560,7 +566,7 @@ mod tests { fn split_children_trailing_newline() { let child_list = parse_jsx_children("a b \n \t "); - let children = jsx_split_children(&child_list).unwrap(); + let children = jsx_split_children(&child_list, &Comments::default()).unwrap(); assert_eq!(3, children.len()); assert_word(&children[0], "a"); @@ -572,7 +578,7 @@ mod tests { fn split_children_empty_expression() { let child_list = parse_jsx_children(r#"a{' '}c{" "}"#); - let children = jsx_split_children(&child_list).unwrap(); + let children = jsx_split_children(&child_list, &Comments::default()).unwrap(); assert_eq!( 4, diff --git a/crates/rome_js_formatter/src/utils/member_chain/chain_member.rs b/crates/rome_js_formatter/src/utils/member_chain/chain_member.rs index 9b689a2cfed..3d74b494279 100644 --- a/crates/rome_js_formatter/src/utils/member_chain/chain_member.rs +++ b/crates/rome_js_formatter/src/utils/member_chain/chain_member.rs @@ -14,35 +14,33 @@ use std::fmt::Debug; #[derive(Clone, Debug)] pub(crate) enum ChainMember { /// Holds onto a [rome_js_syntax::JsStaticMemberExpression] - StaticMember(JsStaticMemberExpression), + StaticMember { + expression: JsStaticMemberExpression, + root: bool, + }, + /// Holds onto a [rome_js_syntax::JsCallExpression] - CallExpression(JsCallExpression), + CallExpression { + expression: JsCallExpression, + root: bool, + }, + /// Holds onto a [rome_js_syntax::JsComputedMemberExpression] - ComputedMember(JsComputedMemberExpression), + ComputedMember { + expression: JsComputedMemberExpression, + root: bool, + }, + /// Any other node that are not [rome_js_syntax::JsCallExpression] or [rome_js_syntax::JsStaticMemberExpression] /// Are tracked using this variant Node(JsSyntaxNode), } impl ChainMember { - pub(crate) fn has_trailing_comments(&self) -> bool { - self.syntax().has_trailing_comments() - } - - /// Returns true if the member any of it's ancestor parentheses nodes has any leading comments. - pub(crate) fn has_leading_comments(&self) -> SyntaxResult { - let has_operator_comment = match self { - ChainMember::StaticMember(node) => node.operator_token()?.has_leading_comments(), - _ => false, - }; - - Ok(self.syntax().has_leading_comments() || has_operator_comment) - } - /// checks if the current node is a [rome_js_syntax::JsCallExpression], [rome_js_syntax::JsImportExpression] or a [rome_js_syntax::JsNewExpression] pub fn is_loose_call_expression(&self) -> bool { match self { - ChainMember::CallExpression(_) => true, + ChainMember::CallExpression { .. } => true, ChainMember::Node(node) => { JsImportCallExpression::can_cast(node.kind()) | JsNewExpression::can_cast(node.kind()) @@ -53,15 +51,15 @@ impl ChainMember { pub(crate) fn syntax(&self) -> &JsSyntaxNode { match self { - ChainMember::StaticMember(node) => node.syntax(), - ChainMember::CallExpression(node) => node.syntax(), - ChainMember::ComputedMember(node) => node.syntax(), + ChainMember::StaticMember { expression, .. } => expression.syntax(), + ChainMember::CallExpression { expression, .. } => expression.syntax(), + ChainMember::ComputedMember { expression, .. } => expression.syntax(), ChainMember::Node(node) => node, } } pub const fn is_computed_expression(&self) -> bool { - matches!(self, ChainMember::ComputedMember(..)) + matches!(self, ChainMember::ComputedMember { .. }) } pub(crate) fn is_this_expression(&self) -> bool { @@ -102,10 +100,10 @@ impl ChainMember { || text.starts_with('$') } - if let ChainMember::StaticMember(static_member, ..) = self { + if let ChainMember::StaticMember { expression, .. } = self { if check_left_hand_side { if let JsAnyExpression::JsIdentifierExpression(identifier_expression) = - static_member.object()? + expression.object()? { let value_token = identifier_expression.name()?.value_token()?; let text = value_token.text_trimmed(); @@ -114,7 +112,7 @@ impl ChainMember { Ok(false) } } else { - Ok(check_str(static_member.member()?.text().as_str())) + Ok(check_str(expression.member()?.text().as_str())) } } else if let ChainMember::Node(node, ..) = self { if let Some(identifier_expression) = JsIdentifierExpression::cast(node.clone()) { @@ -130,9 +128,9 @@ impl ChainMember { } pub(crate) fn has_short_name(&self, tab_width: TabWidth) -> SyntaxResult { - if let ChainMember::StaticMember(static_member, ..) = self { + if let ChainMember::StaticMember { expression, .. } = self { if let JsAnyExpression::JsIdentifierExpression(identifier_expression) = - static_member.object()? + expression.object()? { let value_token = identifier_expression.name()?.value_token()?; let text = value_token.text_trimmed(); @@ -149,34 +147,45 @@ impl ChainMember { impl Format for ChainMember { fn fmt(&self, f: &mut JsFormatter) -> FormatResult<()> { match self { - ChainMember::StaticMember(static_member) => { + ChainMember::StaticMember { expression, root } => { let JsStaticMemberExpressionFields { // Formatted as part of the previous item object: _, operator_token, member, - } = static_member.as_fields(); - write![f, [operator_token.format(), member.format()]] + } = expression.as_fields(); + write![ + f, + [ + (!root).then_some(format_leading_comments(expression.syntax())), + operator_token.format(), + member.format(), + (!root).then_some(format_trailing_comments(expression.syntax())) + ] + ] } - ChainMember::CallExpression(call_expression) => { + + ChainMember::CallExpression { expression, root } => { let JsCallExpressionFields { // Formatted as part of the previous item callee: _, optional_chain_token, type_arguments, arguments, - } = call_expression.as_fields(); + } = expression.as_fields(); write!( f, [ + (!root).then_some(format_leading_comments(expression.syntax())), optional_chain_token.format(), type_arguments.format(), - arguments.format() + arguments.format(), + (!root).then_some(format_trailing_comments(expression.syntax())) ] ) } - ChainMember::ComputedMember(computed_member) => { + ChainMember::ComputedMember { expression, root } => { let JsComputedMemberExpressionFields { // Formatted as part of the previous item object: _, @@ -184,14 +193,16 @@ impl Format for ChainMember { l_brack_token, member, r_brack_token, - } = computed_member.as_fields(); + } = expression.as_fields(); write!( f, [ + (!root).then_some(format_leading_comments(expression.syntax())), optional_chain_token.format(), l_brack_token.format(), member.format(), r_brack_token.format(), + (!root).then_some(format_trailing_comments(expression.syntax())) ] ) } diff --git a/crates/rome_js_formatter/src/utils/member_chain/groups.rs b/crates/rome_js_formatter/src/utils/member_chain/groups.rs index 949fc2b1411..b146877f47b 100644 --- a/crates/rome_js_formatter/src/utils/member_chain/groups.rs +++ b/crates/rome_js_formatter/src/utils/member_chain/groups.rs @@ -2,8 +2,8 @@ use crate::context::TabWidth; use crate::parentheses::NeedsParentheses; use crate::prelude::*; use crate::utils::member_chain::chain_member::ChainMember; -use rome_formatter::write; -use rome_js_syntax::JsCallExpression; +use rome_formatter::{write, Comments}; +use rome_js_syntax::{JsCallExpression, JsLanguage}; use rome_rowan::SyntaxResult; use std::mem; @@ -94,42 +94,35 @@ pub(super) struct MemberChainGroups { impl MemberChainGroups { /// This function checks if the current grouping should be merged with the first group. - pub fn should_merge(&self, head_group: &MemberChainGroup) -> SyntaxResult { + pub fn should_merge( + &self, + head_group: &MemberChainGroup, + comments: &Comments, + ) -> SyntaxResult { Ok(!self.groups.len() >= 1 && self.should_not_wrap(head_group)? && !self.groups[0] .members .first() - .map_or(false, |item| item.has_trailing_comments())) + .map_or(false, |item| comments.has_trailing_comments(item.syntax()))) } /// Checks if the groups contain comments. - pub fn has_comments(&self) -> SyntaxResult { - let mut has_leading_comments = false; - - let flat_groups = self.groups.iter().flat_map(|item| item.members.iter()); - for item in flat_groups { - if item.has_leading_comments()? { - has_leading_comments = true; - break; - } - } + pub fn has_comments(&self, comments: &Comments) -> SyntaxResult { + let mut members = self.groups.iter().flat_map(|item| item.members.iter()); - let has_trailing_comments = self - .groups - .iter() - .flat_map(|item| item.members.iter()) - .any(|item| item.has_trailing_comments()); + let has_comments = members.any(|item| { + comments.has_trailing_comments(item.syntax()) + || comments.has_leading_comments(item.syntax()) + }); let cutoff_has_leading_comments = if self.groups.len() >= self.cutoff as usize { let group = self.groups.get(self.cutoff as usize); if let Some(group) = group { let first_item = group.members.first(); - if let Some(first_item) = first_item { - first_item.has_leading_comments()? - } else { - false - } + first_item.map_or(false, |first_item| { + comments.has_leading_comments(first_item.syntax()) + }) } else { false } @@ -137,7 +130,7 @@ impl MemberChainGroups { false }; - Ok(has_leading_comments || has_trailing_comments || cutoff_has_leading_comments) + Ok(has_comments || cutoff_has_leading_comments) } /// Filters the stack of [FlattenItem] and return only the ones that @@ -147,8 +140,8 @@ impl MemberChainGroups { .iter() .flat_map(|group| group.members.iter()) .filter_map(|item| { - if let ChainMember::CallExpression(call_expression, ..) = item { - Some(call_expression) + if let ChainMember::CallExpression { expression, .. } = item { + Some(expression) } else { None } @@ -198,8 +191,9 @@ impl MemberChainGroups { pub(crate) fn should_merge_with_first_group( &mut self, head_group: &MemberChainGroup, + comments: &Comments, ) -> Option> { - if self.should_merge(head_group).unwrap_or(false) { + if self.should_merge(head_group, comments).unwrap_or(false) { let mut new_groups = self.groups.split_off(1); // self.groups is now the head (one element), while `new_groups` is a new vector without the // first element. @@ -214,8 +208,11 @@ impl MemberChainGroups { /// Here we check if the length of the groups exceeds the cutoff or there are comments /// This function is the inverse of the prettier function /// [Prettier applies]: https://github.com/prettier/prettier/blob/a043ac0d733c4d53f980aa73807a63fc914f23bd/src/language-js/print/member-chain.js#L342 - pub(crate) fn is_member_call_chain(&self) -> SyntaxResult { - Ok(self.groups.len() > self.cutoff as usize || self.has_comments()?) + pub(crate) fn is_member_call_chain( + &self, + comments: &Comments, + ) -> SyntaxResult { + Ok(self.groups.len() > self.cutoff as usize || self.has_comments(comments)?) } pub(super) fn iter(&self) -> impl Iterator { @@ -247,8 +244,10 @@ impl MemberChainGroup { self.members.extend(group) } - pub(super) fn has_comments(&self) -> bool { - self.members.iter().any(|item| item.has_trailing_comments()) + pub(super) fn has_comments(&self, comments: &Comments) -> bool { + self.members + .iter() + .any(|item| comments.has_trailing_comments(item.syntax())) } } @@ -263,8 +262,8 @@ impl Format for MemberChainGroup { let last = self.members.last(); let needs_parens = last.map_or(false, |last| match last { - ChainMember::StaticMember(member) => member.needs_parentheses(), - ChainMember::ComputedMember(member) => member.needs_parentheses(), + ChainMember::StaticMember { expression, .. } => expression.needs_parentheses(), + ChainMember::ComputedMember { expression, .. } => expression.needs_parentheses(), _ => false, }); diff --git a/crates/rome_js_formatter/src/utils/member_chain/mod.rs b/crates/rome_js_formatter/src/utils/member_chain/mod.rs index e227afb660e..5de5197d1c4 100644 --- a/crates/rome_js_formatter/src/utils/member_chain/mod.rs +++ b/crates/rome_js_formatter/src/utils/member_chain/mod.rs @@ -117,15 +117,19 @@ use rome_js_syntax::{JsAnyExpression, JsCallExpression, JsExpressionStatement, J use rome_rowan::{AstNode, SyntaxResult}; #[derive(Debug, Clone)] -pub(crate) struct MemberChain { +pub(crate) struct MemberChain<'a> { calls_count: usize, + root: &'a JsCallExpression, head: MemberChainGroup, tail: MemberChainGroups, } -impl MemberChain { +impl MemberChain<'_> { /// It tells if the groups should be break on multiple lines - pub(crate) fn groups_should_break(&self) -> FormatResult { + pub(crate) fn groups_should_break( + &self, + comments: &Comments, + ) -> FormatResult { // Do not allow the group to break if it only contains a single call expression if self.calls_count <= 1 { return Ok(false); @@ -139,7 +143,8 @@ impl MemberChain { // TODO: add here will_break logic - let node_has_comments = self.tail.has_comments()? || self.head.has_comments(); + let node_has_comments = + self.tail.has_comments(comments)? || self.head.has_comments(comments); let should_break = node_has_comments || call_expressions_are_not_simple; @@ -161,12 +166,12 @@ impl MemberChain { } } -impl Format for MemberChain { +impl Format for MemberChain<'_> { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { // TODO use Alternatives once available write!(f, [&self.head])?; - if self.groups_should_break()? { + if self.groups_should_break(f.context().comments())? { write!( f, [indent(&format_args!( @@ -184,10 +189,10 @@ impl Format for MemberChain { } } -pub(crate) fn get_member_chain( - call_expression: &JsCallExpression, +pub(crate) fn get_member_chain<'a>( + call_expression: &'a JsCallExpression, f: &mut JsFormatter, -) -> SyntaxResult { +) -> SyntaxResult> { let mut chain_members = vec![]; let parent_is_expression_statement = call_expression.syntax().parent().map_or(false, |parent| { @@ -198,6 +203,7 @@ pub(crate) fn get_member_chain( &mut chain_members, call_expression.clone().into(), f.context().comments(), + true, )?; chain_members.push(root); @@ -228,7 +234,9 @@ pub(crate) fn get_member_chain( // Here we check if the first element of Groups::groups can be moved inside the head. // If so, then we extract it and concatenate it together with the head. - if let Some(group_to_merge) = rest_of_groups.should_merge_with_first_group(&head_group) { + if let Some(group_to_merge) = + rest_of_groups.should_merge_with_first_group(&head_group, f.comments()) + { let group_to_merge = group_to_merge .into_iter() .flat_map(|group| group.into_members()); @@ -237,6 +245,7 @@ pub(crate) fn get_member_chain( Ok(MemberChain { calls_count, + root: call_expression, head: head_group, tail: rest_of_groups, }) @@ -260,7 +269,7 @@ fn compute_first_group_index(flatten_items: &[ChainMember]) -> usize { // - `something[1][2][4]` // - `something[1]()[3]()` // - `something()[2].something.else[0]` - ChainMember::CallExpression(_) | ChainMember::ComputedMember(_) => true, + ChainMember::CallExpression { .. } | ChainMember::ComputedMember { .. } => true, // SAFETY: The check `flatten_items[index + 1]` will never panic at runtime because // 1. The array will always have at least two items @@ -291,11 +300,11 @@ fn compute_first_group_index(flatten_items: &[ChainMember]) -> usize { // and the next one is a call expression... the `matches!` fails and the loop is stopped. // // The last element of the array is always a `CallExpression`, which allows us to avoid the overflow of the array. - ChainMember::StaticMember(_) => { + ChainMember::StaticMember { .. } => { let next_flatten_item = &flatten_items[index + 1]; matches!( next_flatten_item, - ChainMember::StaticMember(_) | ChainMember::ComputedMember(_) + ChainMember::StaticMember { .. } | ChainMember::ComputedMember { .. } ) } _ => false, @@ -323,10 +332,10 @@ fn compute_groups( let mut groups_builder = MemberChainGroupsBuilder::new(in_expression_statement, f.options().tab_width()); for item in flatten_items { - let has_trailing_comments = item.syntax().has_trailing_comments(); + let has_trailing_comments = f.comments().has_trailing_comments(item.syntax()); match item { - ChainMember::StaticMember(_) => { + ChainMember::StaticMember { .. } => { // if we have seen a JsCallExpression, we want to close the group. // The resultant group will be something like: [ . , then, () ]; // `.` and `then` belong to the previous StaticMemberExpression, @@ -340,14 +349,14 @@ fn compute_groups( groups_builder.start_or_continue_group(item); } } - ChainMember::CallExpression(_) => { + ChainMember::CallExpression { .. } => { let is_loose_call_expression = item.is_loose_call_expression(); groups_builder.start_or_continue_group(item); if is_loose_call_expression { has_seen_call_expression = true; } } - ChainMember::ComputedMember(_) => { + ChainMember::ComputedMember { .. } => { groups_builder.start_or_continue_group(item); } ChainMember::Node(_) => groups_builder.continue_group(item), @@ -373,6 +382,7 @@ fn flatten_member_chain( queue: &mut Vec, node: JsAnyExpression, comments: &Comments, + root: bool, ) -> SyntaxResult { use JsAnyExpression::*; @@ -380,32 +390,44 @@ fn flatten_member_chain( return Ok(ChainMember::Node(node.into_syntax())); } - match node { + let member = match node { JsCallExpression(call_expression) => { let callee = call_expression.callee()?; - let left = flatten_member_chain(queue, callee, comments)?; + let left = flatten_member_chain(queue, callee, comments, false)?; queue.push(left); - Ok(ChainMember::CallExpression(call_expression)) + ChainMember::CallExpression { + expression: call_expression, + root, + } } + JsStaticMemberExpression(static_member) => { let object = static_member.object()?; - let left = flatten_member_chain(queue, object, comments)?; + let left = flatten_member_chain(queue, object, comments, false)?; queue.push(left); - Ok(ChainMember::StaticMember(static_member)) + ChainMember::StaticMember { + expression: static_member, + root, + } } JsComputedMemberExpression(computed_expression) => { let object = computed_expression.object()?; - let left = flatten_member_chain(queue, object, comments)?; + let left = flatten_member_chain(queue, object, comments, false)?; queue.push(left); - Ok(ChainMember::ComputedMember(computed_expression)) + ChainMember::ComputedMember { + expression: computed_expression, + root, + } } - expression => Ok(ChainMember::Node(expression.into_syntax())), - } + expression => ChainMember::Node(expression.into_syntax()), + }; + + Ok(member) } /// Here we check if the length of the groups exceeds the cutoff or there are comments @@ -417,5 +439,5 @@ pub fn is_member_call_chain( ) -> SyntaxResult { let chain = get_member_chain(expression, f)?; - chain.tail.is_member_call_chain() + chain.tail.is_member_call_chain(f.context().comments()) } diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/first-argument-expansion/test.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/first-argument-expansion/test.js.snap index 6592cec2c55..6ce9f1ef7a1 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/first-argument-expansion/test.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/first-argument-expansion/test.js.snap @@ -1,5 +1,7 @@ --- source: crates/rome_js_formatter/tests/prettier_tests.rs +info: + test_file: js/first-argument-expansion/test.js --- # Input @@ -232,23 +234,6 @@ func((args) => { compose( (a) => { -@@ -146,12 +132,10 @@ - 500, - ); - --setTimeout( -- /* blip */ function () { -- thing(); -- }, -- 500, --); -+setTimeout(/* blip */ -+function () { -+ thing(); -+}, 500); - - func( - (args) => { ``` # Output @@ -388,10 +373,12 @@ setTimeout( 500, ); -setTimeout(/* blip */ -function () { - thing(); -}, 500); +setTimeout( + /* blip */ function () { + thing(); + }, + 500, +); func( (args) => { diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/function-first-param/function_expression.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/function-first-param/function_expression.js.snap deleted file mode 100644 index ec1834e469a..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/js/function-first-param/function_expression.js.snap +++ /dev/null @@ -1,141 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -//https://github.com/prettier/prettier/issues/3002 -beep.boop().baz("foo", -{ - some: { - thing: { - nested: true - } - } -}, -{ another: { thing: true } }, -() => {}); - - -//https://github.com/prettier/prettier/issues/2984 -db.collection('indexOptionDefault').createIndex({ a: 1 }, { - indexOptionDefaults: true, - w: 2, - wtimeout: 1000 -}, function(err) { - test.equal(null, err); - test.deepEqual({ w: 2, wtimeout: 1000 }, commandResult.writeConcern); - - client.close(); - done(); -});``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -1,30 +1,34 @@ - //https://github.com/prettier/prettier/issues/3002 --beep.boop().baz( -- "foo", -- { -- some: { -- thing: { -- nested: true, -+beep -+ .boop() -+ .baz( -+ "foo", -+ { -+ some: { -+ thing: { -+ nested: true, -+ }, - }, - }, -- }, -- { another: { thing: true } }, -- () => {}, --); -+ { another: { thing: true } }, -+ () => {}, -+ ); - - //https://github.com/prettier/prettier/issues/2984 --db.collection("indexOptionDefault").createIndex( -- { a: 1 }, -- { -- indexOptionDefaults: true, -- w: 2, -- wtimeout: 1000, -- }, -- function (err) { -- test.equal(null, err); -- test.deepEqual({ w: 2, wtimeout: 1000 }, commandResult.writeConcern); -+db -+ .collection("indexOptionDefault") -+ .createIndex( -+ { a: 1 }, -+ { -+ indexOptionDefaults: true, -+ w: 2, -+ wtimeout: 1000, -+ }, -+ function (err) { -+ test.equal(null, err); -+ test.deepEqual({ w: 2, wtimeout: 1000 }, commandResult.writeConcern); - -- client.close(); -- done(); -- }, --); -+ client.close(); -+ done(); -+ }, -+ ); -``` - -# Output - -```js -//https://github.com/prettier/prettier/issues/3002 -beep - .boop() - .baz( - "foo", - { - some: { - thing: { - nested: true, - }, - }, - }, - { another: { thing: true } }, - () => {}, - ); - -//https://github.com/prettier/prettier/issues/2984 -db - .collection("indexOptionDefault") - .createIndex( - { a: 1 }, - { - indexOptionDefaults: true, - w: 2, - wtimeout: 1000, - }, - function (err) { - test.equal(null, err); - test.deepEqual({ w: 2, wtimeout: 1000 }, commandResult.writeConcern); - - client.close(); - done(); - }, - ); -``` - - - From eda700488582ef97376e42eaedce51335d0ae026 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Wed, 7 Sep 2022 10:54:08 +0200 Subject: [PATCH 04/45] change `dangling` comments to be for nodes rather than tokens --- crates/rome_formatter/src/comments.rs | 239 ++++++------------ crates/rome_formatter/src/lib.rs | 20 +- crates/rome_formatter/src/prelude.rs | 2 +- crates/rome_formatter/src/token.rs | 84 +++--- crates/rome_js_formatter/src/comments.rs | 24 +- .../src/js/auxiliary/function_body.rs | 2 +- .../src/js/bindings/parameters.rs | 2 +- .../src/js/expressions/array_expression.rs | 2 +- .../src/js/expressions/call_arguments.rs | 2 +- .../src/js/lists/template_element_list.rs | 2 +- .../src/js/statements/block_statement.rs | 4 +- .../src/js/statements/break_statement.rs | 5 +- .../src/js/statements/continue_statement.rs | 5 +- .../src/js/statements/for_statement.rs | 6 +- .../src/js/statements/return_statement.rs | 10 +- .../src/jsx/lists/child_list.rs | 1 - crates/rome_js_formatter/src/lib.rs | 6 +- crates/rome_js_formatter/src/utils/jsx.rs | 4 +- .../src/utils/member_chain/mod.rs | 14 +- .../src/utils/object_like.rs | 2 +- crates/rome_rowan/src/syntax.rs | 2 +- crates/rome_rowan/src/syntax/element.rs | 2 +- 22 files changed, 186 insertions(+), 254 deletions(-) diff --git a/crates/rome_formatter/src/comments.rs b/crates/rome_formatter/src/comments.rs index 7c716e1da92..fcf2eef463b 100644 --- a/crates/rome_formatter/src/comments.rs +++ b/crates/rome_formatter/src/comments.rs @@ -254,7 +254,7 @@ pub enum CommentPosition { }, Dangling { - token: SyntaxToken, + node: SyntaxNode, comment: DecoratedComment, }, @@ -337,22 +337,12 @@ impl Comments { } } - /// Returns `true` if the given [node] has - /// * any leading comments - /// * any trailing comments - /// * if any direct child token has any dangling trivia (either a skipped token trivia or a comment) - pub fn has_trivia(&self, node: &SyntaxNode) -> bool { - self.has_leading_comments(node) - || self.has_trailing_comments(node) - || self.has_node_dangling_trivia(node) - } - /// Returns `true` if the given `node` has any leading or trailing comments. #[inline] pub fn has_comments(&self, node: &SyntaxNode) -> bool { self.has_leading_comments(node) + || self.has_dangling_comments(node) || self.has_trailing_comments(node) - || self.has_node_dangling_trivia(node) } /// Returns `true` if the given [node] has any leading comments. @@ -371,32 +361,18 @@ impl Comments { .any(|comment| comment.lines_after() > 0) } - /// Returns `true` if the given [node] has any trailing comments. - /// By default, a comment is a node's trailing comment if: - /// * the next sibling is a token - /// * there's **no** line break between the node and this comment. + /// Returns the [node]'s leading comments. #[inline] - pub fn has_trailing_comments(&self, node: &SyntaxNode) -> bool { - !self.trailing_comments(node).is_empty() + pub fn leading_comments(&self, node: &SyntaxNode) -> &[SourceComment] { + self.data.leading_comments.get(node) } - /// Returns `true` if any direct child token of [node] has any dangling trivia. - pub fn has_node_dangling_trivia(&self, node: &SyntaxNode) -> bool { - node.tokens().any(|token| self.has_dangling_trivia(&token)) + pub fn has_dangling_comments(&self, node: &SyntaxNode) -> bool { + !self.dangling_comments(node).is_empty() } - pub fn node_dangling_comments<'a>( - &'a self, - node: &'a SyntaxNode, - ) -> impl Iterator> + 'a { - node.tokens() - .flat_map(|token| self.dangling_comments(&token)) - } - - /// Returns the [node]'s leading comments. - #[inline] - pub fn leading_comments(&self, node: &SyntaxNode) -> &[SourceComment] { - self.data.leading_comments.get(node) + pub fn dangling_comments(&self, node: &SyntaxNode) -> &[SourceComment] { + self.data.dangling_comments.get(node) } /// Returns the [node]'s trailing comments. @@ -405,6 +381,15 @@ impl Comments { self.data.trailing_comments.get(node) } + /// Returns `true` if the given [node] has any trailing comments. + /// By default, a comment is a node's trailing comment if: + /// * the next sibling is a token + /// * there's **no** line break between the node and this comment. + #[inline] + pub fn has_trailing_comments(&self, node: &SyntaxNode) -> bool { + !self.trailing_comments(node).is_empty() + } + pub fn leading_trailing_comments( &self, node: &SyntaxNode, @@ -414,43 +399,19 @@ impl Comments { .chain(self.trailing_comments(node).iter()) } - pub fn node_comments<'a>( + pub fn all<'a>( &'a self, node: &'a SyntaxNode, ) -> impl Iterator> + 'a { self.leading_comments(node) .iter() - .chain(self.node_dangling_comments(node)) + .chain(self.dangling_comments(node).iter()) .chain(self.trailing_comments(node).iter()) } - /// Skipped token trivia or comment trivia that - #[inline] - pub fn dangling_trivia(&self, token: &SyntaxToken) -> &[DanglingTrivia] { - self.data.dangling_trivia.get(token) - } - - pub fn dangling_comments( - &self, - token: &SyntaxToken, - ) -> impl Iterator> { - self.dangling_trivia(token) - .iter() - .filter_map(|trivia| match trivia { - DanglingTrivia::Comment(comment) => Some(comment), - DanglingTrivia::SkippedToken(_) => None, - }) - } - #[inline] - pub fn has_dangling_trivia(&self, token: &SyntaxToken) -> bool { - !self.dangling_trivia(token).is_empty() - } - - pub fn has_dangling_comments(&self, token: &SyntaxToken) -> bool { - let dangling_trivia = self.dangling_trivia(token); - - !dangling_trivia.is_empty() && dangling_trivia.iter().all(|trivia| trivia.is_comment()) + pub fn has_skipped(&self, token: &SyntaxToken) -> bool { + self.data.with_skipped.contains(token) } /// Returns `true` if the passed `node` has a leading suppression comment. @@ -529,11 +490,12 @@ struct CommentsData { /// Stores all leading node comments by node leading_comments: AppendOnlyMultiMap, SourceComment>, + dangling_comments: AppendOnlyMultiMap, SourceComment>, + /// Stores the trailing node comments by node trailing_comments: AppendOnlyMultiMap, SourceComment>, - /// Stores the dangling trivia by token - dangling_trivia: AppendOnlyMultiMap, DanglingTrivia>, + with_skipped: HashSet>, /// Stores all nodes for which [Comments::is_suppressed] has been called. /// This index of nodes that have been checked if they have a suppression comments is used to @@ -555,8 +517,9 @@ where Self { is_suppression: |_| false, leading_comments: Default::default(), + dangling_comments: Default::default(), trailing_comments: Default::default(), - dangling_trivia: Default::default(), + with_skipped: Default::default(), #[cfg(debug_assertions)] checked_suppressions: Default::default(), } @@ -576,21 +539,21 @@ impl std::fmt::Debug for CommentsData { ); } - for node in self.trailing_comments.keys() { + for node in self.dangling_comments.keys() { debug_comments.extend( - self.trailing_comments + self.dangling_comments .get(node) .iter() - .map(|comment| DebugComment::Trailing { node, comment }), + .map(|comment| DebugComment::Dangling { node, comment }), ); } - for token in self.dangling_trivia.keys() { + for node in self.trailing_comments.keys() { debug_comments.extend( - self.dangling_trivia - .get(token) + self.trailing_comments + .get(node) .iter() - .map(|trivia| DebugComment::Dangling { token, trivia }), + .map(|comment| DebugComment::Trailing { node, comment }), ); } @@ -611,21 +574,17 @@ enum DebugComment<'a, L: Language> { node: &'a SyntaxNode, }, Dangling { - trivia: &'a DanglingTrivia, - token: &'a SyntaxToken, + comment: &'a SourceComment, + node: &'a SyntaxNode, }, } impl DebugComment<'_, L> { fn start(&self) -> TextSize { match self { - DebugComment::Leading { comment, .. } | DebugComment::Trailing { comment, .. } => { - comment.piece.text_range().start() - } - DebugComment::Dangling { trivia, .. } => match trivia { - DanglingTrivia::Comment(comment) => comment.piece.text_range().start(), - DanglingTrivia::SkippedToken(skipped) => skipped.piece.text_range().start(), - }, + DebugComment::Leading { comment, .. } + | DebugComment::Trailing { comment, .. } + | DebugComment::Dangling { comment, .. } => comment.piece.text_range().start(), } } } @@ -638,16 +597,16 @@ impl std::fmt::Debug for DebugComment<'_, L> { .field("node", node) .field("comment", comment) .finish(), + DebugComment::Dangling { node, comment } => f + .debug_struct("Dangling") + .field("node", node) + .field("comment", comment) + .finish(), DebugComment::Trailing { node, comment } => f .debug_struct("Trailing") .field("node", node) .field("comment", comment) .finish(), - DebugComment::Dangling { trivia, token } => f - .debug_struct("Dangling") - .field("token", token) - .field("trivia", trivia) - .finish(), } } } @@ -655,7 +614,7 @@ impl std::fmt::Debug for DebugComment<'_, L> { #[derive(Debug, Default)] struct CommentsBuilderVisitor { node_comments: NodeCommentsBuilder, - dangling_trivia: DanglingTriviaBuilder, + with_skipped: HashSet>, preceding_node: Option>, following_node: Option>, last_token: Option>, @@ -668,7 +627,7 @@ where fn new() -> Self { Self { node_comments: NodeCommentsBuilder::default(), - dangling_trivia: DanglingTriviaBuilder::default(), + with_skipped: HashSet::new(), preceding_node: None, following_node: None, last_token: None, @@ -691,12 +650,15 @@ where } WalkEvent::Leave(node) => { + if node.kind().is_list() { + return; + } + if self.following_node.as_ref() == Some(&node) { self.following_node = None; } - if !node.kind().is_list() { - self.preceding_node = Some(node); - } + + self.preceding_node = Some(node); } } } @@ -712,7 +674,7 @@ where .filter_map(|piece| piece.as_comments()) { if let Some(last_comment) = last_comment.take() { - self.handle_comment(last_comment, &token); + self.handle_comment(last_comment); } last_comment = Some(DecoratedComment { @@ -734,41 +696,24 @@ where for leading in token.leading_trivia().pieces() { if leading.is_newline() { lines_before += 1; - } else if let Some(skipped) = leading.as_skipped() { + } else if leading.is_skipped() { if let Some(mut last_comment) = last_comment.take() { last_comment.lines_after = lines_before; - self.handle_comment(last_comment, &token); + self.handle_comment(last_comment); } - self.dangling_trivia.insert_trivia( - token.clone(), - DanglingTrivia::SkippedToken(SkippedTokenTrivia { - lines_before, - piece: skipped, - }), - ); + self.with_skipped.insert(token.clone()); lines_before = 0; has_skipped = true; } else if let Some(comment) = leading.as_comments() { if let Some(mut last_comment) = last_comment.take() { last_comment.lines_after = lines_before; - self.handle_comment(last_comment, &token); + self.handle_comment(last_comment); } let kind = Language::CommentStyle::get_comment_kind(&comment); - if has_skipped { - self.dangling_trivia.insert_trivia( - token.clone(), - DanglingTrivia::Comment(SourceComment { - lines_before, - // FIXME - lines_after: 0, - piece: comment, - kind, - }), - ); - } else { + if !has_skipped { last_comment = Some(DecoratedComment { preceding: self.preceding_node.clone(), following: self.following_node.clone(), @@ -786,7 +731,7 @@ where if let Some(mut last_comment) = last_comment.take() { last_comment.lines_after = lines_before; - self.handle_comment(last_comment, &token); + self.handle_comment(last_comment); } // Any comment following now is preceded by 'token' and not a node. @@ -804,11 +749,7 @@ where self.last_token = Some(token); } - fn handle_comment( - &mut self, - comment: DecoratedComment, - token: &SyntaxToken, - ) { + fn handle_comment(&mut self, comment: DecoratedComment) { match Language::CommentStyle::position_comment(comment) { CommentPosition::Leading { node, comment } => { self.node_comments @@ -818,9 +759,9 @@ where self.node_comments .insert_trailing_comment(node, comment.into()); } - CommentPosition::Dangling { token, comment } => self - .dangling_trivia - .insert_trivia(token, DanglingTrivia::Comment(comment.into())), + CommentPosition::Dangling { node, comment } => self + .node_comments + .insert_dangling_comment(node, comment.into()), CommentPosition::Default(mut comment) => { if comment.is_trailing_token_trivia() { let enclosing = comment.enclosing_node(); @@ -872,10 +813,8 @@ where .insert_leading_comment(following, comment.into()); } (None, None) => { - self.dangling_trivia.insert_trivia( - token.clone(), - DanglingTrivia::Comment(comment.into()), - ); + self.node_comments + .insert_dangling_comment(enclosing, comment.into()); } } } else { @@ -896,10 +835,8 @@ where .insert_trailing_comment(preceding, comment.into()); } (None, None) => { - self.dangling_trivia.insert_trivia( - token.clone(), - DanglingTrivia::Comment(comment.into()), - ); + self.node_comments + .insert_dangling_comment(comment.enclosing_node(), comment.into()); } } } @@ -908,14 +845,14 @@ where } fn finish(self) -> CommentsData { - let (leading_comments, trailing_comments) = self.node_comments.finish(); - let dangling_trivia = self.dangling_trivia.finish(); + let (leading_comments, dangling_comments, trailing_comments) = self.node_comments.finish(); CommentsData { is_suppression: Language::CommentStyle::is_suppression, leading_comments, + dangling_comments, trailing_comments, - dangling_trivia, + with_skipped: self.with_skipped, #[cfg(debug_assertions)] checked_suppressions: RefCell::default(), @@ -927,6 +864,7 @@ where #[derive(Debug)] struct NodeCommentsBuilder { leading_comments: AppendOnlyMultiMap, SourceComment>, + dangling_comments: AppendOnlyMultiMap, SourceComment>, trailing_comments: AppendOnlyMultiMap, SourceComment>, } @@ -935,6 +873,10 @@ impl NodeCommentsBuilder { self.leading_comments.append(node, comment); } + fn insert_dangling_comment(&mut self, node: SyntaxNode, comment: SourceComment) { + self.dangling_comments.append(node, comment); + } + fn insert_trailing_comment(&mut self, node: SyntaxNode, comment: SourceComment) { self.trailing_comments.append(node, comment); } @@ -944,8 +886,13 @@ impl NodeCommentsBuilder { ) -> ( AppendOnlyMultiMap, SourceComment>, AppendOnlyMultiMap, SourceComment>, + AppendOnlyMultiMap, SourceComment>, ) { - (self.leading_comments, self.trailing_comments) + ( + self.leading_comments, + self.dangling_comments, + self.trailing_comments, + ) } } @@ -953,34 +900,12 @@ impl Default for NodeCommentsBuilder { fn default() -> Self { Self { leading_comments: AppendOnlyMultiMap::new(), + dangling_comments: AppendOnlyMultiMap::new(), trailing_comments: AppendOnlyMultiMap::new(), } } } -#[derive(Debug)] -struct DanglingTriviaBuilder { - trivia: AppendOnlyMultiMap, DanglingTrivia>, -} - -impl DanglingTriviaBuilder { - fn insert_trivia(&mut self, token: SyntaxToken, trivia: DanglingTrivia) { - self.trivia.append(token, trivia) - } - - fn finish(self) -> AppendOnlyMultiMap, DanglingTrivia> { - self.trivia - } -} - -impl Default for DanglingTriviaBuilder { - fn default() -> Self { - Self { - trivia: AppendOnlyMultiMap::new(), - } - } -} - /// Multimap implementation that uses a shared vector to store the values for each key. /// /// The map uses a single vector to store the values of all keys together with a map diff --git a/crates/rome_formatter/src/lib.rs b/crates/rome_formatter/src/lib.rs index 8595e3d2a23..5147c7fe6f1 100644 --- a/crates/rome_formatter/src/lib.rs +++ b/crates/rome_formatter/src/lib.rs @@ -59,6 +59,7 @@ pub use comments::{ pub use format_element::{normalize_newlines, FormatElement, Text, Verbatim, LINE_TERMINATORS}; pub use group_id::GroupId; use indexmap::IndexSet; +use rome_rowan::syntax::SyntaxElementKey; use rome_rowan::{ Language, SyntaxElement, SyntaxError, SyntaxNode, SyntaxResult, SyntaxToken, SyntaxTriviaPiece, TextRange, TextSize, TokenAtOffset, @@ -1279,7 +1280,7 @@ pub struct FormatState { /// Storing the position is sufficient because comments are guaranteed to not be empty /// (all start with a specific comment sequence) and thus, no two comments can have the same /// absolute position. - manually_formatted_token_trivia: IndexSet, + manually_formatted_dangling_comments: IndexSet, // This is using a RefCell as it only exists in debug mode, // the Formatter is still completely immutable in release builds @@ -1304,7 +1305,7 @@ impl FormatState { Self { context, group_id_builder: Default::default(), - manually_formatted_token_trivia: IndexSet::default(), + manually_formatted_dangling_comments: IndexSet::default(), #[cfg(debug_assertions)] printed_tokens: Default::default(), } @@ -1335,16 +1336,15 @@ impl FormatState { /// /// This can be accomplished by manually formatting the leading/trailing trivia of the string literal expression /// before/after the close parentheses and then mark the comments as handled. - pub fn mark_token_trivia_formatted(&mut self, token: &SyntaxToken) { - self.manually_formatted_token_trivia - .insert(token.text_range().start()); + pub fn mark_dangling_comments_formatted(&mut self, node: &SyntaxNode) { + self.manually_formatted_dangling_comments.insert(node.key()); } /// Returns `true` if this comment has already been formatted manually /// and shouldn't be formatted again when formatting the token to which the comment belongs. - pub fn is_token_trivia_formatted(&self, token: &SyntaxToken) -> bool { - self.manually_formatted_token_trivia - .contains(&token.text_range().start()) + pub fn has_formatted_dangling_comments(&self, node: &SyntaxNode) -> bool { + self.manually_formatted_dangling_comments + .contains(&node.key()) } /// Returns the context specifying how to format the current CST @@ -1394,7 +1394,7 @@ where { pub fn snapshot(&self) -> FormatStateSnapshot { FormatStateSnapshot { - manually_handled_trivia_len: self.manually_formatted_token_trivia.len(), + manually_handled_trivia_len: self.manually_formatted_dangling_comments.len(), #[cfg(debug_assertions)] printed_tokens: self.printed_tokens.clone(), } @@ -1407,7 +1407,7 @@ where printed_tokens, } = snapshot; - self.manually_formatted_token_trivia + self.manually_formatted_dangling_comments .truncate(manual_handled_comments_len); cfg_if::cfg_if! { if #[cfg(debug_assertions)] { diff --git a/crates/rome_formatter/src/prelude.rs b/crates/rome_formatter/src/prelude.rs index 969bf9ead3e..e8061c685a7 100644 --- a/crates/rome_formatter/src/prelude.rs +++ b/crates/rome_formatter/src/prelude.rs @@ -4,7 +4,7 @@ pub use crate::format_extensions::{FormatOptional as _, MemoizeFormat, Memoized} pub use crate::formatter::Formatter; pub use crate::printer::PrinterOptions; pub use crate::token::{ - format_dangling_trivia, format_leading_comments, format_only_if_breaks, format_removed, + format_dangling_comments, format_leading_comments, format_only_if_breaks, format_removed, format_replaced, format_trailing_comments, format_trimmed_token, }; diff --git a/crates/rome_formatter/src/token.rs b/crates/rome_formatter/src/token.rs index 37ae40b0d1e..146065c7d40 100644 --- a/crates/rome_formatter/src/token.rs +++ b/crates/rome_formatter/src/token.rs @@ -1,7 +1,7 @@ use crate::prelude::*; use crate::{ - write, Argument, Arguments, CommentKind, CstFormatContext, DanglingTrivia, FormatRefWithRule, - GroupId, SourceComment, + write, Argument, Arguments, CommentKind, CstFormatContext, FormatRefWithRule, GroupId, + SourceComment, }; use rome_rowan::{Language, SyntaxNode, SyntaxToken}; @@ -134,24 +134,24 @@ where } } -pub const fn format_dangling_trivia( - token: &SyntaxToken, -) -> FormatDanglingTrivia { - FormatDanglingTrivia { - token, +pub const fn format_dangling_comments( + node: &SyntaxNode, +) -> FormatDanglingComments { + FormatDanglingComments { + node, indent: false, ignore_formatted_check: false, } } /// Formats the dangling trivia of `token`. -pub struct FormatDanglingTrivia<'a, L: Language> { - token: &'a SyntaxToken, +pub struct FormatDanglingComments<'a, L: Language> { + node: &'a SyntaxNode, indent: bool, ignore_formatted_check: bool, } -impl FormatDanglingTrivia<'_, L> { +impl FormatDanglingComments<'_, L> { pub fn indented(mut self) -> Self { self.indent = true; self @@ -163,42 +163,35 @@ impl FormatDanglingTrivia<'_, L> { } } -impl Format for FormatDanglingTrivia<'_, Context::Language> +impl Format for FormatDanglingComments<'_, Context::Language> where Context: CstFormatContext, { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { - if !self.ignore_formatted_check && f.state().is_token_trivia_formatted(self.token) { + if !self.ignore_formatted_check && f.state().has_formatted_dangling_comments(self.node) { return Ok(()); } let comments = f.context().comments().clone(); - let dangling_trivia = comments.dangling_trivia(self.token); + let dangling_comments = comments.dangling_comments(self.node); let mut leading_comments_end = 0; let mut last_line_comment = false; let format_leading_comments = format_once(|f| { - if self.indent && matches!(dangling_trivia.first(), Some(DanglingTrivia::Comment(_))) { + if self.indent && !dangling_comments.is_empty() { write!(f, [hard_line_break()])?; } // Write all comments up to the first skipped token trivia or the token let mut join = f.join_with(hard_line_break()); - for trivia in dangling_trivia { - match trivia { - DanglingTrivia::Comment(comment) => { - let format_comment = - FormatRefWithRule::new(comment, Context::CommentRule::default()); - join.entry(&format_comment); - - last_line_comment = comment.kind().is_line(); - leading_comments_end += 1; - } - _ => { - break; - } - } + for comment in dangling_comments { + let format_comment = + FormatRefWithRule::new(comment, Context::CommentRule::default()); + join.entry(&format_comment); + + last_line_comment = comment.kind().is_line(); + leading_comments_end += 1; } join.finish() @@ -214,11 +207,7 @@ where } } - if leading_comments_end != dangling_trivia.len() { - panic!("Skipped token trivia not yet supported"); - } - - f.state_mut().mark_token_trivia_formatted(self.token); + f.state_mut().mark_dangling_comments_formatted(self.node); Ok(()) } @@ -274,7 +263,7 @@ where fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { f.state_mut().track_token(self.token); - write!(f, [format_dangling_trivia(self.token)]) + write!(f, [format_skipped_token_trivia(self.token)]) } } @@ -314,7 +303,7 @@ where fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { f.state_mut().track_token(self.token); - write!(f, [format_dangling_trivia(self.token)])?; + write!(f, [format_skipped_token_trivia(self.token)])?; f.write_fmt(Arguments::from(&self.content)) } @@ -368,9 +357,32 @@ where [ if_group_breaks(&Arguments::from(&self.content)).with_group_id(self.group_id), // Print the trivia otherwise - if_group_fits_on_line(&format_dangling_trivia(self.token)) + if_group_fits_on_line(&format_skipped_token_trivia(self.token)) .with_group_id(self.group_id), ] ) } } + +pub const fn format_skipped_token_trivia( + token: &SyntaxToken, +) -> FormatSkippedTokenTrivia { + FormatSkippedTokenTrivia { token } +} + +pub struct FormatSkippedTokenTrivia<'a, L: Language> { + token: &'a SyntaxToken, +} + +impl Format for FormatSkippedTokenTrivia<'_, Context::Language> +where + Context: CstFormatContext, +{ + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + if f.comments().has_skipped(self.token) { + panic!("Skipped token trivia not yet supported."); + } + + Ok(()) + } +} diff --git a/crates/rome_js_formatter/src/comments.rs b/crates/rome_js_formatter/src/comments.rs index e72827bb22b..ae576a039cb 100644 --- a/crates/rome_js_formatter/src/comments.rs +++ b/crates/rome_js_formatter/src/comments.rs @@ -10,8 +10,8 @@ use rome_js_syntax::{ JsSyntaxKind, JsSyntaxNode, JsSyntaxToken, }; use rome_rowan::{ - declare_node_union, match_ast, Language, SyntaxKind, SyntaxResult, SyntaxSlot, - SyntaxTriviaPieceComments, TextLen, + declare_node_union, match_ast, Language, SyntaxResult, SyntaxSlot, SyntaxTriviaPieceComments, + TextLen, }; #[derive(Default)] @@ -207,13 +207,11 @@ impl CommentStyle for JsCommentStyle { { let block = JsBlockStatement::unwrap_cast(following_node.clone()); - if let (Ok(_), Ok(r_curly_token)) = - (block.l_curly_token(), block.r_curly_token()) - { + if let (Ok(_), Ok(_)) = (block.l_curly_token(), block.r_curly_token()) { return match block.statements().first() { Some(JsAnyStatement::JsEmptyStatement(_)) => { CommentPosition::Dangling { - token: r_curly_token, + node: block.into_syntax(), comment, } } @@ -222,7 +220,7 @@ impl CommentStyle for JsCommentStyle { comment, }, _ => CommentPosition::Dangling { - token: r_curly_token, + node: block.into_syntax(), comment, }, }; @@ -235,7 +233,7 @@ impl CommentStyle for JsCommentStyle { { let function_body = JsFunctionBody::unwrap_cast(following_node.clone()); - if let (Ok(_), Ok(r_curly_token)) = + if let (Ok(_), Ok(_)) = (function_body.l_curly_token(), function_body.r_curly_token()) { let first_directive = function_body @@ -253,7 +251,7 @@ impl CommentStyle for JsCommentStyle { } } else { CommentPosition::Dangling { - token: r_curly_token, + node: function_body.into_syntax(), comment, } }; @@ -266,14 +264,6 @@ impl CommentStyle for JsCommentStyle { }; match enclosing_node.kind() { - // TODO move to general formatter handling? - // WHat does that mean for invalid syntax... - kind if kind.is_unknown() => { - return CommentPosition::Dangling { - token: comment.enclosing_token(), - comment, - } - } // Handles comments attached to operators of binary like expressions. // // Associates trailing comments with the left expression if they're directly followed by a line break. diff --git a/crates/rome_js_formatter/src/js/auxiliary/function_body.rs b/crates/rome_js_formatter/src/js/auxiliary/function_body.rs index ff3684ce196..abf82a3403a 100644 --- a/crates/rome_js_formatter/src/js/auxiliary/function_body.rs +++ b/crates/rome_js_formatter/src/js/auxiliary/function_body.rs @@ -23,7 +23,7 @@ impl FormatNodeRule for FormatJsFunctionBody { f, [ l_curly_token.format(), - format_dangling_trivia(&r_curly_token).indented(), + format_dangling_comments(node.syntax()).indented(), r_curly_token.format() ] ) diff --git a/crates/rome_js_formatter/src/js/bindings/parameters.rs b/crates/rome_js_formatter/src/js/bindings/parameters.rs index 0b5ffc37a69..bf132a75ad5 100644 --- a/crates/rome_js_formatter/src/js/bindings/parameters.rs +++ b/crates/rome_js_formatter/src/js/bindings/parameters.rs @@ -60,7 +60,7 @@ impl Format for FormatJsAnyParameters { f, [ l_paren_token.format(), - format_dangling_trivia(&r_paren_token), + format_dangling_comments(self.syntax()), r_paren_token.format() ] ) diff --git a/crates/rome_js_formatter/src/js/expressions/array_expression.rs b/crates/rome_js_formatter/src/js/expressions/array_expression.rs index 4cd4d9979a6..019a146a0f8 100644 --- a/crates/rome_js_formatter/src/js/expressions/array_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/array_expression.rs @@ -27,7 +27,7 @@ impl FormatNodeRule for FormatJsArrayExpression { f, [ l_brack_token.format(), - format_dangling_trivia(&r_brack_token).indented(), + format_dangling_comments(node.syntax()).indented(), r_brack_token.format(), ] ) diff --git a/crates/rome_js_formatter/src/js/expressions/call_arguments.rs b/crates/rome_js_formatter/src/js/expressions/call_arguments.rs index 6bd6cd9b417..b984f358de1 100644 --- a/crates/rome_js_formatter/src/js/expressions/call_arguments.rs +++ b/crates/rome_js_formatter/src/js/expressions/call_arguments.rs @@ -27,7 +27,7 @@ impl FormatNodeRule for FormatJsCallArguments { f, [ l_paren_token.format(), - format_dangling_trivia(&r_paren_token), + format_dangling_comments(node.syntax()), r_paren_token.format() ] ); diff --git a/crates/rome_js_formatter/src/js/lists/template_element_list.rs b/crates/rome_js_formatter/src/js/lists/template_element_list.rs index 82fdbeb6664..4a99822a965 100644 --- a/crates/rome_js_formatter/src/js/lists/template_element_list.rs +++ b/crates/rome_js_formatter/src/js/lists/template_element_list.rs @@ -3,7 +3,7 @@ use crate::js::expressions::template_element::{AnyTemplateElement, TemplateEleme use crate::context::TabWidth; use crate::prelude::*; -use rome_formatter::{Comments, CstFormatContext}; +use rome_formatter::Comments; use rome_js_syntax::{ JsAnyExpression, JsAnyLiteralExpression, JsAnyTemplateElement, JsLanguage, JsTemplateElementList, TsAnyTemplateElement, TsTemplateElementList, diff --git a/crates/rome_js_formatter/src/js/statements/block_statement.rs b/crates/rome_js_formatter/src/js/statements/block_statement.rs index 8867ab4663e..8f71537fd9a 100644 --- a/crates/rome_js_formatter/src/js/statements/block_statement.rs +++ b/crates/rome_js_formatter/src/js/statements/block_statement.rs @@ -24,7 +24,7 @@ impl FormatNodeRule for FormatJsBlockStatement { let comments = f.context().comments(); if is_empty_block(node, comments) { - let has_dangling_trivia = comments.has_dangling_trivia(&r_curly_token); + let has_dangling_trivia = comments.has_skipped(&r_curly_token); for stmt in statements .iter() @@ -34,7 +34,7 @@ impl FormatNodeRule for FormatJsBlockStatement { } if has_dangling_trivia { - write!(f, [format_dangling_trivia(&r_curly_token).indented()])?; + write!(f, [format_dangling_comments(node.syntax()).indented()])?; } else if is_non_collapsible(node) { write!(f, [hard_line_break()])?; } diff --git a/crates/rome_js_formatter/src/js/statements/break_statement.rs b/crates/rome_js_formatter/src/js/statements/break_statement.rs index 8183c2ceaad..c51a9236dac 100644 --- a/crates/rome_js_formatter/src/js/statements/break_statement.rs +++ b/crates/rome_js_formatter/src/js/statements/break_statement.rs @@ -23,9 +23,10 @@ impl FormatNodeRule for FormatJsBreakStatement { &format_with(|f: &mut JsFormatter| { write!(f, [break_token.format()])?; + // FIXME best place to add dangling comments if let Some(label) = &label_token { - if f.context().comments().has_dangling_trivia(&label) { - write!(f, [space(), format_dangling_trivia(label)])?; + if f.context().comments().has_dangling_comments(&node.syntax()) { + write!(f, [space(), format_dangling_comments(node.syntax())])?; } write!(f, [space(), label.format()])?; diff --git a/crates/rome_js_formatter/src/js/statements/continue_statement.rs b/crates/rome_js_formatter/src/js/statements/continue_statement.rs index 933d4e45e31..e7f3a3e1486 100644 --- a/crates/rome_js_formatter/src/js/statements/continue_statement.rs +++ b/crates/rome_js_formatter/src/js/statements/continue_statement.rs @@ -23,9 +23,10 @@ impl FormatNodeRule for FormatJsContinueStatement { &format_with(|f: &mut JsFormatter| { write!(f, [continue_token.format()])?; + // FIXME is this even possible if let Some(label) = &label_token { - if f.context().comments().has_dangling_trivia(&label) { - write!(f, [space(), format_dangling_trivia(label)])?; + if f.context().comments().has_dangling_comments(node.syntax()) { + write!(f, [space(), format_dangling_comments(node.syntax())])?; } write!(f, [space(), label.format()])?; diff --git a/crates/rome_js_formatter/src/js/statements/for_statement.rs b/crates/rome_js_formatter/src/js/statements/for_statement.rs index 0df9baf78e6..7bf7d3d8f76 100644 --- a/crates/rome_js_formatter/src/js/statements/for_statement.rs +++ b/crates/rome_js_formatter/src/js/statements/for_statement.rs @@ -30,12 +30,12 @@ impl FormatNodeRule for FormatJsForStatement { // Move dangling trivia between the `for /* this */ (` to the top of the `for` and // add a line break after. let comments = f.context().comments(); - let dangling_trivia = comments.dangling_trivia(&l_paren_token); - if !dangling_trivia.is_empty() && dangling_trivia.iter().all(|trivia| trivia.is_comment()) { + let dangling_comments = comments.dangling_comments(node.syntax()); + if !dangling_comments.is_empty() { write!( f, [ - format_dangling_trivia(&l_paren_token), + format_dangling_comments(node.syntax()), soft_line_break_or_space() ] )?; diff --git a/crates/rome_js_formatter/src/js/statements/return_statement.rs b/crates/rome_js_formatter/src/js/statements/return_statement.rs index 469cee82d28..7969f1c207b 100644 --- a/crates/rome_js_formatter/src/js/statements/return_statement.rs +++ b/crates/rome_js_formatter/src/js/statements/return_statement.rs @@ -35,17 +35,19 @@ impl Format for JsAnyStatementWithArgument { } let comments = f.context().comments(); - let has_dangling_comments = comments.has_dangling_comments(&semicolon); + let has_dangling_comments = comments.has_dangling_comments(self.syntax()); let is_last_comment_line = has_dangling_comments && comments - .dangling_comments(&semicolon) + .dangling_comments(self.syntax()) + .iter() .chain(comments.trailing_comments(self.syntax())) .last() .map_or(false, |comment| comment.kind().is_line()); // We'll format it after the semicolon - f.state_mut().mark_token_trivia_formatted(&semicolon); + f.state_mut() + .mark_dangling_comments_formatted(&self.syntax()); if is_last_comment_line { write!(f, [semicolon.format()])?; @@ -56,7 +58,7 @@ impl Format for JsAnyStatementWithArgument { f, [ space(), - format_dangling_trivia(&semicolon).ignore_formatted_check() + format_dangling_comments(&self.syntax()).ignore_formatted_check() ] )?; } diff --git a/crates/rome_js_formatter/src/jsx/lists/child_list.rs b/crates/rome_js_formatter/src/jsx/lists/child_list.rs index 26d94d401b3..2e26ccdcbee 100644 --- a/crates/rome_js_formatter/src/jsx/lists/child_list.rs +++ b/crates/rome_js_formatter/src/jsx/lists/child_list.rs @@ -215,7 +215,6 @@ impl FormatJsxChildList { /// [JsxText] and [JsxExpressionChild] and instead, formats the nodes itself. #[cfg(debug_assertions)] fn disarm_debug_assertions(&self, node: &JsxChildList, f: &mut JsFormatter) { - use rome_formatter::CstFormatContext; use rome_js_syntax::{JsAnyExpression, JsAnyLiteralExpression}; use JsxAnyChild::*; diff --git a/crates/rome_js_formatter/src/lib.rs b/crates/rome_js_formatter/src/lib.rs index 2b3061c3c24..384beddf249 100644 --- a/crates/rome_js_formatter/src/lib.rs +++ b/crates/rome_js_formatter/src/lib.rs @@ -276,6 +276,7 @@ use crate::comments::JsCommentStyle; use crate::context::{JsFormatContext, JsFormatOptions}; use crate::cst::FormatJsSyntaxNode; use crate::syntax_rewriter::transform; +use rome_formatter::token::format_skipped_token_trivia; use std::iter::FusedIterator; use std::marker::PhantomData; @@ -479,7 +480,10 @@ impl FormatRule for FormatJsSyntaxToken { write!( f, - [format_dangling_trivia(token), format_trimmed_token(token),] + [ + format_skipped_token_trivia(token), + format_trimmed_token(token), + ] ) } } diff --git a/crates/rome_js_formatter/src/utils/jsx.rs b/crates/rome_js_formatter/src/utils/jsx.rs index ecc73215e77..02b9f039683 100644 --- a/crates/rome_js_formatter/src/utils/jsx.rs +++ b/crates/rome_js_formatter/src/utils/jsx.rs @@ -156,10 +156,10 @@ pub(crate) fn is_whitespace_jsx_expression( literal.value_token(), child.r_curly_token(), ) { - (Ok(l_curly_token), Ok(value_token), Ok(r_curly_token)) => { + (Ok(_), Ok(value_token), Ok(r_curly_token)) => { let is_empty = matches!(value_token.text_trimmed(), "\" \"" | "' '"); - let has_comments = comments.has_dangling_trivia(&r_curly_token) + let has_comments = comments.has_skipped(&r_curly_token) || comments.has_comments(literal.syntax()); is_empty && !has_comments diff --git a/crates/rome_js_formatter/src/utils/member_chain/mod.rs b/crates/rome_js_formatter/src/utils/member_chain/mod.rs index 5de5197d1c4..daccc2bc99c 100644 --- a/crates/rome_js_formatter/src/utils/member_chain/mod.rs +++ b/crates/rome_js_formatter/src/utils/member_chain/mod.rs @@ -117,14 +117,13 @@ use rome_js_syntax::{JsAnyExpression, JsCallExpression, JsExpressionStatement, J use rome_rowan::{AstNode, SyntaxResult}; #[derive(Debug, Clone)] -pub(crate) struct MemberChain<'a> { +pub(crate) struct MemberChain { calls_count: usize, - root: &'a JsCallExpression, head: MemberChainGroup, tail: MemberChainGroups, } -impl MemberChain<'_> { +impl MemberChain { /// It tells if the groups should be break on multiple lines pub(crate) fn groups_should_break( &self, @@ -166,7 +165,7 @@ impl MemberChain<'_> { } } -impl Format for MemberChain<'_> { +impl Format for MemberChain { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { // TODO use Alternatives once available write!(f, [&self.head])?; @@ -189,10 +188,10 @@ impl Format for MemberChain<'_> { } } -pub(crate) fn get_member_chain<'a>( - call_expression: &'a JsCallExpression, +pub(crate) fn get_member_chain( + call_expression: &JsCallExpression, f: &mut JsFormatter, -) -> SyntaxResult> { +) -> SyntaxResult { let mut chain_members = vec![]; let parent_is_expression_statement = call_expression.syntax().parent().map_or(false, |parent| { @@ -245,7 +244,6 @@ pub(crate) fn get_member_chain<'a>( Ok(MemberChain { calls_count, - root: call_expression, head: head_group, tail: rest_of_groups, }) diff --git a/crates/rome_js_formatter/src/utils/object_like.rs b/crates/rome_js_formatter/src/utils/object_like.rs index 994047c9233..0822da648b3 100644 --- a/crates/rome_js_formatter/src/utils/object_like.rs +++ b/crates/rome_js_formatter/src/utils/object_like.rs @@ -59,7 +59,7 @@ impl Format for JsObjectLike { f, [ self.l_curly_token().format(), - format_dangling_trivia(&r_curly).indented(), + format_dangling_comments(self.syntax()).indented(), r_curly.format() ] ) diff --git a/crates/rome_rowan/src/syntax.rs b/crates/rome_rowan/src/syntax.rs index e61fca767cf..1a66016b1b2 100644 --- a/crates/rome_rowan/src/syntax.rs +++ b/crates/rome_rowan/src/syntax.rs @@ -11,7 +11,7 @@ pub use trivia::{ TriviaPieceKind, }; -pub use element::SyntaxElement; +pub use element::{SyntaxElement, SyntaxElementKey}; pub(crate) use node::SyntaxSlots; pub use node::{ Preorder, PreorderWithTokens, SendNode, SyntaxElementChildren, SyntaxNode, SyntaxNodeChildren, diff --git a/crates/rome_rowan/src/syntax/element.rs b/crates/rome_rowan/src/syntax/element.rs index 1a8262dece1..b071a6a541b 100644 --- a/crates/rome_rowan/src/syntax/element.rs +++ b/crates/rome_rowan/src/syntax/element.rs @@ -124,7 +124,7 @@ impl From> for SyntaxElement { } } -#[derive(Copy, Clone, Eq, PartialEq)] +#[derive(Copy, Clone, Eq, PartialEq, Hash)] pub struct SyntaxElementKey { node_data: NonNull<()>, offset: TextSize, From bcb99fe1c8049a0aa290f92f30077bf421eb406b Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Wed, 7 Sep 2022 12:13:20 +0200 Subject: [PATCH 05/45] Move some things around --- crates/rome_formatter/src/comments.rs | 469 ++---------------- crates/rome_formatter/src/comments/builder.rs | 303 +++++++++++ .../rome_formatter/src/comments/multimap.rs | 105 ++++ crates/rome_formatter/src/lib.rs | 3 +- crates/rome_js_formatter/src/comments.rs | 66 ++- crates/rome_js_formatter/src/context.rs | 10 +- .../src/js/bindings/parameters.rs | 7 +- .../expressions/arrow_function_expression.rs | 6 +- .../src/js/expressions/call_arguments.rs | 12 +- .../src/js/lists/array_element_list.rs | 6 +- .../src/js/lists/template_element_list.rs | 6 +- .../src/js/statements/block_statement.rs | 6 +- .../src/js/statements/break_statement.rs | 2 +- .../src/js/statements/return_statement.rs | 15 +- .../attribute/expression_attribute_value.rs | 6 +- .../src/jsx/lists/child_list.rs | 6 +- .../src/jsx/tag/opening_element.rs | 9 +- crates/rome_js_formatter/src/lib.rs | 6 +- crates/rome_js_formatter/src/prelude.rs | 10 +- .../ts/declarations/interface_declaration.rs | 4 +- .../src/utils/assignment_like.rs | 10 +- .../src/utils/format_class.rs | 6 +- crates/rome_js_formatter/src/utils/jsx.rs | 8 +- .../src/utils/member_chain/groups.rs | 17 +- .../src/utils/member_chain/mod.rs | 11 +- 25 files changed, 559 insertions(+), 550 deletions(-) create mode 100644 crates/rome_formatter/src/comments/builder.rs create mode 100644 crates/rome_formatter/src/comments/multimap.rs diff --git a/crates/rome_formatter/src/comments.rs b/crates/rome_formatter/src/comments.rs index fcf2eef463b..a188c6bb362 100644 --- a/crates/rome_formatter/src/comments.rs +++ b/crates/rome_formatter/src/comments.rs @@ -1,12 +1,15 @@ -use crate::{FormatLanguage, TextSize}; -use rome_rowan::syntax::SyntaxTriviaPieceSkipped; +mod builder; +mod multimap; + +use self::{builder::CommentsBuilderVisitor, multimap::AppendOnlyMultiMap}; +use crate::TextSize; use rome_rowan::{ Direction, Language, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, SyntaxTriviaPieceComments, WalkEvent, }; #[cfg(debug_assertions)] use std::cell::RefCell; -use std::collections::{HashMap, HashSet}; +use std::collections::HashSet; use std::rc::Rc; #[derive(Copy, Clone, Eq, PartialEq, Debug)] @@ -136,24 +139,6 @@ impl SourceComment { } } -#[derive(Debug, Clone)] -pub struct SkippedTokenTrivia { - lines_before: u32, - piece: SyntaxTriviaPieceSkipped, -} - -#[derive(Debug, Clone)] -pub enum DanglingTrivia { - Comment(SourceComment), - SkippedToken(SkippedTokenTrivia), -} - -impl DanglingTrivia { - pub const fn is_comment(&self) -> bool { - matches!(self, DanglingTrivia::Comment(_)) - } -} - #[derive(Debug, Clone)] pub struct DecoratedComment { preceding: Option>, @@ -240,8 +225,34 @@ impl From> for SourceComment { } } +#[derive(Debug, Copy, Clone)] +enum DecoratedCommentKind { + /// A comment that is separated by at least one line break from the following token + /// + /// ```javascript + /// a; /* this */ // or this + /// b; + EndOfLine, + + /// A Comment that is separated by at least one line break from the preceding token + /// + /// ```javascript + /// a; + /// /* comment */ /* or this */ + /// b; + /// ``` + OwnLine, + + /// A comment that is placed on the same line as the preceding and following token. + /// + /// ```javascript + /// a /* comment */ + b + /// ``` + SameLine, +} + #[derive(Debug)] -pub enum CommentPosition { +pub enum CommentPlacement { /// Overrides the positioning of the comment to be a leading node comment. Leading { node: SyntaxNode, @@ -253,6 +264,7 @@ pub enum CommentPosition { comment: DecoratedComment, }, + /// Makes this comment a dangling comment of `node` Dangling { node: SyntaxNode, comment: DecoratedComment, @@ -275,9 +287,8 @@ pub trait CommentStyle { /// Returns the (kind)[CommentKind] of the comment fn get_comment_kind(comment: &SyntaxTriviaPieceComments) -> CommentKind; - fn position_comment( - comment: DecoratedComment, - ) -> CommentPosition; + fn place_comment(comment: DecoratedComment) + -> CommentPlacement; } /// Type that stores the comments of a tree and gives access to: @@ -313,7 +324,7 @@ impl Comments { where FormatLanguage: crate::FormatLanguage, { - let mut builder = CommentsBuilderVisitor::::new(); + let mut builder = CommentsBuilderVisitor::::default(); for event in root.preorder_with_tokens(Direction::Next) { match event { @@ -367,10 +378,12 @@ impl Comments { self.data.leading_comments.get(node) } + /// Returns `true` if node has any dangling comments. pub fn has_dangling_comments(&self, node: &SyntaxNode) -> bool { !self.dangling_comments(node).is_empty() } + /// Returns the dangling comments of `node` pub fn dangling_comments(&self, node: &SyntaxNode) -> &[SourceComment] { self.data.dangling_comments.get(node) } @@ -390,6 +403,7 @@ impl Comments { !self.trailing_comments(node).is_empty() } + /// Returns an iterator over the leading and trailing comments of `node`. pub fn leading_trailing_comments( &self, node: &SyntaxNode, @@ -399,7 +413,8 @@ impl Comments { .chain(self.trailing_comments(node).iter()) } - pub fn all<'a>( + /// Returns an iterator over the leading, dangling, and trailing comments of `node`. + pub fn leading_dangling_trailing_comments<'a>( &'a self, node: &'a SyntaxNode, ) -> impl Iterator> + 'a { @@ -409,6 +424,7 @@ impl Comments { .chain(self.trailing_comments(node).iter()) } + /// Returns `true` if that node has skipped token trivia attached. #[inline] pub fn has_skipped(&self, token: &SyntaxToken) -> bool { self.data.with_skipped.contains(token) @@ -610,402 +626,3 @@ impl std::fmt::Debug for DebugComment<'_, L> { } } } - -#[derive(Debug, Default)] -struct CommentsBuilderVisitor { - node_comments: NodeCommentsBuilder, - with_skipped: HashSet>, - preceding_node: Option>, - following_node: Option>, - last_token: Option>, -} - -impl CommentsBuilderVisitor -where - Language: FormatLanguage, -{ - fn new() -> Self { - Self { - node_comments: NodeCommentsBuilder::default(), - with_skipped: HashSet::new(), - preceding_node: None, - following_node: None, - last_token: None, - } - } - - fn visit_node(&mut self, event: WalkEvent>) { - match event { - WalkEvent::Enter(node) => { - // Lists cannot have comments attached. They either belong to the entire parent or to - // the first child. - if node.kind().is_list() { - return; - } - - // Associate comments with the most outer node - if self.following_node.is_none() { - self.following_node = Some(node); - } - } - - WalkEvent::Leave(node) => { - if node.kind().is_list() { - return; - } - - if self.following_node.as_ref() == Some(&node) { - self.following_node = None; - } - - self.preceding_node = Some(node); - } - } - } - - fn visit_token(&mut self, token: SyntaxToken) { - // Store the last processed comment so that we can set `line_break_after` - let mut last_comment = None; - - if let Some(last_token) = self.last_token.take() { - for piece in last_token - .trailing_trivia() - .pieces() - .filter_map(|piece| piece.as_comments()) - { - if let Some(last_comment) = last_comment.take() { - self.handle_comment(last_comment); - } - - last_comment = Some(DecoratedComment { - preceding: self.preceding_node.clone(), - following: self.following_node.clone(), - following_token: token.clone(), - lines_before: 0, - lines_after: 0, - trailing_token_comment: true, - kind: Language::CommentStyle::get_comment_kind(&piece), - comment: piece, - }); - } - } - - let mut lines_before = 0; - let mut has_skipped = false; - - for leading in token.leading_trivia().pieces() { - if leading.is_newline() { - lines_before += 1; - } else if leading.is_skipped() { - if let Some(mut last_comment) = last_comment.take() { - last_comment.lines_after = lines_before; - self.handle_comment(last_comment); - } - - self.with_skipped.insert(token.clone()); - - lines_before = 0; - has_skipped = true; - } else if let Some(comment) = leading.as_comments() { - if let Some(mut last_comment) = last_comment.take() { - last_comment.lines_after = lines_before; - self.handle_comment(last_comment); - } - - let kind = Language::CommentStyle::get_comment_kind(&comment); - if !has_skipped { - last_comment = Some(DecoratedComment { - preceding: self.preceding_node.clone(), - following: self.following_node.clone(), - following_token: token.clone(), - lines_before, - lines_after: 0, - trailing_token_comment: false, - kind, - comment, - }); - } - lines_before = 0; - } - } - - if let Some(mut last_comment) = last_comment.take() { - last_comment.lines_after = lines_before; - self.handle_comment(last_comment); - } - - // Any comment following now is preceded by 'token' and not a node. - - // TODO: Difference to prettier: - // - Prettier keeps the preceding node around, even if there has been a token in between - // - Prettier keeps the following node around, even if there has been a token in between - // - a = b; a is still the preceding even if positioned at b; - // - same with following. It takes the first node that follows (and belongs to the same parent) - // They then use a breakTie in situations where there are preceding and following nodes set. - // Emphasis the importance of nodes even more. Reduces the places where dangling comments can appear - // Has mainly become relevant for trailing comments. Is there also a noticable difference for leading? - self.preceding_node = None; - self.following_node = None; - self.last_token = Some(token); - } - - fn handle_comment(&mut self, comment: DecoratedComment) { - match Language::CommentStyle::position_comment(comment) { - CommentPosition::Leading { node, comment } => { - self.node_comments - .insert_leading_comment(node, comment.into()); - } - CommentPosition::Trailing { node, comment } => { - self.node_comments - .insert_trailing_comment(node, comment.into()); - } - CommentPosition::Dangling { node, comment } => self - .node_comments - .insert_dangling_comment(node, comment.into()), - CommentPosition::Default(mut comment) => { - if comment.is_trailing_token_trivia() { - let enclosing = comment.enclosing_node(); - - // The enclosing can only ever be a list if the comment is a leading or trailing comment of a - // separator token in a separated list. - // Example: - // ```js - // [ - // a, // test - // b - // ] - // ``` - // The default algorithm would make `// test` a leading comment of the node `b` but - // it should be a trailing comment of `a` because that's most likely what the user intended. - if enclosing.kind().is_list() && comment.lines_after() > 0 { - if let Some(SyntaxElement::Node(node)) = - comment.comment.as_piece().token().prev_sibling_or_token() - { - self.node_comments - .insert_trailing_comment(node, comment.into()); - return; - } - } - - match (comment.take_preceding_node(), comment.take_following_node()) { - (Some(preceding), Some(following)) => { - // Always attach suppression with the next node. - if Language::CommentStyle::is_suppression(comment.comment.text()) { - self.node_comments - .insert_leading_comment(following, comment.into()); - } else { - // Attach comments with both preceding and following node to the preceding - // because there's a line break separating it from the following node. - // ```javascript - // a; // comment - // b - // ``` - self.node_comments - .insert_trailing_comment(preceding, comment.into()); - } - } - (Some(preceding), None) => { - self.node_comments - .insert_trailing_comment(preceding, comment.into()); - } - (None, Some(following)) => { - self.node_comments - .insert_leading_comment(following, comment.into()); - } - (None, None) => { - self.node_comments - .insert_dangling_comment(enclosing, comment.into()); - } - } - } else { - match (comment.take_following_node(), comment.take_preceding_node()) { - // Following always wins for a leading comment - // ```javascript - // a; - // // comment - // b - // ``` - // attach the comment to the `b` expression statement - (Some(following), _) => { - self.node_comments - .insert_leading_comment(following, comment.into()); - } - (None, Some(preceding)) => { - self.node_comments - .insert_trailing_comment(preceding, comment.into()); - } - (None, None) => { - self.node_comments - .insert_dangling_comment(comment.enclosing_node(), comment.into()); - } - } - } - } - } - } - - fn finish(self) -> CommentsData { - let (leading_comments, dangling_comments, trailing_comments) = self.node_comments.finish(); - - CommentsData { - is_suppression: Language::CommentStyle::is_suppression, - leading_comments, - dangling_comments, - trailing_comments, - with_skipped: self.with_skipped, - - #[cfg(debug_assertions)] - checked_suppressions: RefCell::default(), - } - } -} - -// TODO necessary? -#[derive(Debug)] -struct NodeCommentsBuilder { - leading_comments: AppendOnlyMultiMap, SourceComment>, - dangling_comments: AppendOnlyMultiMap, SourceComment>, - trailing_comments: AppendOnlyMultiMap, SourceComment>, -} - -impl NodeCommentsBuilder { - fn insert_leading_comment(&mut self, node: SyntaxNode, comment: SourceComment) { - self.leading_comments.append(node, comment); - } - - fn insert_dangling_comment(&mut self, node: SyntaxNode, comment: SourceComment) { - self.dangling_comments.append(node, comment); - } - - fn insert_trailing_comment(&mut self, node: SyntaxNode, comment: SourceComment) { - self.trailing_comments.append(node, comment); - } - - fn finish( - self, - ) -> ( - AppendOnlyMultiMap, SourceComment>, - AppendOnlyMultiMap, SourceComment>, - AppendOnlyMultiMap, SourceComment>, - ) { - ( - self.leading_comments, - self.dangling_comments, - self.trailing_comments, - ) - } -} - -impl Default for NodeCommentsBuilder { - fn default() -> Self { - Self { - leading_comments: AppendOnlyMultiMap::new(), - dangling_comments: AppendOnlyMultiMap::new(), - trailing_comments: AppendOnlyMultiMap::new(), - } - } -} - -/// Multimap implementation that uses a shared vector to store the values for each key. -/// -/// The map uses a single vector to store the values of all keys together with a map -/// that stores the the value range for each key. The upside of using a single vector for all -/// values is that it avoids allocating a new vector for every element. The downside is that the values -/// for a key must all be appended in order. -#[derive(Clone)] -struct AppendOnlyMultiMap { - index: HashMap, - values: Vec, -} - -impl AppendOnlyMultiMap { - pub fn new() -> Self { - Self { - index: HashMap::new(), - values: Vec::new(), - } - } - - /// Appends the `value` to the `key`'s values. - /// - /// # Panics - /// If `key` is already present in the map but other keys have been inserted since it was initially inserted. - pub fn append(&mut self, key: K, value: V) { - if let Some(range) = self.index.get_mut(&key) { - assert_eq!(self.values.len(), range.end()); - - self.values.push(value); - range.increment_end(); - } else { - let range = ValueRange::single(self.values.len()); - self.values.push(value); - self.index.insert(key, range); - } - } - - /// Returns an iterator over all the keys - pub fn keys(&self) -> impl Iterator { - self.index.keys() - } - - /// Returns a slice of the values associated with `key`. - pub fn get(&self, key: &K) -> &[V] { - if let Some(range) = self.index.get(key) { - &self.values[range.start()..range.end()] - } else { - &[] - } - } -} - -impl Default for AppendOnlyMultiMap { - fn default() -> Self { - Self { - values: Vec::new(), - index: HashMap::new(), - } - } -} - -impl std::fmt::Debug for AppendOnlyMultiMap -where - K: std::fmt::Debug, - V: std::fmt::Debug, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut builder = f.debug_map(); - - for (key, range) in &self.index { - builder.entry(&key, &&self.values[range.start()..range.end()]); - } - - builder.finish() - } -} - -#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] -struct ValueRange { - start: u32, - end: u32, -} - -impl ValueRange { - fn single(position: usize) -> Self { - Self { - start: position as u32, - end: (position + 1) as u32, - } - } - - fn start(&self) -> usize { - self.start as usize - } - - fn end(&self) -> usize { - self.end as usize - } - - fn increment_end(&mut self) { - self.end += 1; - } -} diff --git a/crates/rome_formatter/src/comments/builder.rs b/crates/rome_formatter/src/comments/builder.rs new file mode 100644 index 00000000000..642357a88b7 --- /dev/null +++ b/crates/rome_formatter/src/comments/builder.rs @@ -0,0 +1,303 @@ +use crate::comments::multimap::AppendOnlyMultiMap; +use crate::comments::CommentsData; +use crate::{CommentPlacement, CommentStyle, DecoratedComment, FormatLanguage, SourceComment}; +use rome_rowan::{Language, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, WalkEvent}; +use std::cell::RefCell; +use std::collections::HashSet; + +#[derive(Debug)] +pub(super) struct CommentsBuilderVisitor { + node_comments: NodeCommentsBuilder, + with_skipped: HashSet>, + preceding_node: Option>, + following_node: Option>, + last_token: Option>, +} + +impl Default for CommentsBuilderVisitor { + fn default() -> Self { + Self { + node_comments: Default::default(), + with_skipped: Default::default(), + preceding_node: Default::default(), + following_node: Default::default(), + last_token: Default::default(), + } + } +} + +impl CommentsBuilderVisitor +where + Language: FormatLanguage, +{ + pub(super) fn visit_node(&mut self, event: WalkEvent>) { + match event { + WalkEvent::Enter(node) => { + // Lists cannot have comments attached. They either belong to the entire parent or to + // the first child. + if node.kind().is_list() { + return; + } + + // Associate comments with the most outer node + if self.following_node.is_none() { + self.following_node = Some(node); + } + } + + WalkEvent::Leave(node) => { + if node.kind().is_list() { + return; + } + + if self.following_node.as_ref() == Some(&node) { + self.following_node = None; + } + + self.preceding_node = Some(node); + } + } + } + + pub(super) fn visit_token(&mut self, token: SyntaxToken) { + // Store the last processed comment so that we can set `line_break_after` + let mut last_comment = None; + + if let Some(last_token) = self.last_token.take() { + for piece in last_token + .trailing_trivia() + .pieces() + .filter_map(|piece| piece.as_comments()) + { + if let Some(last_comment) = last_comment.take() { + self.handle_comment(last_comment); + } + + last_comment = Some(DecoratedComment { + preceding: self.preceding_node.clone(), + following: self.following_node.clone(), + following_token: token.clone(), + lines_before: 0, + lines_after: 0, + trailing_token_comment: true, + kind: Language::CommentStyle::get_comment_kind(&piece), + comment: piece, + }); + } + } + + let mut lines_before = 0; + let mut has_skipped = false; + + for leading in token.leading_trivia().pieces() { + if leading.is_newline() { + lines_before += 1; + } else if leading.is_skipped() { + if let Some(mut last_comment) = last_comment.take() { + last_comment.lines_after = lines_before; + self.handle_comment(last_comment); + } + + self.with_skipped.insert(token.clone()); + + lines_before = 0; + has_skipped = true; + } else if let Some(comment) = leading.as_comments() { + if let Some(mut last_comment) = last_comment.take() { + last_comment.lines_after = lines_before; + self.handle_comment(last_comment); + } + + let kind = Language::CommentStyle::get_comment_kind(&comment); + if !has_skipped { + last_comment = Some(DecoratedComment { + preceding: self.preceding_node.clone(), + following: self.following_node.clone(), + following_token: token.clone(), + lines_before, + lines_after: 0, + trailing_token_comment: false, + kind, + comment, + }); + } + lines_before = 0; + } + } + + if let Some(mut last_comment) = last_comment.take() { + last_comment.lines_after = lines_before; + self.handle_comment(last_comment); + } + + // Any comment following now is preceded by 'token' and not a node. + + // TODO: Difference to prettier: + // - Prettier keeps the preceding node around, even if there has been a token in between + // - Prettier keeps the following node around, even if there has been a token in between + // - a = b; a is still the preceding even if positioned at b; + // - same with following. It takes the first node that follows (and belongs to the same parent) + // They then use a breakTie in situations where there are preceding and following nodes set. + // Emphasis the importance of nodes even more. Reduces the places where dangling comments can appear + // Has mainly become relevant for trailing comments. Is there also a noticable difference for leading? + self.preceding_node = None; + self.following_node = None; + self.last_token = Some(token); + } + + fn handle_comment(&mut self, comment: DecoratedComment) { + match Language::CommentStyle::place_comment(comment) { + CommentPlacement::Leading { node, comment } => { + self.node_comments + .insert_leading_comment(node, comment.into()); + } + CommentPlacement::Trailing { node, comment } => { + self.node_comments + .insert_trailing_comment(node, comment.into()); + } + CommentPlacement::Dangling { node, comment } => self + .node_comments + .insert_dangling_comment(node, comment.into()), + CommentPlacement::Default(mut comment) => { + if comment.is_trailing_token_trivia() { + let enclosing = comment.enclosing_node(); + + // The enclosing can only ever be a list if the comment is a leading or trailing comment of a + // separator token in a separated list. + // Example: + // ```js + // [ + // a, // test + // b + // ] + // ``` + // The default algorithm would make `// test` a leading comment of the node `b` but + // it should be a trailing comment of `a` because that's most likely what the user intended. + if enclosing.kind().is_list() && comment.lines_after() > 0 { + if let Some(SyntaxElement::Node(node)) = + comment.comment.as_piece().token().prev_sibling_or_token() + { + self.node_comments + .insert_trailing_comment(node, comment.into()); + return; + } + } + + match (comment.take_preceding_node(), comment.take_following_node()) { + (Some(preceding), Some(following)) => { + // Always attach suppression with the next node. + if Language::CommentStyle::is_suppression(comment.comment.text()) { + self.node_comments + .insert_leading_comment(following, comment.into()); + } else { + // Attach comments with both preceding and following node to the preceding + // because there's a line break separating it from the following node. + // ```javascript + // a; // comment + // b + // ``` + self.node_comments + .insert_trailing_comment(preceding, comment.into()); + } + } + (Some(preceding), None) => { + self.node_comments + .insert_trailing_comment(preceding, comment.into()); + } + (None, Some(following)) => { + self.node_comments + .insert_leading_comment(following, comment.into()); + } + (None, None) => { + self.node_comments + .insert_dangling_comment(enclosing, comment.into()); + } + } + } else { + match (comment.take_following_node(), comment.take_preceding_node()) { + // Following always wins for a leading comment + // ```javascript + // a; + // // comment + // b + // ``` + // attach the comment to the `b` expression statement + (Some(following), _) => { + self.node_comments + .insert_leading_comment(following, comment.into()); + } + (None, Some(preceding)) => { + self.node_comments + .insert_trailing_comment(preceding, comment.into()); + } + (None, None) => { + self.node_comments + .insert_dangling_comment(comment.enclosing_node(), comment.into()); + } + } + } + } + } + } + + pub(super) fn finish(self) -> CommentsData { + let (leading_comments, dangling_comments, trailing_comments) = self.node_comments.finish(); + + CommentsData { + is_suppression: Language::CommentStyle::is_suppression, + leading_comments, + dangling_comments, + trailing_comments, + with_skipped: self.with_skipped, + + #[cfg(debug_assertions)] + checked_suppressions: RefCell::default(), + } + } +} + +// TODO necessary? +#[derive(Debug)] +struct NodeCommentsBuilder { + leading_comments: AppendOnlyMultiMap, SourceComment>, + dangling_comments: AppendOnlyMultiMap, SourceComment>, + trailing_comments: AppendOnlyMultiMap, SourceComment>, +} + +impl NodeCommentsBuilder { + fn insert_leading_comment(&mut self, node: SyntaxNode, comment: SourceComment) { + self.leading_comments.append(node, comment); + } + + fn insert_dangling_comment(&mut self, node: SyntaxNode, comment: SourceComment) { + self.dangling_comments.append(node, comment); + } + + fn insert_trailing_comment(&mut self, node: SyntaxNode, comment: SourceComment) { + self.trailing_comments.append(node, comment); + } + + fn finish( + self, + ) -> ( + AppendOnlyMultiMap, SourceComment>, + AppendOnlyMultiMap, SourceComment>, + AppendOnlyMultiMap, SourceComment>, + ) { + ( + self.leading_comments, + self.dangling_comments, + self.trailing_comments, + ) + } +} + +impl Default for NodeCommentsBuilder { + fn default() -> Self { + Self { + leading_comments: AppendOnlyMultiMap::new(), + dangling_comments: AppendOnlyMultiMap::new(), + trailing_comments: AppendOnlyMultiMap::new(), + } + } +} diff --git a/crates/rome_formatter/src/comments/multimap.rs b/crates/rome_formatter/src/comments/multimap.rs new file mode 100644 index 00000000000..d6e0f421c44 --- /dev/null +++ b/crates/rome_formatter/src/comments/multimap.rs @@ -0,0 +1,105 @@ +use std::collections::HashMap; + +/// Multimap implementation that uses a shared vector to store the values for each key. +/// +/// The map uses a single vector to store the values of all keys together with a map +/// that stores the the value range for each key. The upside of using a single vector for all +/// values is that it avoids allocating a new vector for every element. The downside is that the values +/// for a key must all be appended in order. +#[derive(Clone)] +pub(super) struct AppendOnlyMultiMap { + index: HashMap, + values: Vec, +} + +impl AppendOnlyMultiMap { + pub fn new() -> Self { + Self { + index: HashMap::new(), + values: Vec::new(), + } + } + + /// Appends the `value` to the `key`'s values. + /// + /// # Panics + /// If `key` is already present in the map but other keys have been inserted since it was initially inserted. + pub fn append(&mut self, key: K, value: V) { + if let Some(range) = self.index.get_mut(&key) { + assert_eq!(self.values.len(), range.end()); + + self.values.push(value); + range.increment_end(); + } else { + let range = ValueRange::single(self.values.len()); + self.values.push(value); + self.index.insert(key, range); + } + } + + /// Returns an iterator over all the keys + pub fn keys(&self) -> impl Iterator { + self.index.keys() + } + + /// Returns a slice of the values associated with `key`. + pub fn get(&self, key: &K) -> &[V] { + if let Some(range) = self.index.get(key) { + &self.values[range.start()..range.end()] + } else { + &[] + } + } +} + +impl Default for AppendOnlyMultiMap { + fn default() -> Self { + Self { + values: Vec::new(), + index: HashMap::new(), + } + } +} + +impl std::fmt::Debug for AppendOnlyMultiMap +where + K: std::fmt::Debug, + V: std::fmt::Debug, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut builder = f.debug_map(); + + for (key, range) in &self.index { + builder.entry(&key, &&self.values[range.start()..range.end()]); + } + + builder.finish() + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +struct ValueRange { + start: u32, + end: u32, +} + +impl ValueRange { + fn single(position: usize) -> Self { + Self { + start: position as u32, + end: (position + 1) as u32, + } + } + + fn start(&self) -> usize { + self.start as usize + } + + fn end(&self) -> usize { + self.end as usize + } + + fn increment_end(&mut self) { + self.end += 1; + } +} diff --git a/crates/rome_formatter/src/lib.rs b/crates/rome_formatter/src/lib.rs index 5147c7fe6f1..401013dbdf4 100644 --- a/crates/rome_formatter/src/lib.rs +++ b/crates/rome_formatter/src/lib.rs @@ -53,8 +53,7 @@ pub use builders::{ soft_line_break_or_space, soft_line_indent_or_space, space, text, BestFitting, }; pub use comments::{ - CommentKind, CommentPosition, CommentStyle, Comments, DanglingTrivia, DecoratedComment, - SourceComment, + CommentKind, CommentPlacement, CommentStyle, Comments, DecoratedComment, SourceComment, }; pub use format_element::{normalize_newlines, FormatElement, Text, Verbatim, LINE_TERMINATORS}; pub use group_id::GroupId; diff --git a/crates/rome_js_formatter/src/comments.rs b/crates/rome_js_formatter/src/comments.rs index ae576a039cb..4e78875b2a0 100644 --- a/crates/rome_js_formatter/src/comments.rs +++ b/crates/rome_js_formatter/src/comments.rs @@ -1,6 +1,6 @@ use crate::prelude::*; use crate::utils::JsAnyBinaryLikeExpression; -use rome_formatter::{write, CommentPosition, DecoratedComment}; +use rome_formatter::{write, CommentPlacement, Comments, DecoratedComment}; use rome_formatter::{CommentKind, CommentStyle, SourceComment}; use rome_js_syntax::suppression::{parse_suppression_comment, SuppressionCategory}; use rome_js_syntax::{ @@ -14,6 +14,8 @@ use rome_rowan::{ TextLen, }; +pub type JsComments = Comments; + #[derive(Default)] pub struct FormatJsLeadingComment; @@ -150,14 +152,14 @@ impl CommentStyle for JsCommentStyle { } } - fn position_comment( + fn place_comment( comment: DecoratedComment, - ) -> CommentPosition { + ) -> CommentPlacement { let enclosing_node = comment.enclosing_node(); if let Some(following_node) = comment.following_node() { if comment.is_trailing_token_trivia() && is_type_comment(comment.piece()) { - return CommentPosition::Leading { + return CommentPlacement::Leading { node: following_node.clone(), comment, }; @@ -182,9 +184,9 @@ impl CommentStyle for JsCommentStyle { return match first_inner { // Attach any leading comments to the first statement or directive in a module or script to prevent // that a rome-ignore comment on the first statement ignores the whole file. - Some(SyntaxSlot::Node(node)) => CommentPosition::Leading { node, comment }, + Some(SyntaxSlot::Node(node)) => CommentPlacement::Leading { node, comment }, Some(SyntaxSlot::Token(_)) | Some(SyntaxSlot::Empty) | None => { - CommentPosition::Default(comment) + CommentPlacement::Default(comment) } }; } @@ -210,16 +212,16 @@ impl CommentStyle for JsCommentStyle { if let (Ok(_), Ok(_)) = (block.l_curly_token(), block.r_curly_token()) { return match block.statements().first() { Some(JsAnyStatement::JsEmptyStatement(_)) => { - CommentPosition::Dangling { + CommentPlacement::Dangling { node: block.into_syntax(), comment, } } - Some(first_statement) => CommentPosition::Leading { + Some(first_statement) => CommentPlacement::Leading { node: first_statement.into_syntax(), comment, }, - _ => CommentPosition::Dangling { + _ => CommentPlacement::Dangling { node: block.into_syntax(), comment, }, @@ -245,12 +247,12 @@ impl CommentStyle for JsCommentStyle { .first() .map(|node| node.into_syntax()); return if let Some(first_node) = first_directive.or(first_statement) { - CommentPosition::Leading { + CommentPlacement::Leading { node: first_node, comment, } } else { - CommentPosition::Dangling { + CommentPlacement::Dangling { node: function_body.into_syntax(), comment, } @@ -286,18 +288,16 @@ impl CommentStyle for JsCommentStyle { if comment.is_trailing_token_trivia() && comment.lines_after() > 0 { if let Ok(left) = binary_like.left() { - return CommentPosition::Trailing { + return CommentPlacement::Trailing { node: left.into_syntax(), comment, }; } - } else { - if let Ok(right) = binary_like.right() { - return CommentPosition::Leading { - node: right.into_syntax(), - comment, - }; - } + } else if let Ok(right) = binary_like.right() { + return CommentPlacement::Leading { + node: right.into_syntax(), + comment, + }; } } @@ -320,7 +320,7 @@ impl CommentStyle for JsCommentStyle { && (semicolon.is_none() || Some(comment.following_token()) == semicolon.as_ref()) { - return CommentPosition::Trailing { + return CommentPlacement::Trailing { node: enclosing_node, comment, }; @@ -328,7 +328,7 @@ impl CommentStyle for JsCommentStyle { } JsSyntaxKind::JS_FOR_IN_STATEMENT | JsSyntaxKind::JS_FOR_OF_STATEMENT => { - return CommentPosition::Leading { + return CommentPlacement::Leading { node: enclosing_node, comment, } @@ -343,7 +343,7 @@ impl CommentStyle for JsCommentStyle { // Handles array hole comments. Array holes have no token so all comments // become trailing comments by default. Override it that all comments are elading comments. if JsArrayHole::can_cast(preceding_node.kind()) { - return CommentPosition::Leading { + return CommentPlacement::Leading { node: preceding_node.clone(), comment, }; @@ -365,17 +365,17 @@ impl CommentStyle for JsCommentStyle { if !comment.is_trailing_token_trivia() && JsAnyArrayLike::can_cast(enclosing_node.kind()) { - let array = JsAnyArrayLike::unwrap_cast(enclosing_node.clone()); + let array = JsAnyArrayLike::unwrap_cast(enclosing_node); if array.r_brack_token().as_ref() == Ok(comment.following_token()) { if let Some(Ok(last_element)) = array.last_element() { if last_element.kind() == JsSyntaxKind::JS_ARRAY_HOLE { - return CommentPosition::Leading { + return CommentPlacement::Leading { node: last_element, comment, }; } else { - return CommentPosition::Trailing { + return CommentPlacement::Trailing { node: last_element, comment, }; @@ -415,13 +415,11 @@ impl CommentStyle for JsCommentStyle { // // test // ``` if let Some(arguments) = JsCallArguments::cast_ref(&enclosing_node) { - if arguments.r_paren_token().as_ref() == Ok(comment.following_token()) { - if arguments.args().is_empty() && comment.kind().is_line() { - return CommentPosition::Trailing { - node: arguments.into_syntax(), - comment, - }; - } + if arguments.r_paren_token().as_ref() == Ok(comment.following_token()) && arguments.args().is_empty() && comment.kind().is_line() { + return CommentPlacement::Trailing { + node: arguments.into_syntax(), + comment, + }; } } // Makes the last comment in a non-empty call arguments list a trailing comment of the @@ -431,7 +429,7 @@ impl CommentStyle for JsCommentStyle { // ``` else if let Some(arguments_list) = JsCallArgumentList::cast_ref(&enclosing_node) { if let Some(Ok(last_argument)) = arguments_list.last() { - return CommentPosition::Trailing { + return CommentPlacement::Trailing { node: last_argument.into_syntax(), comment, }; @@ -443,7 +441,7 @@ impl CommentStyle for JsCommentStyle { } } - CommentPosition::Default(comment) + CommentPlacement::Default(comment) } } diff --git a/crates/rome_js_formatter/src/context.rs b/crates/rome_js_formatter/src/context.rs index 1bdac97a591..9bfc692b022 100644 --- a/crates/rome_js_formatter/src/context.rs +++ b/crates/rome_js_formatter/src/context.rs @@ -1,7 +1,7 @@ -use crate::comments::{FormatJsLeadingComment, JsCommentStyle}; +use crate::comments::{FormatJsLeadingComment, JsCommentStyle, JsComments}; use rome_formatter::printer::PrinterOptions; use rome_formatter::{ - Comments, CstFormatContext, FormatContext, FormatOptions, IndentStyle, LineWidth, + CstFormatContext, FormatContext, FormatOptions, IndentStyle, LineWidth, TransformSourceMap, }; use rome_js_syntax::{JsLanguage, SourceType}; @@ -15,13 +15,13 @@ pub struct JsFormatContext { options: JsFormatOptions, /// The comments of the nodes and tokens in the program. - comments: Rc>, + comments: Rc, source_map: Option, } impl JsFormatContext { - pub fn new(options: JsFormatOptions, comments: Comments) -> Self { + pub fn new(options: JsFormatOptions, comments: JsComments) -> Self { Self { options, comments: Rc::new(comments), @@ -67,7 +67,7 @@ impl CstFormatContext for JsFormatContext { type Style = JsCommentStyle; type CommentRule = FormatJsLeadingComment; - fn comments(&self) -> &Comments { + fn comments(&self) -> &JsComments { &self.comments } } diff --git a/crates/rome_js_formatter/src/js/bindings/parameters.rs b/crates/rome_js_formatter/src/js/bindings/parameters.rs index bf132a75ad5..a61296ac98a 100644 --- a/crates/rome_js_formatter/src/js/bindings/parameters.rs +++ b/crates/rome_js_formatter/src/js/bindings/parameters.rs @@ -1,5 +1,5 @@ use crate::prelude::*; -use rome_formatter::{write, Comments, CstFormatContext}; +use rome_formatter::{write, CstFormatContext}; use crate::js::expressions::call_arguments::is_test_call_expression; use crate::js::lists::parameter_list::{ @@ -8,8 +8,7 @@ use crate::js::lists::parameter_list::{ use crate::builders::format_delimited; use rome_js_syntax::{ - JsAnyConstructorParameter, JsAnyFormalParameter, JsCallExpression, JsConstructorParameters, - JsLanguage, JsParameters, JsSyntaxKind, JsSyntaxToken, TsType, + JsAnyConstructorParameter, JsAnyFormalParameter, JsCallExpression, JsConstructorParameters, JsParameters, JsSyntaxKind, JsSyntaxToken, TsType, }; use rome_rowan::{declare_node_union, SyntaxResult}; @@ -184,7 +183,7 @@ pub enum ParameterLayout { fn should_hug_function_parameters( parameters: &FormatJsAnyParameters, - comments: &Comments, + comments: &JsComments, ) -> FormatResult { use rome_js_syntax::{ JsAnyBinding::*, JsAnyBindingPattern::*, JsAnyExpression::*, JsAnyFormalParameter::*, diff --git a/crates/rome_js_formatter/src/js/expressions/arrow_function_expression.rs b/crates/rome_js_formatter/src/js/expressions/arrow_function_expression.rs index 5ae824ab9f3..cc498a2cb79 100644 --- a/crates/rome_js_formatter/src/js/expressions/arrow_function_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/arrow_function_expression.rs @@ -1,5 +1,5 @@ use crate::prelude::*; -use rome_formatter::{format_args, write, Comments, CstFormatContext, FormatRuleWithOptions}; +use rome_formatter::{format_args, write, CstFormatContext, FormatRuleWithOptions}; use std::iter::once; use crate::parentheses::{ @@ -11,7 +11,7 @@ use crate::utils::{ }; use rome_js_syntax::{ JsAnyArrowFunctionParameters, JsAnyBindingPattern, JsAnyExpression, JsAnyFormalParameter, - JsAnyFunctionBody, JsAnyParameter, JsAnyTemplateElement, JsArrowFunctionExpression, JsLanguage, + JsAnyFunctionBody, JsAnyParameter, JsAnyTemplateElement, JsArrowFunctionExpression, JsSyntaxKind, JsSyntaxNode, JsTemplate, }; use rome_rowan::SyntaxResult; @@ -428,7 +428,7 @@ impl ArrowFunctionLayout { /// of the different layouts. fn for_arrow( arrow: JsArrowFunctionExpression, - comments: &Comments, + comments: &JsComments, assignment_layout: Option, ) -> SyntaxResult { let mut head = None; diff --git a/crates/rome_js_formatter/src/js/expressions/call_arguments.rs b/crates/rome_js_formatter/src/js/expressions/call_arguments.rs index b984f358de1..c7a09764244 100644 --- a/crates/rome_js_formatter/src/js/expressions/call_arguments.rs +++ b/crates/rome_js_formatter/src/js/expressions/call_arguments.rs @@ -1,10 +1,10 @@ use crate::prelude::*; use crate::utils::{is_call_like_expression, write_arguments_multi_line}; -use rome_formatter::{format_args, write, Comments, CstFormatContext}; +use rome_formatter::{format_args, write, CstFormatContext}; use rome_js_syntax::{ JsAnyCallArgument, JsAnyExpression, JsAnyFunctionBody, JsAnyLiteralExpression, JsAnyName, JsAnyStatement, JsArrayExpression, JsArrowFunctionExpression, JsCallArgumentList, - JsCallArguments, JsCallArgumentsFields, JsCallExpression, JsLanguage, TsReferenceType, + JsCallArguments, JsCallArgumentsFields, JsCallExpression, TsReferenceType, }; use rome_rowan::{AstSeparatedList, SyntaxResult, SyntaxTokenText}; @@ -218,7 +218,7 @@ impl FormatNodeRule for FormatJsCallArguments { /// Checks if the the first argument requires grouping fn should_group_first_argument( list: &JsCallArgumentList, - comments: &Comments, + comments: &JsComments, ) -> SyntaxResult { if list.len() != 2 { return Ok(false); @@ -261,7 +261,7 @@ fn should_group_first_argument( /// Checks if the last group requires grouping fn should_group_last_argument( list: &JsCallArgumentList, - comments: &Comments, + comments: &JsComments, ) -> SyntaxResult { let list_len = list.len(); let mut iter = list.iter(); @@ -306,7 +306,7 @@ fn should_group_last_argument( fn could_group_expression_argument( argument: &JsAnyExpression, is_arrow_recursion: bool, - comments: &Comments, + comments: &JsComments, ) -> SyntaxResult { let result = match argument { JsAnyExpression::JsObjectExpression(object_expression) => { @@ -415,7 +415,7 @@ fn could_group_expression_argument( fn is_react_hook_with_deps_array( first_argument: &JsAnyCallArgument, second_argument: &JsAnyCallArgument, - comments: &Comments, + comments: &JsComments, ) -> SyntaxResult { if comments.has_comments(first_argument.syntax()) || comments.has_comments(second_argument.syntax()) diff --git a/crates/rome_js_formatter/src/js/lists/array_element_list.rs b/crates/rome_js_formatter/src/js/lists/array_element_list.rs index 9a3ab4f6fbc..3779a0cd0f8 100644 --- a/crates/rome_js_formatter/src/js/lists/array_element_list.rs +++ b/crates/rome_js_formatter/src/js/lists/array_element_list.rs @@ -1,9 +1,9 @@ use crate::prelude::*; -use rome_formatter::{write, Comments, CstFormatContext, FormatRuleWithOptions, GroupId}; +use rome_formatter::{write, CstFormatContext, FormatRuleWithOptions, GroupId}; use crate::utils::array::write_array_node; -use rome_js_syntax::{JsArrayElementList, JsLanguage}; +use rome_js_syntax::{JsArrayElementList}; use rome_rowan::{AstNode, AstSeparatedList}; #[derive(Debug, Clone, Default)] @@ -89,7 +89,7 @@ enum ArrayLayout { /// The underlying logic only allows lists of literal expressions /// with 10 or less characters, potentially wrapped in a "short" /// unary expression (+, -, ~ or !) -fn can_print_fill(list: &JsArrayElementList, comments: &Comments) -> bool { +fn can_print_fill(list: &JsArrayElementList, comments: &JsComments) -> bool { use rome_js_syntax::JsAnyArrayElement::*; use rome_js_syntax::JsAnyExpression::*; use rome_js_syntax::JsUnaryOperator::*; diff --git a/crates/rome_js_formatter/src/js/lists/template_element_list.rs b/crates/rome_js_formatter/src/js/lists/template_element_list.rs index 4a99822a965..8a7eab3548f 100644 --- a/crates/rome_js_formatter/src/js/lists/template_element_list.rs +++ b/crates/rome_js_formatter/src/js/lists/template_element_list.rs @@ -3,7 +3,7 @@ use crate::js::expressions::template_element::{AnyTemplateElement, TemplateEleme use crate::context::TabWidth; use crate::prelude::*; -use rome_formatter::Comments; + use rome_js_syntax::{ JsAnyExpression, JsAnyLiteralExpression, JsAnyTemplateElement, JsLanguage, JsTemplateElementList, TsAnyTemplateElement, TsTemplateElementList, @@ -88,7 +88,7 @@ impl AnyTemplateElementList { /// Simple expressions are: /// * Identifiers: `this`, `a` /// * Members: `a.b`, `a[b]`, `a.b[c].d`, `a.b[5]`, `a.b["test"]` - fn is_simple(&self, comments: &Comments) -> bool { + fn is_simple(&self, comments: &JsComments) -> bool { match self { AnyTemplateElementList::JsTemplateElementList(list) => { if list.is_empty() { @@ -147,7 +147,7 @@ declare_node_union! { fn is_simple_member_expression( expression: JsAnyExpression, - comments: &Comments, + comments: &JsComments, ) -> SyntaxResult { let mut current = expression; diff --git a/crates/rome_js_formatter/src/js/statements/block_statement.rs b/crates/rome_js_formatter/src/js/statements/block_statement.rs index 8f71537fd9a..c5e01497a16 100644 --- a/crates/rome_js_formatter/src/js/statements/block_statement.rs +++ b/crates/rome_js_formatter/src/js/statements/block_statement.rs @@ -1,7 +1,7 @@ use crate::prelude::*; -use rome_formatter::{write, Buffer, Comments, CstFormatContext}; +use rome_formatter::{write, Buffer, CstFormatContext}; use rome_js_syntax::{JsAnyStatement, JsEmptyStatement}; -use rome_js_syntax::{JsBlockStatement, JsLanguage}; +use rome_js_syntax::{JsBlockStatement}; use rome_js_syntax::JsBlockStatementFields; use rome_js_syntax::JsSyntaxKind; @@ -46,7 +46,7 @@ impl FormatNodeRule for FormatJsBlockStatement { } } -fn is_empty_block(block: &JsBlockStatement, comments: &Comments) -> bool { +fn is_empty_block(block: &JsBlockStatement, comments: &JsComments) -> bool { // add extra branch to avoid formatting the same code twice and generating different code, // here is an example: // ```js diff --git a/crates/rome_js_formatter/src/js/statements/break_statement.rs b/crates/rome_js_formatter/src/js/statements/break_statement.rs index c51a9236dac..93e6ad06493 100644 --- a/crates/rome_js_formatter/src/js/statements/break_statement.rs +++ b/crates/rome_js_formatter/src/js/statements/break_statement.rs @@ -25,7 +25,7 @@ impl FormatNodeRule for FormatJsBreakStatement { // FIXME best place to add dangling comments if let Some(label) = &label_token { - if f.context().comments().has_dangling_comments(&node.syntax()) { + if f.context().comments().has_dangling_comments(node.syntax()) { write!(f, [space(), format_dangling_comments(node.syntax())])?; } diff --git a/crates/rome_js_formatter/src/js/statements/return_statement.rs b/crates/rome_js_formatter/src/js/statements/return_statement.rs index 7969f1c207b..1bcd20e3ee0 100644 --- a/crates/rome_js_formatter/src/js/statements/return_statement.rs +++ b/crates/rome_js_formatter/src/js/statements/return_statement.rs @@ -1,11 +1,11 @@ use crate::prelude::*; use crate::utils::{FormatWithSemicolon, JsAnyBinaryLikeExpression, JsAnyBinaryLikeLeftExpression}; -use rome_formatter::{format_args, write, Comments, CstFormatContext}; +use rome_formatter::{format_args, write, CstFormatContext}; use crate::parentheses::get_expression_left_side; use rome_js_syntax::{ - JsAnyExpression, JsLanguage, JsReturnStatement, JsSequenceExpression, JsSyntaxToken, + JsAnyExpression, JsReturnStatement, JsSequenceExpression, JsSyntaxToken, JsThrowStatement, }; use rome_rowan::{declare_node_union, SyntaxResult}; @@ -47,7 +47,7 @@ impl Format for JsAnyStatementWithArgument { // We'll format it after the semicolon f.state_mut() - .mark_dangling_comments_formatted(&self.syntax()); + .mark_dangling_comments_formatted(self.syntax()); if is_last_comment_line { write!(f, [semicolon.format()])?; @@ -58,7 +58,7 @@ impl Format for JsAnyStatementWithArgument { f, [ space(), - format_dangling_comments(&self.syntax()).ignore_formatted_check() + format_dangling_comments(self.syntax()).ignore_formatted_check() ] )?; } @@ -74,7 +74,7 @@ impl Format for JsAnyStatementWithArgument { [FormatWithSemicolon::new( &format_with(|f| { if let Some(argument) = &argument { - write!(f, [space(), FormatReturnOrThrowArgument(&argument)])?; + write!(f, [space(), FormatReturnOrThrowArgument(argument)])?; } Ok(()) @@ -138,10 +138,7 @@ impl Format for FormatReturnOrThrowArgument<'_> { /// /// Traversing the left nodes is necessary in case the first node is parenthesized because /// parentheses will be removed (and be re-added by the return statement, but only if the argument breaks) -fn has_argument_leading_comments( - argument: &JsAnyExpression, - comments: &Comments, -) -> bool { +fn has_argument_leading_comments(argument: &JsAnyExpression, comments: &JsComments) -> bool { let mut current: Option = Some(argument.clone().into()); while let Some(expression) = current { diff --git a/crates/rome_js_formatter/src/jsx/attribute/expression_attribute_value.rs b/crates/rome_js_formatter/src/jsx/attribute/expression_attribute_value.rs index 47ec8b2b677..97d18775ae1 100644 --- a/crates/rome_js_formatter/src/jsx/attribute/expression_attribute_value.rs +++ b/crates/rome_js_formatter/src/jsx/attribute/expression_attribute_value.rs @@ -1,8 +1,8 @@ use crate::prelude::*; -use rome_formatter::{format_args, write, Comments, CstFormatContext}; +use rome_formatter::{format_args, write, CstFormatContext}; use rome_js_syntax::{ - JsAnyExpression, JsLanguage, JsxAnyTag, JsxExpressionAttributeValue, + JsAnyExpression, JsxAnyTag, JsxExpressionAttributeValue, JsxExpressionAttributeValueFields, }; @@ -77,7 +77,7 @@ impl FormatNodeRule for FormatJsxExpressionAttribut /// ``` pub(crate) fn should_inline_jsx_expression( expression: &JsAnyExpression, - comments: &Comments, + comments: &JsComments, ) -> bool { use JsAnyExpression::*; diff --git a/crates/rome_js_formatter/src/jsx/lists/child_list.rs b/crates/rome_js_formatter/src/jsx/lists/child_list.rs index 2e26ccdcbee..da9d364babc 100644 --- a/crates/rome_js_formatter/src/jsx/lists/child_list.rs +++ b/crates/rome_js_formatter/src/jsx/lists/child_list.rs @@ -5,9 +5,9 @@ use crate::utils::jsx::{ }; use crate::JsFormatter; use rome_formatter::{ - format_args, write, Comments, CstFormatContext, FormatRuleWithOptions, VecBuffer, + format_args, write, CstFormatContext, FormatRuleWithOptions, VecBuffer, }; -use rome_js_syntax::{JsLanguage, JsxAnyChild, JsxChildList}; +use rome_js_syntax::{JsxAnyChild, JsxChildList}; #[derive(Debug, Clone, Default)] pub struct FormatJsxChildList { @@ -278,7 +278,7 @@ impl FormatJsxChildList { } /// Computes additional meta data about the children by iterating once over all children. - fn children_meta(&self, list: &JsxChildList, comments: &Comments) -> ChildrenMeta { + fn children_meta(&self, list: &JsxChildList, comments: &JsComments) -> ChildrenMeta { let mut has_expression = false; let mut meta = ChildrenMeta::default(); diff --git a/crates/rome_js_formatter/src/jsx/tag/opening_element.rs b/crates/rome_js_formatter/src/jsx/tag/opening_element.rs index 8f966190de8..523e7872b66 100644 --- a/crates/rome_js_formatter/src/jsx/tag/opening_element.rs +++ b/crates/rome_js_formatter/src/jsx/tag/opening_element.rs @@ -1,8 +1,8 @@ use crate::prelude::*; -use rome_formatter::{write, Comments, CstFormatContext}; +use rome_formatter::{write, CstFormatContext}; use rome_js_syntax::{ - JsLanguage, JsSyntaxToken, JsxAnyAttribute, JsxAnyAttributeValue, JsxAnyElementName, + JsSyntaxToken, JsxAnyAttribute, JsxAnyAttributeValue, JsxAnyElementName, JsxAttributeList, JsxOpeningElement, JsxSelfClosingElement, JsxString, TsTypeArguments, }; use rome_rowan::{declare_node_union, SyntaxResult}; @@ -150,10 +150,7 @@ impl JsxAnyOpeningElement { matches!(self, JsxAnyOpeningElement::JsxSelfClosingElement(_)) } - fn compute_layout( - &self, - comments: &Comments, - ) -> SyntaxResult { + fn compute_layout(&self, comments: &JsComments) -> SyntaxResult { let attributes = self.attributes(); let name = self.name()?; diff --git a/crates/rome_js_formatter/src/lib.rs b/crates/rome_js_formatter/src/lib.rs index 384beddf249..303755d6f4f 100644 --- a/crates/rome_js_formatter/src/lib.rs +++ b/crates/rome_js_formatter/src/lib.rs @@ -465,7 +465,7 @@ where } fn prints_comments(&self, _item: &N) -> bool { - return false; + false } } @@ -609,7 +609,7 @@ mod tests { use rome_js_syntax::SourceType; use rome_rowan::{TextRange, TextSize}; - use crate::check_reformat::{check_reformat, CheckReformatParams}; + #[test] fn test_range_formatting() { @@ -770,7 +770,7 @@ fnString = // Comment0 let tree = parse(src, 0, syntax); let options = JsFormatOptions::new(syntax); - let result = format_node(options.clone(), &tree.syntax()) + let result = format_node(options, &tree.syntax()) .unwrap() .print(); // check_reformat(CheckReformatParams { diff --git a/crates/rome_js_formatter/src/prelude.rs b/crates/rome_js_formatter/src/prelude.rs index eda4fcc6f37..f718048d697 100644 --- a/crates/rome_js_formatter/src/prelude.rs +++ b/crates/rome_js_formatter/src/prelude.rs @@ -1,16 +1,16 @@ //! This module provides important and useful traits to help to format tokens and nodes //! when implementing the [crate::FormatNodeRule] trait. +pub use crate::builders::{ + format_or_verbatim, format_suppressed_node, format_unknown_node, format_verbatim_node, +}; pub(crate) use crate::{ - AsFormat as _, FormatNodeRule, FormattedIterExt, JsFormatContext, JsFormatter, + comments::JsComments, AsFormat as _, FormatNodeRule, FormattedIterExt, JsFormatContext, + JsFormatter, }; pub use rome_formatter::prelude::*; pub use rome_rowan::{AstNode as _, AstNodeList as _, AstSeparatedList as _}; -pub use crate::builders::{ - format_or_verbatim, format_suppressed_node, format_unknown_node, format_verbatim_node, -}; - pub use crate::separated::{ FormatAstSeparatedListExtension, FormatSeparatedOptions, TrailingSeparator, }; diff --git a/crates/rome_js_formatter/src/ts/declarations/interface_declaration.rs b/crates/rome_js_formatter/src/ts/declarations/interface_declaration.rs index 9bf1cbefeca..3229c7f7be5 100644 --- a/crates/rome_js_formatter/src/ts/declarations/interface_declaration.rs +++ b/crates/rome_js_formatter/src/ts/declarations/interface_declaration.rs @@ -77,7 +77,7 @@ impl FormatNodeRule for FormatTsInterfaceDeclaration { }; let last_token = last_node.last_token(); - let mut has_trailing_comments = false; + let has_trailing_comments = false; // FIXME // if let Some(last_token) = &last_token { @@ -117,7 +117,7 @@ impl FormatNodeRule for FormatTsInterfaceDeclaration { &format_args![ format_with(|f| { // TODO: See PR Write the manual handled comments - if let Some(last_token) = &last_token { + if let Some(_last_token) = &last_token { if has_trailing_comments { write!(f, [hard_line_break()])?; } diff --git a/crates/rome_js_formatter/src/utils/assignment_like.rs b/crates/rome_js_formatter/src/utils/assignment_like.rs index 95cc6c42cab..b214e15427b 100644 --- a/crates/rome_js_formatter/src/utils/assignment_like.rs +++ b/crates/rome_js_formatter/src/utils/assignment_like.rs @@ -3,13 +3,13 @@ use crate::prelude::*; use crate::utils::member_chain::is_member_call_chain; use crate::utils::object::write_member_name; use crate::utils::JsAnyBinaryLikeExpression; -use rome_formatter::{format_args, write, Comments, CstFormatContext, FormatOptions, VecBuffer}; +use rome_formatter::{format_args, write, CstFormatContext, FormatOptions, VecBuffer}; use rome_js_syntax::JsAnyLiteralExpression; use rome_js_syntax::{ JsAnyAssignmentPattern, JsAnyBindingPattern, JsAnyCallArgument, JsAnyClassMemberName, JsAnyExpression, JsAnyFunctionBody, JsAnyObjectAssignmentPatternMember, JsAnyObjectBindingPatternMember, JsAnyObjectMemberName, JsAnyTemplateElement, - JsAssignmentExpression, JsInitializerClause, JsLanguage, JsLiteralMemberName, + JsAssignmentExpression, JsInitializerClause, JsLiteralMemberName, JsObjectAssignmentPattern, JsObjectAssignmentPatternProperty, JsObjectBindingPattern, JsPropertyClassMember, JsPropertyClassMemberFields, JsPropertyObjectMember, JsSyntaxKind, JsVariableDeclarator, TsAnyVariableAnnotation, TsIdentifierBinding, @@ -819,7 +819,7 @@ impl JsAnyAssignmentLike { fn should_break_after_operator( &self, right: &RightAssignmentLike, - comments: &Comments, + comments: &JsComments, ) -> SyntaxResult { let result = if let Some(expression) = right.as_expression() { should_break_after_operator(&expression, comments)? @@ -834,7 +834,7 @@ impl JsAnyAssignmentLike { /// Checks if the function is entitled to be printed with layout [AssignmentLikeLayout::BreakAfterOperator] pub(crate) fn should_break_after_operator( right: &JsAnyExpression, - comments: &Comments, + comments: &JsComments, ) -> SyntaxResult { if comments.has_leading_own_line_comment(right.syntax()) { return Ok(true); @@ -1074,7 +1074,7 @@ fn is_poorly_breakable_member_or_call_chain( fn is_short_argument( argument: JsAnyCallArgument, threshold: u16, - comments: &Comments, + comments: &JsComments, ) -> SyntaxResult { if comments.has_comments(argument.syntax()) { return Ok(false); diff --git a/crates/rome_js_formatter/src/utils/format_class.rs b/crates/rome_js_formatter/src/utils/format_class.rs index 9771628006d..f006499dc18 100644 --- a/crates/rome_js_formatter/src/utils/format_class.rs +++ b/crates/rome_js_formatter/src/utils/format_class.rs @@ -1,14 +1,14 @@ use crate::builders::format_delimited; use crate::prelude::*; -use rome_formatter::{write, Comments}; -use rome_js_syntax::{JsAnyClass, JsLanguage}; +use rome_formatter::{write}; +use rome_js_syntax::{JsAnyClass}; pub struct FormatClass<'a> { class: &'a JsAnyClass, } impl FormatClass<'_> { - fn should_group(&self, comments: &Comments) -> FormatResult { + fn should_group(&self, comments: &JsComments) -> FormatResult { if let Some(id) = self.class.id()? { if comments.has_trailing_comments(id.syntax()) { return Ok(true); diff --git a/crates/rome_js_formatter/src/utils/jsx.rs b/crates/rome_js_formatter/src/utils/jsx.rs index 02b9f039683..e5dc802b5dc 100644 --- a/crates/rome_js_formatter/src/utils/jsx.rs +++ b/crates/rome_js_formatter/src/utils/jsx.rs @@ -1,8 +1,8 @@ use crate::context::QuoteStyle; use crate::prelude::*; -use rome_formatter::{format_args, write, Comments}; +use rome_formatter::{format_args, write}; use rome_js_syntax::{ - JsAnyExpression, JsAnyLiteralExpression, JsComputedMemberExpression, JsLanguage, + JsAnyExpression, JsAnyLiteralExpression, JsComputedMemberExpression, JsStaticMemberExpression, JsSyntaxKind, JsxAnyChild, JsxExpressionChild, JsxTagExpression, TextLen, }; @@ -145,7 +145,7 @@ impl Format for JsxRawSpace { pub(crate) fn is_whitespace_jsx_expression( child: &JsxExpressionChild, - comments: &Comments, + comments: &JsComments, ) -> bool { match child.expression() { Some(JsAnyExpression::JsAnyLiteralExpression( @@ -173,7 +173,7 @@ pub(crate) fn is_whitespace_jsx_expression( pub(crate) fn jsx_split_children( children: I, - comments: &Comments, + comments: &JsComments, ) -> SyntaxResult> where I: IntoIterator, diff --git a/crates/rome_js_formatter/src/utils/member_chain/groups.rs b/crates/rome_js_formatter/src/utils/member_chain/groups.rs index b146877f47b..baf72ef42b5 100644 --- a/crates/rome_js_formatter/src/utils/member_chain/groups.rs +++ b/crates/rome_js_formatter/src/utils/member_chain/groups.rs @@ -2,8 +2,8 @@ use crate::context::TabWidth; use crate::parentheses::NeedsParentheses; use crate::prelude::*; use crate::utils::member_chain::chain_member::ChainMember; -use rome_formatter::{write, Comments}; -use rome_js_syntax::{JsCallExpression, JsLanguage}; +use rome_formatter::{write}; +use rome_js_syntax::{JsCallExpression}; use rome_rowan::SyntaxResult; use std::mem; @@ -97,7 +97,7 @@ impl MemberChainGroups { pub fn should_merge( &self, head_group: &MemberChainGroup, - comments: &Comments, + comments: &JsComments, ) -> SyntaxResult { Ok(!self.groups.len() >= 1 && self.should_not_wrap(head_group)? @@ -108,7 +108,7 @@ impl MemberChainGroups { } /// Checks if the groups contain comments. - pub fn has_comments(&self, comments: &Comments) -> SyntaxResult { + pub fn has_comments(&self, comments: &JsComments) -> SyntaxResult { let mut members = self.groups.iter().flat_map(|item| item.members.iter()); let has_comments = members.any(|item| { @@ -191,7 +191,7 @@ impl MemberChainGroups { pub(crate) fn should_merge_with_first_group( &mut self, head_group: &MemberChainGroup, - comments: &Comments, + comments: &JsComments, ) -> Option> { if self.should_merge(head_group, comments).unwrap_or(false) { let mut new_groups = self.groups.split_off(1); @@ -208,10 +208,7 @@ impl MemberChainGroups { /// Here we check if the length of the groups exceeds the cutoff or there are comments /// This function is the inverse of the prettier function /// [Prettier applies]: https://github.com/prettier/prettier/blob/a043ac0d733c4d53f980aa73807a63fc914f23bd/src/language-js/print/member-chain.js#L342 - pub(crate) fn is_member_call_chain( - &self, - comments: &Comments, - ) -> SyntaxResult { + pub(crate) fn is_member_call_chain(&self, comments: &JsComments) -> SyntaxResult { Ok(self.groups.len() > self.cutoff as usize || self.has_comments(comments)?) } @@ -244,7 +241,7 @@ impl MemberChainGroup { self.members.extend(group) } - pub(super) fn has_comments(&self, comments: &Comments) -> bool { + pub(super) fn has_comments(&self, comments: &JsComments) -> bool { self.members .iter() .any(|item| comments.has_trailing_comments(item.syntax())) diff --git a/crates/rome_js_formatter/src/utils/member_chain/mod.rs b/crates/rome_js_formatter/src/utils/member_chain/mod.rs index daccc2bc99c..b5c7d416827 100644 --- a/crates/rome_js_formatter/src/utils/member_chain/mod.rs +++ b/crates/rome_js_formatter/src/utils/member_chain/mod.rs @@ -112,8 +112,8 @@ use crate::utils::member_chain::groups::{ MemberChainGroup, MemberChainGroups, MemberChainGroupsBuilder, }; use crate::utils::member_chain::simple_argument::SimpleArgument; -use rome_formatter::{format_args, write, Buffer, Comments, CstFormatContext}; -use rome_js_syntax::{JsAnyExpression, JsCallExpression, JsExpressionStatement, JsLanguage}; +use rome_formatter::{format_args, write, Buffer, CstFormatContext}; +use rome_js_syntax::{JsAnyExpression, JsCallExpression, JsExpressionStatement}; use rome_rowan::{AstNode, SyntaxResult}; #[derive(Debug, Clone)] @@ -125,10 +125,7 @@ pub(crate) struct MemberChain { impl MemberChain { /// It tells if the groups should be break on multiple lines - pub(crate) fn groups_should_break( - &self, - comments: &Comments, - ) -> FormatResult { + pub(crate) fn groups_should_break(&self, comments: &JsComments) -> FormatResult { // Do not allow the group to break if it only contains a single call expression if self.calls_count <= 1 { return Ok(false); @@ -379,7 +376,7 @@ fn compute_groups( fn flatten_member_chain( queue: &mut Vec, node: JsAnyExpression, - comments: &Comments, + comments: &JsComments, root: bool, ) -> SyntaxResult { use JsAnyExpression::*; From 1047d009e1af507077e1d2a910d7e84e8ff1d55a Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Thu, 8 Sep 2022 14:02:40 +0200 Subject: [PATCH 06/45] Format IfStatement comments --- Cargo.lock | 2 + crates/rome_formatter/Cargo.toml | 4 + crates/rome_formatter/src/comments.rs | 88 +-- crates/rome_formatter/src/comments/builder.rs | 728 +++++++++++++----- crates/rome_formatter/src/lib.rs | 5 +- crates/rome_formatter/src/token.rs | 87 ++- crates/rome_js_formatter/src/comments.rs | 556 ++++++------- .../src/js/statements/block_statement.rs | 6 +- .../src/js/statements/expression_statement.rs | 14 +- .../src/js/statements/if_statement.rs | 32 +- crates/rome_js_formatter/src/lib.rs | 22 +- .../js/arrays/numbers-with-holes.js.snap | 19 +- .../js/assignment-comments/call.js.snap | 47 -- .../tests/specs/prettier/js/comments/if.js | 6 +- .../specs/prettier/js/comments/if.js.snap | 227 ------ .../specs/prettier/js/comments/switch.js.snap | 110 --- .../empty-object.js.snap | 83 -- .../trailing_whitespace.js.snap | 137 ---- 18 files changed, 1024 insertions(+), 1149 deletions(-) delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/assignment-comments/call.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/comments/if.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/comments/switch.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/last-argument-expansion/empty-object.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/trailing-comma/trailing_whitespace.js.snap diff --git a/Cargo.lock b/Cargo.lock index 548c1814c7e..6fd6b86ed3e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1570,6 +1570,8 @@ version = "0.0.0" dependencies = [ "cfg-if", "indexmap", + "rome_js_parser", + "rome_js_syntax", "rome_rowan", "rustc-hash", "schemars", diff --git a/crates/rome_formatter/Cargo.toml b/crates/rome_formatter/Cargo.toml index c288a1f6035..c92a4375b27 100644 --- a/crates/rome_formatter/Cargo.toml +++ b/crates/rome_formatter/Cargo.toml @@ -17,5 +17,9 @@ indexmap = "1.8.2" schemars = { version = "0.8.10", optional = true } rustc-hash = "1.1.0" +[dev-dependencies] +rome_js_parser = { path = "../rome_js_parser"} +rome_js_syntax = { path = "../rome_js_syntax" } + [features] serde = ["dep:serde", "schemars", "rome_rowan/serde"] diff --git a/crates/rome_formatter/src/comments.rs b/crates/rome_formatter/src/comments.rs index a188c6bb362..a2e490d08d8 100644 --- a/crates/rome_formatter/src/comments.rs +++ b/crates/rome_formatter/src/comments.rs @@ -3,10 +3,8 @@ mod multimap; use self::{builder::CommentsBuilderVisitor, multimap::AppendOnlyMultiMap}; use crate::TextSize; -use rome_rowan::{ - Direction, Language, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, - SyntaxTriviaPieceComments, WalkEvent, -}; +use rome_rowan::syntax::SyntaxElementKey; +use rome_rowan::{Language, SyntaxKind, SyntaxNode, SyntaxToken, SyntaxTriviaPieceComments}; #[cfg(debug_assertions)] use std::cell::RefCell; use std::collections::HashSet; @@ -141,12 +139,13 @@ impl SourceComment { #[derive(Debug, Clone)] pub struct DecoratedComment { + enclosing: SyntaxNode, preceding: Option>, following: Option>, following_token: SyntaxToken, lines_before: u32, lines_after: u32, - trailing_token_comment: bool, + position: CommentPosition, comment: SyntaxTriviaPieceComments, kind: CommentKind, } @@ -154,23 +153,14 @@ pub struct DecoratedComment { impl DecoratedComment { /// The node that fully encloses the comment (the comment's start and end position are fully in the /// node's bounds). - pub fn enclosing_node(&self) -> SyntaxNode { - // SAFETY: Guaranteed by the fact that comments are extracted from a root node. - self.comment - .as_piece() - .token() - .parent() - .expect("Expected token to have a parent node.") + pub fn enclosing_node(&self) -> &SyntaxNode { + &self.enclosing } pub fn piece(&self) -> &SyntaxTriviaPieceComments { &self.comment } - pub fn enclosing_token(&self) -> SyntaxToken { - self.comment.as_piece().token() - } - /// The node directly preceding the comment or [None] if the comment is preceded by a token or is the first /// token in the program. pub fn preceding_node(&self) -> Option<&SyntaxNode> { @@ -200,8 +190,9 @@ impl DecoratedComment { } /// `true` if the comment is part of the tokens [trailing trivia](SyntaxToken::trailing_trivia) + #[deprecated] pub fn is_trailing_token_trivia(&self) -> bool { - self.trailing_token_comment + self.position.is_same_line() } /// Returns the [kind](CommentKind) of the comment. @@ -209,6 +200,10 @@ impl DecoratedComment { self.kind } + pub fn position(&self) -> CommentPosition { + self.position + } + pub fn following_token(&self) -> &SyntaxToken { &self.following_token } @@ -225,8 +220,8 @@ impl From> for SourceComment { } } -#[derive(Debug, Copy, Clone)] -enum DecoratedCommentKind { +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum CommentPosition { /// A comment that is separated by at least one line break from the following token /// /// ```javascript @@ -251,6 +246,20 @@ enum DecoratedCommentKind { SameLine, } +impl CommentPosition { + pub const fn is_same_line(&self) -> bool { + matches!(self, CommentPosition::SameLine) + } + + pub const fn is_own_line(&self) -> bool { + matches!(self, CommentPosition::OwnLine) + } + + pub const fn is_end_of_line(&self) -> bool { + matches!(self, CommentPosition::EndOfLine) + } +} + #[derive(Debug)] pub enum CommentPlacement { /// Overrides the positioning of the comment to be a leading node comment. @@ -276,19 +285,19 @@ pub enum CommentPlacement { } /// Defines how to format comments for a specific [Language]. -pub trait CommentStyle { +pub trait CommentStyle: Default { type Language: Language; /// Returns `true` if a comment with the given `text` is a `rome-ignore format:` suppression comment. fn is_suppression(text: &str) -> bool; - fn is_open_parentheses(kind: ::Kind) -> bool; - /// Returns the (kind)[CommentKind] of the comment - fn get_comment_kind(comment: &SyntaxTriviaPieceComments) -> CommentKind; + fn get_comment_kind(&self, comment: &SyntaxTriviaPieceComments) -> CommentKind; - fn place_comment(comment: DecoratedComment) - -> CommentPlacement; + fn place_comment( + &self, + comment: DecoratedComment, + ) -> CommentPlacement; } /// Type that stores the comments of a tree and gives access to: @@ -320,31 +329,14 @@ pub struct Comments { impl Comments { /// Extracts all the suppressions from `root` and its child nodes. - pub fn from_node(root: &SyntaxNode) -> Self + pub fn from_node