From 6480ecf38cc70c7b107089f17f79b3895a57c0cc Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Tue, 16 Aug 2022 10:56:51 +0200 Subject: [PATCH] feat(rome_js_formatter): Template formatting This PR improves Rome's formatting of `JsTemplate`s and `TsTemplate`s to closer match Prettier's formatting. It mainly implements: * simple expressions that never break even if the template literal, as a result thereof, exceeds the line width * Aligning expressions in template literals with the last template chunk This PR does not implement Prettier's custom formatting of `Jest` specs, and it doesn't implement custom comments formatting. ## Tests I manually verified the snapshot changes. There are some remaining differences but they are rooted in the fact that some, expression formatting isn't compatible with prettier yet (mainly binary expression, call arguments) --- crates/rome_formatter/src/builders.rs | 5 +- crates/rome_formatter/src/format_element.rs | 2 +- crates/rome_formatter/src/lib.rs | 2 +- crates/rome_formatter/src/printer/mod.rs | 10 +- .../src/printer/printer_options/mod.rs | 38 ++- crates/rome_js_formatter/src/context.rs | 7 +- .../expressions/arrow_function_expression.rs | 37 ++- .../src/js/expressions/template.rs | 81 ++++-- .../js/expressions/template_chunk_element.rs | 44 ++- .../src/js/expressions/template_element.rs | 223 ++++++++++++++- .../src/js/lists/template_element_list.rs | 262 +++++++++++++++++- .../ts/expressions/template_chunk_element.rs | 11 +- .../src/ts/expressions/template_element.rs | 21 +- .../src/ts/lists/template_element_list.rs | 9 +- crates/rome_js_formatter/src/utils/mod.rs | 154 +--------- .../expression/logical_expression.js.snap | 12 +- .../specs/js/module/template/template.js | 6 +- .../specs/js/module/template/template.js.snap | 38 ++- .../js/comments/template-literal.js.snap | 27 +- .../js/line-suffix-boundary/boundary.js.snap | 43 ++- .../comment-inside.js.snap | 130 ++++----- .../js/multiparser-css/issue-5697.js.snap | 85 ------ .../multiparser-css/styled-components.js.snap | 100 ++----- .../multiparser-graphql/graphql-tag.js.snap | 38 ++- .../js/multiparser-html/lit-html.js.snap | 16 +- .../js/strings/template-literals.js.snap | 46 +-- .../prettier/js/template-align/indent.js.snap | 134 --------- .../js/template-literals/expressions.js.snap | 220 --------------- .../specs/prettier/js/template/arrow.js.snap | 52 ---- .../prettier/js/template/comment.js.snap | 18 +- .../js/template/faulty-locations.js.snap | 63 ----- .../prettier/js/template/graphql.js.snap | 78 ------ .../specs/prettier/js/template/indent.js.snap | 78 ------ .../specs/prettier/js/template/inline.js.snap | 11 +- .../multiparser-css/issue-6259.ts.snap | 15 +- .../template-literals/expressions.ts.snap | 36 --- .../tests/specs/ts/type/template_type.ts.snap | 19 +- website/playground/Cargo.toml | 2 +- 38 files changed, 904 insertions(+), 1269 deletions(-) delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/multiparser-css/issue-5697.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/template-align/indent.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/template-literals/expressions.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/template/arrow.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/template/faulty-locations.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/template/graphql.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/template/indent.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/typescript/template-literals/expressions.ts.snap diff --git a/crates/rome_formatter/src/builders.rs b/crates/rome_formatter/src/builders.rs index 48bdb4ca5cb..c51789259e9 100644 --- a/crates/rome_formatter/src/builders.rs +++ b/crates/rome_formatter/src/builders.rs @@ -1504,6 +1504,7 @@ impl Format for ExpandParent { /// ``` /// use rome_formatter::{format_args, format, LineWidth}; /// use rome_formatter::prelude::*; +/// use rome_formatter::printer::PrintWidth; /// /// let context = SimpleFormatContext { /// line_width: LineWidth::try_from(20).unwrap(), @@ -1525,10 +1526,6 @@ impl Format for ExpandParent { /// ]) /// ]).unwrap(); /// -/// let options = PrinterOptions { -/// print_width: LineWidth::try_from(20).unwrap(), -/// ..PrinterOptions::default() -/// }; /// assert_eq!( /// "[\n\t'A somewhat longer string to force a line break',\n\t2,\n\t3,\n]", /// elements.print().as_code() diff --git a/crates/rome_formatter/src/format_element.rs b/crates/rome_formatter/src/format_element.rs index f6979f8d472..d0d9b611801 100644 --- a/crates/rome_formatter/src/format_element.rs +++ b/crates/rome_formatter/src/format_element.rs @@ -777,7 +777,7 @@ impl FormatContext for IrFormatContext { fn as_print_options(&self) -> PrinterOptions { PrinterOptions { tab_width: 2, - print_width: self.line_width(), + print_width: self.line_width().into(), line_ending: LineEnding::LineFeed, indent_style: IndentStyle::Space(2), } diff --git a/crates/rome_formatter/src/lib.rs b/crates/rome_formatter/src/lib.rs index 32b4669f65a..92dc5b415cf 100644 --- a/crates/rome_formatter/src/lib.rs +++ b/crates/rome_formatter/src/lib.rs @@ -270,7 +270,7 @@ impl FormatContext for SimpleFormatContext { fn as_print_options(&self) -> PrinterOptions { PrinterOptions::default() .with_indent(self.indent_style) - .with_print_width(self.line_width) + .with_print_width(self.line_width.into()) } } diff --git a/crates/rome_formatter/src/printer/mod.rs b/crates/rome_formatter/src/printer/mod.rs index 746145feeba..444b35ce847 100644 --- a/crates/rome_formatter/src/printer/mod.rs +++ b/crates/rome_formatter/src/printer/mod.rs @@ -942,7 +942,7 @@ fn fits_element_on_line<'a, 'rest>( state.line_width += char_width as usize; } - if state.line_width > options.print_width.value().into() { + if state.line_width > options.print_width.into() { return Fits::No; } @@ -1080,8 +1080,8 @@ impl<'a, 'rest> MeasureQueue<'a, 'rest> { #[cfg(test)] mod tests { use crate::prelude::*; - use crate::printer::{LineEnding, Printer, PrinterOptions}; - use crate::{format_args, write, FormatState, IndentStyle, LineWidth, Printed, VecBuffer}; + use crate::printer::{LineEnding, PrintWidth, Printer, PrinterOptions}; + use crate::{format_args, write, FormatState, IndentStyle, Printed, VecBuffer}; fn format(root: &dyn Format<()>) -> Printed { format_with_options( @@ -1230,7 +1230,7 @@ two lines`, let options = PrinterOptions { indent_style: IndentStyle::Tab, tab_width: 4, - print_width: LineWidth::try_from(19).unwrap(), + print_width: PrintWidth::new(19), ..PrinterOptions::default() }; @@ -1315,7 +1315,7 @@ two lines`, let document = buffer.into_element(); - let printed = Printer::new(PrinterOptions::default().with_print_width(LineWidth(10))) + let printed = Printer::new(PrinterOptions::default().with_print_width(PrintWidth::new(10))) .print(&document); assert_eq!( diff --git a/crates/rome_formatter/src/printer/printer_options/mod.rs b/crates/rome_formatter/src/printer/printer_options/mod.rs index 0f298b68bb6..0892191dcb6 100644 --- a/crates/rome_formatter/src/printer/printer_options/mod.rs +++ b/crates/rome_formatter/src/printer/printer_options/mod.rs @@ -7,7 +7,7 @@ pub struct PrinterOptions { pub tab_width: u8, /// What's the max width of a line. Defaults to 80 - pub print_width: LineWidth, + pub print_width: PrintWidth, /// The type of line ending to apply to the printed input pub line_ending: LineEnding, @@ -16,8 +16,40 @@ pub struct PrinterOptions { pub indent_style: IndentStyle, } +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct PrintWidth(u32); + +impl PrintWidth { + pub fn new(width: u32) -> Self { + Self(width) + } + + /// Creates a print width that avoids ever breaking content because it exceeds the print width. + pub fn infinite() -> Self { + Self(u32::MAX) + } +} + +impl Default for PrintWidth { + fn default() -> Self { + LineWidth::default().into() + } +} + +impl From for PrintWidth { + fn from(width: LineWidth) -> Self { + Self(u16::from(width) as u32) + } +} + +impl From for usize { + fn from(width: PrintWidth) -> Self { + width.0 as usize + } +} + impl PrinterOptions { - pub fn with_print_width(mut self, width: LineWidth) -> Self { + pub fn with_print_width(mut self, width: PrintWidth) -> Self { self.print_width = width; self } @@ -69,7 +101,7 @@ impl Default for PrinterOptions { fn default() -> Self { PrinterOptions { tab_width: 2, - print_width: LineWidth::default(), + print_width: PrintWidth::default(), indent_style: Default::default(), line_ending: LineEnding::LineFeed, } diff --git a/crates/rome_js_formatter/src/context.rs b/crates/rome_js_formatter/src/context.rs index e62503d0441..72d9acafd72 100644 --- a/crates/rome_js_formatter/src/context.rs +++ b/crates/rome_js_formatter/src/context.rs @@ -105,7 +105,7 @@ impl FormatContext for JsFormatContext { fn as_print_options(&self) -> PrinterOptions { PrinterOptions::default() .with_indent(self.indent_style) - .with_print_width(self.line_width) + .with_print_width(self.line_width.into()) } } @@ -160,7 +160,10 @@ 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::L_PAREN + | JsSyntaxKind::L_BRACK + | JsSyntaxKind::L_CURLY + | JsSyntaxKind::DOLLAR_CURLY ) } 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 814fc2b5c6f..606c3f77925 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 @@ -3,8 +3,8 @@ use rome_formatter::{format_args, write}; use crate::utils::{is_simple_expression, resolve_expression, starts_with_no_lookahead_token}; use rome_js_syntax::{ - JsAnyArrowFunctionParameters, JsAnyExpression, JsAnyFunctionBody, JsArrowFunctionExpression, - JsArrowFunctionExpressionFields, + JsAnyArrowFunctionParameters, JsAnyExpression, JsAnyFunctionBody, JsAnyTemplateElement, + JsArrowFunctionExpression, JsArrowFunctionExpressionFields, JsTemplate, }; #[derive(Debug, Clone, Default)] @@ -99,6 +99,9 @@ impl FormatNodeRule for FormatJsArrowFunctionExpressi false, !starts_with_no_lookahead_token(conditional.clone().into())?, ), + JsTemplate(template) => { + (is_multiline_template_starting_on_same_line(template), false) + } expr => (is_simple_expression(expr)?, false), }, }; @@ -125,3 +128,33 @@ impl FormatNodeRule for FormatJsArrowFunctionExpressi } } } + +/// Returns `true` if the template contains any new lines inside of its text chunks. +fn template_literal_contains_new_line(template: &JsTemplate) -> bool { + template.elements().iter().any(|element| match element { + JsAnyTemplateElement::JsTemplateChunkElement(chunk) => chunk + .template_chunk_token() + .map_or(false, |chunk| chunk.text().contains('\n')), + JsAnyTemplateElement::JsTemplateElement(_) => false, + }) +} + +fn is_multiline_template_starting_on_same_line(template: &JsTemplate) -> bool { + let contains_new_line = template_literal_contains_new_line(template); + + let starts_on_same_line = template.syntax().first_token().map_or(false, |token| { + for piece in token.leading_trivia().pieces() { + if let Some(comment) = piece.as_comments() { + if comment.has_newline() { + return false; + } + } else if piece.is_newline() { + return false; + } + } + + true + }); + + contains_new_line && starts_on_same_line +} diff --git a/crates/rome_js_formatter/src/js/expressions/template.rs b/crates/rome_js_formatter/src/js/expressions/template.rs index 65abf36d64c..c268e424f5f 100644 --- a/crates/rome_js_formatter/src/js/expressions/template.rs +++ b/crates/rome_js_formatter/src/js/expressions/template.rs @@ -1,32 +1,79 @@ use crate::prelude::*; use rome_formatter::write; -use rome_js_syntax::JsTemplate; -use rome_js_syntax::JsTemplateFields; +use rome_js_syntax::{ + JsAnyExpression, JsSyntaxToken, JsTemplate, TsTemplateLiteralType, TsTypeArguments, +}; +use rome_rowan::{declare_node_union, SyntaxResult}; #[derive(Debug, Clone, Default)] pub struct FormatJsTemplate; impl FormatNodeRule for FormatJsTemplate { fn fmt_fields(&self, node: &JsTemplate, f: &mut JsFormatter) -> FormatResult<()> { - let JsTemplateFields { - tag, - type_arguments, - l_tick_token, - elements, - r_tick_token, - } = node.as_fields(); - - write![ + JsAnyTemplate::from(node.clone()).fmt(f) + } +} + +declare_node_union! { + JsAnyTemplate = JsTemplate | TsTemplateLiteralType +} + +impl Format for JsAnyTemplate { + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + write!( f, [ - tag.format(), - type_arguments.format(), + self.tag().format(), + self.type_arguments().format(), line_suffix_boundary(), - l_tick_token.format(), - elements.format(), - r_tick_token.format() + self.l_tick_token().format(), ] - ] + )?; + + self.write_elements(f)?; + + write!(f, [self.r_tick_token().format()]) + } +} + +impl JsAnyTemplate { + fn tag(&self) -> Option { + match self { + JsAnyTemplate::JsTemplate(template) => template.tag(), + JsAnyTemplate::TsTemplateLiteralType(_) => None, + } + } + + fn type_arguments(&self) -> Option { + match self { + JsAnyTemplate::JsTemplate(template) => template.type_arguments(), + JsAnyTemplate::TsTemplateLiteralType(_) => None, + } + } + + fn l_tick_token(&self) -> SyntaxResult { + match self { + JsAnyTemplate::JsTemplate(template) => template.l_tick_token(), + JsAnyTemplate::TsTemplateLiteralType(template) => template.l_tick_token(), + } + } + + fn write_elements(&self, f: &mut JsFormatter) -> FormatResult<()> { + match self { + JsAnyTemplate::JsTemplate(template) => { + write!(f, [template.elements().format()]) + } + JsAnyTemplate::TsTemplateLiteralType(template) => { + write!(f, [template.elements().format()]) + } + } + } + + fn r_tick_token(&self) -> SyntaxResult { + match self { + JsAnyTemplate::JsTemplate(template) => template.r_tick_token(), + JsAnyTemplate::TsTemplateLiteralType(template) => template.r_tick_token(), + } } } diff --git a/crates/rome_js_formatter/src/js/expressions/template_chunk_element.rs b/crates/rome_js_formatter/src/js/expressions/template_chunk_element.rs index 9d8a4579d59..ea33f773aae 100644 --- a/crates/rome_js_formatter/src/js/expressions/template_chunk_element.rs +++ b/crates/rome_js_formatter/src/js/expressions/template_chunk_element.rs @@ -1,7 +1,8 @@ use crate::prelude::*; -use crate::utils::format_template_chunk; +use rome_formatter::write; -use rome_js_syntax::{JsTemplateChunkElement, JsTemplateChunkElementFields}; +use rome_js_syntax::{JsSyntaxToken, JsTemplateChunkElement, TsTemplateChunkElement}; +use rome_rowan::{declare_node_union, SyntaxResult}; #[derive(Debug, Clone, Default)] pub struct FormatJsTemplateChunkElement; @@ -12,11 +13,40 @@ impl FormatNodeRule for FormatJsTemplateChunkElement { node: &JsTemplateChunkElement, formatter: &mut JsFormatter, ) -> FormatResult<()> { - let JsTemplateChunkElementFields { - template_chunk_token, - } = node.as_fields(); + AnyTemplateChunkElement::from(node.clone()).fmt(formatter) + } +} + +declare_node_union! { + pub(crate) AnyTemplateChunkElement = JsTemplateChunkElement | TsTemplateChunkElement +} + +impl AnyTemplateChunkElement { + pub(crate) fn template_chunk_token(&self) -> SyntaxResult { + match self { + AnyTemplateChunkElement::JsTemplateChunkElement(chunk) => chunk.template_chunk_token(), + AnyTemplateChunkElement::TsTemplateChunkElement(chunk) => chunk.template_chunk_token(), + } + } +} + +impl Format for AnyTemplateChunkElement { + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + // Per https://tc39.es/ecma262/multipage/ecmascript-language-lexical-grammar.html#sec-static-semantics-trv: + // In template literals, the '\r' and '\r\n' line terminators are normalized to '\n' + + let chunk = self.template_chunk_token()?; - let chunk = template_chunk_token?; - format_template_chunk(chunk, formatter) + write!( + f, + [format_replaced( + &chunk, + &syntax_token_cow_slice( + normalize_newlines(chunk.text_trimmed(), ['\r']), + &chunk, + chunk.text_trimmed_range().start(), + ) + )] + ) } } 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 cf1b2c739bb..4e42a609169 100644 --- a/crates/rome_js_formatter/src/js/expressions/template_element.rs +++ b/crates/rome_js_formatter/src/js/expressions/template_element.rs @@ -1,10 +1,27 @@ use crate::prelude::*; -use crate::utils::{format_template_literal, TemplateElement}; +use rome_formatter::printer::{PrintWidth, Printer}; +use rome_formatter::{format_args, write, FormatContext, FormatRuleWithOptions, VecBuffer}; -use rome_js_syntax::JsTemplateElement; +use crate::context::TabWidth; +use crate::js::lists::template_element_list::{TemplateElementIndention, TemplateElementLayout}; +use rome_js_syntax::{ + JsAnyExpression, JsSyntaxNode, JsSyntaxToken, JsTemplateElement, TsTemplateElement, +}; +use rome_rowan::{declare_node_union, AstNode, SyntaxResult}; #[derive(Debug, Clone, Default)] -pub struct FormatJsTemplateElement; +pub struct FormatJsTemplateElement { + options: TemplateElementOptions, +} + +impl FormatRuleWithOptions for FormatJsTemplateElement { + type Options = TemplateElementOptions; + + fn with_options(mut self, options: Self::Options) -> Self { + self.options = options; + self + } +} impl FormatNodeRule for FormatJsTemplateElement { fn fmt_fields( @@ -12,6 +29,204 @@ impl FormatNodeRule for FormatJsTemplateElement { node: &JsTemplateElement, formatter: &mut JsFormatter, ) -> FormatResult<()> { - format_template_literal(TemplateElement::Js(node.clone()), formatter) + let element = AnyTemplateElement::from(node.clone()); + + FormatTemplateElement::new(element, self.options).fmt(formatter) } } + +declare_node_union! { + pub(crate) AnyTemplateElement = JsTemplateElement | TsTemplateElement +} + +#[derive(Debug, Copy, Clone, Default)] +pub struct TemplateElementOptions { + pub(crate) layout: TemplateElementLayout, + + // The indention to use for this element + pub(crate) indention: TemplateElementIndention, + + // Does the last template chunk (text element) end with a new line? + pub(crate) after_new_line: bool, +} + +pub(crate) struct FormatTemplateElement { + element: AnyTemplateElement, + options: TemplateElementOptions, +} + +impl FormatTemplateElement { + pub(crate) fn new(element: AnyTemplateElement, options: TemplateElementOptions) -> Self { + Self { element, options } + } +} + +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()]) + } + AnyTemplateElement::TsTemplateElement(template) => { + write!(f, [template.ty().format()]) + } + }); + + let format_inner = format_with(|f: &mut JsFormatter| match self.options.layout { + TemplateElementLayout::Simple => { + let mut buffer = VecBuffer::new(f.state_mut()); + write!(buffer, [format_expression])?; + let root = buffer.into_element(); + + let print_options = f + .context() + .as_print_options() + .with_print_width(PrintWidth::infinite()); + let printed = Printer::new(print_options).print(&root); + + write!( + f, + [dynamic_text( + printed.as_code(), + self.element.inner_syntax()?.text_trimmed_range().start() + )] + ) + } + TemplateElementLayout::Normal => { + use JsAnyExpression::*; + + let expression = self.element.expression(); + + // It's preferred to break after/before `${` and `}` rather than breaking in the + // middle of some expressions. + let indent = has_comments + || matches!( + expression, + Some( + JsStaticMemberExpression(_) + | JsComputedMemberExpression(_) + | JsConditionalExpression(_) + | JsSequenceExpression(_) + | TsAsExpression(_) + | JsBinaryExpression(_) + | JsLogicalExpression(_) + | JsInstanceofExpression(_) + | JsInExpression(_) + ) + ); + + if indent { + write!(f, [soft_block_indent(&format_expression)]) + } else { + write!(f, [format_expression]) + } + } + }); + + let format_indented = format_with(|f: &mut JsFormatter| { + if self.options.after_new_line { + write!(f, [dedent_to_root(&format_inner)]) + } else { + write_with_indention( + &format_inner, + self.options.indention, + f.context().tab_width(), + f, + ) + } + }); + + write!( + f, + [group(&format_args![ + self.element.dollar_curly_token().format(), + format_indented, + line_suffix_boundary(), + self.element.r_curly_token().format() + ])] + ) + } +} + +impl AnyTemplateElement { + fn dollar_curly_token(&self) -> SyntaxResult { + match self { + AnyTemplateElement::JsTemplateElement(template) => template.dollar_curly_token(), + AnyTemplateElement::TsTemplateElement(template) => template.dollar_curly_token(), + } + } + + fn inner_syntax(&self) -> SyntaxResult { + match self { + AnyTemplateElement::JsTemplateElement(template) => { + template.expression().map(AstNode::into_syntax) + } + AnyTemplateElement::TsTemplateElement(template) => { + template.ty().map(AstNode::into_syntax) + } + } + } + + fn expression(&self) -> Option { + match self { + AnyTemplateElement::JsTemplateElement(template) => template.expression().ok(), + AnyTemplateElement::TsTemplateElement(_) => None, + } + } + + fn r_curly_token(&self) -> SyntaxResult { + match self { + AnyTemplateElement::JsTemplateElement(template) => template.r_curly_token(), + AnyTemplateElement::TsTemplateElement(template) => template.r_curly_token(), + } + } +} + +fn write_with_indention( + content: &Content, + indention: TemplateElementIndention, + tab_width: TabWidth, + f: &mut JsFormatter, +) -> FormatResult<()> +where + Content: Format, +{ + let level = indention.level(tab_width); + let spaces = indention.align(tab_width); + + if level == 0 && spaces == 0 { + return write!(f, [content]); + } + + let format_indented = format_with(|f| { + // Nest indents to get to the same indention level + if level == 0 { + write!(f, [content]) + } else { + let mut buffer = VecBuffer::new(f.state_mut()); + + write!(buffer, [indent(content)])?; + + let mut indented = buffer.into_element(); + + for _ in 1..level { + indented = FormatElement::Indent(vec![indented].into_boxed_slice()); + } + + f.write_element(indented) + } + }); + + let format_aligned = format_with(|f| { + // Add any potential spaces in the end + if spaces == 0 { + write!(f, [format_indented]) + } else { + write!(f, [align(spaces, &format_indented)]) + } + }); + + write!(f, [dedent_to_root(&format_aligned)]) +} 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 292af8a25a6..9c7e27a2a70 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 @@ -1,5 +1,14 @@ +use crate::js::expressions::template_chunk_element::AnyTemplateChunkElement; +use crate::js::expressions::template_element::{AnyTemplateElement, TemplateElementOptions}; + +use crate::context::TabWidth; use crate::prelude::*; -use rome_js_syntax::JsTemplateElementList; +use rome_js_syntax::{ + JsAnyExpression, JsAnyLiteralExpression, JsAnyTemplateElement, JsLanguage, + JsTemplateElementList, TsAnyTemplateElement, TsTemplateElementList, +}; +use rome_rowan::{declare_node_union, AstNodeListIterator, SyntaxResult}; +use std::iter::FusedIterator; #[derive(Debug, Clone, Default)] pub struct FormatJsTemplateElementList; @@ -8,12 +17,255 @@ impl FormatRule for FormatJsTemplateElementList { type Context = JsFormatContext; fn fmt(&self, node: &JsTemplateElementList, f: &mut JsFormatter) -> FormatResult<()> { - let mut join = f.join(); + AnyTemplateElementList::JsTemplateElementList(node.clone()).fmt(f) + } +} + +pub(crate) enum AnyTemplateElementList { + JsTemplateElementList(JsTemplateElementList), + TsTemplateElementList(TsTemplateElementList), +} + +impl Format for AnyTemplateElementList { + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + let layout = if self.is_simple() { + TemplateElementLayout::Simple + } else { + TemplateElementLayout::Normal + }; + + let mut indention = TemplateElementIndention::default(); + let mut after_new_line = false; + + for element in self.elements() { + match element { + AnyTemplateElementOrChunk::AnyTemplateElement(element) => { + let options = TemplateElementOptions { + after_new_line, + indention, + layout, + }; + + match &element { + AnyTemplateElement::JsTemplateElement(element) => { + element.format().with_options(options).fmt(f)?; + } + AnyTemplateElement::TsTemplateElement(element) => { + element.format().with_options(options).fmt(f)?; + } + } + } + AnyTemplateElementOrChunk::AnyTemplateChunkElement(chunk) => { + match &chunk { + AnyTemplateChunkElement::JsTemplateChunkElement(chunk) => { + chunk.format().fmt(f)?; + } + AnyTemplateChunkElement::TsTemplateChunkElement(chunk) => { + chunk.format().fmt(f)?; + } + } + + let chunk_token = chunk.template_chunk_token()?; + let chunk_text = chunk_token.text(); + + let tab_width = f.context().tab_width(); + + indention = + TemplateElementIndention::after_last_new_line(chunk_text, tab_width); + after_new_line = chunk_text.ends_with('\n'); + } + } + } + + Ok(()) + } +} + +impl AnyTemplateElementList { + fn is_simple(&self) -> bool { + match self { + AnyTemplateElementList::JsTemplateElementList(list) => { + if list.is_empty() { + return false; + } + + let mut expression_elements = list.iter().filter_map(|element| match element { + JsAnyTemplateElement::JsTemplateElement(element) => Some(element), + _ => None, + }); + + expression_elements.all(|expression_element| { + match expression_element.expression() { + Ok(expression) => is_simple_member_expression(expression).unwrap_or(false), + Err(_) => false, + } + }) + } + AnyTemplateElementList::TsTemplateElementList(_) => false, + } + } + + fn elements(&self) -> TemplateElementIterator { + match self { + AnyTemplateElementList::JsTemplateElementList(list) => { + TemplateElementIterator::JsTemplateElementList(list.iter()) + } + AnyTemplateElementList::TsTemplateElementList(list) => { + TemplateElementIterator::TsTemplateElementList(list.iter()) + } + } + } +} + +#[derive(Debug, Copy, Clone)] +pub enum TemplateElementLayout { + /// Applied when all expressions are identifier, `this`, static member expressions, or computed member expressions with number or string literals. + /// Formats every expression without any line breaks. + Simple, + Normal, +} + +impl Default for TemplateElementLayout { + fn default() -> Self { + TemplateElementLayout::Normal + } +} - for element in node { - join.entry(&element.format()); +declare_node_union! { + AnyTemplateElementOrChunk = AnyTemplateElement | AnyTemplateChunkElement +} + +fn is_simple_member_expression(expression: JsAnyExpression) -> SyntaxResult { + let mut current = expression; + + loop { + if current.syntax().has_comments_direct() { + return Ok(false); + } + + current = match current { + JsAnyExpression::JsStaticMemberExpression(expression) => expression.object()?, + JsAnyExpression::JsComputedMemberExpression(expression) => { + if matches!( + expression.member()?, + JsAnyExpression::JsAnyLiteralExpression( + JsAnyLiteralExpression::JsStringLiteralExpression(_) + | JsAnyLiteralExpression::JsNumberLiteralExpression(_) + ) | JsAnyExpression::JsIdentifierExpression(_) + ) { + expression.object()? + } else { + break; + } + } + JsAnyExpression::JsParenthesizedExpression(expression) => expression.expression()?, + JsAnyExpression::JsIdentifierExpression(_) | JsAnyExpression::JsThisExpression(_) => { + return Ok(true); + } + _ => { + break; + } + } + } + + Ok(false) +} + +enum TemplateElementIterator { + JsTemplateElementList(AstNodeListIterator), + TsTemplateElementList(AstNodeListIterator), +} + +impl Iterator for TemplateElementIterator { + type Item = AnyTemplateElementOrChunk; + + fn next(&mut self) -> Option { + match self { + TemplateElementIterator::JsTemplateElementList(inner) => { + let result = match inner.next()? { + JsAnyTemplateElement::JsTemplateChunkElement(chunk) => { + AnyTemplateElementOrChunk::from(AnyTemplateChunkElement::from(chunk)) + } + JsAnyTemplateElement::JsTemplateElement(element) => { + AnyTemplateElementOrChunk::from(AnyTemplateElement::from(element)) + } + }; + Some(result) + } + TemplateElementIterator::TsTemplateElementList(inner) => { + let result = match inner.next()? { + TsAnyTemplateElement::TsTemplateChunkElement(chunk) => { + AnyTemplateElementOrChunk::from(AnyTemplateChunkElement::from(chunk)) + } + TsAnyTemplateElement::TsTemplateElement(element) => { + AnyTemplateElementOrChunk::from(AnyTemplateElement::from(element)) + } + }; + + Some(result) + } + } + } +} + +impl ExactSizeIterator for TemplateElementIterator { + fn len(&self) -> usize { + match self { + TemplateElementIterator::JsTemplateElementList(inner) => inner.len(), + TemplateElementIterator::TsTemplateElementList(inner) => inner.len(), } + } +} + +impl FusedIterator for TemplateElementIterator {} + +/// The indention derived from a position in the source document. Consists of indention level and spaces +#[derive(Debug, Copy, Clone, Default)] +pub struct TemplateElementIndention(u32); + +impl TemplateElementIndention { + /// Returns the indention level + pub(crate) fn level(&self, tab_width: TabWidth) -> u32 { + self.0 / (u8::from(tab_width) as u32) + } + + /// Returns the number of space indents on top of the indent level + pub(crate) fn align(&self, tab_width: TabWidth) -> u8 { + (self.0 % u8::from(tab_width) as u32) as u8 + } + + /// Computes the indention after the last new line character. + fn after_last_new_line(text: &str, tab_width: TabWidth) -> Self { + let by_new_line = text.rsplit_once('\n'); + + let size = match by_new_line { + None => 0, + Some((_, after_new_line)) => { + let tab_width: u32 = u8::from(tab_width).into(); + let mut size: u32 = 0; + + for c in after_new_line.chars() { + match c { + '\t' => { + // Tabs behave in a way that they are aligned to the nearest + // multiple of tab_width: + // number of spaces -> added size + // 0 -> 4, 1 -> 4, 2 -> 4, 3 -> 4 + // 4 -> 8, 5 -> 8, 6 -> 8, 7 -> 8 .. + // Or in other words, it clips the size to the next multiple of tab width. + size = size + tab_width - (size % tab_width); + } + ' ' => { + size += 1; + } + _ => break, + }; + } + + size + } + }; - join.finish() + TemplateElementIndention(size) } } diff --git a/crates/rome_js_formatter/src/ts/expressions/template_chunk_element.rs b/crates/rome_js_formatter/src/ts/expressions/template_chunk_element.rs index 538633cd549..35b2c1bd5a1 100644 --- a/crates/rome_js_formatter/src/ts/expressions/template_chunk_element.rs +++ b/crates/rome_js_formatter/src/ts/expressions/template_chunk_element.rs @@ -1,7 +1,7 @@ use crate::prelude::*; -use crate::utils::format_template_chunk; -use rome_js_syntax::{TsTemplateChunkElement, TsTemplateChunkElementFields}; +use crate::js::expressions::template_chunk_element::AnyTemplateChunkElement; +use rome_js_syntax::TsTemplateChunkElement; #[derive(Debug, Clone, Default)] pub struct FormatTsTemplateChunkElement; @@ -12,11 +12,6 @@ impl FormatNodeRule for FormatTsTemplateChunkElement { node: &TsTemplateChunkElement, formatter: &mut JsFormatter, ) -> FormatResult<()> { - let TsTemplateChunkElementFields { - template_chunk_token, - } = node.as_fields(); - - let chunk = template_chunk_token?; - format_template_chunk(chunk, formatter) + AnyTemplateChunkElement::from(node.clone()).fmt(formatter) } } diff --git a/crates/rome_js_formatter/src/ts/expressions/template_element.rs b/crates/rome_js_formatter/src/ts/expressions/template_element.rs index e3f8de4c902..f40880fa973 100644 --- a/crates/rome_js_formatter/src/ts/expressions/template_element.rs +++ b/crates/rome_js_formatter/src/ts/expressions/template_element.rs @@ -1,10 +1,24 @@ use crate::prelude::*; -use crate::utils::{format_template_literal, TemplateElement}; +use rome_formatter::FormatRuleWithOptions; +use crate::js::expressions::template_element::{ + AnyTemplateElement, FormatTemplateElement, TemplateElementOptions, +}; use rome_js_syntax::TsTemplateElement; #[derive(Debug, Clone, Default)] -pub struct FormatTsTemplateElement; +pub struct FormatTsTemplateElement { + options: TemplateElementOptions, +} + +impl FormatRuleWithOptions for FormatTsTemplateElement { + type Options = TemplateElementOptions; + + fn with_options(mut self, options: Self::Options) -> Self { + self.options = options; + self + } +} impl FormatNodeRule for FormatTsTemplateElement { fn fmt_fields( @@ -12,6 +26,7 @@ impl FormatNodeRule for FormatTsTemplateElement { node: &TsTemplateElement, formatter: &mut JsFormatter, ) -> FormatResult<()> { - format_template_literal(TemplateElement::Ts(node.clone()), formatter) + let element = AnyTemplateElement::from(node.clone()); + FormatTemplateElement::new(element, self.options).fmt(formatter) } } diff --git a/crates/rome_js_formatter/src/ts/lists/template_element_list.rs b/crates/rome_js_formatter/src/ts/lists/template_element_list.rs index 2536087b2f9..8d21848b890 100644 --- a/crates/rome_js_formatter/src/ts/lists/template_element_list.rs +++ b/crates/rome_js_formatter/src/ts/lists/template_element_list.rs @@ -1,3 +1,4 @@ +use crate::js::lists::template_element_list::AnyTemplateElementList; use crate::prelude::*; use rome_js_syntax::TsTemplateElementList; @@ -8,12 +9,6 @@ impl FormatRule for FormatTsTemplateElementList { type Context = JsFormatContext; fn fmt(&self, node: &TsTemplateElementList, f: &mut JsFormatter) -> FormatResult<()> { - let mut join = f.join(); - - for item in node { - join.entry(&group(&item.format())); - } - - join.finish() + AnyTemplateElementList::TsTemplateElementList(node.clone()).fmt(f) } } diff --git a/crates/rome_js_formatter/src/utils/mod.rs b/crates/rome_js_formatter/src/utils/mod.rs index e787b0c1984..d8cfc897f9a 100644 --- a/crates/rome_js_formatter/src/utils/mod.rs +++ b/crates/rome_js_formatter/src/utils/mod.rs @@ -27,13 +27,10 @@ pub(crate) use member_chain::format_call_expression; pub(crate) use object_like::JsObjectLike; pub(crate) use object_pattern_like::JsObjectPatternLike; pub(crate) use parens::starts_with_no_lookahead_token; -use rome_formatter::{format_args, normalize_newlines, write, Buffer, VecBuffer}; -use rome_js_syntax::{ - JsAnyExpression, JsAnyFunction, JsAnyStatement, JsInitializerClause, JsLanguage, - JsTemplateElement, Modifiers, TsTemplateElement, TsType, -}; +use rome_formatter::{format_args, write, Buffer, VecBuffer}; +use rome_js_syntax::{JsAnyExpression, JsAnyStatement, JsInitializerClause, JsLanguage, Modifiers}; use rome_js_syntax::{JsSyntaxKind, JsSyntaxNode, JsSyntaxToken}; -use rome_rowan::{AstNode, AstNodeList, Direction, SyntaxResult}; +use rome_rowan::{AstNode, AstNodeList, Direction}; pub(crate) use simple::*; use std::fmt::Debug; pub(crate) use string_utils::*; @@ -213,151 +210,6 @@ where nodes_and_modifiers } -/// Utility to format -pub(crate) fn format_template_chunk(chunk: JsSyntaxToken, f: &mut JsFormatter) -> FormatResult<()> { - // Per https://tc39.es/ecma262/multipage/ecmascript-language-lexical-grammar.html#sec-static-semantics-trv: - // In template literals, the '\r' and '\r\n' line terminators are normalized to '\n' - - write!( - f, - [format_replaced( - &chunk, - &syntax_token_cow_slice( - normalize_newlines(chunk.text_trimmed(), ['\r']), - &chunk, - chunk.text_trimmed_range().start(), - ) - )] - ) -} - -/// Function to format template literals and template literal types -pub(crate) fn format_template_literal( - literal: TemplateElement, - formatter: &mut JsFormatter, -) -> FormatResult<()> { - write!(formatter, [literal]) -} - -pub(crate) enum TemplateElement { - Js(JsTemplateElement), - Ts(TsTemplateElement), -} - -impl Format for TemplateElement { - fn fmt(&self, f: &mut JsFormatter) -> FormatResult<()> { - let expression_is_plain = self.is_plain_expression()?; - let has_comments = self.has_comments(); - let should_hard_group = expression_is_plain && !has_comments; - - let content = format_with(|f| { - match self { - TemplateElement::Js(template) => { - write!(f, [template.expression().format()])?; - } - TemplateElement::Ts(template) => { - write!(f, [template.ty().format()])?; - } - } - - write!(f, [line_suffix_boundary()]) - }); - - if should_hard_group { - write!( - f, - [ - self.dollar_curly_token().format(), - content, - self.r_curly_token().format() - ] - ) - } else { - write!( - f, - [format_delimited( - &self.dollar_curly_token()?, - &content, - &self.r_curly_token()? - ) - .soft_block_indent()] - ) - } - } -} - -impl TemplateElement { - fn dollar_curly_token(&self) -> SyntaxResult { - match self { - TemplateElement::Js(template) => template.dollar_curly_token(), - TemplateElement::Ts(template) => template.dollar_curly_token(), - } - } - - fn r_curly_token(&self) -> SyntaxResult { - match self { - TemplateElement::Js(template) => template.r_curly_token(), - TemplateElement::Ts(template) => template.r_curly_token(), - } - } - - /// We want to break the template element only when we have articulated expressions inside it. - /// - /// We a plain expression is when it's one of the following: - /// - `loreum ${this.something} ipsum` - /// - `loreum ${a.b.c} ipsum` - /// - `loreum ${a} ipsum` - fn is_plain_expression(&self) -> FormatResult { - match self { - TemplateElement::Js(template_element) => { - let current_expression = template_element.expression()?; - match current_expression { - JsAnyExpression::JsStaticMemberExpression(_) - | JsAnyExpression::JsComputedMemberExpression(_) - | JsAnyExpression::JsIdentifierExpression(_) - | JsAnyExpression::JsAnyLiteralExpression(_) - | JsAnyExpression::JsCallExpression(_) => Ok(true), - - JsAnyExpression::JsParenthesizedExpression(expression) => { - // binary and logical expression have their own grouping inside parenthesis, - // so we mark the current parenthesized expression as not plain - match expression.expression()? { - JsAnyExpression::JsLogicalExpression(_) - | JsAnyExpression::JsBinaryExpression(_) => Ok(false), - _ => Ok(true), - } - } - - _ => { - if let Some(function) = - JsAnyFunction::cast(current_expression.syntax().clone()) - { - Ok(is_simple_function_expression(function)?) - } else { - Ok(false) - } - } - } - } - TemplateElement::Ts(template_element) => { - let is_mapped_type = matches!(template_element.ty()?, TsType::TsMappedType(_)); - Ok(!is_mapped_type) - } - } - } - - fn has_comments(&self) -> bool { - match self { - TemplateElement::Js(template_element) => { - template_element.syntax().has_comments_descendants() - } - TemplateElement::Ts(template_element) => { - template_element.syntax().has_comments_descendants() - } - } - } -} - /// This enum is used to extract a precedence from certain nodes. By comparing the precedence /// of two nodes, it's possible to change the way certain node should be formatted. /// 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 390c50423ac..c55ae740354 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 @@ -245,11 +245,9 @@ undefined === function () { throw undefined; }; -const b = `${ - ( - veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongFoo + veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongBar - ) -}`; +const b = `${( + veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongFoo + veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongBar +)}`; const a = ( @@ -321,6 +319,6 @@ a in ## Lines exceeding width of 80 characters - 124: veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongFoo + veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongBar - 192: veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongFoo instanceof String && veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongBar instanceof Number + 123: veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongFoo + veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongBar + 190: veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongFoo instanceof String && veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongBar instanceof Number diff --git a/crates/rome_js_formatter/tests/specs/js/module/template/template.js b/crates/rome_js_formatter/tests/specs/js/module/template/template.js index db9177d31da..f16c15e67fd 100644 --- a/crates/rome_js_formatter/tests/specs/js/module/template/template.js +++ b/crates/rome_js_formatter/tests/specs/js/module/template/template.js @@ -18,7 +18,7 @@ output `; // don't break -const bar =`but where will ${this.fanta} wrap ${baz} ${"hello"} template literal? ${bar.ff.sss} long long long long ${foo.[3]} long long long long long long`; +const bar =`but where will ${this.fanta} wrap ${baz} ${"hello"} template literal? ${bar.ff.sss} long long long long ${foo[3]} long long long long long long`; const foo = `but where will ${a && b && bar || c && d && g} wrap long long long long long long`; @@ -26,7 +26,7 @@ const foo = `but where will ${lorem && loremlorem && loremlorem || loremc && lor const a = ` let expression_is_simple = is_plain_expression(&expression)?; -${loooooong || loooooong || loooooong || loooooong || loooooong || loooooong || loooooong || loooooong } +${loooooong || loooooong || loooooong || loooooong || loooooong || loooooong || loooooong || loooooong } let expression_is_simple = is_plain_expression(&expression)?; `; @@ -42,4 +42,4 @@ const foo = `but where will ${ `${// $FlowFixMe found when converting React.createClass to ES6 ExampleStory.getFragment('story')} -`; \ No newline at end of file +`; diff --git a/crates/rome_js_formatter/tests/specs/js/module/template/template.js.snap b/crates/rome_js_formatter/tests/specs/js/module/template/template.js.snap index fdff19c69e6..50ae24e6ba8 100644 --- a/crates/rome_js_formatter/tests/specs/js/module/template/template.js.snap +++ b/crates/rome_js_formatter/tests/specs/js/module/template/template.js.snap @@ -23,7 +23,7 @@ output `; // don't break -const bar =`but where will ${this.fanta} wrap ${baz} ${"hello"} template literal? ${bar.ff.sss} long long long long ${foo.[3]} long long long long long long`; +const bar =`but where will ${this.fanta} wrap ${baz} ${"hello"} template literal? ${bar.ff.sss} long long long long ${foo[3]} long long long long long long`; const foo = `but where will ${a && b && bar || c && d && g} wrap long long long long long long`; @@ -31,7 +31,7 @@ const foo = `but where will ${lorem && loremlorem && loremlorem || loremc && lor const a = ` let expression_is_simple = is_plain_expression(&expression)?; -${loooooong || loooooong || loooooong || loooooong || loooooong || loooooong || loooooong || loooooong } +${loooooong || loooooong || loooooong || loooooong || loooooong || loooooong || loooooong || loooooong } let expression_is_simple = is_plain_expression(&expression)?; `; @@ -48,6 +48,7 @@ const foo = `but where will ${ `${// $FlowFixMe found when converting React.createClass to ES6 ExampleStory.getFragment('story')} `; + ============================= # Outputs ## Output 1 @@ -77,15 +78,19 @@ output `test abcd ${() => { - var hey; - const looooooooooong_expression = "loooooooooong_expression"; - return hey; -}} + var hey; + const looooooooooong_expression = "loooooooooong_expression"; + return hey; + }} output `; // don't break -const bar =`but where will ${this.fanta} wrap ${baz} ${"hello"} template literal? ${bar.ff.sss} long long long long ${foo.[3]} long long long long long long`; +const bar = `but where will ${ + this.fanta +} wrap ${baz} ${"hello"} template literal? ${bar.ff.sss} long long long long ${ + foo[3] +} long long long long long long`; const foo = `but where will ${ (a && b && bar) || (c && d && g) @@ -106,7 +111,7 @@ ${ loooooong || loooooong || loooooong -} +} let expression_is_simple = is_plain_expression(&expression)?; `; @@ -117,22 +122,15 @@ const foo = `but where will ${ `
${ this.set && this.set.artist - /* avoid console errors if `this.set` is undefined */ -}
`; +/* avoid console errors if `this.set` is undefined */}`; -`
${ - /* avoid console errors if `this.set` is undefined */ +`
${/* avoid console errors if `this.set` is undefined */ this.set && this.set.artist }
`; -`${ +`${ExampleStory.getFragment( // $FlowFixMe found when converting React.createClass to ES6 - ExampleStory.getFragment("story") -} + "story", +)} `; - -## Lines exceeding width of 80 characters - - 30: const bar =`but where will ${this.fanta} wrap ${baz} ${"hello"} template literal? ${bar.ff.sss} long long long long ${foo.[3]} long long long long long long`; - 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 index 6ffc1ea8fd6..457e8fc50f4 100644 --- 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 @@ -25,32 +25,37 @@ d //comment ```diff --- Prettier +++ Rome -@@ -5,7 +5,7 @@ +@@ -1,14 +1,11 @@ + ` +-${ +- a // comment ++${a // comment + } ${b /* comment */} --${/* comment */ c /* comment */} -+${ /* comment */ c /* comment */} + ${/* comment */ c /* comment */} - ${ - // comment +-${ +- // comment +- d //comment ++${d // comment //comment + }; + `; ``` # Output ```js ` -${ - a // comment +${a // comment } ${b /* comment */} -${ /* comment */ c /* comment */} +${/* comment */ c /* comment */} -${ - // comment - d //comment +${d // comment //comment }; `; ``` 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 index 6e77c7a49ab..5f0c526e8a0 100644 --- 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 @@ -37,16 +37,32 @@ ExampleStory.getFragment('story')} ```diff --- Prettier +++ Rome -@@ -9,7 +9,7 @@ +@@ -3,27 +3,25 @@ + a + } + +-${ +- a // comment ++${a // comment + } ${b /* comment */} --${/* comment */ c /* comment */} -+${ /* comment */ c /* comment */} + ${/* comment */ c /* comment */} + +-${ +- // comment +- d //comment ++${d // comment //comment + } - ${ - // comment -@@ -23,7 +23,8 @@ +-${ ++${ExampleStory.getFragment( + // $FlowFixMe found when converting React.createClass to ES6 +- ExampleStory.getFragment("story") +-} ++ "story", ++)} `;
@@ -67,23 +83,20 @@ ExampleStory.getFragment('story')} a } -${ - a // comment +${a // comment } ${b /* comment */} -${ /* comment */ c /* comment */} +${/* comment */ c /* comment */} -${ - // comment - d //comment +${d // comment //comment } -${ +${ExampleStory.getFragment( // $FlowFixMe found when converting React.createClass to ES6 - ExampleStory.getFragment("story") -} + "story", +)} `;
diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/multiparser-comments/comment-inside.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/multiparser-comments/comment-inside.js.snap index 2d3db4bb5b9..2f63f1fdd1d 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/multiparser-comments/comment-inside.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/multiparser-comments/comment-inside.js.snap @@ -74,30 +74,29 @@ expr1 = html` ```diff --- Prettier +++ Rome -@@ -2,9 +2,9 @@ - html` +@@ -3,56 +3,43 @@
${ -- this.set && this.set.artist + this.set && this.set.artist - /* avoid console errors if `this.set` is undefined */ - } -+ this.set && this.set.artist -+ /* avoid console errors if `this.set` is undefined */ -+} ++/* avoid console errors if `this.set` is undefined */}
`; -@@ -13,36 +13,32 @@ - /* comment */ +-html`${ +- foo +- /* comment */ ++html`${foo ++/* comment */ }`; html` - ${ - foo - /* comment */ - } -+${ -+ foo -+ /* comment */ ++${foo ++/* comment */ +} `; @@ -107,18 +106,16 @@ expr1 = html` - /* comment */ - } -`; -+graphql`${ -+ foo -+ /* comment */ ++graphql`${foo ++/* comment */ +}`; graphql` - ${ - foo - /* comment */ - } -+${ -+ foo -+ /* comment */ ++${foo ++/* comment */ +} `; @@ -128,37 +125,41 @@ expr1 = html` - /* comment */ - } -`; -+css`${ -+ foo -+ /* comment */ ++css`${foo ++/* comment */ +}`; css` - ${ - foo - /* comment */ - } -+${ -+ foo -+ /* comment */ ++${foo ++/* comment */ +} `; - markdown`${ -@@ -59,9 +55,10 @@ - // https://github.com/prettier/prettier/pull/9278#issuecomment-700589195 - expr1 = html` -
-- ${x( -- foo, // fg -- bar, +-markdown`${ +- foo +- /* comment */ ++markdown`${foo ++/* comment */ + }`; + markdown` +-${ +- foo +- /* comment */ ++${foo ++/* comment */ + } + `; + +@@ -62,6 +49,5 @@ + ${x( + foo, // fg + bar, - )} -
-+ ${ -+ x( -+ foo, // fg -+ bar, -+ ) -+}
++ )}
`; ``` @@ -169,65 +170,54 @@ expr1 = html` html`
${ - this.set && this.set.artist - /* avoid console errors if `this.set` is undefined */ -} + this.set && this.set.artist +/* avoid console errors if `this.set` is undefined */}
`; -html`${ - foo - /* comment */ +html`${foo +/* comment */ }`; html` -${ - foo - /* comment */ +${foo +/* comment */ } `; -graphql`${ - foo - /* comment */ +graphql`${foo +/* comment */ }`; graphql` -${ - foo - /* comment */ +${foo +/* comment */ } `; -css`${ - foo - /* comment */ +css`${foo +/* comment */ }`; css` -${ - foo - /* comment */ +${foo +/* comment */ } `; -markdown`${ - foo - /* comment */ +markdown`${foo +/* comment */ }`; markdown` -${ - foo - /* comment */ +${foo +/* comment */ } `; // https://github.com/prettier/prettier/pull/9278#issuecomment-700589195 expr1 = html`
- ${ - x( - foo, // fg - bar, - ) -}
+ ${x( + foo, // fg + bar, + )}
`; ``` diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/multiparser-css/issue-5697.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/multiparser-css/issue-5697.js.snap deleted file mode 100644 index 0b3e9092c60..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/js/multiparser-css/issue-5697.js.snap +++ /dev/null @@ -1,85 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -const StyledH1 = styled.div` - font-size: 2.5em; - font-weight: ${(props) => (props.strong ? 500 : 100)}; - font-family: ${constants.text.displayFont.fontFamily}; - letter-spacing: ${(props) => (props.light ? '0.04em' : 0)}; - color: ${(props) => props.textColor}; - ${(props) => - props.center - ? ` display: flex; - align-items: center; - justify-content: center; - text-align: center;` - : ''} - @media (max-width: ${(props) => (props.noBreakPoint ? '0' : constants.layout.breakpoint.break1)}px) { - font-size: 2em; - } -`; -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -4,15 +4,18 @@ - font-family: ${constants.text.displayFont.fontFamily}; - letter-spacing: ${(props) => (props.light ? "0.04em" : 0)}; - color: ${(props) => props.textColor}; -- ${(props) => -+ ${ -+ (props) => - props.center - ? ` display: flex; - align-items: center; - justify-content: center; - text-align: center;` -- : ""} -- @media (max-width: ${(props) => -- props.noBreakPoint ? "0" : constants.layout.breakpoint.break1}px) { -+ : "" -+} -+ @media (max-width: ${ -+ (props) => (props.noBreakPoint ? "0" : constants.layout.breakpoint.break1) -+}px) { - font-size: 2em; - } - `; -``` - -# Output - -```js -const StyledH1 = styled.div` - font-size: 2.5em; - font-weight: ${(props) => (props.strong ? 500 : 100)}; - font-family: ${constants.text.displayFont.fontFamily}; - letter-spacing: ${(props) => (props.light ? "0.04em" : 0)}; - color: ${(props) => props.textColor}; - ${ - (props) => - props.center - ? ` display: flex; - align-items: center; - justify-content: center; - text-align: center;` - : "" -} - @media (max-width: ${ - (props) => (props.noBreakPoint ? "0" : constants.layout.breakpoint.break1) -}px) { - font-size: 2em; - } -`; -``` - - - diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/multiparser-css/styled-components.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/multiparser-css/styled-components.js.snap index 8ac449ef06e..60e4754dc85 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/multiparser-css/styled-components.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/multiparser-css/styled-components.js.snap @@ -296,11 +296,11 @@ const StyledDiv = styled.div` const TomatoButton = Button.extend` - color: tomato; + color : tomato ; -+ -+border-color : tomato -+ ; - border-color: tomato; ++border-color : tomato ++ ; ++ `; Button.extend.attr({})` @@ -404,84 +404,50 @@ const StyledDiv = styled.div` `; const Single2 = styled.div` -@@ -198,11 +196,12 @@ - height: 40px; +@@ -199,8 +197,7 @@ width: 40px; -- ${(props) => + ${(props) => - (props.complete || props.inProgress) && - css` -+ ${ -+ (props) => + (props.complete || props.inProgress) && css` border-color: rgba(var(--green-rgb), 0.15); -- `} -+ ` -+} + `} - div { - background-color: var(--purpleTT); -@@ -211,39 +210,44 @@ - color: var(--purpleTT); +@@ -212,15 +209,13 @@ display: inline-flex; -- ${(props) => + ${(props) => - props.complete && - css` -+ ${ -+ (props) => -+ props.complete && css` ++ props.complete && css` background-color: var(--green); border-width: 7px; -- `} -+ ` -+} + `} -- ${(props) => + ${(props) => - (props.complete || props.inProgress) && - css` -+ ${ -+ (props) => -+ (props.complete || props.inProgress) && css` ++ (props.complete || props.inProgress) && css` border-color: var(--green); -- `} -+ ` -+} + `} } - `; - - const A = styled.a` +@@ -230,11 +225,10 @@ display: inline-block; color: #fff; -- ${(props) => + ${(props) => - props.a && - css` - display: none; - `} - height: 30px; -+ ${ -+ (props) => + props.a && css` + display: none; -+ ` -+} ++ `} + height: 30px; `; const Foo = styled.p` - max-width: 980px; -- ${mediaBreakpointOnlyXs` -+ ${ -+ mediaBreakpointOnlyXs` - && { - font-size: 0.8rem; - } -- `} -+ ` -+} - - &.bottom { - margin-top: 3rem; ``` # Output @@ -685,12 +651,10 @@ const bar = styled.div` height: 40px; width: 40px; - ${ - (props) => + ${(props) => (props.complete || props.inProgress) && css` border-color: rgba(var(--green-rgb), 0.15); - ` -} + `} div { background-color: var(--purpleTT); @@ -699,44 +663,36 @@ const bar = styled.div` color: var(--purpleTT); display: inline-flex; - ${ - (props) => - props.complete && css` + ${(props) => + props.complete && css` background-color: var(--green); border-width: 7px; - ` -} + `} - ${ - (props) => - (props.complete || props.inProgress) && css` + ${(props) => + (props.complete || props.inProgress) && css` border-color: var(--green); - ` -} + `} } `; const A = styled.a` display: inline-block; color: #fff; - ${ - (props) => + ${(props) => props.a && css` display: none; - ` -} + `} height: 30px; `; const Foo = styled.p` max-width: 980px; - ${ - mediaBreakpointOnlyXs` + ${mediaBreakpointOnlyXs` && { font-size: 0.8rem; } - ` -} + `} &.bottom { margin-top: 3rem; diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/multiparser-graphql/graphql-tag.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/multiparser-graphql/graphql-tag.js.snap index 12d192156f3..f4c3b69504e 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/multiparser-graphql/graphql-tag.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/multiparser-graphql/graphql-tag.js.snap @@ -233,30 +233,30 @@ fragment another on User { name +# comment +${one}${two} ${three} +${four} -+ + +- ${five} +- # comment +- ${six} +${five} +# comment +${six} -+ + +- # comment +- ${seven} +- # comment +# comment +${seven} +# comment + +${eight} - -- ${five} -- # comment -- ${six} ++ + # comment with trailing whitespace -- # comment -- ${seven} -- # comment - - ${eight} -+# blank line above this comment - # comment with trailing whitespace ++# blank line above this comment ++ - # blank line above this comment `; @@ -344,20 +344,18 @@ fragment another on User { name + + +${USER_DETAILS_FRAGMENT} -+ + +- ${FRIENDS_FRAGMENT} + # Comment + # that continues on a new line + + + # and has a blank line in the middle - -- ${FRIENDS_FRAGMENT} ++ + ${FRIENDS_FRAGMENT} ${generateFragment({ -- totally: "a good idea", -- })} -+ totally: "a good idea", -+})} + totally: "a good idea", + })} - ${fragment} - #comment @@ -514,8 +512,8 @@ ${USER_DETAILS_FRAGMENT} ${FRIENDS_FRAGMENT} ${generateFragment({ - totally: "a good idea", -})} + totally: "a good idea", + })} ${fragment}#comment diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/multiparser-html/lit-html.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/multiparser-html/lit-html.js.snap index 964a1b14646..003bf398251 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/multiparser-html/lit-html.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/multiparser-html/lit-html.js.snap @@ -115,7 +115,7 @@ ${ foo}:${bar}; ```diff --- Prettier +++ Rome -@@ -14,48 +14,64 @@ +@@ -14,48 +14,63 @@ render() { return html` @@ -175,11 +175,10 @@ ${ foo}:${bar};

Bar List

- ${bars.map((bar) => html`

${bar}

`)} + ${bars.map( -+ (bar) => -+ html` ++ (bar) => html` +

${bar}

+ `, -+ )} ++ )} `; } @@ -199,7 +198,7 @@ ${ foo}:${bar}; const closingScriptTagShouldBeEscapedProperly = /* HTML */ ` `; @@ -311,11 +310,10 @@ function HelloWorld() { return html`

Bar List

${bars.map( - (bar) => - html` + (bar) => html`

${bar}

`, - )} + )} `; } @@ -360,6 +358,6 @@ ${foo}:${bar}; # Lines exceeding max width of 80 characters ``` - 82: const closingScriptTag2 = /* HTML */ `