feat(rome_formatter): traits for tokens
MODIFY
crates/rome_formatter/src/formatter.rs
crates/rome_formatter/src/formatter.rs
@@ -153,7 +153,6 @@ impl Formatter {
/// # Examples
///
/// ```
- ///
/// use rome_formatter::{Formatter, token};
/// use rslint_parser::{SyntaxNode, T, SyntaxToken, JsLanguage, JsSyntaxKind, SyntaxTreeBuilder};
/// use rome_rowan::{NodeOrToken};
@@ -171,6 +170,7 @@ impl Formatter {
///
/// assert_eq!(Ok(token("'abc'")), result)
/// ```
+ // #[deprecated = "Please use the traits available in 'crate::formatter_traits' which allow better developer experience"]
pub fn format_token<T>(&self, syntax_token: &T) -> FormatResult<T::Output>
where
T: token::FormattableToken,
@@ -203,6 +203,7 @@ impl Formatter {
/// Formats each child and returns the result as a list.
///
/// Returns [None] if a child couldn't be formatted.
+ // #[deprecated = "Please use the traits available in 'crate::formatter_traits' which allow better developer experience"]
pub fn format_nodes<T: AstNode + ToFormatElement>(
&self,
nodes: impl IntoIterator<Item = T>,

ADD
crates/rome_formatter/src/formatter_traits.rs
+use crate::{empty_element, FormatElement, FormatResult, Formatter, ToFormatElement};
+use rslint_parser::{AstNode, SyntaxResult, SyntaxToken};
+
+/// Utility trait used to simplify the formatting of optional tokens
+pub trait FormatOptionalTokenAndNode {
+ /// This function tries to format an optional [token](rslint_parser::SyntaxToken) or [node](rslint_parser::AstNode).
+ /// If the token doesn't exist, an [empty token](FormatElement::Empty) is created
+ ///
+ /// ## Examples
+ ///
+ /// ```
+ /// use rome_formatter::{Formatter, empty_element};
+ /// use rslint_parser::{SyntaxToken};
+ /// use rome_formatter::formatter_traits::{FormatOptionalTokenAndNode};
+ ///
+ /// let formatter = Formatter::default();
+ /// let token: Option<SyntaxToken> = None;
+ /// // we wrap the token in [Ok] so we can simulate SyntaxResult.
+ /// let result = token.format_or_empty(&formatter);
+ ///
+ /// assert_eq!(Ok(empty_element()), result)
+ fn format_or_empty(&self, formatter: &Formatter) -> FormatResult<FormatElement> {
+ self.format_or(formatter, empty_element)
+ }
+
+ /// This function tries to format an optional [token](rslint_parser::SyntaxToken). If the token doesn't exist,
+ /// an [empty token](crate::FormatElement::Empty) is created. If exists, the utility
+ /// formats the token and passes it to the closure.
+ ///
+ /// ## Examples
+ ///
+ /// ```
+ /// use rome_formatter::{Formatter, empty_element, space_token, format_elements, token};
+ /// use rslint_parser::{SyntaxToken};
+ /// use rome_formatter::formatter_traits::{FormatOptionalTokenAndNode};
+ /// use rslint_parser::{SyntaxTreeBuilder, JsSyntaxKind};
+ ///
+ /// let formatter = Formatter::default();
+ /// let empty_token: Option<SyntaxToken> = None;
+ ///
+ /// let mut builder = SyntaxTreeBuilder::new();
+ ///
+ /// builder.start_node(JsSyntaxKind::JS_STRING_LITERAL_EXPRESSION);
+ /// builder.token(JsSyntaxKind::JS_STRING_LITERAL, "'abc'");
+ /// builder.finish_node();
+ /// let node = builder.finish();
+ /// let syntax_token = node.first_token();
+ ///
+ /// // we wrap the token in [Ok] so we can simulate SyntaxResult.
+ /// let empty_result = empty_token.format_with_or_empty(&formatter, |token| token);
+ /// let with_result = syntax_token.format_with_or_empty(&formatter, |token| {
+ /// format_elements![space_token(), token]
+ /// });
+ ///
+ /// assert_eq!(Ok(empty_element()), empty_result);
+ /// assert_eq!(Ok(format_elements![space_token(), token("'abc'")]), with_result);
+ fn format_with_or_empty<With>(
+ &self,
+ formatter: &Formatter,
+ with: With,
+ ) -> FormatResult<FormatElement>
+ where
+ With: FnOnce(FormatElement) -> FormatElement,
+ {
+ self.format_with_or(formatter, with, empty_element)
+ }
+
+ /// This function tries to format an optional [token](rslint_parser::SyntaxToken) as is. If the token doesn't exist,
+ /// it calls the passed closure, which has to return a [create::FormatElement]
+ ///
+ /// ## Examples
+ ///
+ /// ```
+ /// use rome_formatter::{Formatter, token};
+ /// use rslint_parser::{SyntaxToken};
+ /// use rome_formatter::formatter_traits::{FormatOptionalTokenAndNode};
+ ///
+ /// let formatter = Formatter::default();
+ /// let empty_token: Option<SyntaxToken> = None;
+ ///
+ /// let result = empty_token.format_or(&formatter, || token(" other result"));
+ ///
+ /// assert_eq!(Ok(token(" other result")), result);
+ fn format_or<Or>(&self, formatter: &Formatter, op: Or) -> FormatResult<FormatElement>
+ where
+ Or: FnOnce() -> FormatElement,
+ {
+ self.format_with_or(formatter, |token| token, op)
+ }
+
+ /// If the token/node exists, it will call the first closure which will accept formatted element.
+ ///
+ /// If the token/node don't exist, the second closure will be called.
+ ///
+ /// Both closures have to return a [create::FormatElement]. This function will make sure the wrap them into [Ok].
+ ///
+ /// ## Examples
+ ///
+ /// ```
+ /// use rome_formatter::{Formatter, empty_element, space_token, format_elements, token};
+ /// use rslint_parser::{SyntaxToken};
+ /// use rome_formatter::formatter_traits::{FormatOptionalTokenAndNode};
+ /// use rslint_parser::{SyntaxTreeBuilder, JsSyntaxKind};
+ ///
+ /// let formatter = Formatter::default();
+ /// let empty_token: Option<SyntaxToken> = None;
+ ///
+ /// let mut builder = SyntaxTreeBuilder::new();
+ ///
+ /// builder.start_node(JsSyntaxKind::JS_STRING_LITERAL_EXPRESSION);
+ /// builder.token(JsSyntaxKind::JS_STRING_LITERAL, "'abc'");
+ /// builder.finish_node();
+ /// let node = builder.finish();
+ /// let syntax_token = node.first_token();
+ ///
+ /// // we wrap the token in [Ok] so we can simulate SyntaxResult.
+ /// let empty_result = empty_token.format_with_or(&formatter, |token| token, || {
+ /// token("empty")
+ /// });
+ /// let with_result = syntax_token.format_with_or(&formatter, |token| {
+ /// format_elements![space_token(), token]
+ /// }, || empty_element());
+ ///
+ /// assert_eq!(Ok(token("empty")), empty_result);
+ /// assert_eq!(Ok(format_elements![space_token(), token("'abc'")]), with_result);
+ fn format_with_or<With, Or>(
+ &self,
+ formatter: &Formatter,
+ with: With,
+ op: Or,
+ ) -> FormatResult<FormatElement>
+ where
+ With: FnOnce(FormatElement) -> FormatElement,
+ Or: FnOnce() -> FormatElement;
+}
+
+/// Utility trait to help to format nodes and tokens
+pub trait FormatTokenAndNode {
+ /// Simply format a token or node by calling [create::Formatter::format_node] or [create::Formatter::format_token]
+ /// respectively.
+ ///
+ /// ## Examples
+ ///
+ /// ```
+ /// use rome_formatter::{Formatter, token, space_token};
+ /// use rome_formatter::formatter_traits::FormatTokenAndNode;
+ /// use rslint_parser::{SyntaxTreeBuilder, JsSyntaxKind};
+ ///
+ /// let mut builder = SyntaxTreeBuilder::new();
+ ///
+ /// builder.start_node(JsSyntaxKind::JS_STRING_LITERAL_EXPRESSION);
+ /// builder.token(JsSyntaxKind::JS_STRING_LITERAL, "'abc'");
+ /// builder.finish_node();
+ /// let node = builder.finish();
+ /// let syntax_token = node.first_token().unwrap();
+ ///
+ /// let formatter = Formatter::default();
+ /// // we wrap the token in [Ok] so we can simulate SyntaxResult.
+ /// let result = Ok(syntax_token).format(&formatter);
+ ///
+ /// assert_eq!(Ok(token("'abc'")), result)
+ fn format(&self, formatter: &Formatter) -> FormatResult<FormatElement> {
+ self.format_with(formatter, |token| token)
+ }
+
+ /// Allows to chain a formatted token/node with another [elements](FormatElement)
+ ///
+ /// The function will decorate the result with [Ok]
+ ///
+ /// The formatted element is passed to the closure, which then can appended to additional elements.
+ /// This method is useful in case, for example, a token has to be chained with a space.
+ ///
+ /// ## Examples
+ ///
+ /// ```
+ /// use rome_formatter::{Formatter, token, format_elements, space_token};
+ /// use rslint_parser::{SyntaxNode, SyntaxTreeBuilder, JsSyntaxKind};
+ /// use rome_formatter::formatter_traits::FormatTokenAndNode;
+ ///
+ /// let mut builder = SyntaxTreeBuilder::new();
+ /// builder.start_node(JsSyntaxKind::JS_STRING_LITERAL_EXPRESSION);
+ /// builder.token(JsSyntaxKind::JS_STRING_LITERAL, "'abc'");
+ /// builder.finish_node();
+ /// let node = builder.finish();
+ /// let syntax_token = node.first_token().unwrap();
+ ///
+ /// let formatter = Formatter::default();
+ /// // we wrap the token in [Ok] so we can simulate SyntaxResult.
+ /// let result = Ok(syntax_token).format_with(&formatter, |token| {
+ /// format_elements![token.clone(), space_token(), token.clone()]
+ /// });
+ ///
+ /// assert_eq!(Ok(format_elements![token("'abc'"), space_token(), token("'abc'")]), result)
+ fn format_with<With>(&self, formatter: &Formatter, with: With) -> FormatResult<FormatElement>
+ where
+ With: FnOnce(FormatElement) -> FormatElement;
+}
+
+impl FormatOptionalTokenAndNode for Option<SyntaxToken> {
+ fn format_with_or<With, Or>(
+ &self,
+ formatter: &Formatter,
+ with: With,
+ op: Or,
+ ) -> FormatResult<FormatElement>
+ where
+ With: FnOnce(FormatElement) -> FormatElement,
+ Or: FnOnce() -> FormatElement,
+ {
+ match self {
+ None => Ok(op()),
+ Some(token) => Ok(with(formatter.format_token(token)?)),
+ }
+ }
+}
+
+impl FormatTokenAndNode for SyntaxResult<SyntaxToken> {
+ fn format_with<With>(&self, formatter: &Formatter, with: With) -> FormatResult<FormatElement>
+ where
+ With: FnOnce(FormatElement) -> FormatElement,
+ {
+ match self {
+ Ok(token) => {
+ let formatted_token = formatter.format_token(token)?;
+ Ok(with(formatted_token))
+ }
+ Err(err) => Err(err.into()),
+ }
+ }
+}
+
+impl<Node: AstNode + ToFormatElement> FormatOptionalTokenAndNode for Option<Node> {
+ fn format_with_or<With, Or>(
+ &self,
+ formatter: &Formatter,
+ with: With,
+ op: Or,
+ ) -> FormatResult<FormatElement>
+ where
+ With: FnOnce(FormatElement) -> FormatElement,
+ Or: FnOnce() -> FormatElement,
+ {
+ match self {
+ None => Ok(op()),
+ Some(node) => Ok(with(formatter.format_node(node)?)),
+ }
+ }
+}
+
+impl<Node: AstNode + ToFormatElement> FormatTokenAndNode for Node {
+ fn format_with<With>(&self, formatter: &Formatter, with: With) -> FormatResult<FormatElement>
+ where
+ With: FnOnce(FormatElement) -> FormatElement,
+ {
+ let formatted_node = formatter.format_node(self)?;
+ Ok(with(formatted_node))
+ }
+}

MODIFY
crates/rome_formatter/src/lib.rs
crates/rome_formatter/src/lib.rs
@@ -51,6 +51,7 @@ mod cst;
mod format_element;
mod format_elements;
mod formatter;
+pub mod formatter_traits;
mod intersperse;
mod printer;
mod ts;
@@ -105,6 +106,14 @@ impl From<SyntaxError> for FormatError {
}
}
+impl From<&SyntaxError> for FormatError {
+ fn from(syntax_error: &SyntaxError) -> Self {
+ match syntax_error {
+ SyntaxError::MissingRequiredChild(_node) => FormatError::MissingRequiredChild,
+ }
+ }
+}
+
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum IndentStyle {
/// Tab

MODIFY
crates/rome_formatter/src/ts/statements/continue_statement.rs
crates/rome_formatter/src/ts/statements/continue_statement.rs
+use crate::formatter_traits::FormatOptionalTokenAndNode;
use crate::{
- empty_element, format_elements, space_token, token, FormatElement, FormatResult, Formatter,
- ToFormatElement,
+ format_elements, space_token, token, FormatElement, FormatResult, Formatter, ToFormatElement,
};
use rslint_parser::ast::JsContinueStatement;
impl ToFormatElement for JsContinueStatement {
fn to_format_element(&self, formatter: &Formatter) -> FormatResult<FormatElement> {
- let label = if let Some(label_token) = self.label_token() {
- format_elements![space_token(), formatter.format_token(&label_token)?]
- } else {
- empty_element()
- };
+ let label = self
+ .label_token()
+ .format_with_or_empty(formatter, |token| format_elements![space_token(), token])?;
- let semicolon = formatter
- .format_token(&self.semicolon_token())?
- .unwrap_or_else(|| token(";"));
+ let semicolon = self.semicolon_token().format_or(formatter, || token(";"))?;
Ok(format_elements![
formatter.format_token(&self.continue_token()?)?,

MODIFY
crates/rome_formatter/src/ts/statements/return_statement.rs
crates/rome_formatter/src/ts/statements/return_statement.rs
+use crate::formatter_traits::FormatOptionalTokenAndNode;
+use crate::formatter_traits::FormatTokenAndNode;
use crate::{
concat_elements, space_token, token, FormatElement, FormatResult, Formatter, ToFormatElement,
};
@@ -5,18 +7,14 @@ use rslint_parser::ast::JsReturnStatement;
impl ToFormatElement for JsReturnStatement {
fn to_format_element(&self, formatter: &Formatter) -> FormatResult<FormatElement> {
- let mut tokens = vec![formatter.format_token(&self.return_token()?)?];
+ let mut tokens = vec![self.return_token().format(formatter)?];
if let Some(argument) = self.argument() {
tokens.push(space_token());
- tokens.push(formatter.format_node(&argument)?);
+ tokens.push(argument.format(formatter)?);
}
- tokens.push(
- formatter
- .format_token(&self.semicolon_token())?
- .unwrap_or_else(|| token(";")),
- );
+ tokens.push(self.semicolon_token().format_or(formatter, || token(";"))?);
Ok(concat_elements(tokens))
}

MODIFY
crates/rome_formatter/src/ts/statements/try_statement.rs
crates/rome_formatter/src/ts/statements/try_statement.rs
+use crate::formatter_traits::FormatTokenAndNode;
use crate::{
empty_element, format_elements, group_elements, soft_block_indent, space_token, FormatElement,
FormatResult, Formatter, ToFormatElement,
@@ -9,7 +10,7 @@ use rslint_parser::ast::{
impl ToFormatElement for JsTryStatement {
fn to_format_element(&self, formatter: &Formatter) -> FormatResult<FormatElement> {
Ok(format_elements![
- formatter.format_token(&self.try_token()?)?,
+ self.try_token().format(formatter)?,
space_token(),
formatter.format_node(&self.body()?)?,
space_token(),
@@ -27,7 +28,7 @@ impl ToFormatElement for JsTryFinallyStatement {
};
Ok(format_elements![
- formatter.format_token(&self.try_token()?)?,
+ self.try_token().format(formatter)?,
space_token(),
formatter.format_node(&self.body()?)?,
formatted_catch_clause,
@@ -41,7 +42,7 @@ impl ToFormatElement for JsCatchClause {
fn to_format_element(&self, formatter: &Formatter) -> FormatResult<FormatElement> {
if let Some(declaration) = self.declaration() {
Ok(format_elements![
- formatter.format_token(&self.catch_token()?)?,
+ self.catch_token().format(formatter)?,
space_token(),
formatter.format_node(&declaration)?,
space_token(),
@@ -49,7 +50,7 @@ impl ToFormatElement for JsCatchClause {
])
} else {
Ok(format_elements![
- formatter.format_token(&self.catch_token()?)?,
+ self.catch_token().format(formatter)?,
space_token(),
formatter.format_node(&self.body()?)?
])
@@ -76,7 +77,7 @@ impl ToFormatElement for JsCatchDeclaration {
impl ToFormatElement for JsFinallyClause {
fn to_format_element(&self, formatter: &Formatter) -> FormatResult<FormatElement> {
Ok(format_elements![
- formatter.format_token(&self.finally_token()?)?,
+ self.finally_token().format(formatter)?,
space_token(),
formatter.format_node(&self.body()?)?
])

feat(rome_formatter): traits for tokens (#2012)