tools icon indicating copy to clipboard operation
tools copied to clipboard

fix(rome_js_parser): TsPropertySignatureClassMember with initializer

Open IWANABETHATGUY opened this issue 3 years ago • 10 comments
trafficstars

Summary

  1. Part of #3000
  2. Part of https://github.com/rome/tools/issues/2310
  3. TsPropertySignatureClassMember with initializer is allowed in Typescript, see https://www.typescriptlang.org/play?#code/MYGwhgzhAEAq0G8BQ1oBMCmowCcPTzDQHsA7EAT2gBcMJroBeaAZgG4kBfIA

Test Plan

  1. cargo test should pass.

IWANABETHATGUY avatar Aug 04 '22 09:08 IWANABETHATGUY

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

IWANABETHATGUY avatar Aug 04 '22 09:08 IWANABETHATGUY

  • https://github.com/microsoft/TypeScript/pull/26313
  • https://github.com/babel/babel/pull/14817

magic-akari avatar Aug 04 '22 11:08 magic-akari

Also, this is a known issue of our parser, see https://github.com/rome/tools/issues/2310#issuecomment-1083630383.

IWANABETHATGUY avatar Aug 05 '22 05:08 IWANABETHATGUY

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 avatar Aug 05 '22 05:08 IWANABETHATGUY

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 https://github.com/microsoft/TypeScript/pull/26313 did. cc @MichaReiser

IWANABETHATGUY avatar Aug 05 '22 05:08 IWANABETHATGUY

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?

MichaReiser avatar Aug 05 '22 06:08 MichaReiser

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 avatar Aug 05 '22 06:08 IWANABETHATGUY

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 avatar Aug 05 '22 06:08 IWANABETHATGUY

I think checking phase could left to https://github.com/rome/tools/pull/3000

IWANABETHATGUY avatar Aug 05 '22 06:08 IWANABETHATGUY

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

MichaReiser avatar Aug 08 '22 07:08 MichaReiser

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

github-actions[bot] avatar Sep 07 '22 12:09 github-actions[bot]

Any update?

magic-akari avatar Sep 08 '22 08:09 magic-akari

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

ematipico avatar Sep 15 '22 15:09 ematipico

@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.

IWANABETHATGUY avatar Sep 15 '22 15:09 IWANABETHATGUY

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?

MichaReiser avatar Sep 23 '22 15:09 MichaReiser

I prefer the third one.

IWANABETHATGUY avatar Sep 23 '22 15:09 IWANABETHATGUY

I prefer the third one.

Could you explain why?

MichaReiser avatar Sep 23 '22 16:09 MichaReiser

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.

IWANABETHATGUY avatar Sep 23 '22 16:09 IWANABETHATGUY

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?

MichaReiser avatar Sep 27 '22 13:09 MichaReiser

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

IWANABETHATGUY avatar Sep 27 '22 14:09 IWANABETHATGUY

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."

xunilrj avatar Sep 27 '22 15:09 xunilrj

Deploy Preview for docs-rometools ready!

Name Link
Latest commit c932a1b713e510437d917260f4a6c1306c924a73
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.

netlify[bot] avatar Oct 03 '22 11:10 netlify[bot]