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

fix(rome_js_parser): TsPropertySignatureClassMember with initializer #3003

Conversation

IWANABETHATGUY
Copy link
Contributor

@IWANABETHATGUY IWANABETHATGUY commented Aug 4, 2022

@IWANABETHATGUY
Copy link
Contributor Author

@MichaReiser , It looks that Initializer in TsPropertySignatureClassMember is valid,
image
here is the ast.

@@ -719,6 +719,7 @@ TsPropertySignatureClassMember =
modifiers: TsPropertySignatureModifierList
name: JsAnyClassMemberName
property_annotation: TsAnyPropertySignatureAnnotation?
value: JsInitializerClause?
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you please list valid and invalid cases so that we can decide on where the initializer should be placed.

For example, the initializer isn't valid in combination with the definite assignment operator but this AST would now allow for a property having an initializer and a definite assignment operator.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

image
A 'const' initializer in an ambient context must be a string or numeric literal or literal enum reference.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do we really need to check this for now? If we do, we could only check for string or numeric literal, we can't check for literal enum reference

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What about adding some todos? And finish this after we have the ability to type checking?

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think we have to rush this change as I would argue, that it's probably not a too common case to have an initializer in an ambient context.

Also, changing AST structures is rather involved which is why I would prefer not to rush it. What's important for now is to fully understand valid and invalid patterns and we can then start thinking about how we want to change the AST structure.

Copy link
Contributor

@magic-akari magic-akari Aug 4, 2022

Choose a reason for hiding this comment

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

@magic-akari
Copy link
Contributor

@IWANABETHATGUY
Copy link
Contributor Author

Also, this is a known issue of our parser, see #2310 (comment).

@IWANABETHATGUY
Copy link
Contributor Author

Thanks @magic-akari for the nice research!

Valid cases in ambient context:

  1. numerical literal
class T {
    declare readonly a = 3;
}
  1. string literal
class T1 {
    declare readonly a = 'test';
}

class T3 {
    declare readonly a = `test`;
}
  1. literal enum reference.

Invalid case in ambient context

  1. TemplateStringLiteral with JsTemplateElement
class T2 {
    declare readonly a = `test${20}`;
}

any other cases are considered to be invalid.

@IWANABETHATGUY
Copy link
Contributor Author

IWANABETHATGUY commented Aug 5, 2022

A few solutions I could give for now:

  1. Allow TS declare readonly fields with initializers without checking, just like babel did.
  2. Only checking string literal and numerical literal like Allow literal initializers of readonly properties in declaration files microsoft/TypeScript#26313 did.
    cc @MichaReiser

@MichaReiser
Copy link
Contributor

Thanks @magic-akari for the nice research!

Valid cases in ambient context:

  1. numerical literal
class T {

    declare readonly a = 3;

}
  1. string literal
class T1 {

    declare readonly a = 'test';

}



class T3 {

    declare readonly a = `test`;

}
  1. literal enum reference.

Invalid case in ambient context

  1. TemplateStringLiteral with JsTemplateElement
class T2 {

    declare readonly a = `test${20}`;

}

any other cases are considered to be invalid.

Thanks for this listing. How does the syntax work in combination with the optional and definite operators?

Is it only allowed if the readonly modifier is present?

@IWANABETHATGUY
Copy link
Contributor Author

Thanks @magic-akari for the nice research!

Valid cases in ambient context:

  1. numerical literal
class T {

    declare readonly a = 3;

}
  1. string literal
class T1 {

    declare readonly a = 'test';

}



class T3 {

    declare readonly a = `test`;

}
  1. literal enum reference.

Invalid case in ambient context

  1. TemplateStringLiteral with JsTemplateElement
class T2 {

    declare readonly a = `test${20}`;

}

any other cases are considered to be invalid.

Thanks for this listing. How does the syntax work in combination with the optional and definite operators?

Is it only allowed if the readonly modifier is present?

Yes,
image
Only working if there is no type annotation and with a readonly modifier

@IWANABETHATGUY
Copy link
Contributor Author

Thanks @magic-akari for the nice research!

Valid cases in ambient context:

  1. numerical literal
class T {

    declare readonly a = 3;

}
  1. string literal
class T1 {

    declare readonly a = 'test';

}



class T3 {

    declare readonly a = `test`;

}
  1. literal enum reference.

Invalid case in ambient context

  1. TemplateStringLiteral with JsTemplateElement
class T2 {

    declare readonly a = `test${20}`;

}

any other cases are considered to be invalid.

Thanks for this listing. How does the syntax work in combination with the optional and definite operators?

Is it only allowed if the readonly modifier is present?

See the Typescript implementation https://github.com/microsoft/TypeScript/pull/26313/files#diff-d9ab6589e714c71e657f601cf30ff51dfc607fc98419bf72e04f6b0fa92cc4b8R29425-R29429

@IWANABETHATGUY
Copy link
Contributor Author

I think checking phase could left to #3000

@MichaReiser
Copy link
Contributor

I'm conflicted on how to best add this to our AST so that the API encodes as many constraints as possible.

  • readonly: Modifiers is an array, which is why it isn't possible to encode the must have a readonly modifier
  • no type annotation: One option is to add JsInitializerClause as a variant to TsAnyPropertySignatureAnnotation. This has the added benefit that it encodes the fact that the initializer is only valid if there is no type annotation (and the parser automatically marking it as an unknown node if it does have one). However, I do dislike that an initializer isn't an annotation in that sense. But comes with the downside that users of our AST may not know in which circumstances an initializer may be present.
  • Adding the initializer directly to the TsPropertySignatureTypeMember has the benefit that the validation could potentially be delayed until the linter phase.

What's your preference @IWANABETHATGUY and for what reasons?

I guess, I overall dislike TypeScript design decision behind adding support of this syntax rather than using `readonly prop: "value" but that's obviously out of my control :D

@github-actions
Copy link

github-actions bot commented Sep 7, 2022

This PR is stale because it has been open 14 days with no activity.

@magic-akari
Copy link
Contributor

Any update?

@github-actions github-actions bot removed the S-Stale label Sep 8, 2022
@ematipico
Copy link
Contributor

@MichaReiser @IWANABETHATGUY what's the status of this PR? Is it ready for merging/approved? Or should we put it on hold?

@IWANABETHATGUY
Copy link
Contributor Author

@MichaReiser @IWANABETHATGUY what's the status of this PR? Is it ready for merging/approved? Or should we put it on hold?

Last week I am working on the instantiation expression, I will have a look at this pr this week.

@ematipico ematipico added this to the 10.0.0 milestone Sep 23, 2022
@ematipico ematipico added L-TypeScript Area: TypeScript support in Rome A-Parser Area: parser labels Sep 23, 2022
@MichaReiser
Copy link
Contributor

I'm conflicted on how to best add this to our AST so that the API encodes as many constraints as possible.

* `readonly`: Modifiers is an array, which is why it isn't possible to encode the _must have a readonly_ modifier

* no type annotation: One option is to add `JsInitializerClause` as a variant to `TsAnyPropertySignatureAnnotation`. This has the added benefit that it encodes the fact that the `initializer` is only valid if there is no type annotation (and the parser automatically marking it as an unknown node if it does have one). However, I do dislike that an initializer isn't an annotation in that sense. But comes with the downside that users of our AST may not know in which circumstances an initializer may be present.

* Adding the initializer directly to the `TsPropertySignatureTypeMember` has the benefit that the validation could potentially be delayed until the linter phase.

What's your preference @IWANABETHATGUY and for what reasons?

I guess, I overall dislike TypeScript design decision behind adding support of this syntax rather than using `readonly prop: "value" but that's obviously out of my control :D

@IWANABETHATGUY what's your opinion on this?

@IWANABETHATGUY
Copy link
Contributor Author

I prefer the third one.

@MichaReiser
Copy link
Contributor

I prefer the third one.

Could you explain why?

@IWANABETHATGUY
Copy link
Contributor Author

I prefer the third one.

Could you explain why?

Making our Ast shape more compatible with Typescript, we could validate the error either in the parser phase or analyzer phase which is more flexible.

@MichaReiser
Copy link
Contributor

This new syntax is even more subtle:

  • Only allowed inside classes but not interfaces
  • Don't allow a type annotation

That's why 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'm undecided if we want a specific initializer only allowing JsStringLiteralExpression and JsNumberLiteralExpressions.

@IWANABETHATGUY what do you think of a new member type that we only add to classes?

@IWANABETHATGUY
Copy link
Contributor Author

IWANABETHATGUY commented Sep 27, 2022

This new syntax is even more subtle:

  • Only allowed inside classes but not interfaces
  • Don't allow a type annotation

That's why 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'm undecided if we want a specific initializer only allowing JsStringLiteralExpression and JsNumberLiteralExpressions.

@IWANABETHATGUY what do you think of a new member type that we only add to classes?

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

@xunilrj
Copy link
Contributor

xunilrj commented Sep 27, 2022

We already have a code that checks this:

crates\rome_js_parser\src\syntax\class.rs:944

else if modifiers.has(ModifierKind::Declare) || p.state.in_ambient_context() {
            // test_err ts ts_property_initializer_ambient_context
            // declare class A { prop = "test" }
            // class B { declare prop = "test" }
            p.error(
                p.err_builder("Initializers are not allowed in ambient contexts.")
                    .primary(initializer.range(p), ""),
            );
        }

We can "fix" the example just doing:

declare class A { readonly prop = "test" }

So my suggestion is actually to use TS_PROPERTY_SIGNATURE_CLASS_MEMBER with optional initializer and error with: "Properties with initializers in ambient contexts needs to be read only."

@netlify
Copy link

netlify bot commented Oct 3, 2022

Deploy Preview for docs-rometools ready!

Name Link
🔨 Latest commit c932a1b
🔍 Latest deploy log https://app.netlify.com/sites/docs-rometools/deploys/633ac71f26b8b60008604767
😎 Deploy Preview https://deploy-preview-3003--docs-rometools.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site settings.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
A-Parser Area: parser L-TypeScript Area: TypeScript support in Rome
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

5 participants