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

refactor(rome_js_formatter): Support FormatRules with options #2757

Merged
merged 2 commits into from
Jun 22, 2022

Conversation

MichaReiser
Copy link
Contributor

@MichaReiser MichaReiser commented Jun 21, 2022

Summary

To this point, it hasn't been possible to customize the formatting of a FormatRule. The only way to pass additional data has been by creating an additional method on the struct that takes additional parameters and call that method instead. Doing so comes with the risk that this will accidentally by-pass the node's suppression check.

This PR adds a way to FormatRule s to be parametrized by options which can be useful to e.g. pass a group id from one rule to another. I did so by

  • Changing the fmt signature of FormatRule to take self by reference (so that the rule is able to access the options)
  • Add a new FormatRuleWithOptions trait that rules with options can implement and that FormatOwnedWithRule and FormatRefWithRule automatically implement if the inner rule supports options. This makes it possible to do write elements.format().with_options(options) rather than using format_with(|f| MyFormatRule::new(options).fmt(elements, f))
  • Refactor FormatNodeRule to a trait so that each rule has its own struct that can have additional options

Alternatives

An alternative to the design I implemented in this PR is to extend FormatContext or FormatState to store additional data, either as a bag of values or using static values. Storing the data on the context would have the added benefit that it becomes possible to pass data arbitrarily deep but comes at the cost that the data needs to be part of the snapshot and tracking where the data gets written and read becomes more difficult.

The main reasons why I didn't go for that approach is that it isn't needed today and that the data is part of the state and needs to support snapshots, as well as nested calls. Supporting snapshots can be as straightforward as copying the data (comes at a cost) and recurring can be supported in a similar way as we support it in the parser by storing the old value on the stack and putting it back into place if the inner formatting exits.

Test Plan

  • cargo test
  • FormatJsArrayElementList now uses the new options infrastructure.
  • Deleted a union, list, separated list, and regular node formatting file and verified that the code gen re-generates the files with valid code

@MichaReiser MichaReiser self-assigned this Jun 21, 2022
@MichaReiser MichaReiser temporarily deployed to aws June 21, 2022 11:13 Inactive
@cloudflare-pages
Copy link

cloudflare-pages bot commented Jun 21, 2022

Deploying with  Cloudflare Pages  Cloudflare Pages

Latest commit: 18c8f7c
Status: ✅  Deploy successful!
Preview URL: https://7051f04f.tools-8rn.pages.dev
Branch Preview URL: https://feat-format-rule-options.tools-8rn.pages.dev

View logs

impl FormatRule<JsArrayElementList> for FormatJsArrayElementList {
type Context = JsFormatContext;
#[derive(Debug, Clone, Default)]
pub struct FormatJsArrayElementList {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Format rule with custom options

let elements = format_with(|f| {
FormatJsArrayElementList::format_with_group_id(&elements, f, Some(group_id))
});
let elements = elements.format().with_options(Some(group_id));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Usage of the new API

@MichaReiser MichaReiser temporarily deployed to aws June 21, 2022 11:20 Inactive
@github-actions
Copy link

github-actions bot commented Jun 21, 2022

@MichaReiser
Copy link
Contributor Author

!bench_formatter

@github-actions
Copy link

Formatter Benchmark Results

group                                    main                                   pr
-----                                    ----                                   --
formatter/checker.ts                     1.00    375.7±2.66ms     6.9 MB/sec    1.04    390.6±1.49ms     6.7 MB/sec
formatter/compiler.js                    1.00    233.8±1.18ms     4.5 MB/sec    1.03    240.7±1.81ms     4.4 MB/sec
formatter/d3.min.js                      1.00    187.7±0.82ms  1430.3 KB/sec    1.03    193.9±1.15ms  1384.5 KB/sec
formatter/dojo.js                        1.00     13.1±0.06ms     5.2 MB/sec    1.02     13.4±0.15ms     5.1 MB/sec
formatter/ios.d.ts                       1.00    240.4±1.47ms     7.8 MB/sec    1.05    252.3±1.13ms     7.4 MB/sec
formatter/jquery.min.js                  1.00     50.7±0.17ms  1669.2 KB/sec    1.02     51.6±0.17ms  1640.5 KB/sec
formatter/math.js                        1.00    363.6±2.40ms  1823.8 KB/sec    1.03    373.9±2.56ms  1773.4 KB/sec
formatter/parser.ts                      1.00      8.0±0.02ms     6.0 MB/sec    1.03      8.3±0.02ms     5.8 MB/sec
formatter/pixi.min.js                    1.00    203.8±1.34ms     2.2 MB/sec    1.04    211.6±0.98ms     2.1 MB/sec
formatter/react-dom.production.min.js    1.00     62.2±0.20ms  1895.3 KB/sec    1.02     63.6±0.21ms  1853.3 KB/sec
formatter/react.production.min.js        1.00      3.0±0.01ms     2.1 MB/sec    1.02      3.0±0.01ms     2.0 MB/sec
formatter/router.ts                      1.00      6.1±0.03ms     9.7 MB/sec    1.03      6.3±0.03ms     9.4 MB/sec
formatter/tex-chtml-full.js              1.00    467.6±1.20ms  1995.7 KB/sec    1.03    483.7±2.44ms  1929.3 KB/sec
formatter/three.min.js                   1.00    237.3±0.59ms     2.5 MB/sec    1.05    249.0±3.71ms     2.4 MB/sec
formatter/typescript.js                  1.00   1513.5±7.92ms     6.3 MB/sec    1.03   1556.1±7.23ms     6.1 MB/sec
formatter/vue.global.prod.js             1.00     79.9±0.23ms  1543.4 KB/sec    1.02     81.6±0.28ms  1511.3 KB/sec

@MichaReiser
Copy link
Contributor Author

Formatter Benchmark Results

group                                    main                                   pr
-----                                    ----                                   --
formatter/checker.ts                     1.00    375.7±2.66ms     6.9 MB/sec    1.04    390.6±1.49ms     6.7 MB/sec
formatter/compiler.js                    1.00    233.8±1.18ms     4.5 MB/sec    1.03    240.7±1.81ms     4.4 MB/sec
formatter/d3.min.js                      1.00    187.7±0.82ms  1430.3 KB/sec    1.03    193.9±1.15ms  1384.5 KB/sec
formatter/dojo.js                        1.00     13.1±0.06ms     5.2 MB/sec    1.02     13.4±0.15ms     5.1 MB/sec
formatter/ios.d.ts                       1.00    240.4±1.47ms     7.8 MB/sec    1.05    252.3±1.13ms     7.4 MB/sec
formatter/jquery.min.js                  1.00     50.7±0.17ms  1669.2 KB/sec    1.02     51.6±0.17ms  1640.5 KB/sec
formatter/math.js                        1.00    363.6±2.40ms  1823.8 KB/sec    1.03    373.9±2.56ms  1773.4 KB/sec
formatter/parser.ts                      1.00      8.0±0.02ms     6.0 MB/sec    1.03      8.3±0.02ms     5.8 MB/sec
formatter/pixi.min.js                    1.00    203.8±1.34ms     2.2 MB/sec    1.04    211.6±0.98ms     2.1 MB/sec
formatter/react-dom.production.min.js    1.00     62.2±0.20ms  1895.3 KB/sec    1.02     63.6±0.21ms  1853.3 KB/sec
formatter/react.production.min.js        1.00      3.0±0.01ms     2.1 MB/sec    1.02      3.0±0.01ms     2.0 MB/sec
formatter/router.ts                      1.00      6.1±0.03ms     9.7 MB/sec    1.03      6.3±0.03ms     9.4 MB/sec
formatter/tex-chtml-full.js              1.00    467.6±1.20ms  1995.7 KB/sec    1.03    483.7±2.44ms  1929.3 KB/sec
formatter/three.min.js                   1.00    237.3±0.59ms     2.5 MB/sec    1.05    249.0±3.71ms     2.4 MB/sec
formatter/typescript.js                  1.00   1513.5±7.92ms     6.3 MB/sec    1.03   1556.1±7.23ms     6.1 MB/sec
formatter/vue.global.prod.js             1.00     79.9±0.23ms  1543.4 KB/sec    1.02     81.6±0.28ms  1511.3 KB/sec

This is interesting. I also noticed that the rome_js_formatter crate increases by roughly 10Kb to 465KB. The only causes that I can think of are:

  • FormatNodeRule is now a trait. But that shouldn't matter much because the compiler unrolled the generic node parameter before.
  • FormatRule::fmt now takes self as a parameter but I would have expected that this doesn't make a difference for the majority of FormatRules because they are all zero sized types.

@leops @xunilrj do you have any idea where this regression might be coming from?

@MichaReiser MichaReiser temporarily deployed to aws June 21, 2022 16:11 Inactive
@MichaReiser
Copy link
Contributor Author

!bench_formatter

@github-actions
Copy link

Formatter Benchmark Results

group                                    main                                   pr
-----                                    ----                                   --
formatter/checker.ts                     1.01    424.3±7.36ms     6.1 MB/sec    1.00    421.7±4.48ms     6.2 MB/sec
formatter/compiler.js                    1.00    260.4±3.36ms     4.0 MB/sec    1.00    259.4±2.22ms     4.0 MB/sec
formatter/d3.min.js                      1.00    209.3±2.42ms  1282.2 KB/sec    1.01    212.2±1.21ms  1264.8 KB/sec
formatter/dojo.js                        1.00     13.9±0.15ms     4.9 MB/sec    1.03     14.3±0.41ms     4.8 MB/sec
formatter/ios.d.ts                       1.00    277.6±2.71ms     6.7 MB/sec    1.01    280.8±3.25ms     6.6 MB/sec
formatter/jquery.min.js                  1.00     56.0±0.97ms  1510.4 KB/sec    1.01     56.8±0.92ms  1490.2 KB/sec
formatter/math.js                        1.00    415.7±3.88ms  1595.0 KB/sec    1.00    414.4±3.61ms  1600.0 KB/sec
formatter/parser.ts                      1.00      9.0±0.11ms     5.4 MB/sec    1.01      9.0±0.05ms     5.3 MB/sec
formatter/pixi.min.js                    1.00    237.9±1.91ms  1889.5 KB/sec    1.00    236.9±1.32ms  1896.9 KB/sec
formatter/react-dom.production.min.js    1.00     66.8±1.27ms  1764.0 KB/sec    1.04     69.7±1.21ms  1690.4 KB/sec
formatter/react.production.min.js        1.00      3.3±0.04ms  1886.0 KB/sec    1.02      3.4±0.02ms  1854.3 KB/sec
formatter/router.ts                      1.00      6.8±0.07ms     8.7 MB/sec    1.01      6.9±0.01ms     8.6 MB/sec
formatter/tex-chtml-full.js              1.01    540.7±4.25ms  1725.8 KB/sec    1.00    535.1±8.81ms  1743.9 KB/sec
formatter/three.min.js                   1.00    275.0±2.58ms     2.1 MB/sec    1.00    275.1±1.59ms     2.1 MB/sec
formatter/typescript.js                  1.01  1722.2±16.88ms     5.5 MB/sec    1.00  1701.3±20.29ms     5.6 MB/sec
formatter/vue.global.prod.js             1.00     88.1±1.64ms  1400.2 KB/sec    1.02     90.1±0.87ms  1369.0 KB/sec

@ematipico
Copy link
Contributor

ematipico commented Jun 21, 2022

I noticed that you force pushed and the benchmarker is now in a decent shape. What did you change between the two benchs?

@ematipico ematipico added the A-Formatter Area: formatter label Jun 21, 2022
impl FormatNodeFields<TsVoidType> for FormatNodeRule<TsVoidType> {
fn fmt_fields(node: &TsVoidType, f: &mut JsFormatter) -> FormatResult<()> {
#[derive(Debug, Clone, Default)]
pub struct FormatTsVoidType;
Copy link
Contributor

Choose a reason for hiding this comment

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

Should everything be pub? Isn't pub(crate) enough?

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 used pub(crate) first with the same motivation you just mentioned but the problem is that the types are used as associated types in AsFormat and IntoFormat

error[E0446]: crate-private type `FormatJsScript` in public interface
  --> crates/rome_js_formatter/src/generated.rs:12:5
   |
12 | /     type Format = FormatRefWithRule<
13 | |         'a,
14 | |         rome_js_syntax::JsScript,
15 | |         crate::js::auxiliary::script::FormatJsScript,
16 | |     >;
   | |______^ can't leak crate-private type
   |
  ::: crates/rome_js_formatter/src/js/auxiliary/script.rs:9:1
   |
9  |   pub(crate) struct FormatJsScript;
   |   --------------------------------- `FormatJsScript` declared as crate-private

impl FormatNodeFields<JsExportDefaultExpressionClause> for FormatNodeRule<JsExportDefaultExpressionClause> {
fn fmt_fields(node: &JsExportDefaultExpressionClause, f: &mut JsFormatter) -> FormatResult<()> {
#[derive(Debug, Clone, Default)]
pub struct FormatJsExportDefaultExpressionClause;
Copy link
Contributor

Choose a reason for hiding this comment

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

So now the structs are created inside the single file and in the generated.rs we have only the implementation of the traits?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's correct. The definition in the implementation file is necessary so that you can customise the rules fields without the codegen overriding it.

@MichaReiser
Copy link
Contributor Author

I noticed that you force pushed and the benchmarker is now in a decent shape. What did you change between the two benchs?

I added an #[inline] to the FormatRefWithRule and FormatOwnedWithRule constructors.

@MichaReiser MichaReiser merged commit df266dd into main Jun 22, 2022
@MichaReiser MichaReiser deleted the feat/format-rule-options branch June 22, 2022 08:12
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
A-Formatter Area: formatter
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

None yet

3 participants