Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Recover from broken JSX prop #6660

Closed

Conversation

shulhi
Copy link
Member

@shulhi shulhi commented Feb 29, 2024

Fix for #6559

Comment on lines +1030 to +1050
| Equal -> (
Parser.next state;
match state.Parser.token with
(* arrived at k1={ *)
| Lbrace -> (
Parser.next state;
match state.Parser.token with
| Rbrace -> false
| _ ->
goToClosing Rbrace state;
isPossibleAfterRbrace state.Parser.token)
(* arrived at k1=v1 *)
| token when isPossibleAfterEqual token -> (
Parser.next state;
match state.Parser.token with
(* arrived at k1=v1 =v2 *)
| Equal -> false
| _ -> true)
(* arrived at k1=x *)
| _ -> false)
| _ -> false)
Copy link
Member Author

Choose a reason for hiding this comment

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

When looking ahead, there are two possibilities when we encounter a prop,

  1. prop where the value is wrapped with {} like prop={...}.
    • any expression can live inside this {}
  2. prop where the value is specified without {} like prop=true.
    • this can be number literal, string literal, list, array, module, etc

Copy link
Member Author

Choose a reason for hiding this comment

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

I think for the first case, we could just check for { (Lbrace) but for completeness’s sake, it's probably a good idea to be this explicit.

Copy link
Collaborator

Choose a reason for hiding this comment

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

To keep things simple, I would do nothing in the { case, and avoid long look aheads searching for }.
Then keep the changes and additional code to a minimum.

Copy link
Collaborator

Choose a reason for hiding this comment

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

In fact I can't think of a case other than x = y = that one needs to handle.
The situation is one starts typing x = in a jsx context and expects completion.
This currently does not happen if it's before something of the form y = and I don't know if there are other cases to consider in this scenario.

Copy link
Collaborator

Choose a reason for hiding this comment

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

One more case that is not covered is prop punning. If we have just y instead of y=e and the user types x=, then we get x=y with no parse error at all. It's also totally not obvious that the intention is to autocomplete.
In other words, this approach does not work at all with prop pinning.

@zth perhaps one can try with a different approach directly in the vscode extension: in case of x=y if the cursor is after = and y is a valid prop name for the component, then infer the intent to autocomplete x=. This would cover punned as well as non punned cases. Thoughts?

@shulhi
Copy link
Member Author

shulhi commented Feb 29, 2024

@zth marked as draft for now as I'm not 100% sure if this is the best way to check for well formed props (I mean it works but it could be better).

@shulhi shulhi force-pushed the fix/recover-broken-jsx-prop branch from dcad755 to 7d7cf16 Compare February 29, 2024 14:56
if optional then Asttypes.Optional name else Asttypes.Labelled name
in
Some (label, attrExpr))
else
Copy link
Collaborator

Choose a reason for hiding this comment

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

This branch should probably raise an error. Otherwise a hole is added and the parser can continue as if the parsing was successful.

Comment on lines +1030 to +1050
| Equal -> (
Parser.next state;
match state.Parser.token with
(* arrived at k1={ *)
| Lbrace -> (
Parser.next state;
match state.Parser.token with
| Rbrace -> false
| _ ->
goToClosing Rbrace state;
isPossibleAfterRbrace state.Parser.token)
(* arrived at k1=v1 *)
| token when isPossibleAfterEqual token -> (
Parser.next state;
match state.Parser.token with
(* arrived at k1=v1 =v2 *)
| Equal -> false
| _ -> true)
(* arrived at k1=x *)
| _ -> false)
| _ -> false)
Copy link
Collaborator

Choose a reason for hiding this comment

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

In fact I can't think of a case other than x = y = that one needs to handle.
The situation is one starts typing x = in a jsx context and expects completion.
This currently does not happen if it's before something of the form y = and I don't know if there are other cases to consider in this scenario.

Comment on lines +1030 to +1050
| Equal -> (
Parser.next state;
match state.Parser.token with
(* arrived at k1={ *)
| Lbrace -> (
Parser.next state;
match state.Parser.token with
| Rbrace -> false
| _ ->
goToClosing Rbrace state;
isPossibleAfterRbrace state.Parser.token)
(* arrived at k1=v1 *)
| token when isPossibleAfterEqual token -> (
Parser.next state;
match state.Parser.token with
(* arrived at k1=v1 =v2 *)
| Equal -> false
| _ -> true)
(* arrived at k1=x *)
| _ -> false)
| _ -> false)
Copy link
Collaborator

Choose a reason for hiding this comment

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

One more case that is not covered is prop punning. If we have just y instead of y=e and the user types x=, then we get x=y with no parse error at all. It's also totally not obvious that the intention is to autocomplete.
In other words, this approach does not work at all with prop pinning.

@zth perhaps one can try with a different approach directly in the vscode extension: in case of x=y if the cursor is after = and y is a valid prop name for the component, then infer the intent to autocomplete x=. This would cover punned as well as non punned cases. Thoughts?

@zth
Copy link
Collaborator

zth commented Feb 29, 2024

@shulhi, @cristianoc made a good point in the review, that it's probably not possible to make this work with a lookahead because of punning. I went back to the drawing board, and I think I have an acceptable trade off solution that just affects the editor tooling, so we don't have to change the parser at all: rescript-lang/rescript-vscode#935

This was some great work though and much appreciated! But if we can get away with not changing the parser, then that's of course better. Sorry for leading you down that rabbit hole.

@shulhi
Copy link
Member Author

shulhi commented Mar 1, 2024

@shulhi, @cristianoc made a good point in the review, that it's probably not possible to make this work with a lookahead because of punning.

I thought about punning when implementing the look ahead and decided that it was probably okay to accept the case where prop1= prop2 as prop1=prop2. It will still give an error that prop2 can't be found or something, but I get it that it can be misleading.

This was some great work though and much appreciated! But if we can get away with not changing the parser, then that's of course better. Sorry for leading you down that rabbit hole.

No worries, there are still plenty of issues to work on 😄

I'll close the PR in favor of rescript-lang/rescript-vscode#935

@shulhi shulhi closed this Mar 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants