Skip to content
This repository has been archived by the owner on Aug 31, 2023. It is now read-only.

Commit

Permalink
refactor(rome_js_formatter): Move node formatting into its own trait (#…
Browse files Browse the repository at this point in the history
…2476)

Today, the formatting that must happen for each JS node happens inside `Formatter.format_syntax_node` and the `formatter_traits`.

This PR moves the common logic applied by all nodes and tokens into the `ToFormatElement` implementation and renames it to `Format`.

* Rename `ToFormatElement` to `Format` and `to_format_element` to `format`
* Implement `Format` for `JsSyntaxToken`. CSS, JSON etc. can implement their own generic token formatting logic
* Introduce a new `FormatNode` trait (language specific) that implements the formatting of a node and calls into `format_fields` that is implemented for each field.
* Make the `FormatterTraits` node and language independent. Rename to `FormatTraits`.

`Format`, `FormatterTraits`, `FormatResult`, and `FormatError` are now all language independent. However, some still depend on the `Formatter`.

The next step is to move many of the `Formatter`'s helper function out of the Formatter so that it becomes language independent
  • Loading branch information
MichaReiser committed Apr 21, 2022
1 parent e57154e commit 77ab407
Show file tree
Hide file tree
Showing 403 changed files with 3,284 additions and 2,070 deletions.
18 changes: 9 additions & 9 deletions crates/rome_js_formatter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,22 @@ rome_js_formatter = { version = "0.0.0", path = "../rome_js_formatter" }

The foundation of the formatter relies on two pillars:

- the usage of a [*trait*](https://doc.rust-lang.org/reference/items/traits.html) called `ToFormatElement`;
- the usage of the [*trait*](https://doc.rust-lang.org/reference/items/traits.html) generic `Format` trait and `FormatNode` for nodes.
- the creation of an intermediate IR via a series of helpers

Import the `ToFormatElement` trait and implement it for the data structure you need.
Import the `FormatNode` trait and implement it for your Node.

```rust
use rome_js_formatter::{ToFormatElement, FormatElement, format_elements, token}
use rome_js_formatter::{Format, FormatNode, FormatElement, format_elements, token};

struct Buzz {
blast: String
}

impl ToFormatElement for Buzz {
fn to_format_element(&self, formatter: &Formatter) -> FormatResult<FormatElement> {
// implementation goes here
format_elements![token("_"), blast.as_str(), token("_")]
impl FormatNode for Buzz {
fn format_fields(&self, formatter: &Formatter) -> FormatResult<FormatElement> {
// implementation goes here
format_elements![token("_"), blast.as_str(), token("_")]
}
}

Expand All @@ -38,8 +38,8 @@ impl ToFormatElement for Buzz {
1. if a token is mandatory and the AST has that information, please use that token instead, for example:

```rust
fn to_format_element(&self, formatter: &Formatter) -> FormatResult<FormatElement> {
let l_paren_yes = formatter.format_token(&self.l_paren_token()?)?; // yes
fn format_fields(&self, formatter: &Formatter) -> FormatResult<FormatElement> {
let l_paren_yes = &self.l_paren_token()?.format(formatter)?; // yes
let l_paren_no = toke("("); // no
}
```
Expand Down
21 changes: 10 additions & 11 deletions crates/rome_js_formatter/docs/implement_the_formatter.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Implement the formatter

Our formatter is node based. Meaning that each AST node knows how to format itself. In order to implement
the formatting, a node has to implement the trait `ToFormatElement`.
the formatting, a node has to implement the trait `FormatNode`.

`rome` has an automatic code generation that creates automatically the files out of the grammar.
By default, all implementations will format verbatim,
Expand Down Expand Up @@ -31,8 +31,8 @@ This will automatically build and open a browser tab to the documentation.

1. Use the `*Fields` struct to extract all the tokens/nodes
```rust
impl ToFormatElement for JsExportDefaultExpressionClause {
fn to_format_element(&self, formatter: &Formatter) -> FormatResult<FormatElement> {
impl FormatNode for JsExportDefaultExpressionClause {
fn format_fields(&self, formatter: &Formatter) -> FormatResult<FormatElement> {
let JsExportDefaultExpressionClauseFields {
default_token,
expression,
Expand All @@ -44,8 +44,8 @@ This will automatically build and open a browser tab to the documentation.
2. When using `.as_fields()` with the destructuring, don't use the `..` feature. Prefer extracting all fields and ignore them
using the `_`
```rust
impl ToFormatElement for JsExportDefaultExpressionClause {
fn to_format_element(&self, formatter: &Formatter) -> FormatResult<FormatElement> {
impl FormatNode for JsExportDefaultExpressionClause {
fn format_fields(&self, formatter: &Formatter) -> FormatResult<FormatElement> {
let JsExportDefaultExpressionClauseFields {
default_token,
expression: _,
Expand All @@ -55,16 +55,16 @@ This will automatically build and open a browser tab to the documentation.
}
```
The reason why we want to promote this pattern is because we want to make explicit when a token/node is excluded;
3. Use the APIs provided by `format_element.rs` and `formatter` and `formatter_traits.rs`.
3. Use the APIs provided by `format_element.rs` and `formatter` and `format_traits.rs`.
1. `formatter_element.rs` exposes a series of utilities to craft the formatter IR; please refer to their internal
documentation to understand what the utilities are for;
2. `formatter` exposes a set of functions to help to format some recurring patterns; please refer to their internal
documentation to understand how to use them and when;
3. `formatter_traits.rs`: with these traits, we give the ability to nodes and tokens to implements certain methods
3. `format_traits.rs`: with these traits, we give the ability to nodes and tokens to implements certain methods
that are exposed based on its type. If you have a good IDE support, this feature will help you. For example:
```rust
impl ToFormatElement for JsExportDefaultExpressionClause {
fn to_format_element(&self, formatter: &Formatter) -> FormatResult<FormatElement> {
impl FormatNode for JsExportDefaultExpressionClause {
fn format_fields(&self, formatter: &Formatter) -> FormatResult<FormatElement> {
let JsExportDefaultExpressionClauseFields {
default_token,
expression, // it's a mandatory node
Expand All @@ -82,8 +82,7 @@ This will automatically build and open a browser tab to the documentation.
}
}
```
Traits are much safer and, they have an additional checks around nodes e.g. comments suppression; the golden rule
is that when you have a **typed node at hand**, prefer the `format*` traits instead of `.to_format_element`

4. Use our [playground](https://play.rome.tools) to inspect the code that you want to format. You can inspect
the AST given by a certain snippet. This will help you to understand which nodes need to be implemented/modified
in order to implement formatting. Alternatively, you can locally run the playground by following
Expand Down
8 changes: 4 additions & 4 deletions crates/rome_js_formatter/src/cst.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::{FormatElement, FormatResult, Formatter, ToFormatElement};
use crate::{Format, FormatElement, FormatResult, Formatter};
use rome_js_syntax::{map_syntax_node, JsSyntaxNode};

impl ToFormatElement for JsSyntaxNode {
fn to_format_element(&self, formatter: &Formatter) -> FormatResult<FormatElement> {
map_syntax_node!(self.clone(), node => node.to_format_element(formatter))
impl Format for JsSyntaxNode {
fn format(&self, formatter: &Formatter) -> FormatResult<FormatElement> {
map_syntax_node!(self.clone(), node => node.format(formatter))
}
}
Loading

0 comments on commit 77ab407

Please sign in to comment.