Skip to content
This repository was archived by the owner on Jun 15, 2023. It is now read-only.

Conversation

@nkrkv
Copy link
Contributor

@nkrkv nkrkv commented Jun 22, 2022

Closes #570

This is my first lines of code in OCaml, so I’m not sure I’m doing it right 😺

@cknitt
Copy link
Member

cknitt commented Jun 23, 2022

This is awesome! Thanks a lot!

Just wondering about performance when computing hasNestedJsx - I think we could avoid the list creation done by ParsetreeViewer.collectListExpressions here.

/cc @IwanKaramazow

@cristianoc
Copy link
Contributor

This is awesome! Thanks a lot!

Just wondering about performance when computing hasNestedJsx - I think we could avoid the list creation done by ParsetreeViewer.collectListExpressions here.

/cc @IwanKaramazow

Indeed.
Bonus point: do it with without allocating.
For example, creating a list and walking a list is allocating.
Technically, even creating a single closure to pass to List.exists would be allocating, but that's a very small allocation.

@nkrkv
Copy link
Contributor Author

nkrkv commented Jun 23, 2022

I think we could avoid the list creation done by ParsetreeViewer.collectListExpressions here.
Bonus point: do it with without allocating.

Thank you gentlemen. Would you point me a little bit, how can I traverse expression children without creating a list? Just a function name, a very rough idea, or a hint is enough. Otherwise I can go wrong direction again :)

@cknitt
Copy link
Member

cknitt commented Jun 23, 2022

I'd say iterate/recurse like ParsetreeViewer.collectListExpressions does, but do not build a list, instead directly check each item if its a JSX expression (ParsetreeViewer.isJsxExpression). Return true if a JSX expression was found, otherwise continue recursion.

@cristianoc
Copy link
Contributor

I'd say iterate/recurse like ParsetreeViewer.collectListExpressions does, but do not build a list, instead directly check each item if its a JSX expression (ParsetreeViewer.isJsxExpression). Return true if a JSX expression was found, otherwise continue recursion.

So create a little helper function which is a recursive traversal.
Then call that function.
This will also take care of the little code duplication in the current PR.

@nkrkv
Copy link
Contributor Author

nkrkv commented Jun 23, 2022

Thanks for pointing the direction. I think I’ve done it, not sure though :) Would you look at the newest commit?

@nkrkv nkrkv force-pushed the put-each-jsx-tag-on-own-line branch from 56cbf9d to af9fa3b Compare June 23, 2022 19:57
Copy link
Contributor

@cristianoc cristianoc left a comment

Choose a reason for hiding this comment

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

Looks great.
Some tiny style comments.

| Pexp_construct ({txt = Longident.Lident "[]"}, _) -> false
| Pexp_construct
( {txt = Longident.Lident "::"},
Some {pexp_desc = Pexp_tuple (hd :: [tail])} ) ->
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: this is normally written [hd, tail].

Copy link
Contributor Author

@nkrkv nkrkv Jun 24, 2022

Choose a reason for hiding this comment

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

Hmmm, this gives me:

File "src/res_printer.ml", line 106, characters 39-44:
106 |         Some {pexp_desc = Pexp_tuple ([hd, _])} ) ->
                                             ^^^^^
Error: This pattern matches values of type 'a * 'b
       but a pattern was expected which matches values of type
         Parsetree.expression

My OCaml knowledge is not enough to understand why is it different. Perhaps, type inference fails somehow in the [hd, tail] case. Anyway, I’ve stolen this pattern from ParsetreeViewer.collectListExpressions.

Copy link
Contributor

@cristianoc cristianoc Jun 24, 2022

Choose a reason for hiding this comment

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

Sorry [hd; tail] not [hd, tail].

Copy link
Contributor

Choose a reason for hiding this comment

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

As a final step, before and after: make bench ... If you are on macos, that is

I’m on Linux and have no access to a macOS machine, unfortunately. Would someone help with running bench?

Will do once the PR is final.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry [hd; tail] not [hd, tail]

Muscle memory :) Fixed it.

| Pexp_construct
( {txt = Longident.Lident "::"},
Some {pexp_desc = Pexp_tuple (hd :: [tail])} ) ->
hasNestedJsx hd || hasNestedJsx tail
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this be ParsetreeViewer.isJsxExpression hd ? and the next line do nothing? (and just return false)

Copy link
Member

Choose a reason for hiding this comment

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

Yes. Otherwise it behaves differently than before this commit (see the spread change in this commit which I think we do not want).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, now I understand what was the reason for spread behavior change. Fixed it, and the function has became even more simple and even non-rec. Thanks.

@cristianoc
Copy link
Contributor

This is looking great.
As a final step, before and after: make bench. See what it says about allocation.

@cristianoc
Copy link
Contributor

If you are on macos, that is. Does not work on other architectures at the moment.

@nkrkv
Copy link
Contributor Author

nkrkv commented Jun 24, 2022

As a final step, before and after: make bench ... If you are on macos, that is

I’m on Linux and have no access to a macOS machine, unfortunately. Would someone help with running bench?

@cknitt
Copy link
Member

cknitt commented Jun 24, 2022

Hmm, and what do we actually do if we have a lot of children, but none is a JSX expression?

Like

<A> {a} {b} {c} {d} {e} </A>

Maybe the logic would need to be something like: expand if a child is a JSX expression or if there is more than 1 child?

What do you think @cristianoc?

@cristianoc
Copy link
Contributor

Hmm, and what do we actually do if we have a lot of children, but none is a JSX expression?

Like

<A> {a} {b} {c} {d} {e} </A>

Maybe the logic would need to be something like: expand if a child is a JSX expression or if there is more than 1 child?

What do you think @cristianoc?

What does prettier do?

@nkrkv
Copy link
Contributor Author

nkrkv commented Jun 24, 2022

What does prettier do?

<A>{a} {b} {c} {d}</A>;

👇

<A>
  {a} {b} {c} {d}
</A>;

Do we want the same? It would made the algorithm slightly more complex (line separator between children and after/before tags differs) but doable nevertheless.

@cknitt
Copy link
Member

cknitt commented Jun 24, 2022

I tried

<A>{a}{b}{c}{d}</A>;

in one of our JS projects using prettier and got

<A>
  {a}
  {b}
  {c}
  {d}
</A>;

Maybe our prettier config is different than yours.

@cknitt
Copy link
Member

cknitt commented Jun 24, 2022

And

<A>{a}</A>;

stays unmodified for me.

@nkrkv
Copy link
Contributor Author

nkrkv commented Jun 24, 2022

It is from their playground with default settings

Replicating the behavior with your prettier config should be easy to implement

@nkrkv
Copy link
Contributor Author

nkrkv commented Jun 24, 2022

Done. Now the behavior aligns with Prettier

<div className="meter text-4xl font-bold text-gray-600 mt-4">
{values[0]->Js.Int.toString->string} {meterSuffix->Belt.Option.getWithDefault(null)}
{values[0]->Js.Int.toString->string}
{meterSuffix->Belt.Option.getWithDefault(null)}
Copy link
Member

Choose a reason for hiding this comment

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

Definitely an improvement in this example!

@cknitt
Copy link
Member

cknitt commented Jun 24, 2022

I think it can be done in one match without the "isConent":

let hasNestedJsxOrMoreThanOneChild expr =
  let rec loop inRecursion expr =
    match expr.Parsetree.pexp_desc with
    | Pexp_construct
        ({txt = Longident.Lident "::"}, Some {pexp_desc = Pexp_tuple [hd; tail]})
      ->
      if inRecursion || ParsetreeViewer.isJsxExpression hd then true
      else loop true tail
    | _ -> false
  in
  loop false expr

@nkrkv
Copy link
Contributor Author

nkrkv commented Jun 24, 2022

Yes, thanks. This is definitely more clear and I like the name improvement.

Copy link
Member

@cknitt cknitt left a comment

Choose a reason for hiding this comment

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

Thanks! 👍

@cristianoc cristianoc merged commit 2074447 into rescript-lang:master Jun 24, 2022
@nkrkv nkrkv deleted the put-each-jsx-tag-on-own-line branch June 24, 2022 16:12
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.

Put each JSX tag on its own line

3 participants