Description
Before You File a Proposal Please Confirm You Have Done The Following...
- I have searched for related issues and found none that match my proposal.
- I have searched the current rule list and found no rules that match my proposal.
- I have read the FAQ and my problem is not listed.
My proposal is suitable for this project
- I believe my proposal would be useful to the broader TypeScript community (meaning it is not a niche proposal).
Link to the rule's documentation
https://typescript-eslint.io/rules/parameter-properties/
Description
TL;DR
This issue proposes the following changes to the rule parameter-properties
:
- [Primary] Additionally check the error-prone usage of the feature
parameter-property
under the class inheritance:class Foo { constructor(public a: string) { /* Empty */ } } class Bar extends Foo { // vvvvvv Error: Unnecessary TS visibility modifier for parameter property since the variable `a` is initialized by its parent class constructor(public a: string, public b: number) { super(a); } }
- [Optional] Merge the check by the rule
no-unnecessary-parameter-property-assignment
to this rule.
Background
Currently, the rule parameter-properties
strictly reports the following TS parameter property usage when Options.prefer
is set to parameter-property
and conditions marked in comments are met:
class Foo {
// vvv Any TS visibility modifier
public a: string;
// v No TS visibility modifier
constructor(a: string) {
// ^ ^^^^^^ The same variable name and type as defined in line 2
this.a = a;
// ^^^^^^^ The assignment expression AT THE BEGINNING of the constructor
}
}
which expects it to be refactored as the following (Though no auto-fix was provided) to prefer parameter-property
:
class Foo {
constructor(public a: string) { /* Empty */ }
}
However, the TS feature parameter-property
comes with an error-prone usage (duplication):
constructor(public a: string) {
this.a = a; // Unnecessary assignment expression since TS will generate this line while compiling to JS
}
A developer would not intentionally write code like this, so the rule no-unnecessary-parameter-property-assignment
detects it.
Motivation
During our feature usage analysis, we found another error-prone usage involving the super
call (that is, the class inheritance):
class Foo {
// vvvvvv This makes parameter `a` a property of class `Foo`
constructor(public a: string) { /* Empty */ }
}
class Bar extends Foo {
// vvvvvv This also makes parameter `a` a property of class `Bar`
constructor(public a: string, public b: number) {
super(a);
}
}
causing the assignment expression this.a = a
appears twice in class Foo
and class Bar
respectively (See JS code in this playground).
This is not a niche proposal since a real-world case can be found in a famous repository BabylonJS/Babylon.js
, where
defaultViewer.ts
declares a classDefaultViewer
that extends from a parent classAbstractViewerWithTemplate
.- The ultimate parent class of
DefaultViewer
can be traced back to classAbstractViewer
inviewer.ts
. - The parent class
AbstractViewer
declares a parameter property withpublic containerElement: Element
. - The child class
DefaultViewer
also DECLARES a parameter property withpublic containerElement: Element
and sends it to thesuper()
call. - As demonstrated previously, this duplicates the assignment expression, causing similar problems that the rule
no-unnecessary-parameter-property-assignment
detects.
This case is considered a bad practice by us because, on the contrary, we also found the following best practice as demonstrated by the repository glideapps/quicktype
, where
Dart.ts
classDartRenderer
-extends->ConvenienceRenderer.ts
classConvenienceRenderer
-extends->Renderer.ts
classRenderer
.- The first constructor parameter
targetLanguage
is markedprotected readonly
in the parent class and not marked in the following derived classes. - This only initializes the class property once at the parent class, preventing it from accidentally undoing the parent's initialization.
Proposal
Given the motivation described above, we would like to (mainly) propose the enhancement of the unnecessary-check under the class inheritance:
- Ideally, a cross-file inheritance chain analysis is desired, given the proposed scenario may involve two classes in different files.
- A loose constraint may simplify the implementation in case the precise cross-file type analysis is currently not available or time-consuming:
- In a derived class's constructor, a parameter is marked with TS visibility modifier.
- That parameter is sent to the
super
call.
This check benefits a better use of the feature parameter-property
.
Optional Goal
Like the rule no-unnecessary-parameter-property-assignment
, a developer would not intentionally write duplicated code once they determined to prefer: 'parameter-property'
. We consider violations of best practices like these should be implemented seamlessly and automatically without another rule to turn on.
The base rule parameter-properties
was discussed in 2019 and implemented in 2022, and the rule no-unnecessary-parameter-property-assignment
was discussed in 2023 and introduced later in 2024. We have read all discussions in PRs and issues and found that merging them into one rule was not discussed previously. By the chance of enhancing the base rule, we would like to pose the discussion for a more concise and compact rule set.
Fail
class Foo {
constructor(public a: string) { /* Empty */ }
}
class Bar extends Foo {
// vvvvvv Error: Unnecessary modifier, has already been initialized in the parent class.
constructor(public a: string, public b: string) {
super(a);
}
}
Pass
class Foo {
constructor(public a: string) { /* Empty */ }
}
class Bar extends Foo {
constructor(a: string, public b: string) {
// v ^ If the parameter is passed to the `super` call, it would most likely unnecessary to be a parameter property.
super(a);
}
}
Additional Info
Current rules can not detect the bad practice discussed in this proposal.