Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork2.8k
fix(eslint-plugin): [prefer-readonly] autofixer doesn't add type to property that is mutated in the constructor#10552
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Changes fromall commits
7993076f044615a63f335125b56eb77a28902c3124File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -173,6 +173,38 @@ export default createRule<Options, MessageIds>({ | ||
| }; | ||
| } | ||
| function getTypeAnnotationForViolatingNode( | ||
| node: TSESTree.Node, | ||
| type: ts.Type, | ||
| initializerType: ts.Type, | ||
| ) { | ||
| const annotation = checker.typeToString(type); | ||
| // verify the about-to-be-added type annotation is in-scope | ||
| if (tsutils.isTypeFlagSet(initializerType, ts.TypeFlags.EnumLiteral)) { | ||
MemberAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. I wrote about this on#1056 (comment), but on some occasions (as far as I know only enums), the rule won't be able to add a type annotation: // foo.tsenumFoo{Bar,Bazz,}exportconstbar=Foo.Bar;// index.tsimport{bar}from'./foo';exportclassTest{privatefoo=bar;} I chose not to add a type annotation on these cases, but an alternative I think is also valid is to have this be a suggestion instead of an auto-fix. Would be happy to to hear opinions on this. Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. I have a vague memory of this coming up once in a while... I think it's fine for now. We don't have any kind of shared/standard plumbing for importing constructs in fixes or suggestions. | ||
| const scope = context.sourceCode.getScope(node); | ||
| const variable = ASTUtils.findVariable(scope, annotation); | ||
| if (variable == null) { | ||
| return null; | ||
| } | ||
| const definition = variable.defs.find(def => def.isTypeDefinition); | ||
| if (definition == null) { | ||
| return null; | ||
| } | ||
| const definitionType = services.getTypeAtLocation(definition.node); | ||
| if (definitionType !== type) { | ||
| return null; | ||
| } | ||
| } | ||
| return annotation; | ||
| } | ||
| return { | ||
| [`${functionScopeBoundaries}:exit`]( | ||
| node: | ||
| @@ -229,13 +261,62 @@ export default createRule<Options, MessageIds>({ | ||
| } | ||
| })(); | ||
| const typeAnnotation = (() => { | ||
| if (esNode.type !== AST_NODE_TYPES.PropertyDefinition) { | ||
| return null; | ||
| } | ||
| if (esNode.typeAnnotation || !esNode.value) { | ||
| return null; | ||
| } | ||
| if (nameNode.type !== AST_NODE_TYPES.Identifier) { | ||
| return null; | ||
| } | ||
Comment on lines +273 to +275 MemberAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. To my understanding, this can realistically only be an identifier. This means some cases that should have a type annotation added won't receive it, for example: classTestOverlappingClassVariable{private'overlappingClassVariable'=7;constructor(){this.overlappingClassVariable=10;}} This seems like an existing bug that's not necessarily related to this addition. I've opened#10655 as a separate issue to address this. | ||
| const hasConstructorModifications = | ||
| finalizedClassScope.memberHasConstructorModifications( | ||
| nameNode.name, | ||
| ); | ||
| if (!hasConstructorModifications) { | ||
| return null; | ||
| } | ||
| const violatingType = services.getTypeAtLocation(esNode); | ||
| const initializerType = services.getTypeAtLocation(esNode.value); | ||
| // if the RHS is a literal, its type would be narrowed, while the | ||
| // type of the initializer (which isn't `readonly`) would be the | ||
| // widened type | ||
| if (initializerType === violatingType) { | ||
| return null; | ||
| } | ||
| if (!tsutils.isLiteralType(initializerType)) { | ||
JoshuaKGoldberg marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
| return null; | ||
| } | ||
| return getTypeAnnotationForViolatingNode( | ||
| esNode, | ||
| violatingType, | ||
| initializerType, | ||
| ); | ||
| })(); | ||
| context.report({ | ||
| ...reportNodeOrLoc, | ||
| messageId: 'preferReadonly', | ||
| data: { | ||
| name: context.sourceCode.getText(nameNode), | ||
| }, | ||
| *fix(fixer) { | ||
| yield fixer.insertTextBefore(nameNode, 'readonly '); | ||
| if (typeAnnotation) { | ||
| yield fixer.insertTextAfter(nameNode, `: ${typeAnnotation}`); | ||
| } | ||
| }, | ||
| }); | ||
| } | ||
| }, | ||
| @@ -288,6 +369,8 @@ class ClassScope { | ||
| private readonly classType: ts.Type; | ||
| private constructorScopeDepth = OUTSIDE_CONSTRUCTOR; | ||
| private readonly memberVariableModifications = new Set<string>(); | ||
| private readonly memberVariableWithConstructorModifications = | ||
| new Set<string>(); | ||
| private readonly privateModifiableMembers = new Map< | ||
| string, | ||
| ParameterOrPropertyDeclaration | ||
| @@ -358,6 +441,7 @@ class ClassScope { | ||
| relationOfModifierTypeToClass === TypeToClassRelation.Instance && | ||
| this.constructorScopeDepth === DIRECTLY_INSIDE_CONSTRUCTOR | ||
| ) { | ||
| this.memberVariableWithConstructorModifications.add(node.name.text); | ||
| return; | ||
| } | ||
| @@ -465,4 +549,8 @@ class ClassScope { | ||
| return TypeToClassRelation.Instance; | ||
| } | ||
| public memberHasConstructorModifications(name: string) { | ||
| return this.memberVariableWithConstructorModifications.has(name); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.