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

feat(rome_js_formatter): Hard line breaks in object expressions. #2458

Merged
merged 1 commit into from
May 23, 2022

Conversation

cpojer
Copy link
Contributor

@cpojer cpojer commented Apr 17, 2022

Summary

Fixes #2408.

This is the initial draft for respecting a newline in an object expression (or import specifier) in between an opening curly brace { and the first item in the member list. This should make Rome match prettier in this situation. Again, I have no idea what I'm doing :)

Open questions to get this over the line:

  • For some reason formatter::js_module::specs::js::module::arrow::arrow_nested fails and I'm unsure why. Please help!
  • See some inline questions.

Test Plan

  • Added tests.

/// Formats a group delimited by an opening and closing token, placing the
/// content in an [indent] group with [hard_line_break] tokens at the
/// start and end
pub(crate) fn format_delimited_block_hard_line_break(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is mostly a copy of format_delimited_soft_block_spaces and honestly I don't really understand the name of that function, so I'm not sure if the name of this function here makes sense. Please let me know if there is a better one.

Comment on lines +21 to +26
let a = {
get foo() {},
};
let b = {
set foo(a) {},
};
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This matches the input in getter_setter.js.

Copy link
Contributor

Choose a reason for hiding this comment

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

That file is the source code, you should check the file that finishes with .prettier-snap.

Check their playground

Comment on lines 14 to 23
let has_newlines = has_newline(&members.syntax());
let members = members.format(formatter)?;

if members.is_empty() {
formatter.format_delimited_soft_block_indent(&l_curly_token?, members, &r_curly_token?)
} else if has_newlines == true {
formatter.format_delimited_block_hard_line_break(
&l_curly_token?,
members,
&r_curly_token?,
)
} else {
formatter.format_delimited_soft_block_spaces(&l_curly_token?, members, &r_curly_token?)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We have about 10 callsites for format_delimited_soft_block_spaces. So far I'm only adding this feature to two of them because I'm looking for guidance on the optimal approach:

  1. Use the above approach and add the newline check and explicit call to format_delimited_block_hard_line_break to every callsite.
  2. Rename format_delimited_soft_block_spaces to ??? and pass a has_newlines boolean to the call, then decide on using soft or hard breaks within that single function.

Option 1 is more explicit, option 2 is less code. Which fits better with Rome?

Copy link
Contributor

Choose a reason for hiding this comment

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

The functions format_delimited_* were an attempt to group common patterns that we were starting to see across the board. If you think that this is going to be a common pattern, then it makes sense to make it generic, instead if this is just a single case, then it should stay isolated.

The has_newlines argument won't be needed, as explained in my comment: https://github.com/rome/tools/pull/2458/files#r851766933

Copy link
Contributor

@MichaReiser MichaReiser left a comment

Choose a reason for hiding this comment

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

Thanks Christoph.

Regarding the failing :arrow_nested test.

The test is failing because the formatter output isn't stable. Meaning, that re-formatting the formatted output yields a different result. Why or how it can be avoided I don't know right now. @leops and @ematipico have more experience with this kind of issues.

crates/rome_js_formatter/src/formatter.rs Outdated Show resolved Hide resolved
crates/rome_js_formatter/src/utils/mod.rs Outdated Show resolved Hide resolved
@cpojer cpojer force-pushed the hard-line-break-object-expressions branch from b029f4c to 8e21da3 Compare April 18, 2022 00:09
@cpojer
Copy link
Contributor Author

cpojer commented Apr 18, 2022

Thanks for the feedback so far. Remaining questions:

  • The unstable printing test issue.
  • Are you happy with the has_leading_newline + format_delimited_block_indent calls or do you somehow want to pull responsibility into format_delimited_block_indent so it can be handled generically? The decision on that will determine the final state of this PR.

@leops
Copy link
Contributor

leops commented Apr 19, 2022

After looking into it, the stability issue seem to be caused by the has_hard_line_breaks check in the formatting of call expression (at crates\rome_js_formatter\src\utils\call_expression.rs:L480): replacing the soft line breaks with hard ones in the formatting of the object expression causes the outer call chain to emit an additional group that cause the entire statement to print differently.
This check was originally intended to approximately replicate the logic of the printer but the approximation becomes evident in the presence of soft line breaks, so I think we should get rid of this logic in favor of something closer to what Prettier does (and doesn't need to inspect FormatElement) since we're aiming for better compatibility. But that's outside of the scope of this PR ...

@MichaReiser
Copy link
Contributor

After looking into it, the stability issue seem to be caused by the has_hard_line_breaks check in the formatting of call expression (at crates\rome_js_formatter\src\utils\call_expression.rs:L480): replacing the soft line breaks with hard ones in the formatting of the object expression causes the outer call chain to emit an additional group that cause the entire statement to print differently. This check was originally intended to approximately replicate the logic of the printer but the approximation becomes evident in the presence of soft line breaks, so I think we should get rid of this logic in favor of something closer to what Prettier does (and doesn't need to inspect FormatElement) since we're aiming for better compatibility. But that's outside of the scope of this PR ...

Is anyone looking into this? If not, can we track this as an issue?

@leops
Copy link
Contributor

leops commented Apr 21, 2022

Is anyone looking into this? If not, can we track this as an issue?

I don't think anyone is working on this right now, I can create an independent issue for it. Since it seems to be blocking for this PR I'm not really sure how we want to handle that, do would we hold on to merging this until the issue is fixed or merge this change now and fix the stability issue later ?

@cpojer
Copy link
Contributor Author

cpojer commented Apr 21, 2022

We cannot merge this PR unless we disable the test. It would be great to fix this issue, and then I can rebase my work on that.

@MichaReiser Still looking for guidance on my second question about whether the implementation in this PR is acceptable or whether things should be moved around.

@MichaReiser
Copy link
Contributor

We cannot merge this PR unless we disable the test. It would be great to fix this issue, and then I can rebase my work on that.

@MichaReiser Still looking for guidance on my second question about whether the implementation in this PR is acceptable or whether things should be moved around.

format_delimited_block_indent is used for example for the {} of classes where prettier doesn't preserve the line breaks. There are more call sites where this is the case as well. That's why I think your current implementation that explicitly handles it for objects is the right choice.

@cpojer cpojer force-pushed the hard-line-break-object-expressions branch 3 times, most recently from 4507fc0 to 5390c57 Compare April 21, 2022 15:54
@cpojer
Copy link
Contributor Author

cpojer commented Apr 21, 2022

Ok this PR is ready for review. It updates a lot of snapshots to look like the input code (I initially got confused and was comparing the snapshot changes to the previous snapshot version, which obviously has changed!). I also changed how TypeScript interface declarations are printed since in Prettier they will always break, even with just one prop (thanks @MichaReiser).

The only blocker before merging besides review comments is the failing arrows test. I think it would also be great to fix the test infra so that it keeps running instead of crashing on the first failing test like this one. It's quite confusing otherwise when it doesn't make it through the whole test run.

@cpojer cpojer force-pushed the hard-line-break-object-expressions branch 2 times, most recently from 5fee185 to e2d4d9c Compare April 21, 2022 16:46
Copy link
Contributor

@MichaReiser MichaReiser left a comment

Choose a reason for hiding this comment

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

Thanks, @cpojer for updating your PR and sorry for the merge conflicts.

The code looks good to me but this PR now goes further than what prettier does. Prettier only preserves line breaks for object literals and object type literals, but not for imports/exports/assertions/etc...

I would recommend keeping compatibility with Prettier for now.

@cpojer cpojer force-pushed the hard-line-break-object-expressions branch from e2d4d9c to 861a683 Compare April 22, 2022 06:05
@cpojer
Copy link
Contributor Author

cpojer commented Apr 22, 2022

Oh that's unfortunate. Thank you so much for double checking. Subjectively, what I had is better for consistency but objectively it's better to align with prettier. I updated the PR and reverted the changes.

@MichaReiser
Copy link
Contributor

Oh that's unfortunate. Thank you so much for double checking. Subjectively, what I had is better for consistency but objectively it's better to align with prettier. I updated the PR and reverted the changes.

Thanks. Would you mind having a look at the lint and test failures. We should then be good to merge

@cpojer cpojer force-pushed the hard-line-break-object-expressions branch from 861a683 to 286c2b9 Compare April 22, 2022 06:10
@cpojer
Copy link
Contributor Author

cpojer commented Apr 22, 2022

Done (I think!).

@ematipico
Copy link
Contributor

Tests still failing 😔

It seems there are some snapshots that break when reformatting... And few needs to be updated again.

This happens because if a change affects prettier test suite AND our internal suite, we need to run and update the snapshots twice.

Not sure why this happens but for now that's how it is (sorry)

@cpojer cpojer changed the title feat(rome_js_formatter): Initial version of hard line breaks in object expressions. feat(rome_js_formatter): Hard line breaks in object expressions. Apr 22, 2022
@cpojer
Copy link
Contributor Author

cpojer commented Apr 22, 2022

@ematipico how do I update the snapshots? When I run cargo test the only failing test is formatter::js_module::specs::js::module::arrow::arrow_nested like we discussed.

@ematipico
Copy link
Contributor

cargo insta review

Instead if you keep getting a diff error, it means that what it is formatted the first time doesn't match the output of the second pass of the formatter (on the emitted code of the first time)

@cpojer cpojer force-pushed the hard-line-break-object-expressions branch from 1245005 to 3f8eb9c Compare April 22, 2022 11:47
@cpojer cpojer force-pushed the hard-line-break-object-expressions branch from 3f8eb9c to 7a6a006 Compare April 28, 2022 23:08
@cpojer
Copy link
Contributor Author

cpojer commented Apr 28, 2022

@ematipico Rebased and resolved conflicts, now the arrow test is failing with a different issue it looks like.

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.

It seems that the algorithm is not working as intended.

Now the object expressions always break, regardless if they have new lines or not.

I strongly suggest to create a new test case in our test suite, ideally the code that Micha attached to the issue. This will help me to identify the new file and see if it matches the intended behaviour

Comment on lines +21 to +26
let a = {
get foo() {},
};
let b = {
set foo(a) {},
};
Copy link
Contributor

Choose a reason for hiding this comment

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

That file is the source code, you should check the file that finishes with .prettier-snap.

Check their playground

@cpojer
Copy link
Contributor Author

cpojer commented Apr 29, 2022

As explained on Discord, this is very confusing but the code here is correct except for the example where it breaks the outer parameters list in a function call into newlines which prettier doesn't do.

The input code for all of these examples has those newlines in place, so the snapshots are being updated from not printing those newlines to printing them. When you look at the snapshot changes, it looks like things are broken, but when you compare the source with the new snapshot versions you'll see that it is correct. I got confused about the exact same thing before I wrapped my head around that.

@cpojer cpojer force-pushed the hard-line-break-object-expressions branch 2 times, most recently from 4ee69b5 to 0539c50 Compare May 3, 2022 01:18
@ematipico ematipico added the PR: on hold A PR that needs some upstream work before getting merged. label May 3, 2022
@ematipico
Copy link
Contributor

ematipico commented May 3, 2022

The reason why this PR keeps failing is because we don't yet format call arguments as prettier does.

Prettier uses #2529 (conditional groups) and an extensive heuristic to how to print them. The work in tracked in #2421 , so I guess we should put this PR on hold until we have everything we need in place.

@cpojer cpojer force-pushed the hard-line-break-object-expressions branch 2 times, most recently from ea7b3cc to 0cfc66c Compare May 7, 2022 01:10
MichaReiser added a commit that referenced this pull request May 13, 2022
This PR simplifies the member printing logic to make sure its output is stable.
This results in a small regression

```
Before:
**File Based Average Prettier Similarity**: 69.29%
**Line Based Average Prettier Similarity**: 64.28%

After:
**File Based Average Prettier Similarity**: 69.13%
**Line Based Average Prettier Similarity**: 64.06%
```

But unblocks my work on refactoring the printer behaviour (which seems to trigger this now a lot), and #2458
MichaReiser added a commit that referenced this pull request May 13, 2022
This PR simplifies the member printing logic to make sure its output is stable.
This results in a small regression

```
Before:
**File Based Average Prettier Similarity**: 69.29%
**Line Based Average Prettier Similarity**: 64.28%

After:
**File Based Average Prettier Similarity**: 69.13%
**Line Based Average Prettier Similarity**: 64.06%
```

But unblocks my work on refactoring the printer behaviour (which seems to trigger this now a lot), and #2458
MichaReiser added a commit that referenced this pull request May 13, 2022
This PR simplifies the member printing logic to make sure its output is stable.
This results in a small regression

```
Before:
**File Based Average Prettier Similarity**: 69.29%
**Line Based Average Prettier Similarity**: 64.28%

After:
**File Based Average Prettier Similarity**: 69.13%
**Line Based Average Prettier Similarity**: 64.06%
```

But unblocks my work on refactoring the printer behaviour (which seems to trigger this now a lot), and #2458
@cpojer cpojer force-pushed the hard-line-break-object-expressions branch from 0cfc66c to 3d50ac9 Compare May 22, 2022 12:05
@cpojer cpojer removed the PR: on hold A PR that needs some upstream work before getting merged. label May 22, 2022
@cpojer
Copy link
Contributor Author

cpojer commented May 22, 2022

@MichaReiser I redid this PR with the new formatting infra, please check out whether this matches expectations!

@cpojer
Copy link
Contributor Author

cpojer commented May 22, 2022

Not sure if I did it right (env REPORT_PRETTIER=1 cargo test -p rome_js_formatter --test prettier_tests), but here is the diff compared to main:

File Based Average Prettier Similarity: 69.38% → 70.89%
Line Based Average Prettier Similarity: 64.39% → 65.39%

Copy link
Contributor

@MichaReiser MichaReiser left a comment

Choose a reason for hiding this comment

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

This is such an awesome contribution. Thank you @cpojer .

@MichaReiser MichaReiser merged commit e044cfc into rome:main May 23, 2022
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.

Object literal & type line break after {
4 participants