-
Notifications
You must be signed in to change notification settings - Fork 659
fix(rome_js_formatter): in JSX, some spaces are removed #3211 #3251
Conversation
✅ Deploy Preview for docs-rometools ready!
To edit notification comments on pull requests, go to your Netlify site settings. |
Regarding the open question, there's one difference that may be of importance:
Prettier prints the
For items, the item is only printed if it doesn't fit on the line, regardless of the next item or separator. |
Could you please explain more? What is the difference between Prettier |
As a small note. I noticed an instability with JSX formatting when adding the prettier tests and the fix is now included in the comments formatting. It required to move the best fitting element to the |
The difference isn't around
Fill, in Rome and prettier, is a sequence of The printing of tools/crates/rome_formatter/src/printer/mod.rs Lines 360 to 482 in a124d36
The logic is as follow: Print the first item in flat if it fits on the line, otherwise print it in expanded mode. For all remaining separator & item pairs do:
That's why we should move the softline break into the separator slot. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is looking good to me. I added a few comments that would be good to address before merging.
I further recommend rebasing on main to get the prettier snapshot tests.
d03f026
to
373e234
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good. Would you mind adding the prettier compatibility metrics to the test plan.
And there seems to be an issue with the codegen CI job.
Edit: rebasing on main should fix the CI job failures
After 1022663 the bug started reproducing again, need to figure out (: |
Hmm that's odd. You aren't introspecting any IR, aren't you? Let me know if you need any help. |
Hm, previous version of
which tested a I'm try to find the same condition in current version. But it seems that now |
The corresponding code now is covered by tools/crates/rome_formatter/src/printer/mod.rs Lines 459 to 462 in f3079b4
The
|
I checked out your branch, generated the IR, and then rebased it on main and again, looked at the IR for:
Not rebased
Rebased
Notice how the
Is the second child of |
I think I managed to narrow it down. The issue is with tools/crates/rome_js_formatter/src/jsx/lists/child_list.rs Lines 177 to 180 in d03f026
This used to work because tools/crates/rome_js_formatter/src/jsx/lists/child_list.rs Lines 503 to 505 in d03f026
assumed that The rebased version that uses I'm not entirely sure what's the best fix. The easy fix is to simply add a method to the multiline builder that let's you write a single entry without caring that item/separator are balanced. I don't mind that overly much but it eases that item/separator position can be "accidentally" flipped. |
Yes, you're right! Thank you so much. I've just tried your suggestion and it worked. I'm going to push it. |
I don't think this is something that should be fixed inside of the printer because the printer can't know what entry is intended to be the item or separator. Correctly writing the pairs is the responsibility of the formatting code. Edit: That's why I think fixing it in |
373e234
to
d053d46
Compare
This PR: Main: |
2165d8e
to
e21972b
Compare
e21972b
to
3880b2d
Compare
@MichaReiser
Here we can call next on the iterator for
|
@@ -139,7 +144,7 @@ impl FormatJsxChildList { | |||
let is_trailing_or_only_whitespace = children_iter.peek().is_none(); | |||
|
|||
if is_trailing_or_only_whitespace || is_after_line_break { | |||
multiline.write_with_empty_separator(&JsxRawSpace, f); | |||
multiline.write_without_separator(&JsxRawSpace, f); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Here, we're writing the separator and we obviously don't want to write a separator with a separator :D
That's why I think it would be good to add a multiline.write_separator(...)
method that has the same behaviour as write_without_separator
but only exists to document that we differentiate between items that normally should be written with a separator and separators that... are separators and don't need a separator.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done (:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you so much for working on this. I have a small recommendation to introduce a new method on the builder to make it more clear that it sometimes writes an item without a separator (requires documentation), a separator (fine to not write a second separator), or an item with a separator.
@ematipico are you OK merging this change during my PTO in case @denbezrukov updates the PR after today?
3880b2d
to
df702b6
Compare
Sure, no problem 😃 |
df702b6
to
daae385
Compare
daae385
to
24b13bb
Compare
There is another problem =( Let's say that we have:
Rome output in #3251:
Prettier output:
It has the Rome IR in #3251:
which is close to the Prettier one:
It matched Prettier output before #3307 because when the printer checks
tools/crates/rome_formatter/src/printer/mod.rs Lines 198 to 233 in 1fd9b7b
AllPredicate to check fits_on_line which included the {" "} element and used expand mode. After #3307 it doesn't include {" "} to check fits_on_line for the group.
|
Yeah... I looked at it today and haven't been able to come up with a good solution. The reason why this works for prettier is that they schedule the item and separator with the corresponding modes and then simply continue printing. That means if the item fits but the separator doesn't, then the print queue looks like this:
That means that if the item contains a I don't think this is entirely ideal because it requires that any
How's this different from what we do today. Today, our implementation measures if the separator and next item fit, but never if the item + next separator do. This is important for the issue we're seeing now because we must expand the item if neither the flat nor expanded separator fit (there's no real alternative). The challenge I'm facing is how to measure if the item + separator in expanded mode fit. The approach I have in mind is to create a |
The code for this could be similar to this: while (not at end) {
let mut fits_on_line = FitsOnLine::new(queue, stack, self);
let item_fits = fits_on_line.fits_fill_entry(PrintMode::Flat);
// Not even the item fits, print item and separator in expanded mode.
if !item_fits {
drop(fits_on_line);
self.print_entry(queue, stack , PrintMode::Expanded),
self.print_entry(queue, stack, PrintMode:Expanded),
continue;
}
let separator_fits = fits_on_line.fits_fill_entry(PrintMode::Flat);
// The item fits, but now the separator doesn't...
if !separator_fits {
drop(fits_on_line);
// Now test if the separator fits in expanded mode.
let mut fits_on_line = FitsOnLine::new(queue, stack, self);
// skip item, we know it fits
queue.skip_content(TagKind::Entry);
let separator_fits = fits_on_line.fits_fill_entry(PrintMode::Expanded);
drop(fits_on_line);
// If the separator fits, then print the item in flat mode, but expand the separator
if separator_fits {
self.measured_fits_on_line = true;
self.print_entry(queue, stack, PrintMode::Flat);
self.measured_fits_on_line = false;
self.print_entry(queue, stack, PrintMode::Expanded);
} else {
// If not, then print both in expanded mode (the best we can do in this situation).
self.print_entry(queue, stack , PrintMode::Expanded),
self.print_entry(queue, stack, PrintMode:Expanded),
}
continue;
}
// we know here that the item + separator fit. Now the question is if the next item fits as well.
// Test if the next item fits too
let next_fits = fits_on_line.fits_fill_entry(PrintMode::Flat);
drop(fits_on_line);
// Print the item in flat mode because we know the flat or expanded separator always fit.
self.measured_fits_on_line = true;
self.print_entry(queue, stack, PrintMode::Flat);
// Print the separator in flat or expanded mode, depending if the next item also fits on the line
self.measured_fits_on_line = next_fits;
self.print_entry(queue, stack, if next_fits { PrintMode::Flat } else { PrintMode::Expanded })
self.measured_fits_on_line = false;
// Now continue with the next item. We may be clever here and avoid recomputing if the item fits but that might be difficult to accomplish
}
self.measured_fits_on_line = false; The alternative would be to change |
I'm going to try to implement it in another PR. |
I suppose we have to delay this fix to the next release |
24b13bb
to
4c31805
Compare
@denbezrukov How do you feel about updating the failing snapshot test? This would allow us to merge this PR and address the printer issue in a follow up PR. |
4c31805
to
5831a07
Compare
Done. |
Summary
Fix #3211
Prettier removes
softline
,hardline
before and afterjsxWhitespace
.Open Question
There is another one difference between Rome and Prettier. Prettier adds
jsxWhitespace
and break a line afterText
. But IR the same. Can it be a problem withfill
builder?Rome:
Prettier:
Playground
Test Plan
Add new test cases.
cargo test -p rome_js_formatter
This PR:
Average compatibility: 88.44
Compatible lines: 89.60
Main:
Average compatibility: 88.42
Compatible lines: 89.30