refactor(rome_formatter): `FormatLanguage` trait
MODIFY
crates/rome_formatter/src/comments.rs
crates/rome_formatter/src/comments.rs
-use crate::CstFormatContext;
use rome_rowan::{
Direction, Language, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxTriviaPieceComments,
WalkEvent,
@@ -158,9 +157,9 @@ pub struct Comments<L: Language> {
impl<L: Language> Comments<L> {
/// Extracts all the suppressions from `root` and its child nodes.
- pub fn from_node<Context>(root: &SyntaxNode<L>, context: &Context) -> Self
+ pub fn from_node<FormatLanguage>(root: &SyntaxNode<L>, language: &FormatLanguage) -> Self
where
- Context: CstFormatContext<Language = L>,
+ FormatLanguage: crate::FormatLanguage<SyntaxLanguage = L>,
{
let mut suppressed_nodes = HashSet::new();
let mut current_node = None;
@@ -190,7 +189,7 @@ impl<L: Language> Comments<L> {
.pieces()
.filter_map(|piece| piece.as_comments())
{
- if context.comment_style().is_suppression(comment.text()) {
+ if language.comment_style().is_suppression(comment.text()) {
suppressed_nodes.insert(current_node);
break;
}

MODIFY
crates/rome_formatter/src/lib.rs
crates/rome_formatter/src/lib.rs
@@ -230,6 +230,7 @@ pub trait CstFormatContext: FormatContext {
type Style: CommentStyle<Self::Language>;
/// Customizes how comments are formatted
+ #[deprecated(note = "Prefer FormatLanguage::comment_style")]
fn comment_style(&self) -> Self::Style;
/// Returns a ref counted [Comments].
@@ -248,9 +249,6 @@ pub trait CstFormatContext: FormatContext {
/// }
/// ```
fn comments(&self) -> Rc<Comments<Self::Language>>;
-
- /// Consumes `self` and returns a new context with the provided extracted (`comments`)[Comments].
- fn with_comments(self, comments: Rc<Comments<Self::Language>>) -> Self;
}
#[derive(Debug, Default, Eq, PartialEq)]
@@ -819,32 +817,61 @@ where
})
}
+/// Entry point for formatting a [SyntaxNode] for a specific language.
+pub trait FormatLanguage {
+ type SyntaxLanguage: Language;
+
+ /// The type of the formatting context
+ type Context: CstFormatContext<Language = Self::SyntaxLanguage>;
+
+ /// The type specifying how to format comments.
+ type CommentStyle: CommentStyle<Self::SyntaxLanguage>;
+
+ /// The rule type that can format a [SyntaxNode] of this language
+ type FormatRule: FormatRule<SyntaxNode<Self::SyntaxLanguage>, Context = Self::Context> + Default;
+
+ /// Customizes how comments are formatted
+ fn comment_style(&self) -> Self::CommentStyle;
+
+ /// This is used to select appropriate "root nodes" for the
+ /// range formatting process: for instance in JavaScript the function returns
+ /// true for statement and declaration nodes, to ensure the entire statement
+ /// gets formatted instead of the smallest sub-expression that fits the range
+ fn is_range_formatting_node(&self, _node: &SyntaxNode<Self::SyntaxLanguage>) -> bool {
+ true
+ }
+
+ /// Returns the formatting options
+ fn options(&self) -> &<Self::Context as FormatContext>::Options;
+
+ /// Creates the [FormatContext] with the given `source map` and `comments`
+ fn create_context(self, comments: Comments<Self::SyntaxLanguage>) -> Self::Context;
+}
+
/// Formats a syntax node file based on its features.
///
/// It returns a [Formatted] result, which the user can use to override a file.
-pub fn format_node<
- Context: CstFormatContext,
- N: FormatWithRule<Context, Item = SyntaxNode<Context::Language>>,
->(
- context: Context,
- root: &N,
-) -> FormatResult<Formatted<Context>> {
+pub fn format_node<L: FormatLanguage>(
+ root: &SyntaxNode<L::SyntaxLanguage>,
+ language: L,
+) -> FormatResult<Formatted<L::Context>> {
tracing::trace_span!("format_node").in_scope(move || {
- let suppressions = Comments::from_node(root.item(), &context);
- let context = context.with_comments(Rc::new(suppressions));
+ let comments = Comments::from_node(root, &language);
+ let format_node = FormatRefWithRule::new(root, L::FormatRule::default());
+ let context = language.create_context(comments);
let mut state = FormatState::new(context);
let mut buffer = VecBuffer::new(&mut state);
- write!(&mut buffer, [root])?;
+ write!(&mut buffer, [format_node])?;
let document = buffer.into_element();
- state.assert_formatted_all_tokens(root.item());
+ state.assert_formatted_all_tokens(root);
state
.context()
.comments()
- .assert_checked_all_suppressions(root.item());
+ .assert_checked_all_suppressions(root);
Ok(Formatted::new(document, state.into_context()))
})
@@ -890,12 +917,6 @@ where
/// Formats a range within a file, supported by Rome
///
-/// Because this function is language-agnostic, it must be provided with a
-/// `predicate` function that's used to select appropriate "root nodes" for the
-/// range formatting process: for instance in JavaScript the predicate returns
-/// true for statement and declaration nodes, to ensure the entire statement
-/// gets formatted instead of the smallest sub-expression that fits the range
-///
/// This runs a simple heuristic to determine the initial indentation
/// level of the node based on the provided [FormatContext], which
/// must match currently the current initial of the file. Additionally,
@@ -905,17 +926,11 @@ where
///
/// It returns a [Formatted] result with a range corresponding to the
/// range of the input that was effectively overwritten by the formatter
-pub fn format_range<Context, R, P>(
- context: Context,
- root: &SyntaxNode<Context::Language>,
+pub fn format_range<Language: FormatLanguage>(
+ root: &SyntaxNode<Language::SyntaxLanguage>,
mut range: TextRange,
- mut predicate: P,
-) -> FormatResult<Printed>
-where
- Context: CstFormatContext,
- R: FormatRule<SyntaxNode<Context::Language>, Context = Context> + Default,
- P: FnMut(&SyntaxNode<Context::Language>) -> bool,
-{
+ language: Language,
+) -> FormatResult<Printed> {
if range.is_empty() {
return Ok(Printed::new(
String::new(),
@@ -1006,11 +1021,11 @@ where
// the language implementation) in the ancestors of the start and end tokens
let start_node = start_token
.ancestors()
- .find(&mut predicate)
+ .find(|node| language.is_range_formatting_node(node))
.unwrap_or_else(|| root.clone());
let end_node = end_token
.ancestors()
- .find(predicate)
+ .find(|node| language.is_range_formatting_node(node))
.unwrap_or_else(|| root.clone());
let common_root = if start_node == end_node {
@@ -1063,10 +1078,7 @@ where
// Perform the actual formatting of the root node with
// an appropriate indentation level
- let mut printed = format_sub_tree(
- context,
- &FormatRefWithRule::<_, R>::new(common_root, R::default()),
- )?;
+ let mut printed = format_sub_tree(common_root, language)?;
// This finds the closest marker to the beginning of the source
// starting before or at said starting point, and the closest
@@ -1145,17 +1157,13 @@ where
/// even if it's a mismatch from the rest of the block the selection is in
///
/// It returns a [Formatted] result
-pub fn format_sub_tree<
- C: CstFormatContext,
- N: FormatWithRule<C, Item = SyntaxNode<C::Language>>,
->(
- context: C,
- root: &N,
+pub fn format_sub_tree<L: FormatLanguage>(
+ root: &SyntaxNode<L::SyntaxLanguage>,
+ language: L,
) -> FormatResult<Printed> {
- let syntax = root.item();
// Determine the initial indentation level for the printer by inspecting the trivia pieces
// of each token from the first token of the common root towards the start of the file
- let mut tokens = std::iter::successors(syntax.first_token(), |token| token.prev_token());
+ let mut tokens = std::iter::successors(root.first_token(), |token| token.prev_token());
// From the iterator of tokens, build an iterator of trivia pieces (once again the iterator is
// reversed, starting from the last trailing trivia towards the first leading trivia).
@@ -1191,7 +1199,7 @@ pub fn format_sub_tree<
// of indentation type detection yet. Unfortunately this
// may not actually match the current content of the file
let length = trivia.text().len() as u16;
- match context.options().indent_style() {
+ match language.options().indent_style() {
IndentStyle::Tab => length,
IndentStyle::Space(width) => length / u16::from(width),
}
@@ -1201,13 +1209,13 @@ pub fn format_sub_tree<
None => 0,
};
- let formatted = format_node(context, root)?;
+ let formatted = format_node(root, language)?;
let mut printed = formatted.print_with_indent(initial_indent);
let sourcemap = printed.take_sourcemap();
let verbatim_ranges = printed.take_verbatim_ranges();
Ok(Printed::new(
printed.into_code(),
- Some(syntax.text_range()),
+ Some(root.text_range()),
sourcemap,
verbatim_ranges,
))

MODIFY
crates/rome_formatter/src/token.rs
crates/rome_formatter/src/token.rs
@@ -78,6 +78,7 @@ where
// 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)?;
@@ -676,6 +677,7 @@ where
TriviaPrintMode::Trim => 0,
});
+ #[allow(deprecated)]
let comment_kind = f
.context()
.comment_style()
@@ -789,6 +791,7 @@ where
continue;
}
+ #[allow(deprecated)]
let kind = f
.context()
.comment_style()
@@ -800,6 +803,7 @@ where
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 => {}

MODIFY
crates/rome_js_formatter/src/context.rs
crates/rome_js_formatter/src/context.rs
@@ -20,10 +20,10 @@ pub struct JsFormatContext {
}
impl JsFormatContext {
- pub fn new(options: JsFormatOptions) -> Self {
+ pub fn new(options: JsFormatOptions, comments: Comments<JsLanguage>) -> Self {
Self {
options,
- ..JsFormatContext::default()
+ comments: Rc::new(comments),
}
}
}
@@ -62,11 +62,6 @@ impl CstFormatContext for JsFormatContext {
fn comments(&self) -> Rc<Comments<JsLanguage>> {
self.comments.clone()
}
-
- fn with_comments(mut self, comments: Rc<Comments<JsLanguage>>) -> Self {
- self.comments = comments;
- self
- }
}
#[derive(Debug, Clone, Default)]

MODIFY
crates/rome_js_formatter/src/lib.rs
crates/rome_js_formatter/src/lib.rs
@@ -249,7 +249,7 @@ mod ts;
pub mod utils;
use rome_formatter::prelude::*;
-use rome_formatter::{write, CstFormatContext};
+use rome_formatter::{write, Comments, CstFormatContext, FormatLanguage};
use rome_formatter::{Buffer, FormatOwnedWithRule, FormatRefWithRule, Formatted, Printed};
use rome_js_syntax::{
JsAnyDeclaration, JsAnyStatement, JsLanguage, JsSyntaxKind, JsSyntaxNode, JsSyntaxToken,
@@ -259,7 +259,7 @@ use rome_rowan::SyntaxResult;
use rome_rowan::TextRange;
use crate::builders::{format_parenthesize, format_suppressed_node};
-use crate::context::{JsFormatContext, JsFormatOptions};
+use crate::context::{JsCommentStyle, JsFormatContext, JsFormatOptions};
use crate::cst::FormatJsSyntaxNode;
use std::iter::FusedIterator;
use std::marker::PhantomData;
@@ -473,31 +473,26 @@ impl IntoFormat<JsFormatContext> for JsSyntaxToken {
}
}
-/// Formats a range within a file, supported by Rome
-///
-/// This runs a simple heuristic to determine the initial indentation
-/// level of the node based on the provided [JsFormatContext], which
-/// must match currently the current initial of the file. Additionally,
-/// because the reformatting happens only locally the resulting code
-/// will be indented with the same level as the original selection,
-/// even if it's a mismatch from the rest of the block the selection is in
-///
-/// It returns a [Formatted] result with a range corresponding to the
-/// range of the input that was effectively overwritten by the formatter
-pub fn format_range(
+struct JsFormatLanguage {
options: JsFormatOptions,
- root: &JsSyntaxNode,
- range: TextRange,
-) -> FormatResult<Printed> {
- rome_formatter::format_range::<_, FormatJsSyntaxNode, _>(
- JsFormatContext::new(options),
- root,
- range,
- is_range_formatting_root,
- )
+}
+impl JsFormatLanguage {
+ fn new(options: JsFormatOptions) -> Self {
+ Self { options }
+ }
}
-fn is_range_formatting_root(node: &JsSyntaxNode) -> bool {
+impl FormatLanguage for JsFormatLanguage {
+ type SyntaxLanguage = JsLanguage;
+ type Context = JsFormatContext;
+ type CommentStyle = JsCommentStyle;
+ type FormatRule = FormatJsSyntaxNode;
+
+ fn comment_style(&self) -> Self::CommentStyle {
+ JsCommentStyle
+ }
+
+ fn is_range_formatting_node(&self, node: &JsSyntaxNode) -> bool {
let kind = node.kind();
// Do not format variable declaration nodes, format the whole statement instead
@@ -513,6 +508,34 @@ fn is_range_formatting_root(node: &JsSyntaxNode) -> bool {
)
}
+ fn options(&self) -> &JsFormatOptions {
+ &self.options
+ }
+
+ fn create_context(self, comments: Comments<Self::SyntaxLanguage>) -> Self::Context {
+ JsFormatContext::new(self.options, comments)
+ }
+}
+
+/// Formats a range within a file, supported by Rome
+///
+/// This runs a simple heuristic to determine the initial indentation
+/// level of the node based on the provided [JsFormatContext], which
+/// must match currently the current initial of the file. Additionally,
+/// because the reformatting happens only locally the resulting code
+/// will be indented with the same level as the original selection,
+/// even if it's a mismatch from the rest of the block the selection is in
+///
+/// It returns a [Formatted] result with a range corresponding to the
+/// range of the input that was effectively overwritten by the formatter
+pub fn format_range(
+ options: JsFormatOptions,
+ root: &JsSyntaxNode,
+ range: TextRange,
+) -> FormatResult<Printed> {
+ rome_formatter::format_range(root, range, JsFormatLanguage::new(options))
+}
+
/// Formats a JavaScript (and its super languages) file based on its features.
///
/// It returns a [Formatted] result, which the user can use to override a file.
@@ -520,7 +543,7 @@ pub fn format_node(
options: JsFormatOptions,
root: &JsSyntaxNode,
) -> FormatResult<Formatted<JsFormatContext>> {
- rome_formatter::format_node(JsFormatContext::new(options), &root.format())
+ rome_formatter::format_node(root, JsFormatLanguage::new(options))
}
/// Formats a single node within a file, supported by Rome.
@@ -534,7 +557,7 @@ pub fn format_node(
///
/// It returns a [Formatted] result
pub fn format_sub_tree(options: JsFormatOptions, root: &JsSyntaxNode) -> FormatResult<Printed> {
- rome_formatter::format_sub_tree(JsFormatContext::new(options), &root.format())
+ rome_formatter::format_sub_tree(root, JsFormatLanguage::new(options))
}
#[cfg(test)]

refactor(rome_formatter): `FormatLanguage` trait (#3112)