-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
fix(eslint-plugin): [class-literal-property-style] broken fixer when a readonly property is assigned in the constructor #2256
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
Conversation
Thanks for the PR, @JBhagat841! typescript-eslint is a 100% community driven project, and we are incredibly grateful that you are contributing to that community. The core maintainers work on this in their personal time, so please understand that it may not be possible for them to review your work immediately. Thanks again! 🙏 Please, if you or your company is finding typescript-eslint valuable, help us sustain the project by sponsoring it transparently on https://opencollective.com/typescript-eslint. As a thank you, your profile/company logo will be added to our main README which receives thousands of unique visitors per day. |
@JoshuaKGoldberg throwing up a PR for this and wanted to keep you in the loop. |
Codecov Report
@@ Coverage Diff @@
## master #2256 +/- ##
=======================================
Coverage 93.12% 93.12%
=======================================
Files 283 283
Lines 9057 9060 +3
Branches 2482 2484 +2
=======================================
+ Hits 8434 8437 +3
Misses 301 301
Partials 322 322
Flags with carried forward coverage won't be shown. Click here to find out more.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't quite right - we don't want to completely ignore private readonly
properties with a type annotation.
Instead what we want to do is analyse the constructor for assignments, and disable the fixer if there is one.
We still want to report an error for these cases, we just don't want to automatically fix them.
I.e. in this case, we should not auto-fix, but should still report:
export class A {
private readonly foo: string;
constructor(foo: string) {
this.foo = foo;
}
}
{ | ||
code: ` | ||
class Mx { | ||
private readonly foo: string = 'bar'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bradzacher so change this test case to:
export class A {
private readonly foo: string;
constructor(foo: string) {
this.foo = foo;
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would have both.
private readonly foo: string = 'bar';
should be an invalid
case for the config options: ['getters']
, and it should be fixed appropriately.
As mentioned; the case with the assignment in the constructor should still be invalid, but there should be no auto-fix for it (use output: null
to test this).
@@ -100,7 +102,31 @@ export default util.createRule<Options, MessageIds>({ | |||
} | |||
|
|||
return { | |||
'MemberExpression > Identifier:exit'(node: TSESTree.Identifier): void { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@JoshuaKGoldberg read the documentation you suggested. Do you think I'm getting warmer on having a solution that solves the actual bug?
I realize this code can be much cleaner, but wanted to just know if I'm going in the right direction.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok cool @JBhagat841 and I discussed this a bit; summarizing the key takeaways I had:
- Rule walkers do a single linear traversal through the node tree, so these
:exit
s don't quote match up with the expected behavior. - The strategy going forward will be to:
- Collect class properties that might need to be warned or errored on as they are found
- Inside the class constructor, mark any property assignments as warnings for those class properties
- Upon exiting the class constructor, mark any remaining property assignments as errors
- As with
prefer-readonly
, you might have to use parser services and will almost certainly need a stack of classes to make sure you're applying node warnings to the right class
That last point is where the trickiness comes in. Please enjoy these very convoluted edge cases 😄
const keyName1 = 'wat1'
const keyName2 = 'wat2'
class Parent {
private readonly wat1 = true;
private readonly wat2 = true;
constructor() {
this['wat1'] = true;
this[keyName1] = true;
this.wat1 = true;
class Child {
private readonly wat1 = true;
constructor() {
this['wat1'] = true;
this[keyName1] = true;
console.log(class GrandChild extends Child {
private readonly wat2 = true;
constructor() {
super();
this.wat2 = true;
this[keyName2] = true;
}
})
}
}
}
}
(personally, I don't think we need to consider absurdities such as this[keyName1]
as examples of a property being written to for this rule, as those are often impossible to accurately understand the resolved key name in static analysis...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you might have to use parser services
Requiring type information is a breaking change, so I'd prefer to not use it unless we reallllyyyy need it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Translating this into test cases:
- test case of computed value this['wat1'] = 123
- rescursive case of class in constructor
would the following 2 invalid cases capture what you listed?
class Mx {
private readonly wat1 = true;
constructor() {
this['wat1'] = true;
}
}
class Mx {
constructor() {
class Child {
private readonly foo = true;
constructor() {
this.foo = true;
}
}
}
}
A breaking change was reported (#2206 ) such that when the following rules are applied, the following two cases are incorrectly showing errors:
Rules:
Expected Result
Code should be unchanged
Actual Result
Code after fix is invalid
Versions applied when bug was reported: