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

fix(rome_formatter): measure first variant of BestFitting in flat mode #2675

Merged
merged 4 commits into from
Jun 8, 2022

Conversation

yassere
Copy link
Contributor

@yassere yassere commented Jun 7, 2022

Summary

This is a minimal fix for the BestFitting element that was broken by #2645. It now attempts to fit the first variant in flat mode on a single line. If that doesn't fit, it continues attempting the remaining variants in PrintMode::Expanded before defaulting to the most_expanded variant. This seems to match the original intention based on the doc comments for the best_fitting macro. @MichaReiser?

I don't know if this will be sufficient for our needs, but I believe that matching the exact behavior of Prettier's conditionalGroup could involve heavy enough changes to our "fits" measurement and printing that it may be worth trying this out first.

Happy to close this, though, if we'd rather go with a different approach.

Test Plan

Removed the ignore from the broken best_fitting doc test.

@yassere yassere temporarily deployed to aws June 7, 2022 18:47 Inactive
@github-actions
Copy link

github-actions bot commented Jun 7, 2022

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 may fix the test bit I don't think it works as expected because any group inside of the second or later variant always get printed in expanded mode (same as for rest elements). My understanding is that the printing of a variant should work the same as any other content outside of the group where the first group changes the print mode to flat to all its enclosing group and the mode only changes back for the groups from the rest queue

@yassere
Copy link
Contributor Author

yassere commented Jun 7, 2022

This may fix the test bit I don't think it works as expected because any group inside of the second or later variant always get printed in expanded mode (same as for rest elements). My understanding is that the printing of a variant should work the same as any other content outside of the group where the first group changes the print mode to flat to all its enclosing group and the mode only changes back for the groups from the rest queue

Ah, yeah that makes sense.

@yassere yassere marked this pull request as draft June 7, 2022 21:12
@yassere yassere temporarily deployed to aws June 8, 2022 01:15 Inactive
@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Jun 8, 2022

Deploying with  Cloudflare Pages  Cloudflare Pages

Latest commit: 5f9b437
Status: ✅  Deploy successful!
Preview URL: https://e6dd693b.tools-8rn.pages.dev
Branch Preview URL: https://fix-best-fitting.tools-8rn.pages.dev

View logs

@yassere
Copy link
Contributor Author

yassere commented Jun 8, 2022

My understanding is that the printing of a variant should work the same as any other content outside of the group

This seems to be the case, unless I'm misunderstanding what you're saying here. I added a test where the best fitting second variant is printed the same as it would be if it were printed directly. Inner groups are still printed flat when possible. Do you have an example where the current implementation doesn't work as intended? Do you think the problem is with how it measures, how it prints, or both?

Also, I'm not sure about this line:

self.state.measured_group_fits = true;

What's the purpose of setting that as true when measuring in expanded mode?

@MichaReiser
Copy link
Contributor

MichaReiser commented Jun 8, 2022

My understanding is that the printing of a variant should work the same as any other content outside of the group

This seems to be the case, unless I'm misunderstanding what you're saying here. I added a test where the best fitting second variant is printed the same as it would be if it were printed directly. Inner groups are still printed flat when possible. Do you have an example where the current implementation doesn't work as intended? Do you think the problem is with how it measures, how it prints, or both?

It could be but let's say we have the following example:

// variant2: 
token("("), group_elements(
  token("["), soft_line_break(), 
  indent(token("abcdef")), 
  soft_line_break(), 
  token("]"))
)

My understanding is that the printer calls fits_on_line with print_mode: expanded for variant 2. The printer then enqueues the group 2 with the print mode it's currently in, the same as for the variant, which is expanded here

FormatElement::Group(group) => queue.enqueue(PrintElementCall::new(&group.content, args)),

Which will ultimately cause the soft_line_breaks to break because the print_mode is Expanded when the printer measures the first soft_line_break.

You can change the second variant of the best_fitting test to

///             format_args!(
///                 token("("),
///                 group_elements(&format_args![
///                     token("["),
///                     soft_block_indent(&token("1, 2, 3")),
///                     token("]"),
///                 ]),
///                 token(")"),
///             ),

This should not fit because the printer should try to print the inner group as flat and not as expanded but the printer breaks at the soft_line_breaks and, therefore claims that this variant fits.

aVeryLongIdentifier([\n\t1, 2, 3\n])

Also, I'm not sure about this line:

self.state.measured_group_fits = true;

What's the purpose of setting that as true when measuring in expanded mode?

The purpose on that line is to cache the fact that everything following now up to the first line break will fit on the line. So that the printer doesn't need to re-measure if the picked group contains an inner group:

// variant2: 
token("("), group_elements(token("["), token("abcdef"), token("]"))

There's no point in re-calculating if the group containing the array fits because the printer already ensured that this is the case when measuring the variant content.

The boolean gets set to false when printing the next line break and any group following then must be re-measured.

@yassere
Copy link
Contributor Author

yassere commented Jun 8, 2022

Which will ultimately cause the soft_line_breaks to break because the print_mode is Expanded when the printer measures the first soft_line_break.

They won't necessarily break during printing if that group fits on one line. The second variant will be selected if it fits in expanded mode, but a group that's enqueued for printing in expanded mode will still be printed flat if it fits.

If we have:

best_fitting!(
    format_args!(token(
        "Something that will not fit on a 30 character print width."
    )),
    format_args![
        token("("),
        group_elements(&format_args![
            token("["),
            soft_line_break(),
            indent(&token("abcdef")),
            soft_line_break(),
            token("]")
        ])
    ],
    format_args!(token("Most"), hard_line_break(), token("Expanded"))
)

Formatting that at width 80 prints the first variant.
Formatting that at width 30 prints the second variant as:

([abcdef]

Formatting that at width 8 prints the second variant as:

([
abcdef
]

Formatting that at width 1 defaults to printing the most expanded variant.

Does that address your concern or am I still misunderstanding?

This should not fit because the printer should try to print the inner group as flat and not as expanded but the printer breaks at the soft_line_breaks and, therefore claims that this variant fits.

That variant would be selected because it fits in expanded mode, but inner groups would still be printed flat if they fit.
If we only want a variant to be selected if it fits entirely in flat mode, we can change that. But it seems less useful to me because if all of the content could fit on a single line, then shouldn't the first variant fit? In real use cases, I assume you won't generally have tokens in the first variant that can be omitted in later variants.

@MichaReiser
Copy link
Contributor

token("("),

That makes sense. I think where this solution differs from what I thought it does is that inner groups "fit" if the content up to the first line break fits (including soft) whereas what prettier does (I think) is that the group only fits if the whole group in flat mode fits.

We can go forward with your solution and see if it suits our needs. Please add some documentation explaining how this behaviour differs from prettiers

@yassere
Copy link
Contributor Author

yassere commented Jun 8, 2022

I think where this solution differs from what I thought it does is that inner groups "fit" if the content up to the first line break fits (including soft) whereas what prettier does (I think) is that the group only fits if the whole group in flat mode fits.

Yeah, that's a good point. In this implementation, there's no way to enforce that a specific group inside a variant must be flat when measuring if that variant fits. If that ends up being important, we can follow up with a new approach. I think we'd need to add something like should_break to our Group to enable more fine-tuned control of the breaking behavior of variants.

@yassere yassere temporarily deployed to aws June 8, 2022 17:18 Inactive
@yassere yassere marked this pull request as ready for review June 8, 2022 17:27
@yassere yassere requested a review from MichaReiser June 8, 2022 17:27
crates/rome_formatter/src/macros.rs Outdated Show resolved Hide resolved
crates/rome_formatter/src/macros.rs Outdated Show resolved Hide resolved
crates/rome_formatter/src/macros.rs Outdated Show resolved Hide resolved
@yassere yassere temporarily deployed to aws June 8, 2022 19:14 Inactive
@yassere yassere requested a review from ematipico June 8, 2022 19:15
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.

Thank you for addressing my nitpicks! Let's 🚢 it!!

@yassere yassere merged commit 8b595ac into main Jun 8, 2022
@yassere yassere deleted the fix/best-fitting branch June 8, 2022 19:34
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