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

refactor(rome_js_formatter): Move node formatting into its own trait #2476

Merged
merged 2 commits into from
Apr 21, 2022

Conversation

MichaReiser
Copy link
Contributor

@MichaReiser MichaReiser commented Apr 21, 2022

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.

How to review

  • The conceptual changes are in rome_js_formatter/lib and format_traits
  • Review codegen

Test Plan

cargo test

Deleted a union, a list, and a node formatter implementation and regenerated the formatter files. Verified that the generated files have no compile errors.

use rome_js_syntax::JsAnyArrayAssignmentPatternElement;
impl ToFormatElement for JsAnyArrayAssignmentPatternElement {
fn to_format_element(&self, formatter: &Formatter) -> FormatResult<FormatElement> {
impl Format for JsAnyArrayAssignmentPatternElement {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unions don't need to implement FormatNode because the "node" logic will be handled by the inner node.

use rome_js_syntax::JsArrayAssignmentPatternElementList;

impl ToFormatElement for JsArrayAssignmentPatternElementList {
fn to_format_element(&self, formatter: &Formatter) -> FormatResult<FormatElement> {
impl Format for JsArrayAssignmentPatternElementList {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe it's fine to not implement FormatNode for lists because suppressions either apply to the whole wrapper node or a specific element:

// rome-ingore
[ a, b, c] // ignores whole array expression
[
  // rome-ignore
  a, // Ignores first node
  b,
  c
]

Comment on lines 39 to 68
impl<T> Format for &T
where
T: Format + ?Sized,
{
fn format(&self, formatter: &Formatter) -> FormatResult<FormatElement> {
Format::format(&**self, formatter)
}
}

impl<T> Format for &mut T
where
T: ?Sized + Format,
{
fn format(&self, formatter: &Formatter) -> FormatResult<FormatElement> {
Format::format(&**self, formatter)
}
}

impl<T: ?Sized + Format> Format for Ref<'_, T> {
fn format(&self, f: &Formatter) -> FormatResult<FormatElement> {
Format::format(&**self, f)
}
}

impl<T: ?Sized + Format> Format for RefMut<'_, T> {
fn format(&self, f: &Formatter) -> FormatResult<FormatElement> {
Format::format(&*(self.deref()), f)
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I plan to introduce a format_args[node1, node2] macro that accepts anything implementing Format. This implementations will be necessary for such a trait to work

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Apr 21, 2022

Deploying with  Cloudflare Pages  Cloudflare Pages

Latest commit: 5558a65
Status: ✅  Deploy successful!
Preview URL: https://8acf7a7b.tools-8rn.pages.dev

View logs

@MichaReiser MichaReiser temporarily deployed to aws April 21, 2022 08:21 Inactive
@MichaReiser MichaReiser requested review from ematipico and leops and removed request for ematipico and leops April 21, 2022 08:21
@MichaReiser MichaReiser temporarily deployed to aws April 21, 2022 08:24 Inactive
@github-actions
Copy link

github-actions bot commented Apr 21, 2022

Parser conformance results on ubuntu-latest

js/262

Test result main count This PR count Difference
Total 45878 45878 0
Passed 44938 44938 0
Failed 940 940 0
Panics 0 0 0
Coverage 97.95% 97.95% 0.00%

jsx/babel

Test result main count This PR count Difference
Total 39 39 0
Passed 36 36 0
Failed 3 3 0
Panics 0 0 0
Coverage 92.31% 92.31% 0.00%

ts/babel

Test result main count This PR count Difference
Total 588 588 0
Passed 519 519 0
Failed 69 69 0
Panics 0 0 0
Coverage 88.27% 88.27% 0.00%

ts/microsoft

Test result main count This PR count Difference
Total 16257 16257 0
Passed 12391 12391 0
Failed 3866 3866 0
Panics 0 0 0
Coverage 76.22% 76.22% 0.00%

@github-actions
Copy link

github-actions bot commented Apr 21, 2022

@MichaReiser MichaReiser temporarily deployed to aws April 21, 2022 08:36 Inactive
Comment on lines -239 to -278
impl<F: FormatTokenAndNode> FormatTokenAndNode for SyntaxResult<F> {
fn format_with<With, WithResult>(
&self,
formatter: &Formatter,
with: With,
) -> FormatResult<FormatElement>
where
With: FnOnce(FormatElement) -> WithResult,
WithResult: IntoFormatResult,
{
match self {
Ok(token) => with(token.format(formatter)?).into_format_result(),
Err(err) => Err(err.into()),
}
}
}

impl FormatTokenAndNode for JsSyntaxToken {
fn format_with<With, WithResult>(
&self,
formatter: &Formatter,
with: With,
) -> FormatResult<FormatElement>
where
With: FnOnce(FormatElement) -> WithResult,
WithResult: IntoFormatResult,
{
cfg_if::cfg_if! {
if #[cfg(debug_assertions)] {
assert!(formatter.printed_tokens.borrow_mut().insert(self.clone()), "You tried to print the token '{:?}' twice, and this is not valid.", self);
}
}

with(format_elements![
formatter.print_leading_trivia(self, TriviaPrintMode::Full),
Token::from(self),
formatter.print_trailing_trivia(self),
])
.into_format_result()
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to Format and FormatNode

@MichaReiser
Copy link
Contributor Author

!bench_formatter

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.
@MichaReiser MichaReiser temporarily deployed to aws April 21, 2022 08:50 Inactive
@github-actions
Copy link

Formatter Benchmark Results

group                                    main                                   pr
-----                                    ----                                   --
formatter/checker.ts                     1.00    376.8±1.22ms     6.9 MB/sec    1.02    383.3±1.41ms     6.8 MB/sec
formatter/compiler.js                    1.01    230.4±1.14ms     4.5 MB/sec    1.00    229.2±0.93ms     4.6 MB/sec
formatter/d3.min.js                      1.00    178.6±1.09ms  1503.2 KB/sec    1.00    178.2±1.12ms  1506.3 KB/sec
formatter/dojo.js                        1.00     12.3±0.01ms     5.6 MB/sec    1.00     12.4±0.05ms     5.5 MB/sec
formatter/ios.d.ts                       1.00    278.4±0.54ms     6.7 MB/sec    1.01    280.9±0.69ms     6.6 MB/sec
formatter/jquery.min.js                  1.00     46.9±0.34ms  1805.8 KB/sec    1.01     47.2±0.42ms  1793.5 KB/sec
formatter/math.js                        1.00    361.2±1.08ms  1835.6 KB/sec    1.00    359.9±1.01ms  1842.2 KB/sec
formatter/parser.ts                      1.00      8.5±0.01ms     5.7 MB/sec    1.01      8.6±0.01ms     5.6 MB/sec
formatter/pixi.min.js                    1.01    202.2±0.96ms     2.2 MB/sec    1.00    200.6±0.94ms     2.2 MB/sec
formatter/react-dom.production.min.js    1.00     59.8±0.69ms  1970.3 KB/sec    1.00     59.7±0.53ms  1973.3 KB/sec
formatter/react.production.min.js        1.00      2.9±0.00ms     2.1 MB/sec    1.01      2.9±0.01ms     2.1 MB/sec
formatter/router.ts                      1.00      6.1±0.00ms     9.9 MB/sec    1.01      6.1±0.03ms     9.8 MB/sec
formatter/tex-chtml-full.js              1.00    449.4±1.04ms     2.0 MB/sec    1.00    447.3±1.04ms     2.0 MB/sec
formatter/three.min.js                   1.01    229.8±1.00ms     2.6 MB/sec    1.00    228.1±0.83ms     2.6 MB/sec
formatter/typescript.js                  1.00  1455.5±10.08ms     6.5 MB/sec    1.01   1472.8±3.81ms     6.5 MB/sec
formatter/vue.global.prod.js             1.00     78.8±3.04ms  1566.5 KB/sec    1.00     78.6±0.72ms  1569.5 KB/sec

Copy link
Contributor

@ematipico ematipico left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One step forward to make things more generic! I added some feedback and few questions where I have some doubts!

crates/rome_js_formatter/src/lib.rs Show resolved Hide resolved
crates/rome_js_formatter/src/lib.rs Show resolved Hide resolved
crates/rome_js_formatter/src/lib.rs Outdated Show resolved Hide resolved
crates/rome_js_formatter/src/lib.rs Outdated Show resolved Hide resolved
crates/rome_js_formatter/src/lib.rs Show resolved Hide resolved
crates/rome_js_formatter/src/format.rs Show resolved Hide resolved
crates/rome_js_formatter/src/lib.rs Outdated Show resolved Hide resolved
crates/rome_js_formatter/src/lib.rs Outdated Show resolved Hide resolved
@MichaReiser MichaReiser temporarily deployed to aws April 21, 2022 11:53 Inactive
Copy link
Contributor

@ematipico ematipico left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me! Thank you for addressing my questions! This is great!

Copy link
Contributor

@leops leops left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still wonder if it would have been possible this trait hierarchy using only generics somehow instead of relying on codegen, but until specialization gets stabilized (and perhaps even after) I think this is probably the only way this can be expressed with the Rust type system

@MichaReiser
Copy link
Contributor Author

I still wonder if it would have been possible this trait hierarchy using only generics somehow instead of relying on codegen, but until specialization gets stabilized (and perhaps even after) I think this is probably the only way this can be expressed with the Rust type system

I would love that if you find a way. I even tried to go fancy (at least as far as I'm capable of) and played around with From and Into to remove the &T impl from Format but nothing helped. I in the end decided that the code gen is in place and using it to work around Rust's limitation seems reasonable enough.

@MichaReiser MichaReiser merged commit 77ab407 into main Apr 21, 2022
@MichaReiser MichaReiser deleted the refactor/format-node branch April 21, 2022 16:03
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants