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

feat(rome_js_parser): support property init in ambient context #4219

Closed
wants to merge 1 commit into from
Closed

feat(rome_js_parser): support property init in ambient context #4219

wants to merge 1 commit into from

Conversation

Conaclos
Copy link
Contributor

Summary

Fixes #2958

Note: It is the first time I modify the parser and formatter.

Test Plan

Parser and formatter tests included.

Documentation

No change

  • The PR requires documentation
  • I will create a new PR to update the documentation

@netlify
Copy link

netlify bot commented Feb 17, 2023

Deploy Preview for docs-rometools canceled.

Built without sensitive environment variables

Name Link
🔨 Latest commit 40f982f
🔍 Latest deploy log https://app.netlify.com/sites/docs-rometools/deploys/63f3631169626d0008be7075

@Conaclos
Copy link
Contributor Author

@nissy-dev If you have some time to review my PR this could be great :)

@Conaclos
Copy link
Contributor Author

Note: I missed the following draft : #3000 and discussion. I will take a look when I got some time.

@denbezrukov
Copy link
Contributor

I've tried to dig around in previous discussions.
I'm leaning towards @MichaReiser proposal.
What do you think?

We can also add an error test case:

declare class T {
  readonly b: string = '213'
}

class T {
  declare readonly b: string = '213'
}

@Conaclos
Copy link
Contributor Author

Conaclos commented Feb 18, 2023

I'm leaning towards @MichaReiser #3003 (comment).
What do you think?

I'm currently leaning towards introducing its own member type TsInitializedPropertySignatureClassMember (open to better names) that is defined as

TsInitializedPropertySignatureClassMember = 
  modifiers: TsPropertySignatureModifierList
  name: JsAnyClassMemberName
  '?'?
  initializer: JsInitializerClause
  ';'?

I have a preference for adding an optional initializer clause to TsPropertySignatureClassMember (as proposed here and in the previous PR) because this avoids having a new node type.
Furthermore, we just need to exclude class property signature that use both an initializer and a type annotation.
This is really easy to verify in the parse phase.

I'm undecided if we want a specific initializer only allowing JsStringLiteralExpression and JsNumberLiteralExpressions.

Unfortunately it is not possible to restrict ambient property initializer to literal values. Indeed, an enum value can also be used. Thus, this need a semantic model for checking this.

We can also add an error test case:

declare class T {
 readonly b: string = '213'
}

class T {
 declare readonly b: string = '213'
}

+1

@nissy-dev
Copy link
Contributor

I read some discussion and I think it is better to avoid the new type node and your implementation decision is almost good for me! The following comments (#3003 (comment)) is the same as my thought.

I think it is alright to introduce a new type which only used for Class initializer clause, but it is not so scaled when typescript introduces more context-related initialized clause

I think @denbezrukov is a more appropriate reviewer for the code details and I will leave it to them 🙏
(Of course, if they don't have much time, I can review this PR 🙌 )

@denbezrukov
Copy link
Contributor

denbezrukov commented Feb 19, 2023

Actually I also don't know which implementation is better either.

Adding a new optional field is easier because we don't need to support a new node.
I have a concern that, when using the type with the new field in the formatter/liner, we have to remember that one field (AnyTsPropertySignatureAnnotation) can't exist with another (JsInitializerClause) and we can only say that by looking at the parser code because both fields are optional. That why it seems that two nodes live in one.

If we already have such nodes I think that it's okay =)

cc @ematipico What do you think?

@Conaclos
Copy link
Contributor Author

I have a concern that, when using the type with the new field in the formatter/liner, we have to remember that one field
(AnyTsPropertySignatureAnnotation) can't exist with another (JsInitializerClause) and we can only say that by looking at the
parser code because both fields are optional. That why it seems that two nodes live in one.

This is a good mark. Is there other case of two node living in one in the Rome CST?

In my view fewer node at the price of type-safety is better.
However, the Rome codebase seems more bent towards type-safety at the price of more nodes and code.

If there is other case of two node living in one in the Rome codebase, I could keep the current decision of adding an optional field to an existing node.
Otherwise, we can adopt the decision of adding a new node.

@ematipico
Copy link
Contributor

cc @ematipico What do you think?

Both approaches come with different downsides and advantages. Having a new node allows us to design better grammar from the beginning, making the parsing phase way easier; on the other hand, it creates a level of indirection. That's why Micha suggested creating a new node: the rules around this grammar are so subtle that it's better to have a new node with its rules.

I would create a new node, but I don't feel strongly about the implementation. I'm OK with this implementation too.

This is a good mark. Is there other case of two node living in one in the Rome CST?

What do you mean by that?

@Conaclos
Copy link
Contributor Author

Conaclos commented Feb 20, 2023

This is a good mark. Is there other case of two node living in one in the Rome CST?

What do you mean by that?

Are there others nodes that could be splitted into several ones in the Rome codebase?

@ematipico
Copy link
Contributor

Are there others nodes that could be splitted into several ones in the Rome codebase?

Not sure I understand correctly, sorry; usually we use the Any* nodes when we want to match against different types.

@Conaclos
Copy link
Contributor Author

Conaclos commented Feb 20, 2023

@ematipico

Are there others nodes that could be splitted into several ones in the Rome codebase?

Not sure I understand correctly, sorry; usually we use the Any* nodes when we want to match against different types.

I try to understand which approach is most consistent with the previous decisions made in the Rome codebase (intentionally or implicitly).
The goal is to favor familiarity and consistency in the codebase.

As noted by denbezrukov, two nodes live in one in the current proposal:

I have a concern that, when using the type with the new field in the formatter/liner, we have to remember that one field (AnyTsPropertySignatureAnnotation) can't exist with another (JsInitializerClause) and we can only say that by looking at the parser code because both fields are optional. That why it seems that two nodes live in one.

If we have other cases like this in the codebase, I could conclude that it is ok to introduce a new one.
I prefer the simplicity of the current approach.

On the contrary, if there are no other cases like this, it seems more "Romy" to introduce a new node.

@ematipico
Copy link
Contributor

ematipico commented Feb 20, 2023

Oh, I see what you mean now. AFAIK, we don't have an example precisely like this; the logic around the modifiers (readonly, public, private, etc.) is similar. In our grammar, they are defined as a list, but not all can live together simultaneously, e.g. private public would throw an error.

There is a wrong answer, and we usually decide based on the pros and cons of both approaches. TypeScript has many quirks; sometimes, we had to find the right balance between creating new nodes or having more logic inside the parser.

I would create a new node for the following reasons:

  • the new grammar is tracked in the ungram file, and we don't need to "remember" this rule (as mentioned by @denbezrukov);
  • the rules are pretty specific;

@Conaclos
Copy link
Contributor Author

Conaclos commented Feb 21, 2023

I created another PR #4225 that makes the addition of a new node.

@ematipico I let you take the final decision.

@Conaclos
Copy link
Contributor Author

The alternative approach was chosen. See #4225.

@Conaclos Conaclos closed this Feb 22, 2023
@Conaclos Conaclos deleted the rome_js_parser/2958 branch March 7, 2023 16:29
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.

🐛 Parser: Initializers should be allowed for ambientreadonly in TsPropertySignatureClassMember
4 participants