-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
feat(eslint-plugin): [no-unused-private-class-members] new extension rule #10913
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
Changes from all commits
34b77b8
0854007
df85b86
8710810
fe8c300
e72b89a
a43dc70
34e50d5
db7e2a8
73a1c8e
73a1f53
d3077cb
a7d1705
2ccb638
f7beddc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| --- | ||
| description: 'Disallow unused private class members.' | ||
| --- | ||
|
|
||
| import Tabs from '@theme/Tabs'; | ||
| import TabItem from '@theme/TabItem'; | ||
|
|
||
| > 🛑 This file is source code, not the primary documentation location! 🛑 | ||
| > | ||
| > See **https://typescript-eslint.io/rules/no-unused-private-class-members** for documentation. | ||
|
|
||
| This rule extends the base [`eslint/no-unused-private-class-members`](https://eslint.org/docs/rules/no-unused-private-class-members) rule. | ||
| It adds support for members declared with TypeScript's `private` keyword. | ||
|
|
||
| ## Options | ||
|
|
||
| This rule has no options. | ||
|
|
||
| ## Examples | ||
|
|
||
| <Tabs> | ||
| <TabItem value="❌ Incorrect"> | ||
|
|
||
| ```ts | ||
| class A { | ||
| private foo = 123; | ||
| } | ||
| ``` | ||
|
|
||
| </TabItem> | ||
| <TabItem value="✅ Correct"> | ||
|
|
||
| ```tsx | ||
| class A { | ||
| private foo = 123; | ||
|
|
||
| constructor() { | ||
| console.log(this.foo); | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| </TabItem> | ||
| </Tabs> | ||
|
|
||
| ## Limitations | ||
|
|
||
| This rule does not detect the following cases: | ||
|
|
||
| (1) Private members only used via a variable that would require type analysis to resolve the type. | ||
|
|
||
| ```ts | ||
| type T = Foo; | ||
| class Foo { | ||
| private prop = 123; | ||
|
|
||
| method1(a: Foo) { | ||
| // ✅ Detected as a usage | ||
| const prop = this.prop; | ||
|
|
||
| // ✅ Detected as a usage (variables with explicit and simple type annotations are handled) | ||
| const otherProp = a.prop; | ||
| } | ||
|
|
||
| method2(a: T) { | ||
| // ❌ NOT detected as a usage (complex type annotation that requires type information to handle) | ||
| const prop = a.prop; | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| (2) Usages of the private member outside of the class: | ||
|
|
||
| ```ts | ||
| class Foo { | ||
| private prop = 123; | ||
| } | ||
|
|
||
| const instance = new Foo(); | ||
| // ❌ NOT detected as a usage | ||
| console.log(foo['prop']); | ||
| ``` | ||
|
|
||
| (3) Reassignments of `this` multiple times: | ||
|
|
||
| ```ts | ||
| class Foo { | ||
| private prop = 123; | ||
|
|
||
| foo() { | ||
| const self1 = this; | ||
| const self2 = self1; | ||
| return self2.prop; | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| (4) Mutable reassignments of `this`: | ||
|
|
||
| ```ts | ||
| class Foo { | ||
| private prop = 123; | ||
| private parent: Foo | null; | ||
|
|
||
| foo() { | ||
| let self = this; | ||
| while (self.parent != null) { | ||
| self = self.parent; | ||
| } | ||
| return self.prop; | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ## When Not To Use It | ||
|
|
||
| If you don't want to be notified about unused private class members, you can safely turn this rule off. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| import { ESLintUtils } from '@typescript-eslint/utils'; | ||
|
|
||
| import { createRule } from '../util'; | ||
| import { analyzeClassMemberUsage } from '../util/class-scope-analyzer/classScopeAnalyzer'; | ||
|
|
||
| type Options = []; | ||
| export type MessageIds = 'unusedPrivateClassMember'; | ||
|
|
||
| export default createRule<Options, MessageIds>({ | ||
| name: 'no-unused-private-class-members', | ||
| meta: { | ||
| type: 'problem', | ||
| docs: { | ||
| description: 'Disallow unused private class members', | ||
bradzacher marked this conversation as resolved.
Show resolved
Hide resolved
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 think this is a bug and shouldn't be reported (deploy preview playground link): class Test1 {
// reported but shouldn't?
private foo: number | null;
public bar() {
this.foo ??= 1;
}
}
Member
Author
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. This is a weird case that isn't currently reported by It is reported by the base
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. There was just a recent eslint bug report about these; I guess it's intentional for no-unused-vars since
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. Oh, but do note that these are reported by no-useless-assignment so 🤷♂️ 🤷♂️
Member
Author
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. we should probably file a bug for NUPCM then so it matches NUV? |
||
| extendsBaseRule: true, | ||
| requiresTypeChecking: false, | ||
| }, | ||
|
|
||
| messages: { | ||
| unusedPrivateClassMember: | ||
| "Private class member '{{classMemberName}}' is defined but never used.", | ||
| }, | ||
|
|
||
| schema: [], | ||
| }, | ||
| defaultOptions: [], | ||
| create(context) { | ||
| return { | ||
| 'Program:exit'(node) { | ||
| const result = analyzeClassMemberUsage( | ||
| node, | ||
| ESLintUtils.nullThrows( | ||
| context.sourceCode.scopeManager, | ||
| 'Missing required scope manager', | ||
| ), | ||
| ); | ||
|
|
||
| for (const classScope of result.values()) { | ||
| for (const member of [ | ||
| ...classScope.members.instance.values(), | ||
| ...classScope.members.static.values(), | ||
| ]) { | ||
| if ( | ||
| (!member.isPrivate() && !member.isHashPrivate()) || | ||
| member.isUsed() | ||
| ) { | ||
| continue; | ||
| } | ||
|
|
||
| context.report({ | ||
| node: member.nameNode, | ||
| messageId: 'unusedPrivateClassMember', | ||
| data: { classMemberName: member.name }, | ||
| }); | ||
| } | ||
| } | ||
| }, | ||
| }; | ||
| }, | ||
| }); | ||
Uh oh!
There was an error while loading. Please reload this page.