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

feat(rome_js_formatter): call arguments #2711

Merged
merged 16 commits into from
Jun 17, 2022
Merged

feat(rome_js_formatter): call arguments #2711

merged 16 commits into from
Jun 17, 2022

Conversation

ematipico
Copy link
Contributor

@ematipico ematipico commented Jun 14, 2022

Summary

This PR closes #2421 and closes #2439

This PR requires #2685 in order to be merged.

The implementation of the formatting requires the usage of best_fitting, which will impact the performance of formatter, in some way.

The usage of best_fitting also doesn't play well when using dbg_write, which is also understandable.

The implementation is a port of Prettier's algorithm: https://github.dev/prettier/prettier/blob/main/src/language-js/print/call-arguments.js

The implementation doesn't cover ALL the cases, as we don't have a yet a good API/builder for will_break, which means that some cases might not be covered.

The final version of the implementation now surgically caches the node/tokens only when we know they need to be printed multiple times. This was a lesson learned, where it's not good doing an early caching regardless, because this was impacting the performance in visible way.

This PR also fixes #2676, the trailing comma is manually added only in the cases where we know there's a group that might break.

Here's the work that has been done:

  • extracted the logic of trivia formatting out of format_delimited, making it a standalone making it a standalone utility. This was needed because there were case where I needed manually replicate the delimiting logic, by customizing the content. Also, I needed a way to cache the delimiters and print them in different positions based on the condition.
  • added specific cases for handling hooks
  • added specific cases to handle best fitting by following prettier's source code
  • created a new function to print call arguments

Regressions

A known regression is the absence of will_break, which in turns formats anything with comments in the wrong way. Prettier uses willBreak to check if some arguments break, and if so, then the formatting is different. Will break is fundamental because comments (line suffixes to be more precise) mark an element as "yes, it will break".

So a code like this:

runtimeAgent.getProperties(
    objectId,
    false, // ownProperties
    false, // accessorPropertiesOnly
    false, // generatePreview
    (error, properties, internalProperties) => {
      return 1
    },
);

Will print like this:

runtimeAgent.getProperties(objectId, false, false, false, (
	// ownProperties // accessorPropertiesOnly // generatePreview
	error,
	properties,
	internalProperties,
) => {
	return 1;
});

A workaround might be to actually check if each argument has comments, but I am not sure how the outcome is going to be for arguments that have deep nested comments.

Test Plan

I added new test cases, mostly edge cases. The rest of the formatting can be seen inside the prettier's test suite - I believe they speak by themselves.

If you feel the need of more tests, please let me know.

This PR brings us closer to Prettier

main

File Based Average Prettier Similarity: 73.25%
Line Based Average Prettier Similarity: 67.60%

PR:

File Based Average Prettier Similarity: 73.61%
Line Based Average Prettier Similarity: 68.79%

I hoped in an higher number but it's still something :)

@ematipico ematipico temporarily deployed to aws June 14, 2022 20:22 Inactive
@github-actions
Copy link

github-actions bot commented Jun 14, 2022

@github-actions
Copy link

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%

symbols/microsoft

Test result main count This PR count Difference
Total 5946 5946 0
Passed 350 350 0
Failed 5596 5596 0
Panics 0 0 0
Coverage 5.89% 5.89% 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 12393 12393 0
Failed 3864 3864 0
Panics 0 0 0
Coverage 76.23% 76.23% 0.00%

@ematipico
Copy link
Contributor Author

!bench_formatter

@cloudflare-pages
Copy link

cloudflare-pages bot commented Jun 14, 2022

Deploying with  Cloudflare Pages  Cloudflare Pages

Latest commit: 15bcc1d
Status: ✅  Deploy successful!
Preview URL: https://5dde7b29.tools-8rn.pages.dev
Branch Preview URL: https://feat-call-arguments.tools-8rn.pages.dev

View logs

@github-actions
Copy link

Formatter Benchmark Results

group                                    main                                   pr
-----                                    ----                                   --
formatter/checker.ts                     1.00    292.7±3.34ms     8.9 MB/sec    1.20    352.5±4.70ms     7.4 MB/sec
formatter/compiler.js                    1.00    185.8±1.60ms     5.6 MB/sec    1.13    209.3±3.01ms     5.0 MB/sec
formatter/d3.min.js                      1.00    139.5±2.38ms  1923.5 KB/sec    1.12    156.6±1.96ms  1713.8 KB/sec
formatter/dojo.js                        1.00      9.4±0.04ms     7.3 MB/sec    1.17     10.9±0.13ms     6.3 MB/sec
formatter/ios.d.ts                       1.00    207.1±1.61ms     9.0 MB/sec    1.01    208.8±1.16ms     8.9 MB/sec
formatter/jquery.min.js                  1.00     41.5±0.96ms  2039.6 KB/sec    1.08     45.0±0.57ms  1882.5 KB/sec
formatter/math.js                        1.00    305.4±2.11ms     2.1 MB/sec    1.11    339.6±2.46ms  1952.3 KB/sec
formatter/parser.ts                      1.00      6.3±0.04ms     7.7 MB/sec    1.17      7.3±0.16ms     6.6 MB/sec
formatter/pixi.min.js                    1.00    158.8±0.90ms     2.8 MB/sec    1.12    177.1±1.24ms     2.5 MB/sec
formatter/react-dom.production.min.js    1.00     46.7±0.79ms     2.5 MB/sec    1.15     53.8±1.44ms     2.1 MB/sec
formatter/react.production.min.js        1.00      2.2±0.03ms     2.8 MB/sec    1.10      2.4±0.02ms     2.5 MB/sec
formatter/router.ts                      1.00      4.8±0.02ms    12.6 MB/sec    1.12      5.4±0.04ms    11.3 MB/sec
formatter/tex-chtml-full.js              1.00    409.3±2.29ms     2.2 MB/sec    1.03    420.9±4.04ms     2.2 MB/sec
formatter/three.min.js                   1.00    187.5±1.44ms     3.1 MB/sec    1.10    207.0±5.64ms     2.8 MB/sec
formatter/typescript.js                  1.00   1142.2±5.54ms     8.3 MB/sec    1.17   1340.8±7.08ms     7.1 MB/sec
formatter/vue.global.prod.js             1.00     62.2±1.42ms  1982.7 KB/sec    1.17     72.8±1.57ms  1694.4 KB/sec

crates/rome_js_formatter/src/utils/mod.rs Outdated Show resolved Hide resolved
crates/rome_js_formatter/src/utils/mod.rs Outdated Show resolved Hide resolved
@@ -24,7 +25,7 @@ expect(bifornCringerMoshedPerplexSawder.getLongArrayOfNumbers()).toEqual(
```js
expect(bifornCringerMoshedPerplexSawder.getArrayOfNumbers()).toEqual([
1, 2, 3, 4, 5,
],);
Copy link
Contributor

Choose a reason for hiding this comment

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

🥳

},
"letters:",
);
["a", "b", "c"].reduce(function (item, thing) {
Copy link
Contributor

Choose a reason for hiding this comment

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

So much better :)

@ematipico ematipico force-pushed the feat/call-arguments branch 3 times, most recently from 80a7be4 to ac776d2 Compare June 15, 2022 18:05
@ematipico ematipico temporarily deployed to aws June 15, 2022 18:05 Inactive
@ematipico ematipico temporarily deployed to aws June 15, 2022 18:40 Inactive
@ematipico ematipico marked this pull request as ready for review June 15, 2022 18:40
@ematipico
Copy link
Contributor Author

!bench_formatter

@github-actions
Copy link

Formatter Benchmark Results

group                                    main                                   pr
-----                                    ----                                   --
formatter/checker.ts                     1.00    377.0±4.36ms     6.9 MB/sec    1.03    387.0±7.64ms     6.7 MB/sec
formatter/compiler.js                    1.04    249.6±2.07ms     4.2 MB/sec    1.00    239.2±4.39ms     4.4 MB/sec
formatter/d3.min.js                      1.01    184.4±3.10ms  1455.9 KB/sec    1.00    183.1±1.85ms  1465.6 KB/sec
formatter/dojo.js                        1.00     12.8±0.19ms     5.3 MB/sec    1.03     13.2±0.27ms     5.2 MB/sec
formatter/ios.d.ts                       1.01    274.6±3.51ms     6.8 MB/sec    1.00    271.7±6.13ms     6.9 MB/sec
formatter/jquery.min.js                  1.08     56.5±0.83ms  1497.5 KB/sec    1.00     52.5±0.88ms  1613.0 KB/sec
formatter/math.js                        1.05    396.6±4.56ms  1672.1 KB/sec    1.00    376.4±5.33ms  1761.7 KB/sec
formatter/parser.ts                      1.01      8.6±0.09ms     5.6 MB/sec    1.00      8.5±0.27ms     5.7 MB/sec
formatter/pixi.min.js                    1.02    209.0±3.25ms     2.1 MB/sec    1.00    204.4±4.62ms     2.1 MB/sec
formatter/react-dom.production.min.js    1.04     64.3±0.93ms  1833.0 KB/sec    1.00     62.0±1.34ms  1901.2 KB/sec
formatter/react.production.min.js        1.02      3.0±0.04ms     2.0 MB/sec    1.00      3.0±0.07ms     2.1 MB/sec
formatter/router.ts                      1.00      6.6±0.08ms     9.3 MB/sec    1.03      6.8±0.16ms     9.0 MB/sec
formatter/tex-chtml-full.js              1.08    535.1±6.11ms  1743.8 KB/sec    1.00    494.8±4.72ms  1886.0 KB/sec
formatter/three.min.js                   1.04    245.2±2.09ms     2.4 MB/sec    1.00    235.4±4.96ms     2.5 MB/sec
formatter/typescript.js                  1.00  1482.5±23.02ms     6.4 MB/sec    1.01  1497.4±27.99ms     6.3 MB/sec
formatter/vue.global.prod.js             1.01     82.2±0.99ms  1500.7 KB/sec    1.00     81.6±1.76ms  1512.3 KB/sec

Comment on lines 55 to 62
format_with(|f| {
// we don't want to print the trailing separator, so if it's present, we replace it
// with an empty element
if let Some(separator) = second_argument.trailing_separator()? {
return write!(f, [format_removed(separator)]);
}

if token_has_comments(&l_paren_token?) || token_has_comments(&r_paren_token?) {
return Ok(false);
Ok(())
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: I personally prefer to split the write into multiple blocks if I have conditional logic:

write!(f, [l_paren_token.format(), ..., second_argument.node().format()])?;

if let Some(separator) = second_argument.trailing_separator()? {
    write!(f, [format_removed(separator)])?;
}

return write!(f, [r_paren_token.format()]);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's an habit coming from the old API I suppose :)

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 great work @ematipico ! I've a few smaller code suggestions but that's it.

My main question now is how we go about to land this since this PR depends on my PR which isn't handling split_trivia correctly yet.

crates/rome_js_formatter/src/utils/mod.rs Outdated Show resolved Hide resolved
crates/rome_js_formatter/src/utils/mod.rs Outdated Show resolved Hide resolved
crates/rome_js_syntax/src/parameters_ext.rs Outdated Show resolved Hide resolved
crates/rome_rowan/src/syntax/node.rs Outdated Show resolved Hide resolved
/// Whether the node contains trailing comments.
pub fn has_trailing_comments(&self) -> bool {
self.last_token()
.map_or(false, |tok| tok.has_trailing_comments())
}

/// Whether the last token of a node has comments (leading or trailing)
pub fn last_token_has_comments(&self) -> bool {
Copy link
Contributor

Choose a reason for hiding this comment

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

Unrelated to this line: You brought up the issue with comments on discord the other day. Do these helpers address the issue you raised on discord?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, this was the solution I was looking for. It might be an edge case or not, but for now I decided to applying it only to this particular case.

@MichaReiser
Copy link
Contributor

MichaReiser commented Jun 16, 2022

Compatibility:
Before
File Based Average Prettier Similarity: 73.25%
Line Based Average Prettier Similarity: 67.60%

After
File Based Average Prettier Similarity: 73.61%
Line Based Average Prettier Similarity: 68.79%

File Based Average Prettier Similarity: 73.76%
Line Based Average Prettier Similarity: 68.75%

Notable remaining differences: Line break before / after =>

js/arrow-call/arrow_call.js

 it("mocks regexp instances", () => {
-  expect(() =>
-    moduleMocker.generateFromMetadata(moduleMocker.getMetadata(/a/)),
+  expect(
+    () => moduleMocker.generateFromMetadata(moduleMocker.getMetadata(/a/)),
   ).not.toThrow();
 });

object arguments

-deepCopyAndAsyncMapLeavesA(
-  { source: sourceValue, destination: destination[sourceKey] },
-  { valueMapper, overwriteExistingKeys },
-);
+deepCopyAndAsyncMapLeavesA({
+  source: sourceValue,
+  destination: destination[sourceKey],
+}, { valueMapper, overwriteExistingKeys });

Hook formatting

js/break-calls/react.js

+  const { firstName, lastName } = useMemo((
+    aaa,
+    bbb,
+    ccc,
+    ddd,
+    eee,
+    fff,
+    ggg,
+    hhh,
+    iii,
+    jjj,
+    kkk,
+  ) => func(aaa, bbb, ccc, ddd, eee, fff, ggg, hhh, iii, jjj, kkk), [
+    foo,
+    bar,
+    baz,
+  ]);
 }
``


# js/comments/arrow.js

also js/comments/function-declaration.js and js/empty-paren-comment/empty_paren_comment.js

Comments formatting (Let's see if my latest PR's help with this one)
-const fn = (/*event, data*/) => doSomething();
+const fn = (/*event, data*/ ) => doSomething();
 
-const fn2 = (/*event, data*/) => doSomething(anything);
+const fn2 = (/*event, data*/ ) => doSomething(anything);

I don't mind if we fix some of them later but I think we need to have a closer look why the spacing of comments is now broken.

@ematipico ematipico temporarily deployed to aws June 16, 2022 14:06 Inactive
@ematipico ematipico temporarily deployed to aws June 16, 2022 14:15 Inactive
@ematipico ematipico temporarily deployed to aws June 16, 2022 16:42 Inactive
@ematipico ematipico temporarily deployed to aws June 17, 2022 12:55 Inactive
@ematipico ematipico merged commit 3504a1e into main Jun 17, 2022
@ematipico ematipico deleted the feat/call-arguments branch June 17, 2022 13:07
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.

🐛 Formatter adds incorrect unnecessary , Destructuring arguments Call expression arguments formatting
2 participants